-
Notifications
You must be signed in to change notification settings - Fork 395
Multistream Comprehensive Guide
[Work in progress...]
Developers have a variety of options to customize how they choose to display remote videos with multistream media.
The initial step in utilizing Multistream is to include enableMultistream within the joinOptions parameter for both the meeting.join() and meeting.joinWithMedia() methods.
e.g.
const joinOptions = {
enableMultistream: true,
...
};
await meeting.join(joinOptions);
// or
await meeting.joinWithMedia({joinOptions, mediaOptions});Once the media is added, we must invoke the meeting.publishStream() function.
Presently, the entire document is segmented into three primary categories:
- Layouts: Determining the preferred visualization of remote streams.
- Event Listeners: Monitoring all remote streams and additional events.
- Methods and RemoteMediaManager: Facilitating layout updates and other operations.
TODO
When referring to "layout," we are discussing the arrangement of remote streams on the user interface. Multistream provides the freedom to design the layout according to your preferences.
The arguments for addMedia() remain unchanged. For further details, please refer to this link.
A new property, remoteMediaManagerConfig, has been introduced to the AddMediaOptions for multistream functionality.
If no value is provided for remoteMediaManagerConfig, the default settings are as follows. It's worth noting that to implement a custom layout, you can utilize the same data format as demonstrated in the predefined layouts.
Below is the default configuration value for the remote media manager.
const remoteMediaManagerConfig = {
audio: {
numOfActiveSpeakerStreams: 3,
numOfScreenShareStreams: 1,
},
video: {
preferLiveVideo: true,
initialLayoutId: 'AllEqual',
layouts: {
AllEqual: AllEqualLayout,
OnePlusFive: OnePlusFiveLayout,
Single: SingleLayout,
Stage: Stage2x2With6ThumbnailsLayout,
ScreenShareView: RemoteScreenShareWithSmallThumbnailsLayout,
}
}
}The predefined layouts for the default configuration are outlined below.
| Type | Description |
|---|---|
AllEqual (max 9) |
All equal |
OnePlusFive |
One big pane + five small |
Single |
Single Video pane |
Stage |
Stage with thumbnails |
ScreenShareView |
Screen share with thumbnails |
Let's delve into the visualization of all the predefined layouts.
- AllEqual
- OnePlusFive

- Single

- Stage

- ScreenShareView

Below are code samples demonstrating the predefined layouts.
// An "all equal" grid, with size up to 3 x 3 = 9:
const AllEqualLayout: VideoLayout = {
activeSpeakerVideoPaneGroups: [
{
id: 'main',
numPanes: 9,
size: 'best',
priority: 255,
},
],
};
// A layout with 1 big pane for the highest priority active speaker and 5 small panes for other active speakers:
const OnePlusFiveLayout: VideoLayout = {
activeSpeakerVideoPaneGroups: [
{
id: 'mainBigOne',
numPanes: 1,
size: 'large',
priority: 255,
},
{
id: 'secondarySetOfSmallPanes',
numPanes: 5,
size: 'very small',
priority: 254,
},
],
};
// A layout with just a single remote active speaker video pane:
const SingleLayout: VideoLayout = {
activeSpeakerVideoPaneGroups: [
{
id: 'main',
numPanes: 1,
size: 'best',
priority: 255,
},
],
};
// A staged layout with 4 pre-selected meeting participants in the main 2x2 grid and 6 small panes for other active speakers at the top:
const Stage2x2With6ThumbnailsLayout: VideoLayout = {
activeSpeakerVideoPaneGroups: [
{
id: 'thumbnails',
numPanes: 6,
size: 'thumbnail',
priority: 255,
},
],
memberVideoPanes: [
{id: 'stage-1', size: 'medium', csi: undefined},
{id: 'stage-2', size: 'medium', csi: undefined},
{id: 'stage-3', size: 'medium', csi: undefined},
{id: 'stage-4', size: 'medium', csi: undefined},
],
};
// A strip of 8 small video panes (thumbnails) displayed at the top of a remote screenshare:
const RemoteScreenShareWithSmallThumbnailsLayout: VideoLayout = {
screenShareVideo: {size: 'best'},
activeSpeakerVideoPaneGroups: [
{
id: 'thumbnails',
numPanes: 8,
size: 'thumbnail',
priority: 255,
},
],
};{
audio: {
numOfActiveSpeakerStreams: number; // number of audio streams we want to receive
numOfScreenShareStreams: number; // 1 should be enough, because in webex only 1 person at a time can be presenting screen share
};
video: {
preferLiveVideo: boolean; // applies to all pane groups with active speaker policy
initialLayoutId: LayoutId;
layouts: {[key: LayoutId]: VideoLayout}; // a map of all available layouts, a layout can be set via setLayout() method
};
}Properties of audio object
| Name | Description | Type |
|---|---|---|
| numOfActiveSpeakerStreams | The maximum number of speakers you can hear simultaneously. | number |
| numOfScreenShareStreams | The maximum number of screenshare streams (one should suffice, as only one person can present at a time in Webex). | number |
Properties of video object
| Name | Description | Type |
|---|---|---|
| preferLiveVideo | If set to true, you only receive the video stream of participants who have activated their video. | boolean |
| initialLayoutId | One of the keys defined within the layouts object. | string |
| layouts | Key-Value pairs where the value determines the layout (Additional details provided in the following table). | object |
Properties of VideoLayout object
| Name | Description | Type |
|---|---|---|
| screenShareVideo | Defines the screenshare stream. | { size: PaneSize } |
| activeSpeakerVideoPaneGroups | Defines the active speaker panes | Array<ActiveSpeakerPaneObject> |
| memberVideoPanes | Defines the member video panes | Array<MemberPaneObject> |
PaneSize can take the following values:
| PaneSize | Description |
|---|---|
| thumbnail | The smallest possible resolution, 90p or less |
| very small | 180p or less |
| small | 360p or less |
| medium | 720p or less |
| large | 1080p or less |
| best | Highest possible resolution |
Properties of ActiveSpeakerPaneObject
| Name | Description | Type |
|---|---|---|
| id | Arbitrary value | string |
| numPanes | Number of streams that you will receive | number |
| size | Size of each stream | PaneSize (string) |
| priority | Priority of the Streams | number (0-255) |
Properties of MemberPaneObject
| Name | Description | Type |
|---|---|---|
| id | Arbitrary value (e.g. stage-1) |
string |
| size | Size of the stream | PaneSize (string) |
| csi | It serves as a unique identifier for a stream (When a client generates a stream, it assigns a CSI value to it). | number |
Note:
Member video panes are exclusively accessible within the stage layout, allowing for the pinning of specific participants' positions in the layout. The stage layout is instantiated when
memberVideoPanesis included in the configuration of layouts. In this scenario, developers are responsible for managing and displaying participants as they join or leave, contrasting withactiveSpeakerVideoPaneGroups, where a consistent list of streams featuring active speakers/participants is always received. Additionally, the active speaker video pane can be pinned. Therefore, participants can be pinned either usingmemberVideoPanesor by invokingpinActiveSpeakerVideoPane(remoteMedia, csi)foractiveSpeakerVideoPaneGroups.Each member/participant can have up to four CSI values for each stream type (audio, video, screenshareVideo, screenshareAudio). Furthermore, for a single stream type like video, multiple CSIs may exist depending on the number of devices a participant has joined with.
In most scenarios, you'll primarily utilize the
activeSpeakerVideoPaneGroups.
Events can be listened to using the meeting.on() method.
Below is a sample code demonstrating how to listen for events.
meeting.on('[event name]', (data) => {
console.log(data);
});e.g.
meeting.on('media:remoteVideo:layoutChanged', ({
layoutId, activeSpeakerVideoPanes, memberVideoPanes, screenShareVideo
}) => {
console.log('layoutId: ', layoutId);
console.log('activeSpeakerVideoPanes:', activeSpeakerVideoPanes);
console.log('memberVideoPanes:', memberVideoPanes);
console.log('screenShareVideo:', screenShareVideo);
});Note: The event
media:remoteVideo:layoutChangedwill be triggered with theinitialLayoutIdthe first time, even if there are no changes in the layout.
| Event name | Data Received |
|---|---|
media:remoteAudio:created |
List of audio streams. These streams remain unchanged regardless of any layout changes. |
media:remoteScreenShareAudio:created |
Object representing the screen share audio media group |
media:remoteVideo:layoutChanged |
{ layoutId, activeSpeakerVideoPanes, memberVideoPanes, screenShareVideo } |
media:remoteVideoSourceCountChanged |
Number of remote video sources - { numTotalSource, numLiveSources }
|
media:remoteAudioSourceCountChanged |
Number of remote audio sources - { numTotalSource, numLiveSources }
|
media:activeSpeakerChanged |
List of member IDs for active speakers - { memberIds }
|
To obtain the list of remote media elements from the group, you must invoke getRemoteMedia().
const remoteMedia = screenShareAudioMediaGroup.getRemoteMedia()[0];
const remoteMediaStream = remoteMedia.stream;Asynchronous: No
Parameters
| Name | Description | Type | Mandatory |
|---|---|---|---|
| filter | A filtering string to retrieve a particular type of remote media. |
all (Default), pinned, unpinned
|
No |
Returns: Array<RemoteMedia>
| Data Received | Description |
|---|---|
| layoutId |
AllEqual, OnePlusFive, Single, Stage or, ScreenShareView
|
| activeSpeakerVideoPanes | [groupId, groupRemoteMedia] |
| memberVideoPanes | [paneId, remoteMedia] |
| screenShareVideo | RemoteMedia - { id, stream, sourceState (no source, live) , memberId } |
Note: The
layoutIdlist displayed here corresponds to those defined in the configuration. These IDs may vary depending on your configuration settings.
| Data Received | Description |
|---|---|
| numTotalSource | Total number of all video sources |
| numLiveSources | Total number of live video sources |
| Data Received | Description |
|---|---|
| numTotalSource | Total number of all audio sources |
| numLiveSources | Total number of live audio sources |
| Event name | Description |
|---|---|
meeting:stoppedSharingLocal |
TODO |
meeting:startedSharingRemote |
TODO |
meeting:stoppedSharingRemote |
TODO |
The remoteMedia received may contain streams from various participants at different times. It dynamically changes as other participants begin speaking. Therefore, to accurately display the corresponding name/data alongside the stream, the memberId is also provided within the remoteMedia object. This memberId can be utilized to retrieve the name.
Events can be listened to using the remoteMedia.on() method.
e.g.
remoteMedia.on('[Event name]', (data) => {
console.log(data);
});| Event name | Description |
|---|---|
sourceUpdate |
This event is triggered when the source of the remoteMedia changes, such as when another participant begins speaking. |
stopped |
This event is triggered when the participant has deactivated their camera. |
With sourceUpdate, you can inspect the memberId and sourceState to obtain the most recent information about the remoteMedia.
Information received during the sourceUpdate event.
| Data Received | Description |
|---|---|
| 'no source' | There is no video. |
| 'invalid source' | Source is invalid. |
| 'live' | The video is available. |
| 'avatar' | Camera is muted or didn't have any video. Avatar can be shown. |
| 'bandwidth disabled' | There is not enough bandwidth to show video. Avatar can be shown. |
| 'policy violation' | Restriction due to policy where they can not receive the video. |
There exist multiple methods for managing various scenarios and updating the layout.
const csiList = meeting.members.getCsisForMember(memberId, mediaType='video', mediaContent='main');Asynchronous: No
Parameters
| Name | Description | Type | Mandatory |
|---|---|---|---|
| memberId | Member's ID | string | Yes |
| mediaType | The media type |
audio, video (Default) |
No |
| mediaContent | For what type of media content |
main (Default), slides
|
No |
Returns: Array<number>
const member = meeting.members.findMemberByCsi(csi);Asynchronous: No
Parameters:
| Name | Description | Type | Mandatory |
|---|---|---|---|
| csi | CSI number linked to the stream | number | Yes |
Returns: Member
In multistream meetings, the current API meeting.addMedia() will solely establish the media connection without publishing any tracks. However, it will configure streams to receive remote media based on the provided remoteMediaManagerConfig as one of the options.
The Meeting.addMedia() method accepts an optional configuration object to set up the RemoteMediaManager.
meeting.addMedia({
...,
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration;
})RemoteMediaManager configuration object
{
audio: {
numOfActiveSpeakerStreams: number; // number of audio streams we want to receive
numOfScreenShareStreams: number; // 1 should be enough, because in webex only 1 person at a time can be presenting screen share
};
video: {
preferLiveVideo: boolean; // applies to all pane groups with active speaker policy
initialLayoutId: LayoutId;
layouts: {[key: LayoutId]: VideoLayout}; // a map of all available layouts, a layout can be set via setLayout() method
};
}Note: This object's details are outlined in the Explanation of the RemoteMediaManager Object section.
meeting.remoteMediaManager.setRemoteVideoCsi(remoteMedia, csi);Asynchronous: No
Parameters:
| Name | Description | Type | Mandatory |
|---|---|---|---|
| remoteMedia | RemoteMedia object | RemoteMedia | Yes |
| csi | CSI number assiciated with the stream | number | Yes |
Returns: void
const remoteMedia = await meeting.remoteMediaManager.addMemberVideoPane(
{ id: PaneId; size: PaneSize; csi?: CSI; }
);Asynchronous: Yes
Parameters: An object with following parameters
| Name | Description | Type | Mandatory |
|---|---|---|---|
| id | Pane ID | string | Yes |
| size | Size of the video pane |
thumbnail, very small, small, medium, large, best
|
Yes |
| csi | CSI number assiciated with the stream | number | No |
Returns: Promise<RemoteMedia>
const layoutId = meeting.remoteMediaManager?.getLayoutId();Asynchronous: No
Parameters: No
Returns: string
Calling this method will trigger the VideoLayoutChanged event. TODO: Verify the event name
await meeting.remoteMediaManager.setLayout(layoutId);Asynchronous: Yes
Parameters:
| Name | Description | Type | Mandatory |
|---|---|---|---|
| layoutId | ID of the defined layout | string | Yes |
Returns: Promise<void>
meeting.remoteMediaManager.setPreferLiveVideo(preferLiveVideo);Asynchronous: No
Parameters:
| Name | Description | Type | Mandatory |
|---|---|---|---|
| preferLiveVideo | Enabling this option will exclusively retrieve streams with active video. | boolean | Yes |
Returns - void
meeting.remoteMediaManager.setActiveSpeakerCsis(remoteMediaCsis);Asynchronous: No
Parameters: An array of object with the following properties. i.e. Array<{ remoteMedia, csi }>
| Name | Description | Type | Mandatory |
|---|---|---|---|
| remoteMedia | The remote media object | RemoteMedia | Yes |
| csi | CSI number assiciated with the stream | number | No |
Returns: void
Note: For each entry in the remoteMediaCsis array:
- if csi is specified, the RemoteMedia instance is pinned to that CSI
- if csi is undefined, the RemoteMedia instance gets unpinned
meeting.remoteMediaManager.setRemoteVideoCsi(remoteMedia, csi);Asynchronous: No
Parameters:
| Name | Description | Type | Mandatory |
|---|---|---|---|
| remoteMedia | The remote media object to be altered | RemoteMedia | Yes |
| csi | A new CSI value. It can be null if you wish to stop receiving media. |
number, null
|
Yes |
Returns - void
const remoteMedia = await meeting.remoteMediaManager.addMemberVideoPane(newPane);Asynchronous: Yes
Parameters: An object with the following properties
| Name | Description | Type | Mandatory |
|---|---|---|---|
| id | Pane ID | string | Yes |
| size | Pane size |
thumbnail, very small, small, medium, large, best
|
Yes |
| csi | A new CSI value | number | No |
Returns - Promise<RemoteMedia>
Note: Changes to the layout are lost after a layout change.
await meeting.remoteMediaManager.removeMemberVideoPane(paneId);Asynchronous: Yes
Parameters: An object with the following properties
| Name | Description | Type | Mandatory |
|---|---|---|---|
| paneId | Pane ID | string | Yes |
Returns - Promise<void>
Note: Changes to the layout are lost after a layout change.
meeting.remoteMediaManager.pinActiveSpeakerVideoPane(remoteMedia, csi);Pins an active speaker remote media object to the given CSI value. From that moment onwards the remote media will only play audio/video from that specific CSI until unpinActiveSpeakerVideoPane() is called or current layout is changed.
Asynchronous: No
Parameters: An object with the following properties
| Name | Description | Type | Mandatory |
|---|---|---|---|
| remoteMedia | Remote media object reference | RemoteMedia | Yes |
| csi | CSI value to pin to, if undefined, then current CSI value is used | number | No |
Returns - void
meeting.remoteMediaManager.unpinActiveSpeakerVideoPane(remoteMedia);Asynchronous: No
Parameters:
| Name | Description | Type | Mandatory |
|---|---|---|---|
| remoteMedia | Remote media object reference | RemoteMedia | Yes |
Returns - void
const isPinned = meeting.remoteMediaManager.isPinned(remoteMedia);Asynchronous: No
Parameters:
| Name | Description | Type | Mandatory |
|---|---|---|---|
| remoteMedia | Remote media object reference | RemoteMedia | Yes |
Returns - boolean
Note: Throws an error if the remote media object doesn't belong to any active speaker remote media group.
If the client is displaying remote video on screens of various sizes and occasionally requires higher or lower resolution video for proper display or bandwidth conservation, they can specify a size hint. Upon receiving this hint, the remote will begin transmitting the video in the requested resolution.
setSizeHint() can be invoked on the remoteMedia object. Each video stream corresponds to a remoteMedia object.
remoteMedia.setSizeHint(width, height);Asynchronous: No
Parameters:
| Name | Description | Type | Mandatory |
|---|---|---|---|
| width | Width of the video element | number | Yes |
| height | Height of the video element | number | Yes |
Returns - void
To explore Multistream functionality, feel free to explore our Meeting Samples App.
Under Manage Meeting section, check the box with label Use a multistream connection before clicking on Join Meeting or Join with Media.
Caution
- Introducing the Webex Web Calling SDK
- Core Concepts
- Quickstart guide
- Authorization
- Basic Features
- Advanced Features
- Introduction
- Quickstart Guide
- Basic Features
- Advanced Features
- Multistream
- Migrating SDK version 1 or 2 to version 3