Пример видеоконференции

Данный пример может использоваться для организации видеоконференции для трех участников на Web Call Server
Участник видеоконференции может публиковать следующие типы потоков

На скриншоте ниже представлен пример клиента участника конференции, к которой присоединились два других участника.


На странице вопроизводятся три видео

В поле Invite указана ссылка с именем "комнаты" конференции.

Код примера

Код данного примера находится на WCS-сервере по следующему пути:

/usr/local/FlashphonerWebCallServer/client2/examples/demo/streaming/conference

conference.css - файл стилей
conference.html - страница участника конференции
conference.js - скрипт, обеспечивающий работу конференции

Тестировать данный пример можно по следующему адресу:

https://host:8888/client2/examples/demo/streaming/conference/conference.html

Здесь host - адрес WCS-сервера.

Работа с кодом примера

Для разбора кода возьмем версию файла conference.js с хешем cf0daabc6b86e21d5a2f9e4605366c8b7f0d27eb, которая находится здесь и доступна для скачивания в соответствующей сборке 0.3.18.1894.

Скрипт конференции использует roomApi, предназначенное для видеочатов, конференций, вебинаров и других приложений, которые предполагают нахождение пользователей в одной виртуальной "комнате".

При подключении пользователя к конференции, используется метод roomApi.connect(), в отличии от прямого подключения к серверу методом createSession().
При присоединении к новой "комнате" методом roomApi.join(), создается объект room для работы с этой "комнатой". Для работы с участниками конференции используются объекты Participant.

Все события, происходящие в "комнате" (присоединение/выход пользователя, отправленные сообщения), транслируются другим участникам, подключенным к этой "комнате".

Например, в следующем коде подключаемся к "комнате" и запрашиваем список других участников:

connection.join({name: getRoomName()}).on(ROOM_EVENT.STATE, function(room){
var participants = room.getParticipants();
...


Здесь получаем данные другого участника, который только что присоединился:

}).on(ROOM_EVENT.JOINED, function(participant){
installParticipant(participant);
addMessage(participant.name(), "joined");
...


1. Инициализация API.

Flashphoner.init() код

Flashphoner.init({flashMediaProviderSwfLocation: '../../../../media-provider.swf'});


2. Подключение к серверу.

Flashphoner.roomApi.connect() код

connection = Flashphoner.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();
}).on(SESSION_STATUS.ESTABLISHED, function(session) {
    setStatus('#status', session.status());
    joinRoom();
});


3. Получение от сервера события, подтверждающего успешное соединение

ConnectionStatusEvent ESTABLISHED код

connection = Flashphoner.roomApi.connect({urlServer: url, username: username}).on(SESSION_STATUS.FAILED, function(session){
    setStatus('#status', session.status());
    onLeft();
}).on(SESSION_STATUS.DISCONNECTED, function(session) {
    ...
}).on(SESSION_STATUS.ESTABLISHED, function(session) {
    ...
});


4. Присоединение к конференции.

connection.join() код

При присоединении передается имя "комнаты" конференции (берется из параметра в URL страницы клиента, или генерируется случайное имя).

connection.join({name: getRoomName()}).on(ROOM_EVENT.STATE, function(room){
    ...
});


5. Получение от сервера события, описывающего статус комнаты

RoomStatusEvent STATE код

При получении данного события:

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) {
        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){
     ...
});


6. Публикация видеопотока.

room.publish() код

При публикации передаем div-элемент localDisplay, в котором будет отображаться видео с камеры.

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 код

connection.join({name: getRoomName()}).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){
     ...
});


8. Получение от сервера события, сигнализирующего о публикации видеопотока другим участником

RoomStatusEvent PUBLISHED код

connection.join({name: getRoomName()}).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){
     ...
});


9. Воспроизведение видеопотока.

participant.play() код

Параметром передается div-элемент, в котором будет отображаться видео.

function playParticipantsStream(participant) {
    if (participant.play) {
        $("[id$=Name]").each(function (index, value) {
            if ($(value).text() == participant.name()) {
                var p = value.id.replace('Name', '');
                var pDisplay = p + 'Display';
                participant.play(document.getElementById(pDisplay)).on(STREAM_STATUS.PLAYING, function (playingStream) {
                    document.getElementById(playingStream.id()).addEventListener('resize', function (event) {
                        resizeVideo(event.target);
                    });
                });
            }
        });
    }
}


10. Остановка публикации видеопотока.

stream.stop() код

function onMediaPublished(stream) {
    $("#localStopBtn").text("Stop").off('click').click(function(){
        $(this).prop('disabled', true);
        stream.stop();
    }).prop('disabled', false);
    ...
}


11. Получение от сервера события, подтверждающего остановку публикации.

StreamStatusEvent UNPUBLISHED код

room.publish(document.getElementById("localDisplay")).on(STREAM_STATUS.FAILED, function (stream) {
    ...
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
    ...
}).on(STREAM_STATUS.UNPUBLISHED, function(stream) {
    setStatus("#localStatus", stream.status());
    onMediaStopped(room);
});


12. Выход из комнаты конференции.

room.leave() код

function onJoined(room) {
    $("#joinBtn").text("Leave").off('click').click(function(){
        $(this).prop('disabled', true);
        room.leave().then(onLeft, onLeft);
    }).prop('disabled', false);
    ...
}


13. Включение/выключение аудио и видео для публикуемого потока. line 187, line 196

stream.isAudioMuted(), stream.isVideoMuted(), stream.muteAudio(), stream.unmuteAudio(), stream.muteVideo(), stream.unmuteVideo() код

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);
}


14. Отправка текстового сообщения.

participant.sendMessage() код

При нажатии на кнопку Send

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("");
}