Subscribing to streams — Web

Once you have connected to a session, you can subscribe to streams in the session. When you subscribe to a stream, its video stream appears in the client page and its audio is played.

This topic includes the following sections:

Detecting when streams are created in a session

The Session object dispatches a streamCreated event when a new stream (other than your own) is created in a session. A stream is created when a client publishes a stream to the session. The streamCreated event is also dispatched for each existing stream in the session when you first connect. This event is defined by the StreamEvent, which has a stream property, representing stream that was created:

session.on("streamCreated", function (event) {
   console.log("New stream in the session: " + event.stream.streamId);
});
// Replace with a valid token:
session.connect(token);

You can subscribe to any stream. See the next section.

Subscribing to a stream

To subscribe to a stream, pass the Stream object into the subscribe method of the Session object:

session.subscribe(stream, replacementElementId);

The subscribe() method takes the following parameters:

  • stream—The Stream object.

  • targetElement— (Optional) Defines the DOM element that the Subscriber video replaces.

  • properties— (Optional) A set of properties that customize the appearance of the Subscriber view in the HTML page (see Customizing the UI) and select whether to subscribe to audio and video (see Adjusting audio and video).

  • completionHandler— (Optional) A function that is called asynchronously when the call to the subscribe() method completes successfully or fails. If the call to the subscribe() method fails, the completion handler is passed an error object. This object has a code and message property that describe the error.

The following code subscribes to all streams, other than those published by your client:

session.on("streamCreated", function(event) {
    session.subscribe(event.stream);
});

// Replace with your API key and token:
session.connect(token, function (error) {
    if(error) {
        // failed to connect
    }
});

The insertMode property of the properties parameter of the Session.subscribe() method specifies how the Publisher object will be inserted in the HTML DOM, in relation to the targetElement parameter. You can set this parameter to one of the following values:

  • "replace" — The Subscriber object replaces contents of the targetElement. This is the default.
  • "after" — The Subscriber object is a new element inserted after the targetElement in the HTML DOM. (Both the Subscriber and targetElement have the same parent element.)
  • "before" — The Subscriber object is a new element inserted before the targetElement in the HTML DOM. (Both the Subscriber and targetElement have the same parent element.)
  • "append" — The Subscriber object is a new element added as a child of the targetElement. If there are other child elements, the Publisher is appended as the last child element of the targetElement.

For example, the following code adds a new Subscriber object as a child of a subscriberContainer DOM element:

session.on('streamCreated', function(event) {
  var subscriberProperties = {insertMode: 'append'};
  var subscriber = session.subscribe(event.stream,
    'subscriberContainer',
    subscriberProperties,
    function (error) {
      if (error) {
        console.log(error);
      } else {
        console.log('Subscriber added.');
      }
  });
});

The Subscriber object has an element property, which is set to the HTML DOM element containing it.

If you do not want to use the default UI, you access the Video element for the Subscriber (see this topic). You can also use your own Video element to display the Subscriber video, and use the Subscriber's MediaStream object as the media source for that Video element (see this topic).

Unsubscribing from a stream

To stop playing a stream you are subscribed to, pass the Subscriber object into the unsubscribe() method of the Session object:

session.unsubscribe(subscriber);

The Subscriber object is destroyed, and the stream display is removed from the HTML DOM.

Detecting when streams leave a session

When a stream, other than your own, leaves a session the Session object dispatches a streamDestroyed event:

session.on("streamDestroyed", function (event) {
  console.log("Stream stopped. Reason: " + event.reason);
});

When a stream you publish leaves a session the Publisher object dispatches a streamDestroyed event:

var publisher = OT.initPublisher();
publisher.on("streamDestroyed", function (event) {
  console.log("Stream stopped. Reason: " + event.reason);
});

The streamDestroyed event is defined by the StreamEvent class. The event includes a reason property, which details why the stream ended. These reasons include "clientDisconnected", "forceDisconnected", "forceUnpublished", or "networkDisconnected". For details, see StreamEvent.

By default, when a streamDestroyed event is dispatched for a stream you are subscribed to, the corresponding Subscriber objects (there could be more than one) are destroyed and removed from the HTML DOM. You can prevent this default behavior by calling the preventDefault() method of the StreamEvent object:

session.on("streamDestroyed", function (event) {
  event.preventDefault();
  var subscribers = session.getSubscribersForStream(event.stream);
  // Now you can adjust the DOM elements around each
  // subscriber to the stream, and then delete it yourself.
});

Note that the getSubscribersForStream() method of a Session object returns all of the Subscriber objects for a Stream.

You may want to prevent the default behavior, and retain the Subscriber, if you want to adjust related DOM elements before deleting the Subscriber yourself. You can then delete the Subscriber object (and its DOM element) by calling the destroy() method of the Subscriber object.

A Subscriber object dispatches a destroyed event when the object has been removed from the HTML DOM. In response to this event, you may choose to adjust (or remove) DOM elements related to the subscriber that was removed.

Automatic reconnection

If a client drops a connection to a subscribed stream (for example, due to a drop in network connectivity in either client), it will attempt to automatically reconnect to the stream. When the stream is dropped and the client tries to reconnect, the Subscriber object dispatches a disconnected event. When the stream is restored, the Subscriber object dispatches a connected event. If the client cannot restore the stream, the Subscriber object dispatched a destroyed event.

In response to these events, your application can (optionally) display user interface notifications indicating the temporary disconnection, reconnection, and destroyed states:

subscriber.on(
  disconnected: function() {
    // Display a user interface notification.
  },
  connected: function() {
    // Adjust user interface.
  },
  destroyed: function() {
    // Adjust user interface.
  }
);

Restricting the frame rate of a subscribed stream

You can also restrict the frame rate of a Subscriber's video stream. To restrict the frame rate of a subscriber, call the restrictFrameRate() method of the Subscriber object, passing in true:

subscriber.restrictFrameRate(true);

Pass in false and the frame rate of the video stream is not restricted:

subscriber.restrictFrameRate(false);

When the frame rate is restricted, the Subscriber video frame will update once or less per second.

This feature is only available in sessions that use the OpenTok Media Router (sessions with the media mode set to routed), not in sessions with the media mode set to relayed. In relayed sessions, calling this method has no effect.

Restricting the subscriber frame rate has the following benefits:

  • It reduces CPU usage.
  • It reduces the network bandwidth consumed by the app.
  • It lets you subscribe to more streams simultaneously.

Reducing a subscriber's frame rate has no effect on the frame rate of the video in other clients.

Detecting when a subscriber's audio is blocked or unblocked

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. See Displaying a custom UI element when Subscriber audio is blocked.

When the subscriber's audio is blocked, the Subscriber object dispatches a audioBlocked event, and it dispatches an audioUnblocked event when the audio is unblocked:

subscriber.on({
  audioBlocked: function(event) {
   console.log("Subscriber audio is blocked.")
  },
  audioUnblocked: function(event) {
   console.log("Subscriber audio is unblocked.")
  }
});

Also, the Subscriber includes an isAudioBlocked() which returns true if the audio is blocked or false if it is not.

Subscriber audio is unblocked when any of the following occurs:

  • The user clicks the default Subscriber audio playback icon
  • The OT.unblockAudio() method is called in response to an HTML element dispatching a click event (if you have disabled the default audio playback icon)
  • The local client gains access to the camera or microphone (for instance, in response to a successful call to OT.initPublisher()).

For more information, see this Mozilla article about autoplay in Firefox and this Google article about autoplay in Chrome.

Detecting when a subscriber's video is disabled

When the subscriber's video is disabled, the Subscriber object dispatches a videoDisabled event:

subscriber.on("videoDisabled", function(event) {
  // You may want to hide the subscriber video element:
  domElement = document.getElementById(subscriber.id);
  domElement.style["visibility"] = "hidden";

  // You may want to add or adjust other UI.
});

When the OpenTok Media Router, or a fallback-enabled publisher, disables the video of a subscriber, you may want to adjust the user interface related to the subscriber.

The reason property of the videoDisabled event object defines the reason the video was disabled. This can be set to one of the following values:

  • "publishVideo" — The publisher stopped publishing video by calling publishVideo(false).

  • "quality" — The OpenTok Media Router, or the publishing client if publisher audio fallback is enabled, stopped sending video to the subscriber based on stream quality changes. This feature of the OpenTok Media Router has a subscriber drop the video stream when connectivity degrades. (The subscriber continues to receive the audio stream, if there is one.) The publisher audio fallback feature has the publisher stop publishing the video stream when publisher connectivity degrades, and subsequently the subscriber drops the video stream.

    Before sending this event, when the Subscriber's stream quality deteriorates, or a fallback-enabled publisher's stream quality deteriorates, to a level that is low enough that the video stream is at risk of being disabled, the Subscriber dispatches a videoDisableWarning event.

    If connectivity improves to support video again, the Subscriber object dispatches a videoEnabled event, and the Subscriber resumes receiving video.

    By default, the Subscriber displays a video disabled indicator when a videoDisabled event with this reason is dispatched and removes the indicator when the videoDisabled event with this reason is dispatched. You can control the display of this icon by calling the setStyle() method of the Subscriber, setting the videoDisabledDisplayMode property; or you can set the style when calling the Session.subscribe() method, setting the style property of the properties parameter.

    This feature is only available in sessions that use the OpenTok Media Router (sessions with the media mode set to routed), or in sessions with a fallback-enabled publisher. See the publisher fallback enabled docs.

    When you publish a stream, you can prevent it from having its video disabled due to stream quality. Set audioFallbackEnabled to false in the properties object passed into the OT.initPublisher() method (this feature will be deprecated), or set subscriber to false in the audioFallback object passed in as the properties parameter of the OT.initPublisher() method.

  • "subscribeToVideo" — The subscriber started or stopped subscribing to video, by calling subscribeToVideo(false).

  • "codecNotSupported" — The subscriber stopped subscribing to video due to an incompatible codec (see the Video codecs developer guide).

The Subscriber dispatches a videoEnabled event when video resumes:

subscriber.on("videoEnabled", function(event) {
  // You may want to display the subscriber video element,
  // if it was hidden:
  domElement = document.getElementById(subscriber.id);
  domElement.style["visibility"] = "visible";

  // You may want to add or adjust other UI.
});

The reason property of the videoEnabled event object defines the reason the video was enabled. This can be set to one of the following values:

  • "publishVideo" — The publisher started publishing video by calling publishVideo(true).

  • "quality" — The OpenTok Media Router, or the fallback-enabled publisher, resumed sending video to the subscriber based on stream quality changes. This feature of the OpenTok Media Router has a subscriber drop the video stream when connectivity degrades and then resume the video stream if the stream quality improves. The publisher audio fallback feature has the publisher stop publishing the video stream when publisher connectivity degrades, and subsequently the subscriber drops the video stream.

    This feature is only available in sessions that use the OpenTok Media Router (sessions with the media mode set to routed), or in sessions with a fallback-enabled publisher.

  • "subscribeToVideo" — The subscriber started or stopped subscribing to video, by calling subscribeToVideo(false).

  • "codecChanged" — The subscriber video was enabled after a codec change from an incompatible codec (see the Video codecs developer guide).

Detecting when a subscriber's stream's video dimensions change

The stream of a subscriber's video dimensions can change if a stream published from a mobile device resizes, based on a change in the device orientation. It can also occur if the video source is a screen-sharing window and the user publishing the stream resizes the window that is the source for the stream. When the video dimensions change, the Subscriber object dispatches a videoDimensionsChanged event.

The following code resizes a subscriber when the stream's video dimensions change:

subscriber.on('videoDimensionsChanged', function(event) {
  subscriber.element.style.width = event.newValue.width + 'px';
  subscriber.element.style.height = event.newValue.height + 'px';
  // You may want to adjust other UI.
});

Getting information about a stream

The Stream object has the following properties that define the stream:

  • connection—The Connection object corresponding to the connection that is publishing the stream. You can compare this to the connection property of the Session object to see if the stream is being published by the local web page.
  • creationTime—The timestamp (a number) for the creation of the stream. This value is calculated in milliseconds. You can convert this value to a Date object by calling new Date(stream.creationTime).
  • hasAudio—(Boolean) Whether the stream has audio. This property can change if the publisher turns on or off audio (by calling Publisher.publishAudio()). When this occurs, the Session object dispatches a streamPropertyChanged event.
  • hasVideo—(Boolean) Whether the stream has video.
  • initials—(Boolean) The initials for the stream (if initials were set when the stream's publisher was initialized).
  • name—(String) The name of the stream. This is, by default, displayed when the user mouses over the Subscriber in the HTML DOM. You can, however, customize the UI to hide the name or display it without mousing over.
  • videoDimensions—This object has two properties: width and height. Both are numbers. The width property is the width of the encoded stream; the height property is the height of the encoded stream. (These are independent of the actual width of Publisher and Subscriber objects corresponding to the stream.) This property can change if a stream published from an iOS device resizes, based on a change in the device orientation.
  • videoType—The type of video: either "camera", "screen", "custom", or undefined. A "screen" video uses screen sharing on the publisher as the video source; a "custom" video uses a VideoTrack element as the video source on the publisher. The videoType is undefined when a stream is voice-only (see the Voice-only guide). This property can change if a stream published from a mobile device changes from a camera to a screen-sharing video type. For more information, see Screen sharing — Web.

The hasAudio, hasVideo, videoDimensions, and videoType properties can change (for example, when the publisher turns on or off video). When this occurs, the Session object dispatches a streamPropertyChanged event (see StreamPropertyChangedEvent.)

The getStats() method of a Subscriber object provides you with information about the subscriber's stream. To get low-level peer connection statistics, use the Subscriber.getRtcStatsReport() method. It returns a promise that, on success, resolves with an RtcStatsReport object for the subscribed stream.

Refer to the client observability developer guide for detailed information.

Setting the preferred frame rate and resolution

When subscribing to a stream that uses the scalable video feature, you have an option to set preferredResolution to "auto" to automatically manage Subscriber video resolution based on the size being rendered to optimize the network and CPU usage. For advanced users, you can also manually set the preferred frame rate and resolution for the stream the subscribing client receives from the OpenTok Media Router. You can set these as the preferredFrameRate and preferredResolution properties of the options you pass into the [`Session.subscribe()`](/video/sdk-reference/js/Session.html#subscribe) method. We recommend setting preferredResolution to "auto". With the "auto" setting, OpenTok.js selects the preferred resolution based on the dimensions of the Subscriber video in the browser. You can also set the preferred frame rate and resolution after subscribing to a stream (see [`Subscriber.setPreferredFrameRate()`](/opentok/sdks/js/reference/Subscriber.html#setPreferredFrameRate) and Subscriber.setPreferredResolution()).

Note: The "auto" resolution setting only applies when you use the default Subscriber Video element created by the SDK. It does not work if you create your own Video element in response to the videoElementCreated event (see this topic).

Applying filters and effects to subscribed audio and video

You can apply filters and effects on audio or video tracks for a subscribed stream — see this topic.

Detecting audio and video quality changes

If a client experiences periods of degraded network connectivity, this may be reflected on the subscriber call quality. The Subscriber object dispatches a qualityScoreChanged event when the calculated audio and video MOS scores change. These scores are reported as integers between 1 (worst) and 5 (best), corresponding to bad, poor, fair, good, and excellent. For more details, see the Subscriber qualityScoreChanged event.

A Subscriber object dispatches this event only when one of the quality scores has changed. Each Subscribe dispatches events with its own audio and video quality scores, depending on whether it is subscribing to audio, video, or both.

In response to these events, your application can (optionally) notify the client of network conditions resulting in degraded call quality:

subscriber.on('qualityScoreChanged', ({qualityScore}) => {
  if (qualityScore.audioQualityScore <= 3){
    // Alert the user that the remote party is experiencing degraded service
  }
  if (qualityScore.videoQualityScore <= 3){
    // Alert the user that the remote party is experiencing degraded service
  }
});

Troubleshooting

Follow the tips in this section to avoid connectivity issues when subscribing. For general information on troubleshooting, see Debugging — Web.

Handling Errors

Handling errors when subscribing is a bit easier than when publishing. There is only one way to subscribe—with the Session.subscribe() method—and pretty much any error that happens when subscribing comes down to a network issue. This can happen if, for example, the user is on a really restrictive network connection that does not allow for WebRTC connections (but the WebSocket connection worked). If the Subscriber fails to connect it will just display its own error message inside the Subscriber. It doesn't look particularly nice and isn't very informative to the end user. We recommend that you handle this case yourself and surface a message to the user indicating that they failed to subscribe and that they should check their network connection. Handling these errors looks like this:

session.subscribe(event.stream, 'subscriber', {insertMode: 'append'}, function (err) {
  if (err) {
    showMessage('Streaming connection failed. This could be due to a restrictive firewall.');
  }
});

Losing Connectivity

Your Subscriber can also lose its connection after it has already succeeded in connecting. More often than not, this will also result in the Session losing its connection, but that's not always the case. Also, it may be that the Publisher on the other side has lost its connection rather than the connection being lost locally. You can handle the Subscriber disconnecting by listening for the streamDestroyed event on the Session with a reason property set to "networkDisconnected", like this:

session.on({
  streamDestroyed: function (event) {
    if (event.reason === 'networkDisconnected') {
      event.preventDefault();
      var subscribers = session.getSubscribersForStream(event.stream);
      if (subscribers.length > 0) {
        var subscriber = document.getElementById(subscribers[0].id);
        // Display error message inside the Subscriber
        subscriber.innerHTML = 'Lost connection. This could be due to your internet connection '
          + 'or because the other party lost their connection.';
        event.preventDefault();   // Prevent the Subscriber from being removed
      }
    }
  }
});