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.
  2. Check support for isSupportPSMode and permission for canStartPSMode, then register for events.
  3. Call StartPSMode with a video capability declaration including width, height, and format. This is the contract for all subsequent sendVideoFrame` calls.
  4. Wait for onStartPSModeResult(true), then wait for onStartSend before pushing any media.
  5. Push video frames and audio buffers only while onStartSend is active, using IZoomSDKPSSender.
  6. 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.

#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.

APIMethodDescription
Register callbackSetEvent(pEvent)Must call before StartPSMode. Returns SDKERR_SUCCESS on success.
Check supportIsSupportPSMode()Whether PS is available in this meeting or client. Check before showing any PS UI.
Check permissionCanStartPSMode()Whether the current user may start PS. Host or co-host only.
Start PSStartPSMode(cap)Declares the video capability contract. Asynchronous - result comes in onStartPSModeResult.
Stop PSStopPSMode()Initiates teardown. Cleanup must happen in onStopSend.
Is PS startedIsPSModeStarted()Whether PS mode is currently active. Use for state checks, not as a send gate.
Get PS user IDGetPSUserID()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.

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.

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.
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.
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.

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.

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.

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.
SDKError e = sender->sendAudio(pcmBuffer, 1920, 48000, ZoomSDKAudioChannel_Mono);

Stop production studio mode

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.

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.