Table of Contents |
---|
Пример видеоконференции
Данный пример может использоваться для организации видеоконференции для трех участников на Web Call Server
Участник видеоконференции может публиковать следующие типы потоков
...
WebRTC поток
На скриншоте ниже представлен пример клиента участника конференции, к которой присоединились два других участника.
На странице вопроизводятся воспроизводятся три видео
- нижнее - видео с камеры данного участника (Alice)
- два верхних - видео от других двух участников (Bob и Cindy)
...
Код примера
Код данного примера находится на WCS-сервере по следующему пути:
...
Здесь host - адрес WCS-сервера.
Работа с кодом примера
Для разбора кода возьмем версию файла conference.js с хешем cf0daabc6b86e21d5a2f9e4605366c8b7f0d27eb90771d4, которая находится здесь и доступна для скачивания в соответствующей сборке 2.0.3218.18.1894.
Скрипт конференции использует roomApiRoomApi, предназначенное для видеочатов, конференций, вебинаров и других приложений, которые предполагают нахождение пользователей в одной виртуальной "комнате".
При подключении пользователя к конференции, используется метод roomApi.connect(), в отличии от прямого подключения к серверу методом createSession().
При присоединении к новой "комнате" методом roomApi.join(), создается объект room для работы с этой "комнатой". Для работы с участниками конференции используются объекты Participant.
Все события, происходящие в "комнате" (присоединение/выход пользователя, отправленные сообщения), транслируются другим участникам, подключенным к этой "комнате".
Например, в следующем коде подключаемся к "комнате" и запрашиваем список других участников:Для того, чтобы использовать RoomApi, необходимо подключить скрипт flashphoner-room-api.js
Code Block | ||||
---|---|---|---|---|
| ||||
<script type="text/javascript" src="../../../../flashphoner-room-api.js"></script> |
При этом для обращения к стандартным методам Flashphoner необходимо использовать объект RoomApi.sdk
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName()}).on(ROOM_EVENT.STATE, function(room){
var participants = room.getParticipants();
... |
...
var Flashphoner = RoomApi.sdk;
...
Flashphoner.init(); |
При подключении пользователя к конференции, используется метод RoomApi.connect(), в отличии от прямого подключения к серверу методом createSession().
При присоединении к новой "комнате" методом connection.join(), создается объект room для работы с этой "комнатой". Для работы с участниками конференции используются объекты Participant.
Все события, происходящие в "комнате" (присоединение/выход пользователя, отправленные сообщения), транслируются другим участникам, подключенным к этой "комнате". Например, в следующем коде подключаемся к "комнате" и запрашиваем список других участников:
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName()}).on(ROOM_EVENT.JOINEDSTATE, function(participantroom){ installParticipant(participantvar participants = room.getParticipants(); addMessage(participant.... |
Здесь получаем данные другого участника, который только что присоединился:
Code Block | ||||
---|---|---|---|---|
| ||||
}).on(ROOM_EVENT.JOINED, function(participant){
installParticipant(participant);
addMessage(participant.name(), "joined");
... |
1. Инициализация API.
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.init({flashMediaProviderSwfLocation: '../../../../media-provider.swf'}); |
2. Подключение к серверу.Запрос доступа к камере и микрофону
Flashphoner.roomApi.connectgetMediaAccess() код code
Code Block | ||||
---|---|---|---|---|
| ||||
connection = Flashphoner.roomApi.connect({urlServer: url, username: username}).on(SESSION_STATUS.FAILED, function(session){ getMediaAccess(null, localDisplay).then(function() { setStatus('#status', session.status()) createConnection(url, username); onLeft(); }).on(SESSION_STATUS.DISCONNECTED, catch(function(sessionerror) { setStatus('#status', session.status()); console.error("User not allowed media access: "+error); onLeft$(); }).on(SESSION_STATUS.ESTABLISHED, function(session) { setStatus('#status', session.status()); joinRoom(); "#failedInfo").text("User not allowed media access. Refresh the page"); onLeft(); }); |
3. Получение от сервера события, подтверждающего успешное соединениеConnectionStatusEvent ESTABLISHED код. Подключение к серверу.
RoomApi.connect() code
Code Block | ||||
---|---|---|---|---|
| ||||
function createConnection(url, username) { connection = FlashphonerRoomApi.roomApi.connect({urlServer: url, username: username}).on(SESSION_STATUS.FAILED, function(session){ setStatus('#status', session.status()); ... onLeft(}); }).on(SESSION_STATUS.DISCONNECTED, function(session) { setStatus('#status', session.status()); onLeft(); |
4. Получение от сервера события, подтверждающего успешное соединение
ConnectionStatusEvent ESTABLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
connection = RoomApi.connect({urlServer: url, username: username}).on(SESSION_STATUS.FAILED, function(session){ ... }).on(SESSION_STATUS.ESTABLISHEDDISCONNECTED, function(session) { setStatus('#status', session.... }).on(SESSION_STATUS.ESTABLISHED, function(session) { setStatus('#status', session.status()); joinRoom(); }); |
45. Присоединение к конференции.
При присоединении передается имя "комнаты" конференции (берется из параметра в URL страницы клиента, или генерируется случайное имя).
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){ installParticipant(participant); addMessage(participant.name(), "joined"); ... }); |
6. Получение от сервера события, описывающего статус комнаты
RoomStatusEvent STATE code
При получении данного события:
- определяется количество участников конференции с помощью метода room.getParticipants(), который возвращает массив объектов participant,
- если к конференции уже присоединилось максимально допустимое количество участников, производится выход из "комнаты" при помощи room.leave()
- если количество участников меньше максимально допустимого, начинается публикация видеопотока
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName(), record: isRecord()}).on(ROOM_EVENT.LEFTSTATE, function(participantroom){ var //remove participant removeParticipant(participantparticipants = room.getParticipants(); addMessage(participant.name(), "left"); }).on(ROOM_EVENT.PUBLISHED, function(participant){ playParticipantsStream(participant); }).on(ROOM_EVENT.FAILED, function(room, info){ console.log("Current number of participants in the room: " + participants.length); if (participants.length >= _participants) { connection console.disconnect(warn("Current room is full"); $('"#failedInfo'").text(info"Current room is full."); } room.leave().on(ROOM_EVENT.MESSAGEthen(onLeft, function(messageonLeft){; addMessage(message.from.name(), message.text); }); |
5. Получение от сервера события, описывающего статус комнаты
RoomStatusEvent STATE код
При получении данного события:
- определяется количество участников конференции с помощью метода room.getParticipants(), который возвращает массив объектов participant,
- если к конференции уже присоединилось максимально допустимое количество участников, производится выход из "комнаты" при помощи room.leave()
- если количество участников меньше максимально допустимого, начинается публикация видеопотока
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName()}).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) { 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) { console.warn("Current room is full") chatState += ","; $("#failedInfo").text("Current room is full."); } room.leave().then(onLeft, onLeft);} return false addMessage("chat", chatState); } else { setInviteAddress(room.name()); if (participants.length > 0) {addMessage("chat", " room is empty"); } var chatState = "participants: " publishLocalMedia(room); for (var i = 0; i < participants.length; i++) onJoined(room); }).on(ROOM_EVENT.JOINED, function(participant){ ... }).on(ROOM_EVENT.LEFT, function(participant){ ... }).on(ROOM_EVENT.PUBLISHED, function(participant){ installParticipant(participants[i]);... }).on(ROOM_EVENT.FAILED, function(room, info){ ... }).on(ROOM_EVENT.MESSAGE, function(message){ chatState += participants[i].name(); ... }); |
7. Проверка режима Low Power Mode при публикации на мобильном устройстве
Flashphoner.playFirstVideo() code
Code Block | ||||
---|---|---|---|---|
| ||||
if (i != participants.length - 1Browser.isSafariWebRTC()) { var display chatState += ","; } } addMessage("chat", chatState); } else {= document.getElementById("localDisplay"); addMessage("chat", " room is empty"); Flashphoner.playFirstVideo(display, true, PRELOADER_URL).then(function() { } publishLocalMedia(room); onJoined(room); }).on(ROOM_EVENT.JOINED, function(participant){ catch(function (error) { installParticipant(participant); addMessage(participant.name(), "joined"); }).on(ROOM_EVENT.LEFT, function(participant){ console.log("Can't atomatically publish local stream, use Publish button"); //remove participant for removeParticipant(participant); var i = 0; addMessage(participant.name(), "left"); }).on(ROOM_EVENT.PUBLISHED, function(participant)i < display.children.length; i++) { playParticipantsStream(participant); }).on(ROOM_EVENT.FAILED, function(room, info){ connection.disconnect(); if $('#failedInfo').text(info); }).on(ROOM_EVENT.MESSAGE, function(message){ display.children[i]) { addMessage(message.from.name(), message.text); }); |
6. Публикация видеопотока.
room.publish() код
При публикации передаем div-элемент localDisplay, в котором будет отображаться видео с камеры.
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);
}); |
7. Получение от сервера события, сигнализирующего о присоединении пользователя к чат-комнате
RoomStatusEvent JOINED код
console.log("remove cached instance id " + display.children[i].id);
display.removeChild(display.children[i]);
}
}
onMediaStopped(room);
});
} |
8. Публикация видеопотока.
room.publish() code
При публикации передаем div-элемент, в котором будет отображаться видео с камеры.
Code Block | ||||||
---|---|---|---|---|---|---|
| connection.join({name: getRoomName()}).on(ROOM_EVENT.STATE, function(room)
| |||||
room.publish({ var participants = room.getParticipants(); console.log("Current number of participants in the room: " + participants.length); display: display, constraints: constraints, if (participants.length >= _participants) { record: false, receiveVideo: false, console.warn("Current room is full"); receiveAudio: false $("#failedInfo"}).text("Current room is full.");on(STREAM_STATUS.FAILED, function (stream) { roomconsole.leave().then(onLeft, onLeftwarn("Local stream failed!"); return falsesetStatus("#localStatus", stream.status()); } setInviteAddressonMediaStopped(room.name()); if (participants.length > 0}).on(STREAM_STATUS.PUBLISHING, function (stream) { var chatState = "participants: " setStatus("#localStatus", stream.status()); onMediaPublished(stream); }).on(STREAM_STATUS.UNPUBLISHED, function(stream) { for (var i = 0; i < participants.length; i++) { setStatus("#localStatus", stream.status()); onMediaStopped(room); }); |
9. Получение от сервера события, сигнализирующего о присоединении пользователя к чат-комнате
RoomStatusEvent JOINED code
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName(), record: installParticipant(participants[i]); chatState += participants[i].name();isRecord()}).on(ROOM_EVENT.STATE, function(room){ if (i != participants.length - 1) ... }).on(ROOM_EVENT.JOINED, function(participant){ installParticipant(participant); chatState +=addMessage(participant.name(), ",joined"); }).on(ROOM_EVENT.LEFT, function(participant){ }... }).on(ROOM_EVENT.PUBLISHED, function(participant){ } addMessage("chat", chatState);... }).on(ROOM_EVENT.FAILED, function(room, info){ ... } else ).on(ROOM_EVENT.MESSAGE, function(message){ addMessage("chat", " room is empty"); } publishLocalMedia(room); onJoined(room); ... }); |
10. Получение от сервера события, сигнализирующего о публикации видеопотока другим участником
RoomStatusEvent PUBLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName(), record: isRecord()}).on(ROOM_EVENT.JOINEDSTATE, function(participantroom){ installParticipant(participant); addMessage(participant.name(), "joined");... }).on(ROOM_EVENT.LEFTJOINED, function(participant){ //remove participant removeParticipant... }).on(ROOM_EVENT.LEFT, function(participant);{ addMessage(participant.name(), "left");... }).on(ROOM_EVENT.PUBLISHED, function(participant){ playParticipantsStream(participant); }).on(ROOM_EVENT.FAILED, function(room, info){ connection.disconnect(); $('#failedInfo').text(info);info){ ... }).on(ROOM_EVENT.MESSAGE, function(message){ addMessage(message.from.name(), message.text); }); |
...
RoomStatusEvent PUBLISHED код
...
language | js |
---|---|
theme | RDark |
...
11. Проверка режима Low Power Mode при воспроизведении на мобильном устройстве
Flashphoner.playFirstVideo() code
Code Block | ||||
---|---|---|---|---|
| ||||
if (Browser.isSafariWebRTC()) { Flashphoner.playFirstVideo(pDisplay, false, PRELOADER_URL).then(function() { console.warn("Current room is full"); $("#failedInfo").text("Current room is full."playStream(participant, pDisplay); room.leave().then(onLeft, onLeft); return false; }).catch(function (error) }{ setInviteAddress(room.name()); if (participants.length > 0) { var chatState = "participants: "; for (var i = 0; i < participants.length; i++) { // Low Power Mode detected, user action is needed to start playback in this mode #WCS-2639 installParticipant(participants[i]); console.log("Can't atomatically play chatStateparticipant" += participants[i]participant.name() + " stream, use Play button"); if (i != participants.length - 1) { for (var i = 0; i chatState += ","; < pDisplay.children.length; i++) { } } addMessage("chat", chatState); } elseif (pDisplay.children[i]) { addMessage("chat", " room is empty"); } publishLocalMedia(room); onJoined(room); }).on(ROOM_EVENT.JOINED, function(participant){ installParticipant(participantconsole.log("remove cached instance id " + pDisplay.children[i].id); addMessage(participant.name(), "joined"); }).on(ROOM_EVENT.LEFT, function(participant){ //remove participant removeParticipant(participant); addMessage(participant.name(), "left"); }).on(ROOM_EVENT.PUBLISHED, function(participant){ playParticipantsStream(participant); }).on(ROOM_EVENT.FAILED, function(room, info){ pDisplay.removeChild(pDisplay.children[i]); connection.disconnect(); $('#failedInfo').text(info); }).on(ROOM_EVENT.MESSAGE, function(message){ } addMessage(message.from.name(), message.text); }); |
9. Воспроизведение видеопотока.
participant.play() код
Параметром передается div-элемент, в котором будет отображаться видео.
Code Block | ||||
---|---|---|---|---|
| ||||
function playParticipantsStream(participant) { if (participant.play) {} $("[id$=Name]").each(function (index, value) { if onParticipantStopped($(value).text() == participant.name()) { participant); }); var p = value.id.replace('Name', ''); } |
12. Воспроизведение видеопотока.
participant.play() code
В метод передаются следующие параметры:
- display - div-элемент, в котором будет отображаться видео;
- options.unmutePlayOnStart - параметр, разрешающий (по умолчанию) или запрещающий (например, в Android Edge) автоматическое включение звука при проигрывании
- options.constraints.audio.deviceId - устройство вывода звука (в примере указано устройство по умолчанию)
Если автоматическое включение звука запрещено, пользователь должен нажать кнопку для включения звука
Code Block | ||||
---|---|---|---|---|
| ||||
var pDisplay = p + 'Display'; participant.play(document.getElementById(pDisplay)).on(STREAM_STATUS.PLAYING, function (playingStream) { var options = { unmutePlayOnStart: true, document.getElementById(playingStream.id()).addEventListener('resize', function (event)constraints: { audio: { resizeVideo(event.target); deviceId: 'default' }); } }); // Leave participant stream } muted in Android Edge });browser #WCS-3445 } } |
10. Остановка публикации видеопотока.
stream.stop() код
Code Block | ||||
---|---|---|---|---|
| ||||
function onMediaPublished(stream) { if $("#localStopBtn").text("Stop").off('click').click(function(){ Browser.isChromiumEdge() && Browser.isAndroid()) { $(this).prop('disabled', true) options.unmutePlayOnStart = false; stream.stop();} }).prop('disabled', false); $("#localAudioToggle").text("Mute A").off('click').click(function(){ participant.getStreams()[0].play(display, options).on(STREAM_STATUS.PLAYING, function (playingStream) { var video if= document.getElementById(streamplayingStream.isAudioMutedid()) { $(this).text("Mute A"); video.addEventListener('resize', function (event) { stream.unmuteAudio( resizeVideo(event.target); }); else { // Set $(this).text("Unmute A"); up participant Stop/Play button if stream.muteAudio(playBtn); { } }).prop('disabled', false); $("#localVideoToggle"playBtn).text("Mute VStop").off('click').click(function() { $(this).prop('disabled', true) { ; if (streamplayingStream.isVideoMutedstop()) { ; $(this}).text("Mute V"prop('disabled', false); stream.unmuteVideo();} } else { // Set up participant audio toggle button #WCS-3445 if $(thisaudioBtn).text("Unmute V"); { stream.muteVideo(); } }).prop('disabled',false); } |
11. Получение от сервера события, подтверждающего остановку публикации.
StreamStatusEvent UNPUBLISHED код
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);
}); |
12. Выход из комнаты конференции.
room.leave() код
Code Block | ||||
---|---|---|---|---|
| ||||
function onJoined(room) { $("#joinBtn").text("Leave").off('click').click(function(){ $(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 $(this).prop('disabled', true participantState.startMutedCheck(playingStream); room}).leaveon().then(onLeftSTREAM_STATUS.STOPPED, onLeft); function () { }).prop('disabled', false onParticipantStopped(participant); $('#sendMessageBtn').off('click').click(function(){ }).on(STREAM_STATUS.FAILED, function () { var message = fieldonParticipantStopped('message'participant); addMessage(connection.username(), message);}); |
13. Остановка публикации видеопотока.
stream.stop() code
Code Block | ||||
---|---|---|---|---|
| ||||
function onMediaPublished(stream) { $('#message$("#localStopBtn").text("Stop").off('click').valclick(function("");{ //broadcast message$(this).prop('disabled', true); var participants = room.getParticipantsstream.stop(); }).prop('disabled', false); for (var i = 0; i < participants.length; i++) ... } |
14. Получение от сервера события, подтверждающего остановку публикации.
StreamStatusEvent UNPUBLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
room.publish({ display: display, participants[i].sendMessage(message);constraints: constraints, record: false, } }).prop('disabled',false);receiveVideo: false, $('#failedInfo').text(""); } |
13. Включение/выключение аудио и видео для публикуемого потока. line 187, line 196
stream.isAudioMuted(), stream.isVideoMuted(), stream.muteAudio(), stream.unmuteAudio(), stream.muteVideo(), stream.unmuteVideo() код
Code Block | ||||
---|---|---|---|---|
| ||||
function onMediaPublishedreceiveAudio: false }).on(STREAM_STATUS.FAILED, function (stream) { $("#localStopBtn").text("Stop").off('click').click(function()... }).on(STREAM_STATUS.PUBLISHING, function (stream) { $(this... }).prop('disabled', true);on(STREAM_STATUS.UNPUBLISHED, function(stream) { setStatus("#localStatus", stream.stopstatus()); onMediaStopped(room); }).prop('disabled', false);; |
15. Выход из комнаты конференции.
room.leave() code
Code Block | ||||
---|---|---|---|---|
| ||||
function onJoined(room) { $("#localAudioToggle#joinBtn").text("Mute ALeave").off('click').click(function(){ if (stream.isAudioMuted()) { $(this).text("Mute A"prop('disabled', true); stream.unmuteAudio(room.leave().then(onLeft, onLeft); } else {).prop('disabled', false); $(this).text("Unmute A"); stream.muteAudio(); }... } |
16. Включение/выключение аудио и видео для публикуемого потока.
stream.isAudioMuted(), stream.isVideoMuted(), stream.muteAudio(), stream.unmuteAudio(), stream.muteVideo(), stream.unmuteVideo() code
Code Block | ||||
---|---|---|---|---|
| ||||
function onMediaPublished(stream) { }).prop('disabled', false);... $("#localVideoToggle#localAudioToggle").text("Mute VA").off('click').click(function() { if (stream.isVideoMutedisAudioMuted()) { $(this).text("Mute VA"); stream.unmuteVideounmuteAudio(); } else { $(this).text("Unmute VA"); stream.muteVideomuteAudio(); } }).prop('disabled', false); $("#localVideoToggle").text("Mute V").off('click').click(function() { } if }).prop('disabled',false); } |
14. Отправка текстового сообщения.
participant.sendMessage() код
При нажатии на кнопку Send
- определяется массив участников конференции с помощью метода room.getParticipants()
- отправляется сообщение каждому участнику
Code Block | ||||
---|---|---|---|---|
| ||||
function onJoined(room) { (stream.isVideoMuted()) { $("#joinBtn"this).text("LeaveMute V").off('click').click(function(){ ; stream.unmuteVideo(); } else { $(this).prop('disabled', truetext("Unmute V"); room stream.leave().then(onLeft, onLeft);muteVideo(); } }).prop('disabled', false); } |
17. Отправка текстового сообщения.
participant.sendMessage() code
При нажатии на кнопку Send
- определяется массив участников конференции с помощью метода room.getParticipants()
- отправляется сообщение каждому участнику
Code Block | ||||
---|---|---|---|---|
| ||||
function onJoined(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("");
} |