# 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 SDK's `PSSender`, instead of - or in addition to - a normal camera or mic path. On Android, the SDK only provides observer support. Your app can query PS state and receive PS status callbacks, but can't start production studio mode or push media. Starting and sending must be done from a macOS or Windows host. ## Get the controller Get the controller from `InMeetingService` after joining a meeting. ```kotlin val inMeetingService = ZoomSDK.getInstance().inMeetingService val psCtrl = inMeetingService.inMeetingProductionStudioController ``` ```java InMeetingService inMeetingService = ZoomSDK.getInstance().getInMeetingService(); InMeetingProductionStudioController psCtrl = inMeetingService.getInMeetingProductionStudioController(); ``` ## Controller APIs All APIs are available on `InMeetingProductionStudioController`. | API | Method | Description | | ------------------- | -------------------------- | ------------------------------------------------------------- | | Register callback | `addListener(listener)` | Register a ProductionStudioCtrlListener to receive PS events. | | Unregister callback | `removeListener(listener)` | Unregister the listener when no longer needed. | | Check support | `isSupportPSMode()` | Whether PS is available in this meeting or client. | | Is PS started | `isPSModeStarted()` | Whether PS mode is currently active. | | Get PS user ID | `getPSUserID()` | User ID of the current PS user. Returns 0 if no PS user. | ## Callbacks Implement `ProductionStudioCtrlListener` and register it via `addListener`. ### onPSUserStatusChanged(userId, bStart) Fires when a production studio user in the meeting starts or stops publishing. - `userId`- the production studio participant's user ID. - `bStart true` - that user began production studio sending. false means that it stopped. - Use for updating UI like showing a **Live** indicator, or correlating with the roster. ```kotlin val psListener = object : ProductionStudioCtrlListener() { override fun onPSUserStatusChanged(userId: Long, bStart: Boolean) { if (bStart) { // userId is now publishing via PS } else { // userId stopped PS publishing } } } psCtrl.addListener(psListener) ``` ```java ProductionStudioCtrlListener psListener = new ProductionStudioCtrlListener() { @Override public void onPSUserStatusChanged(long userId, boolean bStart) { if (bStart) { // userId is now publishing via PS } else { // userId stopped PS publishing } } }; psCtrl.addListener(psListener); ``` ## Use the participant identity On `InMeetingUserInfo`, `isProductionStudioUser()` and `getProductionStudioParent()` describe whether a roster participant is a production studio user. Use them for UI labeling or debugging. Get user information via `InMeetingService`. ```kotlin val userInfo = inMeetingService.getUserInfoById(userId) if (userInfo != null && userInfo.isProductionStudioUser()) { val parentId = userInfo.productionStudioParent } ``` ```java InMeetingUserInfo userInfo = inMeetingService.getUserInfoById(userId); if (userInfo != null && userInfo.isProductionStudioUser()) { long parentId = userInfo.getProductionStudioParent(); } ``` **NOTE** `getProductionStudioParent()` only has a meaningful value for production studio users. Always check with `isProductionStudioUser()` first. ---