Publish: Diagnostics
This guide covers how to gather diagnostics for publishers and resolve common issues.
Getting statistics about a publisher's stream
The Publisher.getStats() method provides you with an array of objects defining the current audio-video statistics for the publisher. For a publisher in a routed session (one that uses the Media Router), this array includes one object, defining the statistics for the single audio-media stream that is sent to the OpenTok Media Router. In a relayed session, the array includes an object for each subscriber to the published stream. Each object in the array contains a stats property that includes the following properties:
- The total number of audio and video packets sent
- The total number of audio and video packets lost
- The total number of audio and video bytes sent
- The current average video frame rate
Additionally, for a publisher in a relayed session, each object in the array contains the following two properties:
connectionId— The unique ID of the client's connection, which matches theidproperty of theconnectionproperty of theconnectionCreatedevent that the Session object dispatched for the remote client.subscriberId— The unique ID of the subscriber, which matches theidproperty of the Subscriber object in the subscribing client's app.
These two properties are undefined for a publisher in a routed session.
To get more detailed stream statistics, use the Publisher.getRtcStatsReport() method. It returns a promise that, on success, resolves with an array of RTCStatsReport objects.
The OTPublisher audioNetworkStats and audioNetworkStats events provide an array of objects defining the current audio or video statistics for the publisher. For a publisher in a routed session (one that uses the Media Router), these event arrays each include one object, defining the statistics for the audio or video that is sent to the Media Router. In a relayed session, the array includes an object for each subscriber to the published stream. Each object in the array has the following properties:
videoBytesSent — The total number of audio and video bytes sent
videoPacketsSent — The total number of audio and video packets sent
videoPacketsSent — The total number of audio and video packets lost
Additionally, for a publisher in a relayed session, each object in the array contains the following two properties:
connectionId — The unique ID of the subscribing client's connection, which matches the connectionId property of the connectionCreated event for that client's connection.
subscriberId — The unique ID of the subscriber.
The following code logs these stats for the publisher's stream every second:
<OTPublisher
eventHandlers={{
audioNetworkStats: event => {
console.log('publisher audioNetworkStats event', event);
},
videoNetworkStats: event => {
console.log('publisher videoNetworkStats event', event);
},
}}
/>
To get more detailed stream statistics, use the OTPublisher.getRtcStatsReport() method. Calling this method results in the OTPublisher instance dispatching an rtcStatsReport event:
<OTPublisher
eventHandlers={{
streamCreated: event => {
console.log('publisher streamCreated', event);
setTimeout(() => this.publisher.getRtcStatsReport(), 12000)
},
rtcStatsReport: event => {
console.log('publisher rtcStatsReport event', event);
},
}}
/>
For a publisher in a routed session, event array includes one object, defining the stats of the stream sent to the OpenTok Media Router. In a relayed session, the array includes an object defining the RTC Stats Reports for each subscriber to the published stream. Each object in the array has a jsonArrayOfReports property that includes the data. The structure of the JSON array is similar to the format of the RtcStatsReport object implemented in web browsers (see the Mozilla docs). Also see this W3C documentation.
The getStats() method of a Publisher object provides you with information about the publisher's stream, including the following:
- The total number of audio and video packets lost
- The total number of audio and video packets received
- The total number of audio and video bytes received
- The current average video frame rate
The following code logs the audio packet loss ratio, the audio bit rate, and the video packet loss ratio, and the video bit rate for a publisher every second:
var prevStats;
window.setInterval(function() {
publisher.getStats(function(error, stats) {
if (error) {
console.error('Error getting publisher stats. ', error.message);
return;
}
if (prevStats) {
var videoPacketLossRatio = stats.video.packetsLost /
(stats.video.packetsLost + stats.video.packetsReceived);
console.log('video packet loss ratio: ', videoPacketLossRatio);
var videoBitRate = 8 * (stats.video.bytesReceived - prevStats.video.bytesReceived);
console.log('video bit rate: ', videoBitRate, 'bps');
var audioPacketLossRatio = stats.audio.packetsLost /
(stats.audio.packetsLost + stats.audio.packetsReceived);
console.log('audio packet loss ratio: ', audioPacketLossRatio);
var audioBitRate = 8 * (stats.audio.bytesReceived - prevStats.audio.bytesReceived);
console.log('audio bit rate: ', audioBitRate, 'bps');
}
prevStats = stats;
});
}, 1000);
To get statistics for a stream published by the local client, you must use a session that uses the OpenTok Media Router (sessions with the media mode set to routed), and you must set the testNetwork property to true in the options object you pass into the Session.subscribe() method:
var publisher = OT.initPublisher();
publisher.on('streamCreated', function(event)) {
var subscriberOptions = {testNetwork: true};
var subscriber = session.subscribe(event.stream, 'publisher-element', subscriberOptions);
}
To get more detailed stream statistics, use the Publisher.getRtcStatsReport() method. It returns a promise that, on success, resolves with an RtcStatsReport object for the subscribed stream:
publisher.getRtcStatsReport()
.then((stats) => stats.forEach(console.log))
.catch(console.log);
To register callbacks methods for periodic reports of audio and video statistics for a publisher, call the PublisherKit.setAudioStatsListener(listener) and PublisherKit.setVideoStatsListener(listener) methods.
Pass in objects that implement the PublisherKit.AudioStatsListener and PublisherKit.VideoStatsListener interfaces.
The implementations of the PublisherKit.AudioStatsListener.onAudioStats(publisher, stats) and PublisherKit.VideoStatsListener.onVideoStats(publisher, stats) methods are called periodically to report audio and video statistics for the publisher. Each method is passed in two objects: the publisher and an array of stats objects.
For a publisher in a routed session (one that uses the Vonage Video Media Router), the stats array includes one object, defining the statistics for the single audio or video media stream that is sent to the Vonage Video Media Router. In a relayed session, the stats array includes an object for each subscriber to the published stream.
The stats object includes the following properties:
- The total number of audio or video packets sent
- The total number of audio or video packets lost
- The total number of audio or video bytes sent
- The timestamp for when the stats were gathered
Additionally, for a publisher in a relayed session, each object in the array contains the following two properties:
connectionId— The connection ID of the client subscribing to the streamsubscriberId— The subscriber ID of the client subscribing to the stream
These two properties are undefined for a publisher in a routed session.
To get more detailed stream statistics, use the PublisherKit.getRtcStatsReport() method. This provides RTC stats reports for the media stream. This is an asynchronous operation. Call the PublisherKit.setRtcStatsReportListener(PublisherKit.PublisherRtcStatsReportListener listener) method, and then implement the PublisherKit.PublisherRtcStatsReportListener.onRtcStatsReport(PublisherKit publisher, PublisherKit.PublisherRtcStats[] stats) method prior to calling PublisherKit.getRtcStatsReport().
When the stats are available, the implementation of the PublisherKit.PublisherRtcStatsReportListener.onRtcStatsReport(PublisherKit publisher, PublisherKit.PublisherRtcStats[] stats) method is called.
An array of PublisherRtcStats objects is passed into that method. The PublisherRtcStats object includes a jsonArrayOfReports property. This is a JSON array of RTC stats reports, which are similar to the format of the RtcStatsReport object implemented in web browsers (see these Mozilla docs). Also see this W3C documentation.
Set the networkStatsDelegate property of an OTPublisherKit to an object implements the OTPublisherKitNetworkStatsDelegate protocol.
This protocol includes an [OTPublisherKitNetworkStatsDelegate publisher: audioNetworkStatsUpdated:] message and an [OTPublisherKitNetworkStatsDelegate publisher: videoNetworkStatsUpdated:] message, which are periodically sent to report publisher audio and video quality statistics.
The second parameter of each of these — audioNetworkStatsUpdated and videoNetworkStatsUpdated — is an array of stats objects that include properties defining the network statistics (audio and video).
For a publisher in a routed session (one that uses the Vonage Video Media Router), the stats array includes one object, defining the statistics for the single audio or video media stream that is sent to the Vonage Video Media Router. In a relayed session, the stats array includes an object for each subscriber to the published stream. The object includes the following properties:
- The total number of audio or video packets sent
- The total number of audio or video packets lost
- The total number of audio or video bytes sent
- The timestamp for when the stats were gathered
- The timestamp from which the cumulative totals started accumulating
Additionally, for a publisher in a relayed session, each stats object in the array contains the following two properties:
connectionId— The connection ID of the client subscribing to the streamsubscriberId— The subscriber ID of the client subscribing to the stream
These two properties are undefined for a publisher in a routed session.
To get more detailed stream statistics, use the [OTPublisherKit getRtcStatsReport:] method. This provides RTC stats reports for the media stream.
This is an asynchronous operation. Set the >[OTPublisherKit rtcStatsReportDelegate]> property and implement the >[OTPublisherKitRtcStatsReportDelegate publisher:rtcStatsReport:]> method prior to calling [OTPublisherKit getRtcStatsReport:].
When the stats are available, the implementation of the >[OTPublisherKitRtcStatsReportDelegate publisher:rtcStatsReport:]> message is sent. The message includes an array of OTPublisherRtcStats objects, which includes a jsonArrayOfReports property.
This is a JSON array of RTC stats reports, which are similar to the format the RtcStatsReport object implemented in web browsers (see these Mozilla docs). Also see this W3C documentation.
To register callbacks methods for periodic reports of audio and video statistics for a publisher, set an event handler for the Publisher AudioStatsUpdated and VideoUpdated events.
These events are sent periodically to report audio and video statistics for the publisher. The event handlers for these are passed in an array of AudioNetworkStats and VideoNetworkStats Each method is passed in two objects: the publisher and an array of stats objects. For a publisher in a routed session (one that uses the Vonage Video Media Router), the array includes one object, defining the statistics for the single audio or video media stream that is sent to the Vonage Video Media Router. In a relayed session, the array includes an object for each subscriber to the published stream. The stats object includes the following properties:
- The total number of audio or video packets sent
- The total number of audio or video packets lost
- The total number of audio or video bytes sent
- The timestamp for when the stats were gathered
Additionally, for a publisher in a relayed session, each object in the array contains the following two properties:
ConnectionId— The connection ID of the client subscribing to the streamSubscriberId— The subscriber ID of the client subscribing to the stream
These two properties are undefined for a publisher in a routed session.
To get more detailed stream statistics, use the Publisher.GetRtcStatsReport() method. This provides RTC stats reports for the media stream. This is an asynchronous operation. When the stats are available, the RtcStatsReport event is sent. The RtcStatsReportArgs object includes an array of PublisherRtcStats objects, which includes a JsonArrayOfReports property. This is a JSON array of RTC stats reports, which are similar to the format the RtcStatsReport object implemented in web browsers (see these Mozilla docs). Also see this W3C documentation.
To register callbacks methods for periodic reports of audio and video statistics for a publisher, set the on_audio_stats() and on_video_stats() callback functions when you initialize the otc_publisher_callbacks struct to be used by the publisher.
These callback functions are called periodically to report audio and video statistics for the publisher. Each function is passed in the following: A pointer to the publisher struct, A pointer to the user_data you set for the publisher, an array of stats, and the number of stats in the array. The stats parameter is defined by the otc_publisher_audio_stats and otc_publisher_video_stats structs. For a publisher in a routed session (one that uses the Media Router), the array includes one object, defining the statistics for the single audio or video media stream that is sent to the Media Router. In a relayed session, the array includes an object for each subscriber to the published stream. The struct passed in as the stats parameter includes the following properties:
- The total number of audio or video packets sent
- The total number of audio or video packets lost
- The total number of audio or video bytes sent
- The timestamp for when the stats were gathered
- The audio level (for audio stats)
Additionally, for a publisher in a relayed session, each object in the array contains the following two properties:
ConnectionId— The connection ID of the client subscribing to the streamSubscriberId— The subscriber ID of the client subscribing to the stream
These two properties are undefined for a publisher in a routed session.
To get more detailed stream statistics, use the otc_publisher_get_rtc_stats_report() function. This provides RTC stats reports for the media stream. This is an asynchronous operation. Create an otc_publisher_rtc_stats_report_cb struct and pass it into the otc_publisher_set_rtc_stats_report_cb function prior to calling otc_publisher_get_rtc_stats_report(). When the stats are available, the otc_publisher_rtc_stats_report_cb.on_rtc_stats_report() callback function is called. This function includes a stats parameter, which is a pointer to an array of otc_publisher_rtc_stats structs. This struct includes a json_array_of_reports property. This is a JSON array of RTC stats reports, which are similar to the format the RtcStatsReport object implemented in web browsers (see these Mozilla docs). Also see this W3C documentation.
Test stream
You can publish a test stream and check its audio and video statistics to determine the type of stream (such as high-resolution or audio-only) supported by your connection.
To get statistics for a stream published by the local client, you must use a session that uses the Media Router (sessions with the media mode set to routed), and you must set the testNetwork property to true in the options object you pass into the Session.subscribe() method. You can then use the getStats() method of the Subscriber object to get audio and video statistics for the stream you publish.
You can use the SubscriberKit.setAudioStatsListener(AudioStatsListener listener) and SubscriberKit.setVideoStatsListener(VideoStatsListener listener) methods of the Subscriber object to get audio and video statistics for the stream you publish.
See this topic for more information.
You can use the networkStatsDelegate method of the OTSubscriberKit object to get audio and video statistics for the stream you publish.
The vonage-video-api-network-test-samples repo includes sample code for showing how to use statistics of a test stream before publishing to a session.
You can use the networkStatsDelegate method of the OTSubscriberKit object to get audio and video statistics for the stream you publish.
The vonage-video-api-network-test-samples repo includes sample code for showing how to use statistics of a test stream before publishing to a session.
You can then subscribe to the stream and use the Subscriber.AudioStatsUpdated and Subscriber.VideoStatsUpdated events to get audio and video statistics for the stream you publish.
Best practices when publishing
This section includes tips for successfully publishing streams.
Allowing Device Access
It is best practice to let your users know that they are going to be asked to allow access to their camera and microphone.
We find that by far the largest number of failures to publish are a result of users clicking the "deny" button or not clicking the allow button at all. We provide you with all of the events you need to be able to guide your users through this process:
publisher.on({
accessDialogOpened: function (event) {
// Show allow camera message
pleaseAllowCamera.style.display = 'block';
},
accessDialogClosed: function (event) {
// Hide allow camera message
pleaseAllowCamera.style.display = 'none';
}
});
It is also a good idea to serve your website over SSL. This is because Chrome only requires users to click to allow access to devices once per domain if that domain is served over SSL. This means that your users (if on Chrome) don't have to deal with that inconvenient allow/deny dialog box every time they load the page.
Split OT.initPublisher() and Session.publish()
Another thing we recommend is splitting the OT.initPublisher() and Session.publish() steps. This speeds up the initial connect time because you're connecting to the session while you're waiting for the user to click the allow button. So instead of:
session.connect(token, function (err) {
{... your error handling code ...}
if (!err) {
var publisher = OT.initPublisher();
session.publish(publisher);
}
});
Move the OT.initPublisher() step to before you connect, as in the following:
var publisher = OT.initPublisher();
session.connect(token, function (err) {
{... your error handling code ...}
if (!err) {
session.publish(publisher);
}
});
Resolution and frame rate
You can set the resolution and frame rate of the Publisher when you initialize it:
OT.initPublisher(divId, {
resolution: '320x240',
frameRate: 15
});
By default the resolution of a Publisher is 640x480, but you can set it to 1920x1080, 1280x720 or 320x240 as well. It is best to try to match the resolution to the size that the video will be displayed. If you are only displaying the video at 320x240 pixels then there is no point in streaming at 1280x720 or 1920x1080. Reducing the resolution can save bandwidth and reduce congestion and connection drops.
By default the frame rate of the video is 30 frames per second, but you can set it to 15, 7, or 1 as well. Reducing the frame rate can reduce the bandwidth required. Smaller resolution videos can have a lower frame rate without as much of a perceived difference to the user. So if you are using a low resolution, you might also want to think about using a low frame rate.
Troubleshooting
Follow the tips in this section to avoid connectivity issues when publishing. For general information on troubleshooting, see Debugging — Web.
Handling Errors
There are callback methods for both Session.publish() and OT.initPublisher(). We recommend handling the error responses to both of these methods. As mentioned earlier, it is best to split up these steps and call OT.initPublisher() before you have started connecting to your Session. It also makes error handling easier if you are not calling both of these methods at the same time. This is because both error handlers will fire if there is any error publishing. It is best to wait for OT.initPublisher() to complete and Session.connect() to complete and then call Session.publish(). This way you can handle all hardware related issues in the OT.initPublisher() callback and all network related issues in the Session.publish() callback.
var connected = false,
publisherInitialized = false;
var publisher = OT.initPublisher(function(err) {
if (err) {
// handle error
} else {
publisherInitialized = true;
publish();
}
});
var publish = function() {
if (connected && publisherInitialized) {
session.publish(publisher);
}
};
session.connect(token, function(err) {
if (err) {
// handle error
} else {
connected = true;
publish();
}
});
Access Denied
The highest number of failures to OT.initPublisher() are a result of the end-user denying access to the camera and microphone. This can either be handled by listening for the accessDenied event or by listening for an error response to the OT.initPublisher() method with a code property set to 1500 and a message property set to "Publisher Access Denied:". We recommend that you handle this case and surface a message to the user indicating that they should try to publish again and allow access to the camera.
publisher.on({
'accessDenied': function() {
showMessage('Please allow access to the Camera and Microphone and try publishing again.');
}
});
Device Access
Another reason for OT.initPublisher() to fail is if OpenTok cannot get access to a camera or microphone. This can happen if there is no camera or microphone attached to the machine, if there is something wrong with the driver for the camera or microphone, or if some other application is using the camera or microphone (this only happens in Windows). You can try to minimize the occurrence of these issues by using our Hardware Setup Component or by calling the OT.getDevices() method directly. However you should also handle any error when calling OT.initPublisher() because something could still go wrong. For example, the user could have denied access to the camera or microphone. In this case, the error.name property is set to "OT_USER_MEDIA_ACCESS_DENIED":
publisher = OT.initPublisher('publisher', {}, function (err) {
if (err) {
if (err.name === 'OT_USER_MEDIA_ACCESS_DENIED') {
// Access denied can also be handled by the accessDenied event
showMessage('Please allow access to the Camera and Microphone and try publishing again.');
} else {
showMessage('Failed to get access to your camera or microphone. Please check that your webcam'
+ ' is connected and not being used by another application and try again.');
}
publisher.destroy();
publisher = null;
}
});
Network Errors
The other reasons for failures in publishing are usually due to some kind of network failure. We handle these in the callback to Session.publish(). If the user is not connected to the network, the callback function is passed an error object with the name property set to "OT_NOT_CONNECTED". If the user is on a really restrictive network connection that does not allow for WebRTC connections, the Publisher fails to connect, and the Publisher element will display a spinning wheel. This error has an name property set to "OT_CREATE_PEER_CONNECTION_FAILED". In this case recommend that you surface a message to the user indicating that they failed to publish and that they should check their network connection. Handling these errors looks like this:
session.publish(publisher, function(err) {
if (err) {
switch (err.name) {
case "OT_NOT_CONNECTED":
showMessage("Publishing your video failed. You are not connected to the internet.");
break;
case "OT_CREATE_PEER_CONNECTION_FAILED":
showMessage("Publishing your video failed. This could be due to a restrictive firewall.");
break;
default:
showMessage("An unknown error occurred while trying to publish your video. Please try again later.");
}
publisher.destroy();
publisher = null;
}
});
Losing Connectivity
Your Publisher 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. You can handle the Publisher disconnecting by listening for the streamDestroyed event with a reason property set to "networkDisconnected" like so:
publisher.on({
streamDestroyed: function (event) {
if (event.reason === 'networkDisconnected') {
showMessage('Your publisher lost its connection. Please check your internet connection and try publishing again.');
}
}
});