...
On the screenshot below the participant is connected, publishing a stream and playing streams from the other two participants.
Three videos are played on the page
...
Here host is the address of the WCS server.
...
Analyzing the code
To analyze the code, let's take the version of file conference.js with hash ecbadc390771d4, which is available here and can be downloaded with corresponding build 2.0.212218.
Script for video conference uses roomApi designed for video chats, video conferences, webinars and other applications that involve presence of users in one virtual "room". To use RoomApi, the script flashphoner-room-api.js should be included
...
1. Initialization of the API.
Flashphoner.init() code
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.init(); |
2. Connection to server.RoomApi.connectCamera and microphone access request
Flashphoner.getMediaAccess() code
Code Block | ||||
---|---|---|---|---|
| ||||
connection = RoomApiFlashphoner.connect({urlServer: url, username: username}).on(SESSION_STATUS.FAILED, function(session)getMediaAccess(null, localDisplay).then(function() { ... }); |
3. Receiving the event confirming successful connection
ConnectionStatusEvent ESTABLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
connection = RoomApi.connect({urlServer: url, username: username}).on(SESSION_STATUS.FAILED, function(session) createConnection(url, username); }).catch(function(error) { ... }).on(SESSION_STATUS.DISCONNECTED, function(session) { ... }).on(SESSION_STATUS.ESTABLISHED, function(session) { setStatus('#status', session.status())console.error("User not allowed media access: "+error); $("#failedInfo").text("User not allowed media access. Refresh the page"); joinRoom(); onLeft(); }); |
4. Joining a conference3. Connection to server.
connectionRoomApi.joinconnect() code
To join, name of the conference room is passed to the method. (The name can be specified as parameter in the URL of the client page; otherwise, random name will be generated.)
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName(), record: isRecord()function createConnection(url, username) { connection = RoomApi.connect({urlServer: url, username: username}).on(ROOMSESSION_EVENTSTATUS.STATEFAILED, function(roomsession){ ... }); } |
54. Receiving Receiving the event describing chat room state
RoomStatusEvent STATE code
On this event:
- the length of the array of Participant objects returned by method Room.getParticipants() is determined to get the number of already connected participants
- if the maximum allowed number of participants had already been reached, the user leaves the "room" (line 85)
- otherwise, the user starts publishing video stream
...
confirming successful connection
ConnectionStatusEvent ESTABLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
connection = RoomApi.joinconnect({nameurlServer: getRoomName()url, recordusername: isRecord()username}).on(ROOMSESSION_EVENTSTATUS.STATEFAILED, function(roomsession){ var participants = room.getParticipants();... }).on(SESSION_STATUS.DISCONNECTED, function(session) { console.log("Current number of participants in the room: " + participants.length... }).on(SESSION_STATUS.ESTABLISHED, function(session) { setStatus('#status', session.status()); if (participants.length >= _participants) { console.warn("Current room is full"); $("#failedInfo").text("Current room is full."); room.leave().then(onLeft, onLeft); return false; } setInviteAddress(room.name()); if (participants.length > 0) joinRoom(); }); |
5. Joining a conference.
connection.join() code
To join, name of the conference room is passed to the method. (The name can be specified as parameter in the URL of the client page; otherwise, random name will be generated.)
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName(), record: isRecord()}).on(ROOM_EVENT.STATE, function(room){
...
}); |
6. Receiving the event describing chat room state
RoomStatusEvent STATE code
On this event:
- the length of the array of Participant objects returned by method Room.getParticipants() is determined to get the number of already connected participants
- if the maximum allowed number of participants had already been reached, the user leaves the "room" (line 85)
- otherwise, the user starts publishing video stream
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName(), record: isRecord()}).on(ROOM_EVENT.STATE, function(room){
var participants = room.getParticipants();
console.log("Current number of participants in the room: " + participants.length);
if (participants.length >= _participants) {
console.warn("Current room is full");
$("#failedInfo").text("Current room is full.");
room.leave().then(onLeft, onLeft);
return false;
}
setInviteAddress(room.name());
if (participants.length > 0) {
var chatState = "participants: ";
for (var i = 0; i < participants.length; i++) {
installParticipant(participants[i]);
chatState += participants[i].name();
if (i != participants.length - 1) {
chatState += ",";
}
}
addMessage("chat", chatState);
} else {
addMessage("chat", " room is empty");
}
publishLocalMedia(room);
onJoined(room);
}).on(ROOM_EVENT.JOINED, function(participant){
...
}).on(ROOM_EVENT.LEFT, function(participant){
...
}).on(ROOM_EVENT.PUBLISHED, function(participant){
...
}).on(ROOM_EVENT.FAILED, function(room, info){
...
}).on(ROOM_EVENT.MESSAGE, function(message){
...
}); |
7. Low Power Mode checking before publishing on mobile device
Flashphoner.playFirstVideo() code
Code Block | ||||
---|---|---|---|---|
| ||||
if (Browser.isSafariWebRTC()) {
var display = document.getElementById("localDisplay");
Flashphoner.playFirstVideo(display, true, PRELOADER_URL).then(function() {
publishLocalMedia(room);
}).catch(function (error) {
console.log("Can't atomatically publish local stream, use Publish button");
for (var i = 0; i < display.children.length; i++) {
if (display.children[i]) {
console.log("remove cached instance id " + display.children[i].id);
display.removeChild(display.children[i]);
}
}
onMediaStopped(room);
});
} |
8. Video streaming.
room.publish() code
<div> element 'localDisplay', in which video from camera will be displayed, is passed to the room.publish() method
Code Block | ||||
---|---|---|---|---|
| ||||
room.publish({ var chatState = "participants: ";display: display, constraints: constraints, record: false, receiveVideo: false, for (var i = 0; i < participants.length; i++receiveAudio: false }).on(STREAM_STATUS.FAILED, function (stream) { console.warn("Local installParticipant(participants[i]stream failed!"); chatState += participants[i].name(setStatus("#localStatus", stream.status()); onMediaStopped(room); if (i != participants.length - 1 }).on(STREAM_STATUS.PUBLISHING, function (stream) { setStatus("#localStatus", stream.status()); chatState += ","; onMediaPublished(stream); }).on(STREAM_STATUS.UNPUBLISHED, function(stream) { } } setStatus("#localStatus", stream.status()); addMessage("chat", chatStateonMediaStopped(room); } else { addMessage("chat", " room is empty"); } publishLocalMedia(room);}); |
9. Receiving the event notifying that other participant joined to the room
RoomStatusEvent JOINED code
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName(), record: isRecord()}).on(ROOM_EVENT.STATE, function(room){ onJoined(room);... }).on(ROOM_EVENT.JOINED, function function(participant){ installParticipant(participant){; ...addMessage(participant.name(), "joined"); }).on(ROOM_EVENT.LEFT, function(participant){ ... }).on(ROOM_EVENT.PUBLISHED, function(participant){ ... }).on(ROOM_EVENT.FAILED, function(room, info){ ... }).on(ROOM_EVENT.MESSAGE, function(message){ ... }); |
6. Low Power Mode checking before publishing on mobile device
Flashphoner.playFirstVideo() 10. Receiving the event notifying that other participant published video stream
RoomStatusEvent PUBLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
if (Browser.isSafariWebRTC()) { var display = document.getElementById("localDisplay"); Flashphoner.playFirstVideo(display, true, PRELOADER_URL).then(function() connection.join({name: getRoomName(), record: isRecord()}).on(ROOM_EVENT.STATE, function(room){ publishLocalMedia(room);... }).on(ROOM_EVENT.JOINED, function(participant){ ... }).catch(function (error) on(ROOM_EVENT.LEFT, function(participant){ ... }).on(ROOM_EVENT.PUBLISHED, function(participant){ console.log("Can't atomatically publish local stream, use Publish button");playParticipantsStream(participant); }).on(ROOM_EVENT.FAILED, function(room, info){ for (var i = 0; i < display.children.length; i++) { ... }).on(ROOM_EVENT.MESSAGE, function(message){ ... }); |
11. Low Power Mode checking before playback on mobile device
Flashphoner.playFirstVideo() code
Code Block | ||||
---|---|---|---|---|
| ||||
if (display.children[i]Browser.isSafariWebRTC()) { consoleFlashphoner.log("remove cached instance id " + display.children[i].id); playFirstVideo(pDisplay, false, PRELOADER_URL).then(function() { display.removeChild(display.children[i]playStream(participant, pDisplay); } }).catch(function }(error) { onMediaStopped(room); }); } |
7. Video streaming.
room.publish() code
<div> element 'localDisplay', in which video from camera will be displayed, is passed to the room.publish() method
Code Block | ||||
---|---|---|---|---|
| ||||
room.publish({ display: display,// Low Power Mode detected, user action is needed to start playback in this mode #WCS-2639 constraints: constraints, record: false, console.log("Can't receiveVideo: false, atomatically play participant" + participant.name() + " stream, use Play button"); receiveAudio: false }).on(STREAM_STATUS.FAILED, function (stream) { console.warn("Local stream failed!"); for (var i = 0; i setStatus("#localStatus", stream.status()); < pDisplay.children.length; i++) { onMediaStopped(room); }).on(STREAM_STATUS.PUBLISHING, function (stream) { setStatus("#localStatus", stream.status()); if onMediaPublished(stream);(pDisplay.children[i]) { }).on(STREAM_STATUS.UNPUBLISHED, function(stream) { setStatus("#localStatus", stream.status()); onMediaStopped(room); }); |
8. Receiving the event notifying that other participant joined to the room
RoomStatusEvent JOINED code
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName(), record: isRecord()}).on(ROOM_EVENT.STATE, function(room){ ... }).on(ROOM_EVENT.JOINED, function(participant){console.log("remove cached instance id " + pDisplay.children[i].id); pDisplay.removeChild(pDisplay.children[i]); installParticipant(participant); addMessage(participant.name(), "joined"); }).on(ROOM_EVENT.LEFT, function(participant){ ... }).on(ROOM_EVENT.PUBLISHED, function(participant){ ... }).on(ROOM_EVENT.FAILED, function(room, info){ } ... }).on(ROOM_EVENT.MESSAGE, function(message){ ... }); |
9. Receiving the event notifying that other participant published video stream
RoomStatusEvent PUBLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName(), record: isRecord()}).on(ROOM_EVENT.STATE, function(room){ } ... }).on(ROOM_EVENT.JOINED, function(participant){ ... }).on(ROOM_EVENT.LEFT, function onParticipantStopped(participant){; ... }).on(ROOM_EVENT.PUBLISHED, function(participant){ playParticipantsStream(participant); }).on(ROOM_EVENT.FAILED, function(room, info){ ... }).on(ROOM_EVENT.MESSAGE, function(message){ ); } |
12. Playback of video stream.
participant.
...
10. Low Power Mode checking before playback on mobile device
Flashphoner.playFirstVideo() code
...
language | js |
---|---|
theme | RDark |
...
play() code
The following parameters are passed to the method:
- display - div element to display remote video;
- options.unmutePlayOnStart - enables (by default) or disables (for example, in Android Edge) automatic audio unmuting while starting playback;
- options.constraints.audio.deviceId - audio output device Id (the example uses default audio device)
A user must click a button if automatic audio playback is disabled
Code Block | ||||
---|---|---|---|---|
| ||||
var Flashphoner.playFirstVideo(pDisplay, false, PRELOADER_URL).then(function() { options = { unmutePlayOnStart: true, playStream(participant, pDisplay);constraints: { }).catch(function (error) audio: { deviceId: 'default' // Low Power Mode detected, user action is needed to start playback in this mode #WCS-2639} } }; console.log("Can't atomatically play participant" + participant.name() + " stream, use Play button"); // Leave participant stream muted in Android Edge browser #WCS-3445 if (Browser.isChromiumEdge() && Browser.isAndroid()) { for (var i options.unmutePlayOnStart = 0false; i < pDisplay.children.length; i++) { } participant.getStreams()[0].play(display, options).on(STREAM_STATUS.PLAYING, function (playingStream) { var video if= document.getElementById(pDisplay.children[i]) {playingStream.id()) video.addEventListener('resize', function (event) { console.log("remove cached instance id " + pDisplay.children[i].idresizeVideo(event.target); }); // Set up participant Stop/Play button if pDisplay.removeChild(pDisplay.children[i]);(playBtn) { $(playBtn).text("Stop").off('click').click(function() { } $(this).prop('disabled', true); } playingStream.stop(); onParticipantStopped(participant}).prop('disabled', false); } }); // Set up participant audio toggle button } |
11. Playback of video stream.
participant.play() code
<div> element, in which the video will be displayed, is passed to the participant.play() method.
Code Block | ||||
---|---|---|---|---|
| ||||
function playParticipantsStream(participant) { #WCS-3445 if (participant.playaudioBtn) { $("[id$=Name]audioBtn).text("Audio").each(function (index, valueoff('click').click(function() { if (playingStream.isRemoteAudioMuted()) { if ($(value).text() == participant.name()) { playingStream.unmuteRemoteAudio(); var p = value.id.replace('Name', '');} else { var pDisplay = p + 'Display' playingStream.muteRemoteAudio(); participant.play(document.getElementById(pDisplay)).on(STREAM_STATUS.PLAYING, function (playingStream) { } document.getElementById(playingStream.id()).addEventListener('resize', function (event) { }).prop('disabled', false); } // Start participant audio state checking resizeVideo(event.target);timer #WCS-3445 participantState.startMutedCheck(playingStream); }); .on(STREAM_STATUS.STOPPED, function () { }onParticipantStopped(participant); }).on(STREAM_STATUS.FAILED, function () } { }onParticipantStopped(participant); } } |
...
}); |
13. Stop of streaming.
stream.stop() code
Code Block | ||||
---|---|---|---|---|
| ||||
function onMediaPublished(stream) { $("#localStopBtn").text("Stop").off('click').click(function(){ $(this).prop('disabled', true); stream.stop(); }).prop('disabled', false); ... } |
1314. Receiving the event confirming successful streaming stop
StreamStatusEvent UNPUBLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
room.publish({ display: display, constraints: constraints, record: false, receiveVideo: false, receiveAudio: false }).on(STREAM_STATUS.FAILED, function (stream) { ... }).on(STREAM_STATUS.PUBLISHING, function (stream) { ... }).on(STREAM_STATUS.UNPUBLISHED, function(stream) { setStatus("#localStatus", stream.status()); onMediaStopped(room); }); |
1415. Leaving conference room
room.leave() code
Code Block | ||||
---|---|---|---|---|
| ||||
function onJoined(room) { $("#joinBtn").text("Leave").off('click').click(function(){ $(this).prop('disabled', true); room.leave().then(onLeft, onLeft); }).prop('disabled', false); ... } |
1516. Mute/unmute audio and video of the published stream
stream.isAudioMuted(), stream.isVideoMuted(), stream.muteAudio(), stream.unmuteAudio(), stream.muteVideo(), stream.unmuteVideo() code
Code Block | ||||
---|---|---|---|---|
| ||||
function onMediaPublished(stream) { ... $("#localAudioToggle").text("Mute A").off('click').click(function(){ if (stream.isAudioMuted()) { $(this).text("Mute A"); stream.unmuteAudio(); } else { $(this).text("Unmute A"); stream.muteAudio(); } }).prop('disabled', false); $("#localVideoToggle").text("Mute V").off('click').click(function() { if (stream.isVideoMuted()) { $(this).text("Mute V"); stream.unmuteVideo(); } else { $(this).text("Unmute V"); stream.muteVideo(); } }).prop('disabled',false); } |
1617. Sending text message.
participant.sendMessage() code
When Send button is clicked,
...