Setting the file. One moment.
Subchapter 150.17
examples/video-rendering.mdMarkdown13 KBView on GitHub
Complete working code for subscribing to and displaying video using the Canvas API.
The Canvas API lets the SDK render video directly to your window. This is the recommended approach for standard video applications.
┌─────────────────────────────────────────────────────────────────┐
│ VIDEO SUBSCRIPTION FLOW │
├─────────────────────────────────────────────────────────────────┤
│ onSessionJoin → Subscribe to self video │
│ onUserVideoStatusChanged → Subscribe to remote video (when ON)│
│ onUserLeave → Unsubscribe and cleanup │
└─────────────────────────────────────────────────────────────────┘// WRONG - Video may not be ready yet!
void onUserJoin(IZoomVideoSDKUserHelper* helper,
IVideoSDKVector<IZoomVideoSDKUser*>* userList) override {
for (int i = 0; i < userList->GetCount(); i++) {
IZoomVideoSDKUser* user = userList->GetItem(i);
// This returns Error 2 (Internal_Error) - video not ready!
user->GetVideoCanvas()->subscribeWithView(hwnd, aspect, resolution);
}
}
// CORRECT - Wait for video status change
void onUserVideoStatusChanged(IZoomVideoSDKVideoHelper* helper,
IVideoSDKVector<IZoomVideoSDKUser*>* userList) override {
IZoomVideoSDKUser* myself = g_sdk->getSessionInfo()->getMyself();
for (int i = 0; i < userList->GetCount(); i++) {
IZoomVideoSDKUser* user = userList->GetItem(i);
// Skip self (handled separately)
if (user == myself) continue;
// Check if video is actually on
ZoomVideoSDKVideoStatus status = user->GetVideoPipe()->getVideoStatus();
if (status.isOn) {
// NOW it's safe to subscribe
SubscribeToUser(user);
} else {
// Video turned off - unsubscribe
UnsubscribeFromUser(user);
}
}
}#pragma once
#include <windows.h>
#include <map>
#include <vector>
#include "zoom_video_sdk_interface.h"
USING_ZOOM_VIDEO_SDK_NAMESPACE
class VideoCanvasManager {
public:
VideoCanvasManager(HWND parentWindow, IZoomVideoSDK* sdk);
~VideoCanvasManager();
// Subscribe to self video
void SubscribeToSelf();
// Subscribe/unsubscribe to remote user
void SubscribeToUser(IZoomVideoSDKUser* user);
void UnsubscribeFromUser(IZoomVideoSDKUser* user);
// Cleanup
void UnsubscribeAll();
// Layout
void UpdateLayout();
private:
HWND CreateVideoWindow();
void DestroyVideoWindow(HWND hwnd);
IZoomVideoSDK* m_sdk;
HWND m_parentWindow;
// Self video
HWND m_selfVideoWindow;
IZoomVideoSDKCanvas* m_selfCanvas;
// Remote users: user -> window mapping
std::map<IZoomVideoSDKUser*, HWND> m_userWindows;
std::map<IZoomVideoSDKUser*, IZoomVideoSDKCanvas*> m_userCanvases;
};#include "VideoCanvasManager.h"
#include <iostream>
VideoCanvasManager::VideoCanvasManager(HWND parentWindow, IZoomVideoSDK* sdk)
: m_parentWindow(parentWindow)
, m_sdk(sdk)
, m_selfVideoWindow(nullptr)
, m_selfCanvas(nullptr) {
}
VideoCanvasManager::~VideoCanvasManager() {
UnsubscribeAll();
}
class MyDelegate : public IZoomVideoSDKDelegate {
private:
VideoCanvasManager* m_videoManager;
public:
MyDelegate(HWND parentWindow, IZoomVideoSDK* sdk) {
m_videoManager = new VideoCanvasManager(parentWindow, sdk);
}
~MyDelegate() {
delete m_videoManager;
}
void onSessionJoin() override {
// Subscribe to self video
m_videoManager->SubscribeToSelf();
}
void onUserVideoStatusChanged(IZoomVideoSDKVideoHelper* helper,
IVideoSDKVector<IZoomVideoSDKUser*>* userList) override {
IZoomVideoSDKUser* myself = g_sdk->getSessionInfo()->getMyself();
for (int i = 0; i < userList->GetCount(); i++) {
IZoomVideoSDKUser* user = userList->GetItem(i);
if (user == myself) continue;
ZoomVideoSDKVideoStatus status = user->GetVideoPipe()->getVideoStatus();
if (status.isOn) {
m_videoManager->SubscribeToUser(user);
} else {
m_videoManager->UnsubscribeFromUser(user);
}
}
}
void onUserLeave(IZoomVideoSDKUserHelper* helper,
IVideoSDKVector<IZoomVideoSDKUser*>* userList) override {
for (int i = 0; i < userList->GetCount(); i++) {
m_videoManager->UnsubscribeFromUser(userList->GetItem(i));
}
}
void onSessionLeave() override {
m_videoManager->UnsubscribeAll();
}
// ... other callbacks
};void onVideoCanvasSubscribeFail(ZoomVideoSDKSubscribeFailReason reason,
IZoomVideoSDKUser* user, void* handle) override {
std::wcout << L"Subscribe failed for: " << user->getUserName()
<< L" reason: " << reason << std::endl;
switch (reason) {
case ZoomVideoSDKSubscribeFailReason_HasSubscribe1080POr720P:
std::cout << "Already have a 1080p/720p subscription" << std::endl;
break;
case ZoomVideoSDKSubscribeFailReason_HasSubscribeExceededLimit:
std::cout << "Subscription limit exceeded" << std::endl;
break;
case ZoomVideoSDKSubscribeFailReason_TooFrequentCall:
std::cout << "Calling too frequently - add Sleep(200)" << std::endl;
break;
}
}| Code | Reason | Solution |
|---|---|---|
| 0 | None | - |
| 1 | HasSubscribe1080POr720P | Already have HD subscription |
| 2 | HasSubscribeTwo720P | Max 2x 720p subscriptions |
| 3 | HasSubscribeExceededLimit | Too many subscriptions |
| 4 | HasSubscribeTwoShare | Max 2 share subscriptions |
| 5 | HasSubscribeVideo1080POr720PAndOneShare | Limit reached |
| 6 | TooFrequentCall | Add Sleep(200) between calls |
| Option | Behavior | Use Case |
|---|---|---|
ZoomVideoSDKVideoAspect_Original | Letterbox/pillarbox | Show full video |
ZoomVideoSDKVideoAspect_FullFilled | Fill, may crop | Full coverage |
ZoomVideoSDKVideoAspect_PanAndScan | Smart crop | Balanced (recommended) |
ZoomVideoSDKVideoAspect_LetterBox | Black bars | Preserve aspect |
| Option | Resolution | Use Case |
|---|---|---|
ZoomVideoSDKResolution_90P | 160x90 | Thumbnails |
ZoomVideoSDKResolution_180P | 320x180 | Small previews |
ZoomVideoSDKResolution_360P | 640x360 | Standard |
ZoomVideoSDKResolution_720P | 1280x720 | HD |
ZoomVideoSDKResolution_1080P | 1920x1080 | Full HD |
ZoomVideoSDKResolution_Auto | SDK chooses | Recommended |
TL;DR: Subscribe to self in onSessionJoin, subscribe to remote users in onUserVideoStatusChanged (when status.isOn == true), unsubscribe in onUserLeave.