...
Note that audio track is playing separately
Example source code
The source code consists of the following modules:
- player.html - HTML page
- player.css - HTML page styles
- player.js - main application logic
Analyzing the code
To analyze the example source code, take the file player.js version with available here
1. Local variables
Local variables declaration to work with constants, SFU SDK, to display video and to work with client configuration
Code Block | ||||
---|---|---|---|---|
| ||||
const constants = SFU.constants;
const sfu = SFU;
const PRELOADER_URL="../commons/media/silence.mp3";
const playStatus = "playStatus";
const playErrorInfo = "playErrorInfo"; |
2. Object to store current publishing/playback state
The object should keep Websocket session data, WebRTC connection data, room data and object to display tracks data
Code Block | ||||
---|---|---|---|---|
| ||||
const CurrentState = function() { let state = { pc: null, session: null, room: null, remoteDisplay: null, roomEnded: false, set: function(pc, session, room) { state.pc = pc; state.session = session; state.room = room; state.roomEnded = false; }, clear: function() { state.room = null; state.session = null; state.pc = null; }, state.roomEnded = false; setDisplay}, setRoomEnded: function(display) { state.remoteDisplayroomEnded = displaytrue; }, disposeDisplayisRoomEnded: function() { ifreturn (state.remoteDisplay) { roomEnded; }, state.remoteDisplay.stopisConnected: function(); { return (state.session && state.session.remoteDisplaystate() === nullconstants.SFU_STATE.CONNECTED); }, } isActive: function() { } }; return state; } |
3. Initialization
init() code
The init() function is called on page load and:
- initializes state objects
- initializes input fields
Code Block | ||||
---|---|---|---|---|
| ||||
const init = function() {
$("#playBtn").prop('disabled', true);
$("#url").prop('disabled', true);
$("#streamName").prop('disabled', true);
onDisconnected(CurrentState());
$("#url").val(setURL());
} |
4. Establishing server connection
connect(), SFU.createRoom() code
The connect() function is called by Play button click:
- creates PeerConnection object
- cleans the previous session state displayed
- sets up room configuration and creates Websocket session
- subscribes to Websocket session events
Code Block | ||||
---|---|---|---|---|
| ||||
const connect = function(state) { // Create peer connection let pc = new RTCPeerConnection(); // Create a config to connect to SFU room const roomConfig = { // Server websocket URL url: $("#url").val(), // Use stream name as room name to play ABR roomName: $("#streamName").val(), // Make a random participant name from stream name nickname: "Player-" + $("#streamName").val() + "-" + createUUID(4), // Set room pin pin: 123456 } // Clean state display items setStatus("playStatus", ""); setStatus("playErrorInfo", ""); // Connect to the server (room should already exist) const session = sfu.createRoom(roomConfig); session.on(constants.SFU_EVENT.CONNECTED, function() { state.set(pc, session, session.room()); onConnected(statereturn (state.room && !state.roomEnded && state.pc); }, setDisplay: function(display) { state.remoteDisplay = display; }, disposeDisplay: function() { if (state.remoteDisplay) { state.remoteDisplay.stop(); state.remoteDisplay = null; } } }; return state; } |
3. Initialization
init() code
The init() function is called on page load and:
- initializes state objects
- initializes input fields
Code Block | ||||
---|---|---|---|---|
| ||||
const init = function() {
$("#playBtn").prop('disabled', true);
$("#url").prop('disabled', true);
$("#streamName").prop('disabled', true);
onDisconnected(CurrentState());
$("#url").val(setURL());
} |
4. Establishing server connection
RTCPeerConnection(), SFU.createRoom() code
The connect() function is called by Play button click:
- creates PeerConnection object
- cleans the previous session state displayed
- sets up room configuration and creates Websocket session
- subscribes to Websocket session events
Code Block | ||||
---|---|---|---|---|
| ||||
const connect = async function(state) {
// Create peer connection
let pc = new RTCPeerConnection();
// Create a config to connect to SFU room
const roomConfig = {
// Server websocket URL
url: $("#url").val(),
// Use stream name as room name to play ABR
roomName: $("#streamName").val(),
// Make a random participant name from stream name
nickname: "Player-" + $("#streamName").val() + "-" + createUUID(4),
// Set room pin
pin: 123456
}
// Clean state display items
setStatus(playStatus, "");
setStatus(playErrorInfo, "");
try {
// Connect to the server (room should already exist)
const session = await sfu.createRoom(roomConfig);
// Set up session ending events
session.on(constants.SFU_EVENT.DISCONNECTED, function() {
onStopClick(state);
onDisconnected(state);
setStatus(playStatus, "DISCONNECTED", "green");
}).on(constants.SFU_EVENT.FAILED, function(e) {
onStopClick(state);
onDisconnected(state);
setStatus(playStatus, "FAILED", "red");
if (e.status && e.statusText) {
setStatus(playErrorInfo, e.status + " " + e.statusText, "red");
} else if (e.type && e.info) {
setStatus(playErrorInfo, e.type + ": " + e.info, "red");
}
});
// Connected successfully
onConnected(state, pc, session);
setStatus(playStatus, "CONNECTING...", "black");
} catch(e) {
onDisconnected(state);
setStatus(playStatus, "FAILED", "red");
setStatus(playErrorInfo, e, "red");
}
} |
5. Playback start after session establishing
onConnected() code
The onConnected() function:
- sets up Stop button click actions
- subscribes to SFU_ROOM_EVENT.PARTICIPANT_LIST event to check if the stream is published in the room
- subscribes to room error events
- calls playback function
Code Block | ||||
---|---|---|---|---|
| ||||
const onConnected = async function(state, pc, session) { state.set(pc, session, session.room()); $("#playBtn").text("Stop").off('click').click(function () { onStopClick(state); }); $('#url').prop('disabled', true); $("#streamName").prop('disabled', true); // Add room event handling state.room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, function(e) { // If the room is empty, the stream is not published yet if (!e.participants || e.participants.length === 0) { setStatus(playErrorInfo, "ABR stream is not published", "red"); onStopClick(state); } else { setStatus(playStatus, "ESTABLISHED", "green"); $("#placeholder").hide(); setStatus("playStatus", "CONNECTING...", "black");} }).on(constants.SFU_ROOM_EVENT.DISCONNECTEDFAILED, function(e) { // Display error state.clear(); onDisconnected(statesetStatus(playErrorInfo, e, "red"); }).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) { setStatus("playStatus", "DISCONNECTED", "green" onOperationFailed(state); }).on(constants.SFU_ROOM_EVENT.FAILEDENDED, function (e) { state.clear(); onDisconnected(state);// Publishing is stopped, dispose playback and close connection setStatus("playStatus"playErrorInfo, "FAILEDABR stream is stopped", "red"); setStatus("playErrorInfo", e.status + " " + e.statusText, "red"state.setRoomEnded(); }onStopClick(state); } |
5. Playback start after session establishing
onConnected() code
The onConnected() function:
...
}).on(constants.SFU_ROOM_EVENT. |
...
Code Block | ||||
---|---|---|---|---|
| ||||
const onConnected = function(state) {DROPPED, function () { // Client dropped from the room, dispose playback and close connection $("#playBtn").text("Stop").off('click').click(function () { setStatus(playErrorInfo, "Playback is dropped due to network issues", "red"); state.setRoomEnded(); onStopClick(state); }).prop('disabled', false; await playStreams(state); $('#url').prop('disabled', true);// Enable button after starting playback #WCS-3635 $("#streamName#playBtn").prop('disabled', true); // Add room event handling state.room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, function(e false); } |
6. Streams playback
playStreams(), SFURoom.join() code
The playStreams() function:
- initializes a base container tag to display incoming media streams
- sets up incoming streams info displaying parameters
- sets up ABR to switch automatically between available qualities when playback channel conditions are changing
- negotiates WebRTC connection
Code Block | ||||
---|---|---|---|---|
| ||||
const playStreams = async function(state) { // If the room is empty, the stream is not published yettry { if(!e.participants || e.participants.length === 0) { setStatus("playErrorInfo", "ABR stream is not published", "red"); // Create remote display item to show remote streams state.setDisplay(initRemoteDisplay({ div: onStopClick(state);document.getElementById("remoteVideo"), } room: state.room, else { peerConnection: state.pc, setStatus("playStatus", "ESTABLISHED", "green"); displayOptions: { $("#placeholder").hide(); } }).on(constants.SFU_ROOM_EVENT.FAILED, function(e) { publisher: false, // Display errorquality: statetrue, setStatus("playErrorInfo", e, "red"); }).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) { type: false, // Display the operation failed abr: true, setStatus("playErrorInfo", e.operation + " failed: " + e.error, "red"); }).on(constants.SFU_ROOM_EVENT.ENDED, function () { abrKeepOnGoodQuality: 20000, // Publishing is stopped, dispose playback and close connection abrTryForUpperQuality: 30000 } setStatus("playErrorInfo", "ABR stream is stopped", "red" })); // Start WebRTC onStopClick(state);negotiation }); await playStreams(state); } |
6. Streams playback
...
.room.join( |
...
The playStreams() function:
- initializes a base container tag to display incoming media streams
- negotiates WebRTC connection
Code Block | ||||
---|---|---|---|---|
| ||||
const playStreams = function(statestate.pc); } catch(e) { // Create remote display item to show remote streams state.setDisplay(initRemoteDisplay({ if (e.type === constants.SFU_ROOM_EVENT.OPERATION_FAILED) { div: document.getElementById("remoteVideo"), onOperationFailed(state, e); } room: state.room,else { peerConnection: state.pc, console.error("Failed to play streams: " displayOptions: {+ e); publisher: false,setStatus(playErrorInfo, e.name, "red"); quality: trueonStopClick(state); } })); state.room.join(state.pc); } |
7. Playback stopping
stopStreams(), CurrentState.disposeDisplay() code
Code Block | ||||
---|---|---|---|---|
| ||||
const stopStreams = function(state) { state.disposeDisplay(); } |
...
onStartClick(), playFirstSound(), connect() code
onStartClick(), playFirstSound(), connect() code
- validates input fields
- in Safari browser, calls playFirstSound() before playback to automatically play incoming audio
- calls connect() function
Code Block | ||||
---|---|---|---|---|
| ||||
const onStartClick = function(state) { if (validateForm("connectionForm")) { $("#playBtn").prop('disabled', true); if (Browser().isSafariWebRTC()) { playFirstSound(document.getElementById("main"), PRELOADER_URL).then(function () { connect(state); }); } else { connect(state); } } } |
...
onStopClick(), Session.disconnect() code
The onStopClick() function:
- stops playback
- disconects Websocket session
Code Block | |||||||
---|---|---|---|---|---|---|---|
| const onStopClick = function(state)
| ||||||
const onStopClick = async function(state) { stopStreams(state); if (state.isConnected()) { $("#playBtn").prop('disabled', true); stopStreams(state); await state.session.disconnect(); onDisconnected(state); } } |
10. Websocket session disconnection actions
onDisconnected() code
The onDisconnected()
...
code:
- sets up Play click actions
- enables Server url and Room name fields access
Code Block | ||||
---|---|---|---|---|
| ||||
const onDisconnected = function(state) {
state.clear();
$("#placeholder").show();
$("#playBtn").text("Play").off('click').click(function () {
onStartClick(state);
}).prop('disabled', false);
$('#url').prop('disabled', false);
$("#streamName").prop('disabled', false);
} |