The example shows how to play a number of streams in one WebRTC connection with simulcast. A room is considered to be a publishing unit, that is, viewers who connect to this room receive all the streams published in it.
On the screenshots below:
Note that audio tracks are playing in a separate audio
tags.
To analyze the example source code, take the version available here, which can be downloaded with build 1.0.1.36
The source code consists of the following modules:
Local variables declaration to work with constants, SFU SDK, to display video and to work with client configuration
const constants = SFU.constants; const sfu = SFU; let mainConfig; let remoteDisplay; let playState; const PLAY = "play"; const STOP = "stop"; const PRELOADER_URL="../commons/media/silence.mp3" |
Default room configuration to use if there is no config.json
file found
const defaultConfig = { room: { url: "ws://127.0.0.1:8080", name: "ROOM1", pin: "1234", nickName: "User1" } }; |
The object should keep Websocket session data, WebRTC connection data and room data, and shoukd form HTML tags ids to access them from code.
const CurrentState = function(prefix) { let state = { prefix: prefix, pc: null, session: null, room: null, set: function(pc, session, room) { state.pc = pc; state.session = session; state.room = room; }, clear: function() { state.room = null; state.session = null; state.pc = null; }, buttonId: function() { return state.prefix + "Btn"; }, buttonText: function() { return (state.prefix.charAt(0).toUpperCase() + state.prefix.slice(1)); }, inputId: function() { return state.prefix + "Name"; }, statusId: function() { return state.prefix + "Status"; }, formId: function() { return state.prefix + "Form"; }, errInfoId: function() { return state.prefix + "ErrorInfo"; }, is: function(value) { return (prefix === value); } }; return state; } |
init() code
The init() function is called on page load and:
config.json
file or default configurationconst init = function() { let configName = getUrlParam("config") || "./config.json"; ... playState = CurrentState(PLAY); $.getJSON(configName, function(cfg){ mainConfig = cfg; onDisconnected(playState); }).fail(function(e){ //use default config console.error("Error reading configuration file " + configName + ": " + e.status + " " + e.statusText) console.log("Default config will be used"); mainConfig = defaultConfig; onDisconnected(playState); }); $("#url").val(setURL()); $("#roomName").val("ROOM1-"+createUUID(4)); $("#playName").val("Player1-"+createUUID(4)); } |
connect(), SFU.createRoom() code
The connect() function is called by Publish or Play click:
const connect = function(state) { //create peer connection pc = new RTCPeerConnection(); //get config object for room creation const roomConfig = getRoomConfig(mainConfig); roomConfig.pc = pc; roomConfig.url = $("#url").val(); roomConfig.roomName = $("#roomName").val(); roomConfig.nickname = $("#" + state.inputId()).val(); // connect to server and create a room if not const session = sfu.createRoom(roomConfig); session.on(constants.SFU_EVENT.CONNECTED, function(room) { state.set(pc, session, room); onConnected(state); setStatus(state.statusId(), "ESTABLISHED", "green"); }).on(constants.SFU_EVENT.DISCONNECTED, function() { state.clear(); onDisconnected(state); setStatus(state.statusId(), "DISCONNECTED", "green"); }).on(constants.SFU_EVENT.FAILED, function(e) { state.clear(); onDisconnected(state); setStatus(state.statusId(), "FAILED", "red"); setStatus(state.errInfoId(), e.status + " " + e.statusText, "red"); }); } |
onConnected() code
The onConnected() function:
const onConnected = function(state) { $("#" + state.buttonId()).text("Stop").off('click').click(function () { onStopClick(state); }).prop('disabled', false); ... // Add errors displaying state.room.on(constants.SFU_ROOM_EVENT.FAILED, function(e) { setStatus(state.errInfoId(), e, "red"); }).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) { setStatus(state.errInfoId(), e.operation + " failed: " + e.error, "red"); }); playStreams(state); } |
playStreams(), SFURoom.join() code
The playStreams() function:
const playStreams = function(state) { //create remote display item to show remote streams remoteDisplay = initRemoteDisplay(document.getElementById("remoteVideo"), state.room, state.pc); state.room.join(); } |
stopStreams(), remoteDisplay.stop() code
const stopStreams = function(state) { if (remoteDisplay) { remoteDisplay.stop(); } } |
onStartClick(), playFirstSound(), connect() code
const onStartClick = function(state) { if (validateForm("connectionForm") && validateForm(state.formId())) { $("#" + state.buttonId()).prop('disabled', true); if (state.is(PLAY) && Browser().isSafariWebRTC()) { playFirstSound(document.getElementById("main"), PRELOADER_URL).then(function () { connect(state); }); } else { connect(state); } } } |
onStopClick(), Session.disconnect() code
The onStopClick() function:
const onStopClick = function(state) { $("#" + state.buttonId()).prop('disabled', true); stopStreams(state); state.session.disconnect(); } |
onDisconnected() code
The onDisconnected() functions:
const onDisconnected = function(state) { $("#" + state.buttonId()).text(state.buttonText()).off('click').click(function () { onStartClick(state); }).prop('disabled', false); $('#url').prop('disabled', false); $("#roomName").prop('disabled', false); $("#" + state.inputId()).prop('disabled', false); } |