These are the adjustments you can make to customize the user interface of OpenTok videos:
When you publish a video, you can specify the DOM element (or its ID) that the publisher will replace. You can also specify the initial width and height of the publisher:
// Replace the first parameter with ID of the target DOM element:
const publisher = OT.initPublisher('myPublisherElementId',
{width:400, height:300});
session.publish(publisher);
You can also set the insertMode
property of the options you pass into
the OT.initPublisher()
method to 'append'
to have the publisher
appended as a child of the DOM element (container) you specify:
const publisherOptions = {
insertMode: 'append',
width: 400,
height: 300
};
const publisher = OT.initPublisher('publisherContainerElementId', publisherOptions);
session.publish(publisher);
Similarly, when you subscribe to a stream, you can specify the target DOM element (or its ID). You can also specify the initial width and height of the subscriber:
const options = {width: 400, height: 300, insertMode: 'append'}
const subscriber = session.subscribe(stream, 'containerElementId', options);
You can also specify the initial width and height of as a percentage of the size of the parent DOM element:
const publisherOptions = {
insertMode: 'append',
width: '100%',
height: '100%'
};
const publisher = OT.initPublisher(publisherContainerElement, publisherOptions);
session.publish(publisher);
If you do not specify a replace element ID (or set it to null), the application appends a new DOM element to the HTML body. The default width is 264 pixels, and the default height is 198 pixels.
If you want to apply multiple CSS rules, apply it to the parent (container) DOM element:
<style>
#publisherContainer.large
{ width: 640px; height: 480px; }
#publisherContainer.small
{ width:100px; height: 100px; }
</style>
<div id="publisherContainer"></div>
<script>
const publisher = OT.initPublisher('publisherContainer',
{width: '100%', height: '100%', insertMode: 'append'}
</script>
To apply a different size to a video on a mobile device, use CSS media queries:
<style>
#publisherContainer
{ width: 100px; height: 100px; }
@media screen and (max-width: 650px) {
#publisherContainer
{ width: 89px; height: 50px; }
}
</style>
<div id="publisherContainer"></div>
<script>
const publisher = OT.initPublisher('publisherContainer',
{width: '100%', height: '100%', insertMode: 'append'}
</script>
If you will want to dynamically resize the video, set the insertMode
to
'append'
and set the height
and width
to '100%'
.
See the next section for information on resizing or repositioning a publisher or subscriber video.
The element
property of the Publisher or Subscriber object is its HTML
DOM element. You can reposition this in the HTML DOM and resize the element by editing its
style.width
and style.height
properties, just as you would any
other DOM element:
document.getElementById("target").appendChild(publisher.element);
publisher.element.style.width = "100px";
publisher.element.style.height = "75px";
If you specify the initial width
and height
of the Publisher or Subscriber object as a percentage (such as "100%:), you can resize it by resizing one of its parent elements. The following example includes a function that resizes a Publisher:
<script type="text/javascript">
const publisherOptions = {
insertMode: "append",
height: "100%",
width: "100%"
}
const publisher = OT.initPublisher("publisherContainer", publisherOptions);
session.publish(publisher);
function resizePublisher() {
const publisherContainer = document.getElementById("publisherContainer");
publisherContainer.style.width = "1000px";
publisherContainer.style.height = "750px";
}
</script>
<div id="container">
<div id="publisherContainer"></div>
<a href="javascript:resizePublisher()">resize</a>
</div>
See the previous section, Setting the initial dimensions of a video for information on setting the initial position and dimensions of a publisher or subscriber.
Important: If you disable the default user interface of the publisher or subscriber by
setting insertDefaultUI
to false
when instantiating the Publisher or
Subscriber object, the element
property of the Publisher or Subscriber will be
undefined. Listen for the videoElementCreated
event and use the element
property of the event object to access the HTML DOM element for the Publisher or Subscriber. See
Directly accessing the video element for a Publisher or Subscriber.
When you create a Publisher, you can (optionally) specify a name to be displayed in the video:
// Replace the first parameter with the target element ID:
const publisher = OT.initPublisher("myPublisher",
{name: "John"})
session.publish(publisher);
You can use this name to identify the client.
Note that you can also add metadata about the client when you create a token. This name is not automatically displayed in the video. However, by adding the data when you create a token, you can add information more securely (since tokens are created on the server, not on the client. For more information, see Creating a token.
Clients can choose to hide the name in a Publisher or Subscriber view. See the next section
When you publish a stream, you can specify a name to be displayed in the video (see the previous section).
When you create a Publisher, you can specify whether the name is displayed in the Publisher
video, by setting the style.nameDisplayMode
property of the options you pass into the
OT.initPublisher()
method:
// Replace the first parameter with the target element ID:
const publisher = OT.initPublisher("myPublisher",
{
name: "John",
style: { nameDisplayMode: "off" }
});
session.publish(publisher);
The style.nameDisplayMode
property can be set to one of three values:
"auto"
The name is displayed when the stream is first displayed and when the user mouses over
the video (the default)."off"
The name is not displayed."on"
The name is displayed.Once you have created the Publisher, you can change the name display mode, by calling the setStyle()
method
of the Publisher object. (See the documentation for
the Publisher.setStyle()
method.)
When you subscribe to a stream, you can specify whether the name is displayed in the Subscriber
video, by setting the style.nameDisplayMode
property of the options you pass into the
Session.subscribe()
method:
// Replace the first two parameters with the stream and target element ID:
const subscriber = session.subscribe(stream,
"mySubscriber",
{
style: { nameDisplayMode: "off" }
});
Once you have created the Subscriber, you can change the name display mode, by calling the setStyle()
method
of the Subscriber object. (See the documentation for
the Subscriber.setStyle()
method.)
By default, the user interface for a Publisher or a Subscriber includes a mute audio button. For a Publisher, the user can click to toggle the mic on and off. For a Subscriber, the user can click to toggle the speaker on and off.
When you publish a stream, you can specify whether the mute button is displayed by passing a
style.buttonDisplayMode
property into the OT.initPublisher()
method:
const publisher = OT.initPublisher(
'publisher-element-id', // Replace with the replacement element ID
{
name: 'John',
style: {buttonDisplayMode: 'on'}
}
);
session.publish(publisher);
The style.buttonDisplayMode
property can be set to one of three values:
"auto"
The mute button is displayed when the stream is first displayed and when the user mouses over
the video (the default)."off"
The mute button is not displayed."on"
The mute button is displayed.
Similarly, when you subscribe to a stream, you can specify whether the mute speaker is displayed by
passing a style.buttonDisplayMode
property into the Session.subscribe()
method:
const subscriber = session.subscribe(stream,
'subscriber-element-id', // Replace with the replacement element ID
{
style: {buttonDisplayMode: 'on'}
}
);
Once you have created the Publisher or Subscriber, you can change the mute button display mode,
by calling the setStyle()
method of the Publisher object or Subscriber object.
(See the documentation for
Publisher.setStyle() and
Subscriber.setStyle().)
You can specify cropping or letterboxing of a Publisher or Subscriber's video by setting the
fitMode
property of the options you pass into OT.initPublisher()
or
Session.subscribe()
. Pass in one of the following two strings:
"cover"
— The video is cropped if its dimensions do not match those of
the DOM element. This is the default setting for videos publishing a camera feed."contain"
— The video is letterboxed if its dimensions do not match
those of the DOM element. This is the default setting for screen-sharing videos.For example, the following code initializes a publisher with the video element letterboxed:
const publisher = OT.initPublisher("publisher-element-id",
{fitMode: "contain"});
The following code subscribes to a stream with the video element cropped:
const subscriber = session.subscribe(stream,
"subscriber-element-id",
{fitMode: "cover"});
The following code captures and displays a static image of the Publisher video:
const imgData = publisher.getImgData();
const img = document.createElement("img");
img.setAttribute("src", "data:image/png;base64," + imgData);
// Replace with the parent DIV for the img
document.getElementById("containerId").appendChild(img);
The following code captures and displays a static image of a Subscriber video:
const imgData = subscriber.getImgData();
const img = document.createElement("img");
img.setAttribute("src", "data:image/png;base64," + imgData);
// Replace with the parent DIV for the img
document.getElementById("containerId").appendChild(img);
You can use the backgroundImageURI
style of a Subscriber to set the
image to be displayed when there is no video. The value you set can be the URL
of an image on the web. It can also be a data:
URL, such as one that you
obtain using the getImgData()
method of the Subscriber object
(see the previous section).
The following code sets the background image of the Subscriber. When the call to
Session.subscribe()
completes successfully, the background image is set. If there
is a video stream, the background is set to a static image captured from the subscriber video;
otherwise it is set to an image loaded from a web URL:
const subscriber = session.subscribe(event.stream, 'subscriberElement', function(error) {
if (error) {
console.log(error.message)'
return;
}
if (subscriber.stream.hasVideo) {
const imgData = subscriber.getImgData();
subscriber.setStyle('backgroundImageURI', imgData);
} else {
subscriber.setStyle('backgroundImageURI',
'https://tokbox.com/img/styleguide/tb-colors-cream.png'
);
}
});
If you do not set a background image for a Subscriber, when there is no video, it will display the initials for the stream, if initials were set when the stream's publisher initialized the stream (see Initializing a Publisher).
Publisher and Subscriber objects dispatch audioLevelUpdated
events
periodically to report the audio level. You can use these events to display an
audio level indicator. You can also use these events to detect active speakers in
a session.
The following example adjusts the value of a meter element that shows volume of a
subscriber. The code sets the audioLevelDisplayMode
style to
'off'
, which disables the default audio level meter displayed in the
Subscriber. Note that the audio level is adjusted logarithmically and a moving average
is applied:
subscriber.setStyle('audioLevelDisplayMode', 'off');
const movingAvg = null;
subscriber.on('audioLevelUpdated', function(event) {
if (movingAvg === null || movingAvg <= event.audioLevel) {
movingAvg = event.audioLevel;
} else {
movingAvg = 0.7 * movingAvg + 0.3 * event.audioLevel;
}
// 1.5 scaling to map the -30 - 0 dBm range to [0,1]
const logLevel = (Math.log(movingAvg) / Math.LN10) / 1.5 + 1;
logLevel = Math.min(Math.max(logLevel, 0), 1);
document.getElementById('subscriberMeter').value = logLevel;
});
The example assumes that there is an HTML meter element with the ID "subscriberMeter".
Note that in audio-only mode, a Publisher or Subscriber DOM element displays a volume indicator by default (in the upper-righthand corner of the element). You can disable this default user interface element and display your own volume meter. See the next topic, Adjusting user interface when video is enabled or disabled.
You can also use the audioLevelUpdated
event to determine when
a publisher or subscriber's audio is loud enough for long enough to label the
participant as having started talking. Or, if the audio has been quiet for long
enough, you can identify the participant as having stopped talking:
const subscriber = session.subscribe(event.stream);
SpeakerDetection(subscriber, function() {
console.log('started talking');
}, function() {
console.log('stopped talking');
});
const SpeakerDetection = function(subscriber, startTalking, stopTalking) {
const activity = null;
subscriber.on('audioLevelUpdated', function(event) {
const now = Date.now();
if (event.audioLevel > 0.2) {
if (!activity) {
activity = {timestamp: now, talking: false};
} else if (activity.talking) {
activity.timestamp = now;
} else if (now- activity.timestamp > 1000) {
// detected audio activity for more than 1s
// for the first time.
activity.talking = true;
if (typeof(startTalking) === 'function') {
startTalking();
}
}
} else if (activity && now - activity.timestamp > 3000) {
// detected low audio activity for more than 3s
if (activity.talking) {
if (typeof(stopTalking) === 'function') {
stopTalking();
}
}
activity = null;
}
});
};
(Instead of logging to the console, your app could adjust a user interface element when the user starts and stops talking.)
Some browsers automatically block audio playback, requiring a click
event before audio playback starts for subscribers. These browsers include Safari,
Firefox 66+, and Chrome 71+.
The Subscriber object displays an audio playback button if audio playback is blocked. You can disable the Subscriber's default audio playback button and display your own UI element that the user will click to start audio playback.
To disable the display of the default audio playback button, set the
style.audioBlockedDisplayMode
property of the options
parameter of the
Session.subscribe() method):
const subscriberOptions = {
style: { audioBlockedDisplayMode: "off" }
};
const subscriber = session.subscribe(stream,
'subscriber-element-id', // Replace with the replacement element ID
subscriberOptions
);
Add event listeners for the audioBlocked
and audioUnblocked
events dispatched by the Subscriber to display and hide your custom UI element
(telling the use to click to play back audio):
subscriber.on({
audioBlocked: function(event) {
// display custom UI
},
audioUnblocked: function(event) {
// hide custom UI
}
});
When the user clicks your custom UI element, call the OT.unblockAudio()
method:
customElement.addEventListener('click', async () => {
try {
await OT.unblockAudio();
} catch (err) {
console.error('Unblocking audio failed.', err);
return;
}
console.log('Unblocked audio successfully.');
});
A Subscriber object dispatches the following events related to the video being enabled or disabled for the subscriber's stream:
videoEnabled
— Dispatched when the video has been enabled after it
was previously disabled.
videoDisabled
— Dispatched when the video has been disabled.
The reason
property of the event object indicates why the video was disabled.
(This event object is an
VideoEnabledChangedEvent
object.)
videoDisableWarning
— Dispatched when the OpenTok Media Router determines
that the stream quality has degraded and the video will be disabled if the quality degrades
more. If the quality degrades further, the Subscriber disables the video and dispatches a
videoDisabled
event. This event may also be dispatched when using the publisher
audio fallback feature if the publisher's stream quality if degraded. See the
audio fallback developer guide.
videoDisableWarningLifted
— The video has been enabled after it was previously
disabled.
The videoDisableWarning
and videoDisableWarningLifted
events are only available
in sessions that use the OpenTok
Media Router (sessions with the media mode set to routed) unless using the
publisher audio fallback feature, where the events
will be available in routed or relayed sessions.
By default, the Subscriber displays a video disabled warning indicator and a video disabled indicator
when the videoDisableWarning
and videoDisableWarningLifted
events are dispatched.
You can disable the default display of the indicator by setting the videoDisabledDisplayMode
style setting of the Subscriber object.
The following example uses the videoDisabledDisplayMode
style setting to have the
video disabled warning indicator and a video disabled indicator blink every one second when the
videoDisableWarning
and videoDisableWarningLifted
events are dispatched:
const indicatorBlinker = new IndicatorBlinker(subscriber);
const IndicatorBlinker = function(subscriber) {
const timer;
const indicatorOn = false;
subscriber.on({
videoDisabled: function(event) {
start();
},
videoDisableWarning: function(event) {
start();
},
videoDisableWarningLifted: function(event) {
stop();
},
videoEnabled: function(event) {
stop();
}
});
const start = function() {
subscriber.setStyle('videoDisabledDisplayMode', 'on');
if (timer) {
clearInterval(timer);
}
timer = setInterval(function() {
if (indicatorOn) {
subscriber.setStyle('videoDisabledDisplayMode', 'off');
} else {
subscriber.setStyle('videoDisabledDisplayMode', 'on');
}
indicatorOn = !indicatorOn;
}, 1000);
indicatorOn = true;
};
const stop = function() {
if (timer) {
clearInterval(timer);
}
};
};
You can also set the videoDisabledDisplayMode
style to 'off'
and add
your own user interface elements based on the videoDisableWarning
,
videoDisabled
, videoDisableWarningLifted
, and videoEnabled
events.
When publisher audio fallback is enabled, the Publisher object dispatches these events in response to changing quality conditions:
videoDisableWarning
— Dispatched when the Publisher determines that the stream
quality has degraded and the video will be disabled if the quality degrades more.videoDisableWarningLifted
— Dispatched when the Publisher determines that
the stream quality has improved to the point at which the video being disabled is not an immediate risk.videoDisabled
— Dispatched when the Publisher determines that the stream
quality has degraded and the outgoing video transport has been disabled.
Note: while the video is disabled, the Publisher still displays the publisher video (such as the camera image)
in the publishing client's UI.videoEnabled
— Dispatched with reason: quality
when the Publisher determines
that the stream quality has improved and outgoing video transport has been re-enabled.By default, the Publisher displays icons when the videoDisableWarning
and
videoDisabled
events occur.
The style
property of the options
parameter for OT.initPublisher()
now includes a videoDisabledDisplayMode
property. You can set the
videoDisabledDisplayMode
property to one of the following string values control how the default
user interface elements are displayed:
auto
(the default) — The icons are automatically displayed when the video is disabled
or at risk of being disabled due to poor stream quality.off
— The icons are not displayed. You can display your own user interface
notifications based on the events described above.on
— The icons are automatically displayed when the video is disabled or at risk
of being disabled due to poor stream quality.For example the following code disables the default video disabled user interface elements, and handles the related events (so that you can provide your own user interface notifications):
// Enabled
const publisher = OT.initPublisher('target', {
audioFallback: {
publisher: true,
},
style: {
videoDisabledDisplayMode: 'off',
}
});
publisher.on({
videoDisableWarning: () => {
// Custom action — for example, add custom UI notification
},
videoDisableWarningLifted: () => {
// Custom action — for example, remove custom UI notification
},
videoDisabled: () => {
// Custom action — for example, add custom UI notification
},
videoEnabled: () => {
// Custom action — for example, remove custom UI notification
},
});
You can also set the videoDisabledDisplayMode
style
dynamically by calling the Publisher.setStyle()
method:
publisher.setStyle('videoDisabledDisplayMode', 'off');
// Alternately:
publisher.setStyle({
videoDisabledDisplayMode: 'off',
// other styles ...
});
The Publisher and Subscriber objects include the following built-in user interface controls:
You can disable all of these by setting the showControls
property to false
in the properties
parameter you pass into the OT.initPublisher()
method or the Session.subscribe()
method.
For example, the following code creates a Publisher object that includes no built-in user interface controls:
const publisherOptions = {
showControls: false
};
const publisher = OT.initPublisher(
'publisher-element-id', // Replace with the replacement element ID
publisherOptions
);
The following code creates a Subscriber object that includes no built-in user interface controls:
const subscriberOptions = {
showControls: false
};
const subscriber = session.subscribe(stream,
'subscriber-element-id', // Replace with the replacement element ID
subscriberOptions
);
You can control the display of individual user interface controls by leaving the
showControls
property set to true
(the
default); then see these topics:
You can disable the default user interface elements for a Publisher or Subscriber and access the
HTML Video
element directly. When publishing or subscribing to a stream, set the
insertDefaultUI
property to false
when calling the
OT.initPublisher() or
Session.subscribe() method.
If you set this option to false
, OpenTok.js does not insert a default UI element in
the HTML DOM, and the element
property of the Publisher or Subscriber object is
undefined. Instead, the Publisher element dispatches a videoElementCreated
event when
the Video
element is created. The element
property of the event object is a reference to the
Video
element. Add it to the HTML DOM to display the video.
The following code initializes a publisher and inserts its Video
element into the
HTML DOM:
const publisher = OT.initPublisher({insertDefaultUI: false});
publisher.on('videoElementCreated', function(event) {
document.getElementById('publisher-video-parent-id').appendChild(event.element);
});
The following code subscribes to a stream and inserts its Video
element into the
HTML DOM:
const subscriber = session.subscribe(stream, {insertDefaultUI: false});
subscriber.on('videoElementCreated', function(event) {
document.getElementById('subscriber-video-parent-id').appendChild(event.element);
});
If you set the insertDefaultUI
property to false
, do not set the
targetElement
parameter when calling OT.initPublisher()
or
Session.subscribe()
. (This results in an error.)
The default user interface element contains user interface controls, a video loading indicator, and automatic video cropping or letter-boxing, in addition to the video. If you leave
insertDefaultUI
set to true
(the default), you can control individual
user interface settings using the fitMode
, showControls
, and
style
options. See the other topics in this page.
You can also access a Subscriber's MediaStream object (and use it in your own Video
element). See the next section.
The MediaStream object can be accessed by listening for the mediaStreamAvailable
event dispatched by
publishers and subscribers. The Media Stream Available API was made available in 2.27.7; prior versions will need to use the
Accessing MediaStream objects for Subscribers before version 2.27.7 guide linked below.
The following provides an example of React Session, Subscriber, and Publisher context hooks using the Media Stream Available API. Although the example uses React, the Media Stream Available API is framework agnostic. Note that the listener should be attached immediately after the publisher and subscribers are created, not as part of the call back.
Session Component
const [subscriberStream, setSubscriberStreams] = useState([]);
async function subscribe(stream, session, options = {}) {
if (session) {
const subscriber = session.current.subscribe(stream, null, { ...options, insertDefaultUI: false });
subscriber.on('mediaStreamAvailable', ({ mediaStream }) => {
setSubscriberStreams((prev) => [...prev, { mediaStream, subscriber }]);
});
}
}
Subscriber Component
import { useEffect, useRef } from 'react';
const Subscriber = ({ subscriber, mediaStream }) => {
const videoRef = useRef(null);
useEffect(() => {
if (mediaStream) {
videoRef.current.srcObject = mediaStream;
}
}, [mediaStream]);
return (
<video
ref={videoRef}
autoPlay
id={subscriber.streamId}
playsInline
muted
></video>
);
}
export default Subscriber;
Publisher Component
import { useEffect, useRef } from 'react';
const Publisher = ({ publisher, mediaStream }) => {
const videoRef = useRef(null);
useEffect(() => {
if (mediaStream) {
videoRef.current.srcObject = mediaStream;
}
}, [mediaStream]);
return (
<video
ref={videoRef}
autoPlay
id={publisher.streamId}
playsInline
muted
></video>
);
}
export default Publisher;
Note that the Media Stream Available API as described above is the preferred method of accessing MediaStreams. This workaround is only necessary in versions of OpenTok prior to 2.27.7.
You can access the MediaStream object used by a Subscriber. The HTMLVideoElement object (in the videoElementCreated
event dispatched by the Subscriber, described in the previous section), has a srcObject
property. This is the MediaStream object for the Subscriber's audio-video stream. You can use that MediaStream object as the source MediaStream for another Video
element (as its srcObject
property):
session.on('streamCreated', function(event) {
const subscriber = session.subscribe(event.stream, { insertDefaultUI: false });
subscriber.on('videoElementCreated', event => {
// myVideoElement is a Video element you have created:
myVideoElement.srcObject = event.element.srcObject;
});
});
In a routed session that uses Adaptive Media Routing, the MediaStream for a subscriber can change when the session switches from relayed to routed streams (see this Help Center knowlege base article). Add an event listener for the play
event for a Subscriber object's Video element to get the updated MediaStream instance:
session.on('streamCreated', function(event) {
const subscriber = session.subscribe(event.stream, { insertDefaultUI: false });
subscriber.on('videoElementCreated', event => {
// myVideoElement is a Video element you have created:
myVideoElement.srcObject = event.element.srcObject;
myVideoElement.play()
event.element.addEventListener('play', () => {
// The MediaStram has changed
myVideoElement.srcObject = event.element.srcObject;
myVideoElement.play();
});
});
});