# Render user video > The code on this page only works with the **custom UI**. In custom UI mode, the SDK provides multiple options to render user video after a user subscribes to another user's video. After following the steps to render a user's video, each option contains a different type of video stream. - **Single attendee** - A single user's video. - **Active speaker** - A single user's video that automatically updates when the active speaker changes. - **Preview** - A preview of the local user's video. - **Share** - The content being shared by another user. Regardless of which render option you are using, the SDK utilizes three main components. - `INormalVideoRenderElement`, `IActiveVideoRenderElement`, or `IPreviewVideoRenderElement` - Interface for video element rendering in custom UI. - `ICustomizedVideoContainer` - An object that sets up the video view. - `ICustomizedVideoContainerEvent` - A delegate that indicates if a video's subscription fails and more. ## Subscribe to error data Before subscribing to the video, implement `ICustomizedVideoContainerEvent` to know when a video subscription fails. To pass it into the SDK, you'll need a reference to the `ICustomizedVideoContainer` instance being set up. ```cpp #include "customized_ui/customized_ui_mgr.h" #include "customized_ui/customized_video_container.h" class MeetingVideoRenderer : public ZOOM_SDK_NAMESPACE::ICustomizedVideoContainerEvent { public: bool Initialize( ZOOM_SDK_NAMESPACE::ICustomizedUIMgr* ui_mgr, HWND parent_wnd, RECT container_rect) { if (!ui_mgr) { return false; } const auto err = ui_mgr->CreateVideoContainer( &video_container_, parent_wnd, container_rect); if (err != ZOOM_SDK_NAMESPACE::SDKERR_SUCCESS || !video_container_) { return false; } video_container_->SetEvent(this); return true; } void CleanupContainer(ZOOM_SDK_NAMESPACE::ICustomizedUIMgr* ui_mgr) { if (!ui_mgr || !video_container_) { return; } video_container_->SetEvent(nullptr); ui_mgr->DestroyVideoContainer(video_container_); video_container_ = nullptr; } void onSubscribeUserFail( ZOOM_SDK_NAMESPACE::ZoomSDKVideoSubscribeFailReason fail_reason, ZOOM_SDK_NAMESPACE::IVideoRenderElement* element) override { } void onRenderUserChanged( ZOOM_SDK_NAMESPACE::IVideoRenderElement* element, unsigned int user_id) override { } void onRenderDataTypeChanged( ZOOM_SDK_NAMESPACE::IVideoRenderElement* element, ZOOM_SDK_NAMESPACE::VideoRenderDataType data_type) override { } void onLayoutNotification(RECT wnd_client_rect) override { } void onVideoRenderElementDestroyed( ZOOM_SDK_NAMESPACE::IVideoRenderElement* element) override { } void onWindowMsgNotification( UINT u_msg, WPARAM w_param, LPARAM l_param) override { } ZOOM_SDK_NAMESPACE::ICustomizedVideoContainer* video_container_ = nullptr; }; ``` ## Define a video element The primary user-facing video element is `IVideoRenderElement` and is inherited by the components `VideoRenderElement_ACTIVE` for active speaker video, `VideoRenderElement_PRVIEW` for local user preview video, and `VideoRenderElement_NORMAL` for local and remote participant video. Create a new `IVideoRenderElement` instance based on which video element you need and cast it to the interface that matches the stream you want to display. ```cpp RECT video_rect = {0, 0, 640, 360}; ZOOM_SDK_NAMESPACE::IVideoRenderElement* element = nullptr; video_container_->CreateVideoElement( &element, ZOOM_SDK_NAMESPACE::VideoRenderElement_NORMAL); auto* normal_video = dynamic_cast(element); ``` ## Subscribe to video streams Each of the `IVideoRenderElement` instance created by the SDK is managed by the `ICustomizedVideoContainer`. Use these to manage the lifecycle of the video subscription rendered on each video view. To subscribe to a single attendee's video, you must provide the `userID` of the user whose video is being subscribed to. Access this in `IUserInfo::GetUserID()`. ```cpp // To subscribe a participant view RECT video_rect = {0, 0, 640, 360}; ZOOM_SDK_NAMESPACE::IVideoRenderElement* element = nullptr; video_container_->CreateVideoElement( &element, ZOOM_SDK_NAMESPACE::VideoRenderElement_NORMAL); auto* attendee_video = dynamic_cast(element); if (attendee_video) { attendee_video->SetPos(video_rect); attendee_video->SetResolution(ZOOM_SDK_NAMESPACE::VideoRenderResolution_360p); unsigned int user_id = user_info->GetUserID(); attendee_video->Subscribe(user_id); attendee_video->Show(); } // To subscribe to the active speaker view RECT active_rect = {0, 0, 640, 360}; ZOOM_SDK_NAMESPACE::IVideoRenderElement* element = nullptr; video_container_->CreateVideoElement( &element, ZOOM_SDK_NAMESPACE::VideoRenderElement_ACTIVE); auto* active_video = dynamic_cast(element); if (active_video) { active_video->SetPos(active_rect); active_video->Start(); } // To subscribe to the local user preview view RECT preview_rect = {0, 0, 320, 180}; ZOOM_SDK_NAMESPACE::IVideoRenderElement* element = nullptr; video_container_->CreateVideoElement( &element, ZOOM_SDK_NAMESPACE::VideoRenderElement_PRVIEW); auto* preview_video = dynamic_cast(element); if (preview_video) { preview_video->SetPos(preview_rect); preview_video->Start(); } ``` ## Manage existing video streams To resize or set resolution to any of the video element, update its position with `SetPos`. To request a specific stream resolution, call `SetResolution`. The SDK may call `onLayoutNotification` when the container layout changes. When that happens, recalculate your `RECT` values and reposition the render elements. ```cpp RECT new_rect = {20, 20, 420, 260}; video_element->SetPos(new_rect); video_element->SetResolution(ZOOM_SDK_NAMESPACE::VideoRenderResolution_360p); Finally, when a render element is no longer being used, end the subscription and remove the video view. if (attendee_video) { const unsigned int user_id = attendee_video->GetCurrentRenderUserId(); if (user_id != 0) { attendee_video->Unsubscribe(user_id); } attendee_video->Hide(); video_container_->DestroyVideoElement(attendee_video); attendee_video = nullptr; } if (active_video) { active_video->Stop(); video_container_->DestroyVideoElement(active_video); active_video = nullptr; } if (preview_video) { preview_video->Stop(); video_container_->DestroyVideoElement(preview_video); preview_video = nullptr; } ```