Table of Contents |
---|
Пример видеочата с отображением экрана
Данный пример может использоваться для видеочата между двумя участниками на Web Call Server с отображением экрана одного из участников.
Участник видеочата может публиковать следующие типы потоков
- WebRTC
- RTMFP
- RTMP
WebRTC поток с веб-камеры и одновременно с этим WebRTC поток с экрана или из окна приложения
Пример окна клиента, публикующего свой экран в браузере Chrome:
Пример окна клиента, получающего поток с экрана в браузере Chrome
Настройка расширения для публикации экрана браузера Chrome описана в примере Screen Sharing.
Код примера
Код данного примера находится на WCS-сервере по следующему пути:
...
Здесь host - адрес WCS-сервера.
Работа с кодом примера
Для разбора кода возьмем версию файла video-chat-and-screen-sharing.js с хешем c306c1bbf49bfcbd8e24be927ae95f63b7dbaaba 90771d4, которая находится здесь и доступна для скачивания в соответствующей сборке 2.0.5218.28.2747.
1. Инициализация API.
Code Block | ||
---|---|---|
| ||
Flashphoner.init({
flashMediaProviderSwfLocation: '../../../../media-provider.swf',
screenSharingExtensionId: extensionId
}); |
2. Подключение к серверу.
Flashphoner.roomApi.connect() код
Code Block | ||
---|---|---|
| ||
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 код
Code Block | ||
---|---|---|
| ||
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();
}); |
4. Присоединение к комнате.
connection.join() код
При присоединении передается имя "комнаты" конференции (берется из параметра в URL страницы клиента, или генерируется случайное имя).
Code Block | |||
---|---|---|---|
| |||
| |||
try {
Flashphoner.init();
} catch(e) {
$("#notifyFlash").text("Your browser doesn't support WebRTC technology needed for this example");
return;
} |
2. Запрос доступа к камере и микрофону
Flashphoner.getMediaAccess() code
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.getMediaAccess(null, localDisplay).then(function() {
createConnection(url, username);
}).catch(function(error) {
console.error("User not allowed media access: "+error);
$("#failedInfo").text("User not allowed media access. Refresh the page");
onLeft();
}); |
3. Подключение к серверу.
RoomApi.connect() code
Code Block | ||||
---|---|---|---|---|
| ||||
function createConnection(url, username) {
connection = RoomApi.connect({urlServer: url, username: username}).on(SESSION_STATUS.FAILED, function(session){
...
});
} |
4. Получение от сервера события, подтверждающего успешное соединение
ConnectionStatusEvent ESTABLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
connection = RoomApi.connect({urlServer: url, username: username}).on(SESSION_STATUS.FAILED, function(session){
...
}).on(SESSION_STATUS.DISCONNECTED, function(session) {
...
}).on(SESSION_STATUS.ESTABLISHED, function(session) {
setStatus('#status', session.status());
joinRoom();
}); |
5. Присоединение к комнате.
connection.join() code
При присоединении передается имя "комнаты" конференции (берется из параметра в URL страницы клиента, или генерируется случайное имя).
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName()}).on(ROOM_EVENT.STATE, function(room){
...
}); |
6. Получение от сервера события, описывающего статус комнаты
RoomStatusEvent STATE code
При получении данного события:
- определяется количество участников конференции с помощью метода room.getParticipants(), который возвращает массив объектов participant,
- если к конференции уже присоединилось максимально допустимое количество участников, производится выход из "комнаты" при помощи room.leave()
- если количество участников меньше максимально допустимого, начинается публикация видеопотока
Code Block | ||||
---|---|---|---|---|
| ||||
connection.join({name: getRoomName()}).on(ROOM_EVENT.LEFTSTATE, function(participantroom){ //remove participant removeParticipant(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); }).on(ROOM_EVENT.MESSAGE, function(message){ addMessage(message.from.name(), message.text); }); |
5. Получение от сервера события, описывающего статус комнаты
RoomStatusEvent STATE код
При получении данного события:
- определяется количество участников конференции с помощью метода room.getParticipants(), который возвращает массив объектов participant,
- если к конференции уже присоединилось максимально допустимое количество участников, производится выход из "комнаты" при помощи room.leave()
- если количество участников меньше максимально допустимого, начинается публикация видеопотока
Code Block | ||
---|---|---|
| ||
connection.join({name: getRoomName() 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; } room_ = room; 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.STATEJOINED, function(roomparticipant){ 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; } room_ = room; 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"); }).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){ connection.disconnect(); $('#failedInfo').text(info); }).on(ROOM_EVENT.MESSAGE, function(message){ addMessage(message.from.name(), message.text); }); |
...
room.publish() код
При публикации передаются параметры:
- параметры видео: ширина, высота, количество кадров в секунду, источник (экран)
- элемент страницы для отображения превью
Code Block | ||
---|---|---|
| ||
var constraints = {
video: {
width: parseInt($('#width').val()),
height: parseInt($('#height').val()),
frameRate: parseInt($('#fps').val()),
},
audio: $("#useMic").prop('checked')
};
constraints.video.type = "screen";
if (Browser.isFirefox()){
constraints.video.mediaSource = "screen";
}
room.publish({
display: document.getElementById("preview"),
constraints: constraints,
name: "screenShare",
cacheLocalResources: false
}).on(STREAM_STATUS.FAILED, function (stream) {
console.warn("Local stream failed!");
onStopSharing();
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
/*
* User can stop sharing screen capture using Chrome "stop" button.
* Catch onended video track event and stop publishing.
*/
document.getElementById(stream.id()).srcObject.getVideoTracks()[0].onended = function (e) {
stream.stop();
};
document.getElementById(stream.id()).addEventListener('resize', function(event){
resizeVideo(event.target);
});
onStartSharing(stream);
}).on(STREAM_STATUS.UNPUBLISHED, function(stream) {
onStopSharing();
}); |
7. Получение от сервера события, сигнализирующего о присоединении пользователя к чат-комнате
RoomStatusEvent JOINED код
Code Block | ||
---|---|---|
| ||
connection.join({name: getRoomName()... }).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. Публикация видеопотока с экрана.
room.publish() code
При публикации передаются параметры:
- параметры видео: ширина, высота, количество кадров в секунду, источник (экран)
- элемент страницы для отображения превью
Code Block | ||||
---|---|---|---|---|
| ||||
var constraints = {
video: {
width: parseInt($('#width').val()),
height: parseInt($('#height').val()),
frameRate: parseInt($('#fps').val()),
withoutExtension: true
},
audio: $("#useMic").prop('checked')
};
constraints.video.type = "screen";
if (Browser.isFirefox()){
constraints.video.mediaSource = "screen";
}
var options = {
name: "screenShare",
display: document.getElementById("preview"),
constraints: constraints,
cacheLocalResources: false
}
if (isSafariMacOS()) {
options.disableConstraintsNormalization = true;
}
room.publish(options).on(STREAM_STATUS.FAILED, function (stream) {
...
}); |
8. Получение от сервера события, сигнализирующего о присоединении пользователя к чат-комнате
RoomStatusEvent JOINED code
Code Block | ||||
---|---|---|---|---|
| ||||
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.STATEMESSAGE, function(roommessage){ 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; } room_ = room; 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); }). }); |
9. Получение от сервера события, сигнализирующего о публикации видеопотока другим участником
RoomStatusEvent PUBLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
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.JOINEDMESSAGE, function(participantmessage){ installParticipant(participant); 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){ connection.disconnect(); $('#failedInfo').text(info); }).on(ROOM_EVENT.MESSAGE, function(message){ addMessage(message.from.name(), message.text); }); |
...
RoomStatusEvent PUBLISHED код
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) {
console.warn("Current room is full");
$("#failedInfo").text("Current room is full.");
room.leave().then(onLeft, onLeft);
return false;
}
room_ = room;
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");
}).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){
connection.disconnect();
$('#failedInfo').text(info);
}).on(ROOM_EVENT.MESSAGE, function(message){
addMessage(message.from.name(), message.text);
}); |
...
participant.play() код
Параметром передается div-элемент, в котором будет отображаться видео, в зависимости от источника - веб-камера или экран
Code Block | ||
---|---|---|
| ||
function playParticipantsStream(participant) {
if (participant.getStreams().length > 0) {
for (var i=0; i<participant.getStreams().length; i++) {
$("[id$=Name]").each(function (index, value) {
if ($(value).text() == participant.name()) {
var p = value.id.replace('Name', '');
var pDisplay = p + 'Display';
// check if we already play this stream
if (document.getElementById(participant.getStreams()[i].id()) == null) {
// setup 1st stream to main div
if (participant.getStreams()[i].streamName().indexOf("screenShare") == -1) {
participant.getStreams()[i].play(document.getElementById(pDisplay)).on(STREAM_STATUS.PLAYING, function (playingStream) {
document.getElementById(playingStream.id()).addEventListener('resize', function (event) {
resizeVideo(event.target);
});
});
} else {
participant.getStreams()[i].play(document.getElementById("sharedDisplay")).on(STREAM_STATUS.PLAYING, function (playingStream) {
document.getElementById(playingStream.id()).addEventListener('resize', function (event) {
resizeVideo(event.target);
});
});
}
}
}
});
}
}
} |
10. Остановка публикации видеопотока.
stream.stop() код
Code Block | ||
---|---|---|
| ||
function onMediaPublished(stream) {
$("#localStopBtn").text("Stop").off('click').click(function(){
$(this).prop('disabled', true);
stream.stop();
}).prop('disabled', false);
$("#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);
$("#shareBtn").prop('disabled',false);
} |
11. Получение от сервера события, подтверждающего остановку публикации.
StreamStatusEvent UNPUBLISHED код
Code Block | ||
---|---|---|
| ||
room.publish({ display: document.getElementById("preview"), constraints: constraints, name: "screenShare", cacheLocalResources: false } ... }); |
10. Воспроизведение видеопотока от другого участника
participant.play() code
Параметром передается div-элемент, в котором будет отображаться видео, в зависимости от источника - веб-камера или экран
Code Block | ||||
---|---|---|---|---|
| ||||
function playParticipantsStream(participant) {
if (participant.getStreams().length > 0) {
for (var i=0; i<participant.getStreams().length; i++) {
$("[id$=Name]").each(function (index, value) {
if ($(value).text() == participant.name()) {
var p = value.id.replace('Name', '');
var pDisplay = p + 'Display';
// check if we already play this stream
if (document.getElementById(participant.getStreams()[i].id()) == null) {
// setup 1st stream to main div
if (participant.getStreams()[i].streamName().indexOf("screenShare") == -1) {
participant.getStreams()[i].play(document.getElementById(pDisplay)).on(STREAM_STATUS.PLAYING, function (playingStream) {
document.getElementById(playingStream.id()).addEventListener('resize', function (event) {
resizeVideo(event.target);
});
});
} else {
participant.getStreams()[i].play(document.getElementById("sharedDisplay")).on(STREAM_STATUS.PLAYING, function (playingStream) {
document.getElementById(playingStream.id()).addEventListener('resize', function (event) {
resizeVideo(event.target);
});
});
}
}
}
});
}
}
} |
11. Остановка публикации видеопотока с экрана
stream.stop() code
Code Block | ||||
---|---|---|---|---|
| ||||
room.publish(options).on(STREAM_STATUS.FAILED, function (stream) { console.warn("Local stream failed!"); onStopSharing(); ... }).on(STREAM_STATUS.PUBLISHING, function (stream) { /* * User can stop sharing screen capture using Chrome "stop" button. * Catch onended video track event and stop publishing. */ document.getElementById(stream.id()).srcObject.getVideoTracks()[0].onended = function (e) { stream.stop(); }; document.getElementById(stream.id()).addEventListener('resize' ... }).on(STREAM_STATUS.UNPUBLISHED, function(eventstream) { resizeVideo(event.target); }); onStartSharing(stream); }) ... }); |
12. Получение от сервера события, подтверждающего остановку публикации.
StreamStatusEvent UNPUBLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
room.publish(options).on(STREAM_STATUS.UNPUBLISHEDFAILED, function (stream) { onStopSharing(); }); |
12. Выход из комнаты чата.
room.leave() код
Code Block | ||
---|---|---|
| ||
function onJoined(room) { $("#joinBtn").text("Leave").off('click').click(function(){ $(this).prop('disabled', true); room.leave().then(onLeft, onLeft); }).prop('disabled', false); $("#shareBtn").prop('disabled',false); $('#sendMessageBtn' ... }).on(STREAM_STATUS.PUBLISHING, function (stream) { ... }).on(STREAM_STATUS.UNPUBLISHED, function(stream) { onStopSharing(); }); |
13. Выход из комнаты чата.
room.leave() code
Code Block | ||||
---|---|---|---|---|
| ||||
function onJoined(room) { $("#joinBtn").text("Leave").off('click').click(function(){ var message = field('message'); addMessage(connection.username(), message); $('#message').val(""); //broadcast message var participants = room.getParticipants($(this).prop('disabled', true); for (var i = 0; i < participants.length; i++) { participants[i].sendMessage(message); } room.leave().then(onLeft, onLeft); }).prop('disabled', false); $('#failedInfo').text(""); ... } |