Table of Contents |
---|
Пример демонстрирует проигрывание потока, опубликованного на WCS сервере по WebRTC в нескольких качествах видео.
На скриншотах ниже:
- Server url - Websocket URL WCS сервера
- Stream name - имя потока
- h, s, m send - кнопки переключения принимаемого качества по именам профилей из файла
/usr/local/FlashphonerWebCallServer/conf/wcs_sfu_bridge_profiles.yml
Обратите внимание, что аудио дорожка проигрывается в отдельном элементе
Исходный код примера
Исходный код разбит на следующие модули:
- player.html - HTML страница
- player.css - стили HTML страницы
- player.js - основная логика приложения
Анализ исходного кода
Для работы с исходным кодом примера возьмем версию файла player.js
, доступную здесь
1. Локальные переменные
Объявление локальных переменных для работы с константами, SFU SDK, для отображения видео и работы с конфигурацией клиента
Code Block | ||||
---|---|---|---|---|
| ||||
const constants = SFU.constants;
const sfu = SFU;
const PRELOADER_URL="../commons/media/silence.mp3";
const playStatus = "playStatus";
const playErrorInfo = "playErrorInfo"; |
2. Объект для хранения текущего состояния проигрывания
Хранит данные Websocket сессии, WebRTC соединения, SFU комнаты и объекта для отображения аудио и видео
Code Block | ||||
---|---|---|---|---|
| ||||
const CurrentState = function() {
let state = {
pc: null,
session: null,
room: null,
remoteDisplay: null,
roomEnded: false,
set: function(pc, session, room) {
state.pc = pc;
state.session = session;
state.room = room;
state.roomEnded = false;
},
clear: function() {
state.room = null;
state.session = null;
state.pc = null;
state.roomEnded = false;
},
setRoomEnded: function() {
state.roomEnded = true;
},
isRoomEnded: function() {
return state.roomEnded;
},
isConnected: function() {
return (state.session && state.session.state() === constants.SFU_STATE.CONNECTED);
},
isActive: function() {
return (state.room && !state.roomEnded && state.pc);
},
setDisplay: function(display) {
state.remoteDisplay = display;
},
disposeDisplay: function() {
if (state.remoteDisplay) {
state.remoteDisplay.stop();
state.remoteDisplay = null;
}
}
};
return state;
} |
3. Инициализация
init() code
Функция init() вызывается после того, как страница загрузится:
- инициализирует объекты состояния
- инициализирует поля ввода
Code Block | ||||
---|---|---|---|---|
| ||||
const init = function() {
$("#playBtn").prop('disabled', true);
$("#url").prop('disabled', true);
$("#streamName").prop('disabled', true);
onDisconnected(CurrentState());
$("#url").val(setURL());
} |
4. Соединение с сервером
RTCPeerConnection(), SFU.createRoom() code
Функция connect() вызывается по нажатию кнопки Play:
- создает объект PeerConnection
- очищает отображение статуса предыдущей сессии
- настраивает конфигурацию комнаты и создает Websocket сессию
- подписывается на события Websocket сессии
Code Block | ||||
---|---|---|---|---|
| ||||
const connect = async function(state) {
// Create peer connection
let pc = new RTCPeerConnection();
// Create a config to connect to SFU room
const roomConfig = {
// Server websocket URL
url: $("#url").val(),
// Use stream name as room name to play ABR
roomName: $("#streamName").val(),
// Make a random participant name from stream name
nickname: "Player-" + $("#streamName").val() + "-" + createUUID(4),
// Set room pin
pin: 123456
}
// Clean state display items
setStatus(playStatus, "");
setStatus(playErrorInfo, "");
try {
// Connect to the server (room should already exist)
const session = await sfu.createRoom(roomConfig);
// Set up session ending events
session.on(constants.SFU_EVENT.DISCONNECTED, function() {
onStopClick(state);
onDisconnected(state);
setStatus(playStatus, "DISCONNECTED", "green");
}).on(constants.SFU_EVENT.FAILED, function(e) {
onStopClick(state);
onDisconnected(state);
setStatus(playStatus, "FAILED", "red");
if (e.status && e.statusText) {
setStatus(playErrorInfo, e.status + " " + e.statusText, "red");
} else if (e.type && e.info) {
setStatus(playErrorInfo, e.type + ": " + e.info, "red");
}
});
// Connected successfully
onConnected(state, pc, session);
setStatus(playStatus, "CONNECTING...", "black");
} catch(e) {
onDisconnected(state);
setStatus(playStatus, "FAILED", "red");
setStatus(playErrorInfo, e, "red");
}
} |
5. Запуск проигрывания при установке соединения
onConnected() code
Функция onConnected():
- настраивает действия по нажатию кнопки Stop
- подписывается на событие SFU_ROOM_EVENT.PARTICIPANT_LIST для проверки, опубликован ли поток в SFU комнате
- подписывается на события об ошибках комнаты
- вызывает функцию проигрывания
Code Block | ||||
---|---|---|---|---|
| ||||
const onConnected = async function(state, pc, session) {
state.set(pc, session, session.room());
$("#playBtn").text("Stop").off('click').click(function () {
onStopClick(state);
});
$('#url').prop('disabled', true);
$("#streamName").prop('disabled', true);
// Add room event handling
state.room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, function(e) {
// If the room is empty, the stream is not published yet
if (!e.participants || e.participants.length === 0) {
setStatus(playErrorInfo, "ABR stream is not published", "red");
onStopClick(state);
}
else {
setStatus(playStatus, "ESTABLISHED", "green");
$("#placeholder").hide();
}
}).on(constants.SFU_ROOM_EVENT.FAILED, function(e) {
// Display error state
setStatus(playErrorInfo, e, "red");
}).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
onOperationFailed(state);
}).on(constants.SFU_ROOM_EVENT.ENDED, function () {
// Publishing is stopped, dispose playback and close connection
setStatus(playErrorInfo, "ABR stream is stopped", "red");
state.setRoomEnded();
onStopClick(state);
}).on(constants.SFU_ROOM_EVENT.DROPPED, function () {
// Client dropped from the room, dispose playback and close connection
setStatus(playErrorInfo, "Playback is dropped due to network issues", "red");
state.setRoomEnded();
onStopClick(state);
});
await playStreams(state);
// Enable button after starting playback #WCS-3635
$("#playBtn").prop('disabled', false);
} |
6. Проигрывание потоков
initRemoteDisplay(), SFURoom.join() code
Функция playStreams():
- инициализирует базовый элемент для отображения входящих медиа потоков
- настраивает параметры отображения информации о входящих потоках
- настраивает ABR для переключения между доступными качествами видео при изменении параметров канала
- настраивает WebRTC соедиение в SFU комнате
Code Block | ||||
---|---|---|---|---|
| ||||
const playStreams = async function(state) {
try {
// Create remote display item to show remote streams
state.setDisplay(initRemoteDisplay({
div: document.getElementById("remoteVideo"),
room: state.room,
peerConnection: state.pc,
displayOptions: {
publisher: false,
quality: true,
type: false,
abr: true,
abrKeepOnGoodQuality: 20000,
abrTryForUpperQuality: 30000
}
}));
// Start WebRTC negotiation
await state.room.join(state.pc);
} catch(e) {
if (e.type === constants.SFU_ROOM_EVENT.OPERATION_FAILED) {
onOperationFailed(state, e);
} else {
console.error("Failed to play streams: " + e);
setStatus(playErrorInfo, e.name, "red");
onStopClick(state);
}
}
} |
7. Остановка проигрывания
CurrentState.disposeDisplay() code
Code Block | ||||
---|---|---|---|---|
| ||||
const stopStreams = function(state) {
state.disposeDisplay();
} |
8. Действия по нажатию кнопки Play
onStartClick(), playFirstSound(), connect() code
Функция onStartClick():
- проверяет правильность заполнения полей ввода
- перед стартом воспроизведения, в браузере Safari вызывает функцию playFirstSound() для автоматического проигрывания аудио
- вызывает функцию connect()
Code Block | ||||
---|---|---|---|---|
| ||||
const onStartClick = function(state) {
if (validateForm("connectionForm")) {
$("#playBtn").prop('disabled', true);
if (Browser().isSafariWebRTC()) {
playFirstSound(document.getElementById("main"), PRELOADER_URL).then(function () {
connect(state);
});
} else {
connect(state);
}
}
} |
9. Действия по нажатию кнопки Stop
onStopClick(), Session.disconnect() code
Функция onStopClick():
- останавливает публикацию или воспроизведение
- разрывает Websocket сессию
Code Block | ||||
---|---|---|---|---|
| ||||
const onStopClick = async function(state) {
stopStreams(state);
if (state.isConnected()) {
$("#playBtn").prop('disabled', true);
await state.session.disconnect();
onDisconnected(state);
}
} |
10. Действия при разрыве Websocket сессии
onDisconnected() code
Функция onDisconnected():
- настраивает действия по нажатию кнопки Publish/Play
- открывает доступ к полям ввода Server url и Room name
Code Block | ||||
---|---|---|---|---|
| ||||
const onDisconnected = function(state) {
state.clear();
$("#placeholder").show();
$("#playBtn").text("Play").off('click').click(function () {
onStartClick(state);
}).prop('disabled', false);
$('#url').prop('disabled', false);
$("#streamName").prop('disabled', false);
} |