Setting the file. One moment.
Skill 126 · Zoom Meeting SDK Windows
Subchapter 126.13
examples/custom-ui-video-rendering.mdMarkdown8 KBView on GitHub
Skill: Zoom Meeting SDK (Windows)
Category: Examples : ,
This example shows how to create a Custom UI meeting app that:
ICustomizedVideoContainer for SDK-rendered videoICustomizedShareRenderInitSDK (with ENABLE_CUSTOMIZED_UI_FLAG)
-> AuthSDK (JWT)
-> JoinMeeting
-> OnConnecting: Create window + CustomUIMgr + VideoContainer
-> OnInMeeting: Create video elements + subscribe to participants
-> Message loop (window events + SDK callbacks)
-> OnEnded: Destroy everythingInitParam initParam;
initParam.strWebDomain = L"https://zoom.us";
initParam.emLanguageID = LANGUAGE_English;
initParam.enableLogByDefault = true;
// CRITICAL: This is what makes it Custom UI mode
initParam.obConfigOpts.optionalFeatures = ENABLE_CUSTOMIZED_UI_FLAG;
SDKError err = InitSDK(initParam);When onMeetingStatusChanged fires with MEETING_STATUS_CONNECTING, create your window and the Custom UI manager:
#include <customized_ui/zoom_customized_ui.h>
#include <customized_ui/customized_ui_mgr.h>
#include <customized_ui/customized_video_container.h>
#include <customized_ui/customized_share_render.h>
ICustomizedUIMgr* pCustomUIMgr = nullptr;
ICustomizedVideoContainer* pVideoContainer = nullptr;
// Create the manager (global SDK function)
SDKError err = CreateCustomizedUIMgr(&pCustomUIMgr);
// Optional: check license (log warning, don't abort)
err = pCustomUIMgr->HasLicense();
if (err != SDKERR_SUCCESS) {
std::cout << "WARNING: HasLicense returned " << err << std::endl;
}
// Register for destroy notifications
pCustomUIMgr->SetEvent(&myUIMgrEventListener);
// Create video container inside your Win32 window
RECT rc;
::GetClientRect(hMyWindow, &rc);
err = pCustomUIMgr->CreateVideoContainer(&pVideoContainer, hMyWindow, rc);
pVideoContainer->SetEvent(&myVideoContainerEventListener);
pVideoContainer->Show();
pVideoContainer->SetBkColor(RGB(30, 30, 30)); // Dark backgroundWhen onMeetingStatusChanged fires with MEETING_STATUS_INMEETING:
IVideoRenderElement* pElement = nullptr;
err = pVideoContainer->CreateVideoElement(&pElement, VideoRenderElement_ACTIVE);
IActiveVideoRenderElement* pActive = dynamic_cast<IActiveVideoRenderElement*>(pElement);
RECT activeRect = { 0, 0, windowWidth, (int)(windowHeight * 0.7) };
pActive->SetPos(activeRect);
pActive->Show();
pActive->Start(); // Begin auto-tracking active speakerIMeetingParticipantsController* pParticipants =
pMeetingService->GetMeetingParticipantsController();
IList<unsigned int>* pUserList = pParticipants->GetParticipantsList();
for (int i = 0; i < pUserList->GetCount() && i < MAX_GALLERY; i++) {
unsigned int userId = pUserList->GetItem(i);
IVideoRenderElement* pNormElement = nullptr;
err = pVideoContainer->CreateVideoElement(&pNormElement, VideoRenderElement_NORMAL);
INormalVideoRenderElement* pNormal =
dynamic_cast<INormalVideoRenderElement*>(pNormElement);
pNormal->Subscribe(userId);
pNormal->SetResolution(VideoRenderResolution_360p);
pNormal->Show();
// Position in gallery strip
int elemWidth = windowWidth / galleryCount;
RECT r = { i * elemWidth, galleryTop, (i+1) * elemWidth, windowHeight };
pNormal->SetPos(r);
}Respond to onLayoutNotification and WM_SIZE to re-layout elements:
void LayoutVideoElements() {
RECT clientRect;
::GetClientRect(hMyWindow, &clientRect);
int totalWidth = clientRect.right - clientRect.left;
int totalHeight = clientRect.bottom - clientRect.top;
// Resize container to fill window
pVideoContainer->Resize(clientRect);
if (galleryElements.empty()) {
// Active speaker only — full window
RECT activeRect = { 0, 0, totalWidth, totalHeight };
pActiveElement->SetPos(activeRect);
} else {
// Active speaker: top 70%, gallery: bottom 30%
int activeHeight = (int)(totalHeight * 0.7);
RECT activeRect = { 0, 0, totalWidth, activeHeight };
pActiveElement->SetPos(activeRect);
int elemWidth = totalWidth / (int)galleryElements.size();
for (int i = 0; i < galleryElements.size(); i++) {
RECT r = { i * elemWidth, activeHeight, (i+1) * elemWidth, totalHeight };
galleryElements[i]->SetPos(r);
}
}
}// Create share render (hidden until someone shares)
ICustomizedShareRender* pShareRender = nullptr;
RECT rc;
::GetClientRect(hMyWindow, &rc);
pCustomUIMgr->CreateShareRender(&pShareRender, hMyWindow, rc);
pShareRender->SetEvent(&myShareEventListener);
pShareRender->Hide();
// In ShareRenderEventListener:
void onSharingSourceNotification(unsigned int nShareSourceID) {
if (nShareSourceID > 0) {
pShareRender->SetShareSourceID(nShareSourceID);
pShareRender->SetViewMode(CSM_FULLFILL);
pShareRender->Show();
} else {
pShareRender->Hide();
}
}void Cleanup() {
if (pVideoContainer) {
pVideoContainer->DestroyAllVideoElement();
pCustomUIMgr->DestroyVideoContainer(pVideoContainer);
pVideoContainer = nullptr;
}
if (pShareRender) {
pCustomUIMgr->DestroyShareRender(pShareRender);
pShareRender = nullptr;
}
if (pCustomUIMgr) {
DestroyCustomizedUIMgr(pCustomUIMgr);
pCustomUIMgr = nullptr;
}
if (hMyWindow) {
DestroyWindow(hMyWindow);
hMyWindow = nullptr;
}
}See Interface Methods Reference for the full list of required virtual methods for:
ICustomizedUIMgrEvent (3 methods)ICustomizedVideoContainerEvent (6 methods)ICustomizedShareRenderEvent (3 methods)#include <windows.h>
#include <cstdint>
#include <zoom_sdk.h>
#include <customized_ui/zoom_customized_ui.h> // CreateCustomizedUIMgr()
#include <customized_ui/customized_ui_mgr.h> // ICustomizedUIMgr, ICustomizedUIMgrEvent
#include <customized_ui/customized_video_container.h> // ICustomizedVideoContainer, elements
#include <customized_ui/customized_share_render.h> // ICustomizedShareRender
#include <meeting_service_interface.h>
#include <meeting_service_components/meeting_audio_interface.h> // Before participants!
#include <meeting_service_components/meeting_participants_ctrl_interface.h>Start() — Show() alone is not enough, you must call Start() to begin active speaker trackingSubscribe(userId) — without this they show nothingSetPos() coordinates are relative to container, not screen or parent windowResize() the container when window resizes — or the D3D surface won’t match the windowSee also: