# Use production studio mode > The code on this page works with either the **default UI** or the **custom UI**. Production studio (PS) mode lets an authorized client publish custom raw video and audio into the Zoom meeting pipeline through the Meeting SDK's `PSSender`, instead of - or in addition to - a normal camera or mic path. Only the host or co-host can start production studio mode using `CanStartPSMode()`. 1. Obtain the `IMeetingProductionStudioController` from the meeting service after joining. 1. Check support for `isSupportPSMode` and permission for `canStartPSMode`, then register for events. 1. Call `StartPSMode with a video capability declaration including width, height, and format. This is the contract for all subsequent `sendVideoFrame` calls. 1. Wait for `onStartPSModeResult(true)`, then wait for `onStartSend` before pushing any media. 1. Push video frames and audio buffers only while `onStartSend` is active, using `IZoomSDKPSSender`. 1. Call `StopPSMode` when done, and release all resources in `onStopSend`. **Note** Keep your `IMeetingProductionStudioCtrlEvent` instance alive for as long as the SDK holds a pointer to it. Call `SetEvent(nullptr)` before destroying the object if you need to unregister early. ## Get the controller Get the controller from the meeting service after joining a meeting. If the SDK is not in a meeting or if production studio mode is unavailable, the return value is `nullptr`. ```cpp #include "meeting_service_interface.h" #include "meeting_service_components/meeting_production_studio_ctrl_interface.h" using namespace ZOOM_SDK_NAMESPACE; IMeetingProductionStudioController* psCtrl = meetingService->GetMeetingProductionStudioController(); if (!psCtrl) { // Not in a meeting, or PS is unavailable } ``` ## Controller APIs All APIs are available on `IMeetingProductionStudioController`. | API | Method | Description | | ----------------- | ------------------- | --------------------------------------------------------------------------------------------- | | Register callback | `SetEvent(pEvent)` | Must call before `StartPSMode`. Returns `SDKERR_SUCCESS` on success. | | Check support | `IsSupportPSMode()` | Whether PS is available in this meeting or client. Check before showing any PS UI. | | Check permission | `CanStartPSMode()` | Whether the current user may start PS. Host or co-host only. | | Start PS | `StartPSMode(cap)` | Declares the video capability contract. Asynchronous - result comes in `onStartPSModeResult`. | | Stop PS | `StopPSMode()` | Initiates teardown. Cleanup must happen in `onStopSend`. | | Is PS started | `IsPSModeStarted()` | Whether PS mode is currently active. Use for state checks, not as a send gate. | | Get PS user ID | `GetPSUserID()` | User ID of the current PS user. Use for roster correlation. | ### PSVideoSourceCapability `PSVideoSourceCapability` is passed to `StartPSMode`. It declares the exact video format your app will send. All subsequent `sendVideoFrame` calls must match these values. ```cpp PSVideoSourceCapability cap{}; cap.width = 1920; cap.height = 1080; cap.format = FrameDataFormat_I420_FULL; ``` ## Start production studio mode Always check support and permission before calling `StartPSMode`. Call `SetEvent` first. ```cpp class PsEvents : public IMeetingProductionStudioCtrlEvent { ... }; void BeginProductionStudio(IMeetingService* meetingService, PsEvents& events) { IMeetingProductionStudioController* psCtrl = meetingService->GetMeetingProductionStudioController(); if (!psCtrl) return; if (psCtrl->SetEvent(&events) != SDKERR_SUCCESS) return; // Register before starting if (!psCtrl->IsSupportPSMode()) return; // PS unavailable in this meeting if (!psCtrl->CanStartPSMode()) return; // Not host/co-host PSVideoSourceCapability cap{}; cap.width = 1920; cap.height = 1080; cap.format = FrameDataFormat_I420_FULL; SDKError err = psCtrl->StartPSMode(cap); if (err != SDKERR_SUCCESS) { // Handle error — no callbacks will follow } } ``` ## Callbacks ### onStartPSModeResult(bSuccess) Fires after `StartPSMode` is processed by the SDK. - `false` - PS mode failed to start. Do not attempt to send media. Show an error if needed. - `true` - PS mode accepted. Do not send yet. Wait for `onStartSend`. ```cpp void PsEvents::onStartPSModeResult(bool bSuccess) { if (!bSuccess) return; // Accepted — wait for onStartSend } ``` ### onStartSend(sender) Fires when the SDK is ready to receive media. The `sender` pointer is valid only until `onStopSend` fires. - Save the sender and begin your capture/encode loop. - Call `sendVideoFrame` and `sendAudio` only from this point. - Sender is `nullptr` if something went wrong. Make sure that the sender is not null before using. ```cpp void PsEvents::onStartSend(IZoomSDKPSSender* sender) { if (!sender) return; m_sender = sender; StartCapturePipeline(); } ``` ### onStopSend Fires when the SDK requires all sending to stop. This can be triggered by: - Your own `StopPSMode` call. - Meeting end or the local user leaving. - SDK internally stopping PS, like a role change. Always stop sending and release the sender here, regardless of what caused the stop. ```cpp void PsEvents::onStopSend() { StopCapturePipeline(); m_sender = nullptr; } ``` ### onPSUserStatusChanged(userID, bStart) Fires when a PS user in the meeting starts or stops publishing. - `userID` - the user ID of the PS participant. - `bStart true` - that user began PS sending; `false:` stopped. - Use for updating UI, such as showing a **Live** indicator for the PS user or for logging. Does not affect your own send pipeline. Use `onStartSend` or `onStopSend` for that. ```cpp void PsEvents::onPSUserStatusChanged(unsigned int userID, bool bStart) { // bStart true = started, false = stopped } ``` ## Send video and audio Call `sendVideoFrame` and `sendAudio` only after `onStartSend` and before `onStopSend` on a single producer thread. ### sendVideoFrame Width, height, and format must exactly match the `PSVideoSourceCapability` passed to `StartPSMode`. Mismatch returns `SDKERR_INVALID_PARAMETER`. ```cpp SDKError e = sender->sendVideoFrame( buffer, 1920, 1080, 1920 * 1080 * 3 / 2, FrameDataFormat_I420_FULL ); ``` ### sendAudio - `data_length` must be an even number. - `sample_rate` is 32000 or 48000. 48000 is recommended. ```cpp SDKError e = sender->sendAudio(pcmBuffer, 1920, 48000, ZoomSDKAudioChannel_Mono); ``` ## Stop production studio mode ```cpp SDKError err = psCtrl->StopPSMode(); // All cleanup must happen in onStopSend() ``` `StopPSMode` initiates teardown asynchronously. Do not release the sender or stop your capture loop here. Wait for `onStopSend`. ## Use the participant identity `IsProductionStudioUser` and `GetProductionStudioParent` describe who a participant is in the roster. Use them for UI labeling or debugging. They have no connection to the send pipeline. ```cpp IUserInfo* info = participantsCtrl->GetUserByUserID(userId); if (info && info->IsProductionStudioUser()) { unsigned int parent = info->GetProductionStudioParent(); } ``` **NOTE** `GetProductionStudioParent()` only has a meaningful value for PS users. Always guard with `IsProductionStudioUser()` first. ---