# Use production studio mode

<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

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

---
