How to use Raw Data function: startRawRecording macOS?

Description
I am trying to get the video streams from a meeting using this docs: https://developers.zoom.us/docs/meeting-sdk/macos/raw-data/

And following this post:

My idea was to use the ZoomSDKSample Objective-C project from here: https://developers.zoom.us/docs/meeting-sdk/macos/get-started/

And add some Swift classes to do something similar of what is done in the post. I will include the Swift code using this: Importing Swift into Objective-C | Apple Developer Documentation

However I am struggling to do the next steps:

  • startRawRecording, How do you trigger this function?

  • where do you call this function?
    zoomSDKMeetingRecordController.startRawRecording()

  • subscribe, How do you subscribe to the stream of each user?

  • where do you call this function?
    zoomSDKRenderer.subscribe(userID, rawDataType:ZoomSDKRawDataType)

  • How do I link the zoomSDKRenderer to this callback onRawDataReceived

Which macOS Meeting SDK version
zoom-sdk-macos-5.14.11.19310

Device:

  • Device: [e.g. Apple MacBook Pro (16-inch, Intel)]
  • OS: [macOS Ventura 13.4.1 ]

@maltamirano , let me try to answer your questions as much as possible

However I am struggling to do the next steps:

  • startRawRecording, How do you trigger this function?

There is a recording helper called ZoomSDKMeetingRecordController
You can access ZoomSDKMeetingRecordController by getting it from the ZoomSDKMeetingService 's getRecordController. You can get ZoomSDKMeetingService from the ZoomSDK singleton object

  • where do you call this function?

You would want to call it when you meet 2 criterial. When you are inmeeting, and when you have recording or host permission. I typically call it using a meeting status callback. This will ensure that I have definitely joined the meeting, hence the “inmeeting” status.

zoomSDKMeetingRecordController.startRawRecording()

  • subscribe, How do you subscribe to the stream of each user?
  • where do you call this function?
    zoomSDKRenderer.subscribe(userID, rawDataType:ZoomSDKRawDataType)
  • How do I link the zoomSDKRenderer to this callback onRawDataReceived

I would create an instance of this interface IZoomSDKRenderer and IZoomSDKRendererDelegate

ZoomSDKRendererDelegate* videoSource = new ZoomSDKRendererDelegate();
IZoomSDKRenderer* videoHelper;

For my case I call this videohelper. The code is in c++ for windows, but it should be similarly implemented on macOS

I would call the code below after doing these things

  • make sure i’m in meeting
  • make sure i have recording or host permission
  • call the StartRawRecording() method

createRenderer(&videoHelper, videoSource); //createRenderer is a provided method
videoHelper->subscribe(getUserID(), RAW_DATA_TYPE_VIDEO); }

1 Like

Hello @chunsiong.zoom

First things first, thanks for your help! I started following the steps you mentioned. Please see code below.

//-- Triggering startRawRecording --

 // Get ZoomSDKMeetingService object from the the ZoomSDK singleton object
 ZoomSDKMeetingService *meetingService = [[ZoomSDK sharedSDK] getMeetingService];

 // Get the ZoomSDKMeetingRecordController object from meetingService object
 ZoomSDKMeetingRecordController *recordController = [meetingService getRecordController];

//Start recording
 [recordController startRawRecording];

But I am stuck in this step: I typically call it using a meeting status callback.

  • Which callback could I use to trigger the raw recording in order to see the inmeeting status?
  • How do I hook the callback to the ongoing meeting?
  • Which callback could I use to trigger the raw recording in order to see the inmeeting status?
  • How do I hook the callback to the ongoing meeting?

On the main thread, you can assign an event to the meetingService. It goes something like this on

############### Main Thread (Windows C++ Code) ######################

meetingService->SetEvent(new MeetingServiceEventListener(&onMeetingJoined, &onMeetingEndsQuitApp, &onInMeeting));

The methods onMeetingJoined, onMeetingEndsQuitApp, and onInMeeting are methods in the main thread. These will be called from the callback onMeetingStatusChanged

######################### code for MeetingServiceEventListener ######################

#include "MeetingServiceEventListener.h"
#include <rawdata/zoom_rawdata_api.h>
#include <iostream>

using namespace std;

MeetingServiceEventListener::MeetingServiceEventListener(void (*onMeetingStarts)(), void (*onMeetingEnds)(), void (*onInMeeting)())
{
	onMeetingEnds_ = onMeetingEnds;
	onMeetingStarts_ = onMeetingStarts;
	onInMeeting_ = onInMeeting;
}

void MeetingServiceEventListener::onMeetingStatusChanged(MeetingStatus status, int iResult)
{
	cout << "onMeetingStatusChanged: " << status << ", iResult: " << iResult << endl;
	switch (status)
	{
	case MEETING_FAIL_MEETING_NOT_EXIST:
		printf("Meeting doesn't exitst, check your Meeting Number.\n");
		break;

	case MEETING_STATUS_INMEETING:
		printf("onMeetingStatusChanged() In Meeting.\n");
		if (onInMeeting_) onInMeeting_();

		break;
	}

	if (status == MEETING_STATUS_ENDED && onMeetingEnds_) onMeetingEnds_();
}

void MeetingServiceEventListener::onMeetingStatisticsWarningNotification(StatisticsWarningType type)
{
	cout << "onMeetingStatisticsWarningNotification, type: " << type << endl;
}

void MeetingServiceEventListener::onMeetingParameterNotification(const MeetingParameter* meeting_param)
{
	cout << "onMeetingParameterNotification" <<endl;
	if (onMeetingStarts_) onMeetingStarts_();
}

void MeetingServiceEventListener::onSuspendParticipantsActivities()
{
	cout << "onSuspendParticipantsActivities" << endl;
}


1 Like

Hello @chunsiong.tan
I did the next steps to create the renderer-object (ZoomSDKRenderer) and to subscribe it to the video stream of one user.

I donwnloaded the ZoomSDKSample project from here: Download | Meeting SDK | macOS

Then I modified the file ZMSDKMeetingStatusMgr.m.
I used the callback called onLocalRecordingPrivilegeRequestStatus to trigger the raw-recording.
If the user that wants to record needs to ask for permission to the host, then to start the local-recording he will need to click 2 times the record button.
The first time to get the permission and the 2nd to start the local-recording.
However, I’m using the 1st click to trigger the raw-recording after he gets the privilege to record. See code below:

//Fired up when privileges are granted/denied to do local-recording or raw-recording
- (void) onLocalRecordingPrivilegeRequestStatus:(ZoomSDKRequestLocalRecordingStatus)status{
          
    //If I am authorized to local recoding then I will trigger raw data recording
    if(status == ZoomSDKRequestLocalRecordingStatus_Granted){
       
        //1. Start recording with recordController
           
         // Get ZoomSDKMeetingService object from the the ZoomSDK singleton object
        ZoomSDKMeetingService *meetingService = [[ZoomSDK sharedSDK] getMeetingService];

        // Get the ZoomSDKMeetingRecordController object from meetingService object
        ZoomSDKMeetingRecordController *recordController = [meetingService getRecordController];

        //Start raw recording
        ZoomSDKError rawRecordingError = [recordController startRawRecording];

After the raw-recording has started I’m creating the renderer-object, then getting the userID from any user, and subscribing the renderer to the video stream of that user. See code below:

if(rawRecordingError == ZoomSDKError_Success){
            
            //2. Subscribe to user stream with renderer
            
            //Creating the renderer getting a raw-controller from the sharedSDK singleton
            //and calling creatRender and passing pointer to renderer
            ZoomSDKRenderer *renderer = nil;
            ZoomSDKError rendererError = [[[ZoomSDK sharedSDK] getRawDataController] creatRender:&renderer];
            
            //Assigning the property delegate to be the same object so we can have the ZoomSDKRendererDelegate
            //callbacks in the same class
            renderer.delegate = self;
                        
            //Remove duplictaes from UserIds
            userIdsNoDuplicates = [userIds valueForKeyPath:@"@distinctUnionOfObjects.self"];
            
            //Subscribing renderer to the active user
            int userID = [[userIdsNoDuplicates objectAtIndex:1] integerValue];
            ZoomSDKError subscribeError = [renderer subscribe:userID rawDataType:ZoomSDKRawDataType_Video];
                  
            if(subscribeError == ZoomSDKError_Success){
                NSLog(@">>renderer subscribed!!");
                //We will trigger onRawDataReceived
            }
                        
        }
        
    }    
    
}        

To implement the callbacks used during the raw-recording I added the protocol ZoomSDKRendererDelegate to the header file ZMSDKMeetingStatusMgr.h. See code below:

@interface ZMSDKMeetingStatusMgr : NSObject  <ZoomSDKMeetingServiceDelegate,ZoomSDKMeetingActionControllerDelegate,ZoomSDKWebinarControllerDelegate,ZoomSDKMeetingRecordDelegate,ZoomSDKRendererDelegate>
{
    ZMSDKMainWindowController* _mainWindowController;
    ZoomSDKMeetingService* _meetingService;
    
    //To store user ids for raw-recording
    NSMutableArray *userIds;
    NSMutableArray *userIdsNoDuplicates;

}
- (id)initWithWindowController:(ZMSDKMainWindowController*)mainWindowController;
@end

Finally, I implemented a callback (onRawDataReceived) that is called after the raw-recording hast started in the file: ZMSDKMeetingStatusMgr.m

#pragma mark -- ZoomSDKRendererDelegate

//3. Create ZoomSDKRendererDelegate to manage raw recording events

// Fired after the renderer is subscribed to the stream of an user
- (void)onRawDataReceived:(ZoomSDKYUVRawDataI420*)data {
   NSLog(@">>Hola from ZMSDKMeetingStatusMgr.m - onRawDataReceived!!");
}

To get the user-ids I’m using the callback onVideoStatusChange from ZoomSDKMeetingActionControllerDelegate. See code below:

- (void)onVideoStatusChange:(ZoomSDKVideoStatus)videoStatus UserID:(unsigned int)userID {
    
    if([ZMSDKCommonHelper sharedInstance].isUseCutomizeUI)
        [[ZMSDKConfUIMgr sharedConfUIMgr].userHelper onVideoStatusChange:videoStatus UserID:userID];
    
    //Fired when any user hide/show video
    NSLog(@">>Hola from ZMSDKMeetingStatusMgr.m - onVideoStatusChange!!");
    NSLog(@"userID: %d\n", userID );
    
    //Save userID
    [userIds addObject: [NSNumber numberWithUnsignedInteger: userID]];
    
}

Thanks for your help!
:slight_smile:

Hello again @chunsiong.zoom
By the way, I tried to use Swift in the ZoomSDKSample project from here: Download | Meeting SDK | macOS to create the renderer-object and subscribe to a video stream.

I integrated the Swift file using an interface described here:

And adding this line to the file: ZMSDKMeetingStatusMgr.m

//Interfce to see the Swift classes created by Xcode
#import "ZoomSDKSample_intel-Swift.h"

However, when I was passing the pointer to the function creatRender the function finished successfully but the object renderer-object was nil.
Using the function creatRender was not a problem in Objective-C because the pointer worked fine, maybe because the creatRender function is designed to work with Objective-C?

Then the subscribe function failed because the renderer-object was nil even if creatRender was successful.

Here I am creating the Swift object called rendererWrapper to later create the renderer-object (ZoomSDKRenderer).

I am passing the sharedSDK (ZoomSDK) to rendererWrapper (ZoomRendererWrapper). I added the next code to the file ZMSDKMeetingStatusMgr.m.

//Fired up when privileges are granted/denied to do local-recording or raw-recording
- (void) onLocalRecordingPrivilegeRequestStatus:(ZoomSDKRequestLocalRecordingStatus)status{
          
    //If I am authorized to local recoding then I will trigger raw data recording
    if(status == ZoomSDKRequestLocalRecordingStatus_Granted){
       
        //1. Start recording with recordController
        
        //SWIFT - Start recording and Create a renderer
        ZoomRendererWrapper *rendererWrapper = [[ZoomRendererWrapper alloc] init];

        [rendererWrapper initWithSharedSDKWithSharedSDK:[ZoomSDK sharedSDK]];

This is the Swift implementation of the ZoomRendererWrapper

@objc class ZoomRendererWrapper: NSObject {

    var meetingService: ZoomSDKMeetingService?
    var recordController: ZoomSDKMeetingRecordController?
    var rawDataController: ZoomSDKRawDataController?
    private(set) var renderer: ZoomSDKRenderer?
    var rendererPointer: AutoreleasingUnsafeMutablePointer<ZoomSDKRenderer?>?
    var error: ZoomSDKError?

    .
    .
    .

@objc func initWithSharedSDK(sharedSDK: ZoomSDK){
        
        //Start recording
        meetingService = sharedSDK.getMeetingService()
        recordController = meetingService?.getRecordController()
        error = recordController?.startRawRecording()
        
        //Create renderer if startRawRecording OK
        if(error == ZoomSDKError_Success){
            rawDataController = sharedSDK.getRawDataController()
            renderer = ZoomSDKRenderer()          
           
            rendererPointer = .init(&renderer)
           
            var rendererError: ZoomSDKError?

            rendererError = rawDataController?.creatRender(rendererPointer)
            
            if(rendererError == ZoomSDKError_Success){
                print(">>initWithSharedSDK renderer created!")
            }
        }
        
    }

During a meeting I was able to start the recording with the Swift object ZoomRendererWrapper. However, creatRender created a ZoomSDKRenderer object with nil value.

Here an example when debugging with Xcode:

Do you know how could I use Swift to create the renderer (ZoomSDKRenderer)?
Do you recommend me to use Objective-C insetad of Swift?

@maltamirano I’m not familiar with MacOS and objective C / Swift, but let me try my best to help

rendererError = rawDataController?.creatRender(rendererPointer)
Is there a specific renderError given?

Hello @chunsiong.zoom

When running this in Swift:

rendererError = rawDataController?.creatRender(rendererPointer)

The results is that rendererError is equal to ZoomSDKError_Success and the renderer is created but in the debugger the value of the renderer is nil as shown in the previous image.

@maltamirano

It does not return nil in objective C right?
If thats the case, let me raise this internally

One last thing, does this happen in newer version of the SDK as well? The 5.15.x

@chunsiong.zoom
When I create the renderer in Objective-C I successfully get the renderer object .

However when I try to do the same with Swift I get a renderer=nil.
Maybe there is a problem with the pointer passed to the function creatRender

I tried also with zoom-sdk-macos-5.15.5.20804 and I got the same error.

Hi @maltamirano , thank you for your patience as we looked into this.

Since you are using the SDK sample app and have made local modifications to it, can you please provide a diff of the changes so we can ensure we’re on the same page? Please remove any private information such as credentials or other sensitive data from the diff before posting it.

Thanks!

Hello @jon.zoom, thanks for your reply. However, I decided to continue working with the SDK-sample with Objective-C instead of Swift. I choose Objective-C over Swift for some reasons:

  • The ZoomErrors are not visible when debugging in Xcode, maybe because it is an Objective-C project and the inrterface-header (the translation of Swift into Objective-C objects) blocks the visibility of the Swift objects in runtime.
  • We need to build the project to see the new functions from a Swift class for autocomplete in Xcode. The interface-header only updates after building the project.

I called the interface-header to the translation that Xcode does automatically using an autogenerated header that you must include in your Objective-C files to use Swift classes. See image:

More info about this interface-header here: Importing Swift into Objective-C | Apple Developer Documentation

Thanks for your help!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.