A streamer example with publishing automatic restore
This example shows how to restore stream publishing automatically by changing codec to VP8 if H264 stream publishing fails. Streaminh can be restored if browser did not reload the page, and Javascript can be executed. The publishing problems are detected by bitrate dropping to 0.
Code of the example
The example code is available on WCS server by the following path:
/usr/local/FlashphonerWebCallServer/client2/examples/demo/streaming/stream-auto-restore
stream-auto-restore.css - styles file
stream-auto-restore.html - client page
stream-auto-restore.js - main script to work
The example can be tested by the following URL:
https://host:8888/client2/examples/demo/streaming/stream_filter/stream-auto-restore.html
Where host - WCS server address.
Analyzing the code
To analyze the code take the file stream_filter.js version with hash f2862b9 which is available here and can be downloaded with SDK build 2.0.207.
1. API initializing.
Flashphoner.init() code
Flashphoner.init();
2. Connecting to the server.
Flashphoner.createSession() code
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function(session){ setStatus("#connectStatus", session.status()); onConnected(session); }).on(SESSION_STATUS.DISCONNECTED, function(){ setStatus("#connectStatus", SESSION_STATUS.DISCONNECTED); onDisconnected(); }).on(SESSION_STATUS.FAILED, function(){ setStatus("#connectStatus", SESSION_STATUS.FAILED); onDisconnected(); });
3. Receiving the event confirming successful connection.
ConnectionStatusEvent ESTABLISHED code
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function(session){ setStatus("#connectStatus", session.status()); onConnected(session); }).on(SESSION_STATUS.DISCONNECTED, function(){ ... }).on(SESSION_STATUS.FAILED, function(){ ... });
4. Video streaming.
session.createStream(), publish() code
The following parameters are passed:
- streamName - stream name
- localVideo - div tag to display video from camera
- stripCodecs - codec to exclude from publishing
session.createStream({ name: streamName, display: localVideo, cacheLocalResources: true receiveVideo: false, receiveAudio: false, stripCodecs: stripCodecs ... }).publish();
5. Receiving the event confirming successful streaming.
StreamStatusEvent PUBLISHING code
session.createStream({ ... }).on(STREAM_STATUS.PUBLISHING, function(stream){ setStatus("#publishStatus", STREAM_STATUS.PUBLISHING); onPublishing(stream); }).on(STREAM_STATUS.UNPUBLISHED, function(){ ... }).on(STREAM_STATUS.FAILED, function(){ ... }).publish();
6. Stream playback.
session.createStream(), play() code.
When stream is created, the following parameters are passed
- streamName - name of the stream (including the stream published on step above)
- remoteVideo - <div> element, in which video playback will be displayed
session.createStream({ name: streamName, display: remoteVideo ... }).play();
7. Receiving the event confirming successful stream playback.
StreamStatusEvent PLAYING code
session.createStream({ name: streamName, display: remoteVideo ... }).on(STREAM_STATUS.PLAYING, function(stream) { setStatus("#playStatus", stream.status()); onPlaying(stream); }).on(STREAM_STATUS.STOPPED, function() { ... }).on(STREAM_STATUS.FAILED, function() { ... }).play();
8. Stream playback stop.
stream.stop() code
function onPlaying(stream) { $("#playBtn").text("Stop").off('click').click(function(){ $(this).prop('disabled', true); stream.stop(); }).prop('disabled', false); $("#playInfo").text(""); }
9. Receiving the event confirming successful playback stop.а.
StreamStatusEvent STOPPED code
session.createStream({ ... }).on(STREAM_STATUS.PLAYING, function(stream) { ... }).on(STREAM_STATUS.STOPPED, function() { setStatus("#playStatus", STREAM_STATUS.STOPPED); onStopped(); }).on(STREAM_STATUS.FAILED, function() { ... }).play();
10. Streaming stop.
stream.stop() code
function onPublishing(stream) { $("#publishBtn").text("Stop").off('click').click(function(){ $(this).prop('disabled', true); stream.stop(); }).prop('disabled', false); $("#publishInfo").text(""); ... }
11. Receiving the event confirming successful streaming stop.
StreamStatusEvent UNPUBLISHED code
session.createStream({ ... }).on(STREAM_STATUS.PUBLISHING, function(stream){ ... }).on(STREAM_STATUS.UNPUBLISHED, function(){ setStatus("#publishStatus", STREAM_STATUS.UNPUBLISHED); onUnpublished(); }).on(STREAM_STATUS.FAILED, function(){ ... }).publish();
12. Publishing bitrate checking parameters.
var statPublishFailureDetector = { failed: false, codec: "", lastBytesSent: 0, counter: { value: 0, threshold: PUBLISH_FAILURE_DETECTOR_MAX_TRIES } };
13. Publishing bitrate chacking start by STREAM_STATUS.PUBLISHING event.
function onPublishing(stream) { ... if(Browser.isChrome()) { detectPublishFailure(stream, PUBLISH_FAILURE_DETECTOR_INTERVAL, PUBLISH_FAILURE_DETECTOR_MAX_TRIES); } }
14. WebRTC statistics gathering from browser, current publishing codec and bitrate detection, publishing stopping if bitrate is constantly drops to 0.
stream.getStats(function(stat) { let videoStats = stat.outboundStream.video; if(!videoStats) { return; } let codec = videoStats.codec; let bytesSent = videoStats.bytesSent; let bitrate = (bytesSent - statPublishFailureDetector.lastBytesSent) * 8; if (bitrate == 0) { statPublishFailureDetector.counter.value++; console.log("Bitrate is 0 (" + statPublishFailureDetector.counter.value + ")"); if (statPublishFailureDetector.counter.value >= statPublishFailureDetector.counter.threshold) { statPublishFailureDetector.failed = true; console.log("Publishing seems to be failed, stop the stream"); stream.stop(); } } else { statPublishFailureDetector.counter.value = 0; } statPublishFailureDetector.lastBytesSent = bytesSent; statPublishFailureDetector.codec = codec; $("#publishInfo").text(statPublishFailureDetector.codec); });
15. Bitrate checking timer stopping and streaming restart function invokation by STREAM_STATUS.UNPUBLISHED event
function onUnpublished() { ... if (publishFailureIntervalID) { clearInterval(publishFailureIntervalID); publishFailureIntervalID = null; } checkPublishFailureConditions(); }
16. Restart publishing with VP8 codec
function checkPublishFailureConditions() { if (statPublishFailureDetector.failed) { $("#publishInfo").text("Failed to publish " + statPublishFailureDetector.codec); if (statPublishFailureDetector.codec == "H264") { console.log("H264 publishing seems to be failed, trying VP8 by stripping H264"); let stripCodecs = "H264"; publishBtnClick(stripCodecs); } else if (statPublishFailureDetector.codec == "VP8") { console.log("VP8 publishing seems to be failed, giving up"); } } }