Table of Contents |
---|
Example of video conference
This example can be used to organize video conference for three participants on Web Call Server.
Each participant can publish a WebRTC stream of one of the following types
- WebRTC
- RTMFP
- RTMP
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
- video from the camera of this participant (Alice) - the lower one
- videos from the other participants (Bob and Cindy)
...
Code of the example
The path to the source code of the example on WCS server is:
...
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 cf0daabc6b86e21d5a2f9e4605366c8b7f0d27eb90771d4, which is available here and can be downloaded with corresponding build 2.0.3.18.1894.218.
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".
Unlike direct connection to server with method createSession(), method roomApi.connect() is used when a user connects to a conference.
Method roomApi.join() is used to join to a new "room". When joining, object 'room' is created for work with the "room", and object 'participant' - for work with the participant.
All events occurring in the "room" (a user joined/left, or sent a message), are sent to all users connected to the "room".For example, in the following code, a user joins to a "room" and gets the list of already connected users.To use RoomApi, the script flashphoner-room-api.js should be included
Code Block | ||||
---|---|---|---|---|
| ||||
<script type="text/javascript" src="../../../../flashphoner-room-api.js"></script> |
In this case, RoomApi.sdk object should be used to access Flashphoner methods
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName()}).on(ROOM_EVENT.STATE, function(room){
var participants = room.getParticipants();
... |
Here user receives data of another participant which have just joined:
Code Block | ||||
---|---|---|---|---|
| ||||
}).on(ROOM_EVENT.JOINED, function(participant){
installParticipant(participant);
addMessage(participant.name(), "joined");
... |
...
var Flashphoner = RoomApi.sdk;
...
Flashphoner.init(); |
Unlike direct connection to server with method createSession(), method roomApi.connect() is used when a user connects to a conference.
Method roomApi.join() is used to join to a new "room". When joining, object 'room' is created for work with the "room", and object 'participant' - for work with the participant.
All events occurring in the "room" (a user joined/left, or sent a message), are sent to all users connected to the "room". For example, in the following code, a user joins to a "room" and gets the list of already connected users.
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphonerconnection.initjoin({flashMediaProviderSwfLocationname: '../../../../media-provider.swf'}); |
2. Connection to server. line 69
Connection to server is established when Join button is clicked.
getRoomName()}).on(ROOM_EVENT.STATE, function(room){
var participants = room.getParticipants();
... |
Here user receives data of another participant which have just joined:
Code Block | ||||
---|---|---|---|---|
| ||||
connection = Flashphoner.roomApi.connect({urlServer: url, username: username}).on(SESSIONROOM_STATUSEVENT.FAILEDJOINED, function(sessionparticipant){ setStatus('#status', session.status() installParticipant(participant); onLeft(); }).on(SESSION_STATUS.DISCONNECTED, function(session) {addMessage(participant.name(), "joined"); setStatus('#status', session.status()); onLeft(); }).on(SESSION_STATUS.ESTABLISHED, function(session) { setStatus('#status', session.status() ... |
1. Initialization of the API.
Flashphoner.init() code
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.init(); |
2. Camera and microphone access request
Flashphoner.getMediaAccess() code
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.getMediaAccess(null, localDisplay).then(function() { createConnection(url, username); joinRoom(); }); |
Session is created with method roomApi.connect(). Callback function, which will be called in case of successfully established connection (status SESSION_STATUS.ESTABLISHED), is added.
3. Joining a conference. line 82
...
}).catch(function(error) {
console.error("User not allowed media access: "+error);
$("#failedInfo").text("User not allowed media access. Refresh the page");
onLeft();
}); |
3. Connection to server.
RoomApi.connect() code
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName()function createConnection(url, username) { connection = RoomApi.connect({urlServer: url, username: username}).on(ROOMSESSION_EVENTSTATUS.STATEFAILED, function(roomsession){ var participants = room.getParticipants();... console.log("Current number of participants in the room: " + participants.length); if (participants.length >= _participants) }); } |
4. Receiving the event confirming successful connection
ConnectionStatusEvent ESTABLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
connection = RoomApi.connect({urlServer: url, username: username}).on(SESSION_STATUS.FAILED, function(session){ console.warn("Current room is full");... }).on(SESSION_STATUS.DISCONNECTED, function(session) { ... }).on(SESSION_STATUS.ESTABLISHED, function(session) { setStatus('#status', session.status()); $("#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++ 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 installParticipant(participants[i]is full"); $("#failedInfo").text("Current room chatState += participants[i].name(is full."); if (i != participants.length - 1) {room.leave().then(onLeft, onLeft); return false; chatState += ","; } setInviteAddress(room.name()); } } if (participants.length > 0) { var chatState = addMessage("chat", chatState)participants: "; } else { for (var i = 0; i < addMessage("chat", " room is empty"); participants.length; i++) { } publishLocalMedia(roominstallParticipant(participants[i]); onJoined(room); }).on(ROOM_EVENT.JOINED, function(participant){chatState += participants[i].name(); installParticipant(participant); if addMessage(participant.name(), "joined"); }).on(ROOM_EVENT.LEFT, function(participant){ (i != participants.length - 1) { //remove participant chatState removeParticipant(participant)+= ","; addMessage(participant.name(), "left"); }).on(ROOM_EVENT.PUBLISHED, function(participant){ } } playParticipantsStream(participantaddMessage("chat", chatState); }).on(ROOM_EVENT.FAILED, function(room, info){ } else { addMessage("chat", " room is empty"); } connection.disconnectpublishLocalMedia(room); $onJoined('#failedInfo').text(info)room); }).on(ROOM_EVENT.MESSAGEJOINED, function(messageparticipant){ addMessage(message.from.name(), message.text); }); |
...
.on(ROOM_EVENT. |
...
LEFT, function(participant){ ... }).on(ROOM_EVENT. |
...
PUBLISHED, function(participant){ ... }).on(ROOM_EVENT.FAILED |
...
, function(room, info){ ... }).on(ROOM_EVENT. |
...
In this callback function,
- 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
ROOM_EVENT.JOINED event is sent by server when a new participants joins the "room"; when this status is received, the interface is changed to display the new participant and playback of video stream published by that participant is started.
ROOM_EVENT.LEFT event is sent by server when one of the participants leaves the conference; when this status is received, appropriate changes in the interface are done.
ROOM_EVENT.PUBLISHED event is sent by server when one of the participants starts publishing; when this status is received, playback of video stream published by that participant is started.
ROOM_EVENT.FAILED - when this status is received, method connection.disconnect() is used to close connection to server.
ROOM_EVENT.MESSAGE event is sent by server when one of the participants sends text message; when this status is received, the message is added to the messages log.
When callback function for event ROOM_EVENT.JOINED, ROOM_EVENT.LEFT or ROOM_EVENT.PUBLISHED is called, object 'participant' describing corresponding participant (which joined / left / started publishing) is passed to the function.
4. Video streaming. line 217
Code Block | ||||
---|---|---|---|---|
| ||||
room.publish(document.getElementById("localDisplay")).on(STREAM_STATUS.FAILED, function (stream) {
console.warn("Local stream failed!");
setStatus("#localStatus", stream.status());
onMediaStopped(room);
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
setStatus("#localStatus", stream.status());
onMediaPublished(stream);
}).on(STREAM_STATUS.UNPUBLISHED, function(stream) {
setStatus("#localStatus", stream.status());
onMediaStopped(room);
}); |
After joining a conference, method room.publish() is called to publish video stream.
<div> element 'localDisplay', in which video from camera will be displayed, is passed to the method.
5. Playback of video stream. line 159
When one of the conference participants starts publishing, method participant.play() is used to start playback of that stream.
<div> element, in which the video will be displayed, is passed to the method.
Code Block | ||||
---|---|---|---|---|
| ||||
participant.play(document.getElementById(pDisplay)).on(STREAM_STATUS.PLAYING, function (playingStream) {
document.getElementById(playingStream.id()).addEventListener('resize', function (event) {
resizeVideo(event.target);
});
}); |
Callback function for event STREAM_STATUS.PLAYING is added to call function resizeVideo(), which is used in the examples to adapt resolution to the element, in which the video will be displayed.
6. Stop of streaming. line 184
The following method is called to stop video streaming
Code Block | ||||
---|---|---|---|---|
| ||||
stream.stop(); |
After calling the method, status STREAM_STATUS.UNPUBLISHED should arrive to confirm stop of streaming from the server side.
7. Leaving conference room. line 22
...
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({
display: display,
constraints: constraints,
record: false,
receiveVideo: false,
receiveAudio: false
}).on(STREAM_STATUS.FAILED, function (stream) {
console.warn("Local stream failed!");
setStatus("#localStatus", stream.status());
onMediaStopped(room);
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
setStatus("#localStatus", stream.status());
onMediaPublished(stream);
}).on(STREAM_STATUS.UNPUBLISHED, function(stream) {
setStatus("#localStatus", stream.status());
onMediaStopped(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){
...
}).on(ROOM_EVENT.JOINED, 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){
...
}); |
10. 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(participant){
...
}).on(ROOM_EVENT.PUBLISHED, function(participant){
playParticipantsStream(participant);
}).on(ROOM_EVENT.FAILED, function(room, info){
...
}).on(ROOM_EVENT.MESSAGE, function(message){
...
}); |
11. Low Power Mode checking before playback on mobile device
Flashphoner.playFirstVideo() code
Code Block | ||||
---|---|---|---|---|
| ||||
if (Browser.isSafariWebRTC()) {
Flashphoner.playFirstVideo(pDisplay, false, PRELOADER_URL).then(function() {
playStream(participant, pDisplay);
}).catch(function (error) {
// 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");
for (var i = 0; i < pDisplay.children.length; i++) {
if (pDisplay.children[i]) {
console.log("remove cached instance id " + pDisplay.children[i].id);
pDisplay.removeChild(pDisplay.children[i]);
}
}
onParticipantStopped(participant);
});
} |
12. Playback of video stream.
participant.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 options = {
unmutePlayOnStart: true,
constraints: {
audio: {
deviceId: 'default'
}
}
};
// Leave participant stream muted in Android Edge browser #WCS-3445
if (Browser.isChromiumEdge() && Browser.isAndroid()) {
options.unmutePlayOnStart = false;
}
participant.getStreams()[0].play(display, options).on(STREAM_STATUS.PLAYING, function (playingStream) {
var video = document.getElementById(playingStream.id())
video.addEventListener('resize', function (event) {
resizeVideo(event.target);
});
// Set up participant Stop/Play button
if (playBtn) {
$(playBtn).text("Stop").off('click').click(function() {
$(this).prop('disabled', true);
playingStream.stop();
}).prop('disabled', false);
}
// Set up participant audio toggle button #WCS-3445
if (audioBtn) {
$(audioBtn).text("Audio").off('click').click(function() {
if (playingStream.isRemoteAudioMuted()) {
playingStream.unmuteRemoteAudio();
} else {
playingStream.muteRemoteAudio();
}
}).prop('disabled', false);
}
// Start participant audio state checking 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);
...
} |
14. 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);
}); |
15. 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);
...
} |
16. 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);
} |
17. Sending text message.
participant.sendMessage() code
When Send button is clicked,
- method room.getParticipants() is used to get the array of connected participants
- the message is sent to each participant
Code Block | ||||
---|---|---|---|---|
|
...
function |
...
onJoined( |
...
8. Mute/unmute audio and video of the published stream. line 187, line 196
The following functions are used to check if audio or video is muted.
- stream.isAudioMuted()
- stream.isVideoMuted()
The following functions are used to mute/unmute audio and video
- stream.unmuteAudio();
- stream.muteAudio();
- stream.unmuteVideo();
- stream.muteVideo();
9. Sending text message. line 29
Method participant.sendMessage() is used for sending text message to other participants. Message text is passed to the method.
When Send button is clicked,
- method room.getParticipants() is used to get the array of connected participants
- the message is sent to each participant
Code Block | ||||
---|---|---|---|---|
| ||||
room) { ... $('#sendMessageBtn').off('click').click(function(){ var message = field('message'); addMessage(connection.username(), message); $('#message').val(""); //broadcast message var participants = room.getParticipants(); for (var i = 0; i < participants.length; i++) { participants[i].sendMessage(message); } }).prop('disabled',false); $('#failedInfo').text(""); } |