1. Локальные переменные
Объявление локальных переменных для работы с константами, SFU SDK, для отображения локального видео и работы с конфигурацией клиента
const constants = SFU.constants; const sfu = SFU; let localDisplay; let cControls;
2. Конфигурация по умолчанию
Объявление конфигурации комнаты и публикации потоков по умолчанию, на случай, если нет файла конфигурации config.json
Клиент настраивается на соединение с сервером по WSS по адресу localhost для входа в комнату "ROOM1" с пин-кодом "1234" под именем "Alice". Секция media задает публикацию аудио и видео дорожек. Видео публикуется двумя дорожками с качествами high (h) и medium (m)
const defaultConfig = { room: { url: "wss://127.0.0.1:8888", name: "ROOM1", pin: "1234", nickName: "Alice" }, media: { audio: { tracks: [ { source: "mic", channels: 1 } ] }, video: { tracks: [ { source: "camera", width: 1280, height: 720, codec: "H264", encodings: [ {rid: "m", active: true, maxBitrate: 300000, scaleResolutionDownBy: 2}, {rid: "h", active: true, maxBitrate: 900000} ] } ] } } };
Отметим, что, начиная со сборки 2.0.248, качества должны быть перечислены по возрастанию
3. Инициализация
init() code
Функция init() вызывается после того, как страница загрузится. Функция загружает config.json или конфигурацию по умолчанию, создает элемент для отображения локального видео и открывает модальное окно входа
/** * Load track configuration and show entrance modal */ const init = function () { //read config $.getJSON("config.json", function (config) { cControls = createControls(config); }).fail(function () { //use default config cControls = createControls(defaultConfig); }); //open entrance modal $('#entranceModal').modal('show'); }
4. Соединение с сервером и создание либо вход в комнату
connect() code
Функция вызывается по щелчку пользователя по кнопке Enter в модальном окне входа
/** * Connect to server and publish preconfigured streams */ async function connect() { // hide modal $('#entranceModal').modal('hide'); // disable controls cControls.muteInput(); //create peer connection const pc = new RTCPeerConnection(); //get config object for room creation const roomConfig = cControls.roomConfig(); //kick off connect to server and local room creation try { const session = await sfu.createRoom(roomConfig); // Now we connected to the server (if no exception was thrown) session.on(constants.SFU_EVENT.FAILED, function (e) { if (e.status && e.statusText) { displayError("CONNECTION FAILED: " + e.status + " " + e.statusText); } else if (e.type && e.info) { displayError("CONNECTION FAILED: " + e.info); } else { displayError("CONNECTION FAILED: " + e); } }).on(constants.SFU_EVENT.DISCONNECTED, function (e) { displayError("DISCONNECTED. Refresh the page to enter the room again"); }); const room = session.room(); room.on(constants.SFU_ROOM_EVENT.FAILED, function (e) { displayError(e); }).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) { displayError(e.operation + " failed: " + e.error); }) // create local display to show local streams localDisplay = initLocalDisplay(document.getElementById("localDisplay")); // display audio and video control tables await cControls.displayTables(); cControls.onTrack(async function (s) { await publishNewTrack(room, pc, s); }); //create and bind chat to the new room const chatDiv = document.getElementById('messages'); const chatInput = document.getElementById('localMessage'); const chatButton = document.getElementById('sendMessage'); createChat(room, chatDiv, chatInput, chatButton); //setup remote display for showing remote audio/video tracks const remoteDisplay = document.getElementById("display"); initDefaultRemoteDisplay(room, remoteDisplay, {quality: true},{thresholds: [ {parameter: "nackCount", maxLeap: 10}, {parameter: "freezeCount", maxLeap: 10}, {parameter: "packetsLost", maxLeap: 10} ], abrKeepOnGoodQuality: ABR_KEEP_ON_QUALITY, abrTryForUpperQuality: ABR_TRY_UPPER_QUALITY, interval: ABR_QUALITY_CHECK_PERIOD}); //get configured local video streams let streams = cControls.getVideoStreams(); //combine local video streams with audio streams streams.push.apply(streams, cControls.getAudioStreams()); // Publish preconfigured streams publishPreconfiguredStreams(room, pc, streams); } catch (e) { console.error(e); displayError(e); } }
5. Подробнее о функции connect()
Скрытие модального окна входа и отключение полей ввода до установки соединения с сервером
//hide modal $('#entranceModal').modal('hide'); //disable controls cControls.muteInput();
Создание объекта PeerConnection и подготовка объекта конфигурации комнаты
//create peer connection const pc = new RTCPeerConnection(); //get config object for room creation const roomConfig = cControls.roomConfig(); roomConfig.pc = pc;
Создание сессии и установка соединения с сервером
const session = await sfu.createRoom(roomConfig);
Подписка на события сессии
session.on(constants.SFU_EVENT.FAILED, function (e) { if (e.status && e.statusText) { displayError("CONNECTION FAILED: " + e.status + " " + e.statusText); } else if (e.type && e.info) { displayError("CONNECTION FAILED: " + e.info); } else { displayError("CONNECTION FAILED: " + e); } }).on(constants.SFU_EVENT.DISCONNECTED, function (e) { displayError("DISCONNECTED. Refresh the page to enter the room again"); });
Создание объекта комнаты и подписка на его события
const room = session.room(); room.on(constants.SFU_ROOM_EVENT.FAILED, function (e) { displayError(e); }).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) { displayError(e.operation + " failed: " + e.error); })
Создание объекта для рендеринга локального видео
localDisplay = initLocalDisplay(document.getElementById("localDisplay")); // display audio and video control tables await cControls.displayTables(); cControls.onTrack(async function (s) { await publishNewTrack(room, pc, s); });
Создание чата
const chatDiv = document.getElementById('messages'); const chatInput = document.getElementById('localMessage'); const chatButton = document.getElementById('sendMessage'); createChat(room, chatDiv, chatInput, chatButton);
Инициализация объекта для отображения потоков от других участников
const remoteDisplay = document.getElementById("display"); initDefaultRemoteDisplay(room, remoteDisplay, {quality: true},{thresholds: [ {parameter: "nackCount", maxLeap: 10}, {parameter: "freezeCount", maxLeap: 10}, {parameter: "packetsLost", maxLeap: 10} ], abrKeepOnGoodQuality: ABR_KEEP_ON_QUALITY, abrTryForUpperQuality: ABR_TRY_UPPER_QUALITY, interval: ABR_QUALITY_CHECK_PERIOD});
Получение настроек публикации локального медиа и публикация треков согласно настройкам
let streams = cControls.getVideoStreams(); //combine local video streams with audio streams streams.push.apply(streams, cControls.getAudioStreams()); // Publish preconfigured streams publishPreconfiguredStreams(room, pc, streams);
6. Завершение публикации потока
subscribeTrackToEndedEvent() code
Вспомогательная функция, которая подписывается на событие "ended" для локального потока. При получении события поток удаляется из PeerConnection, и состояние комнаты обновляется.
const subscribeTrackToEndedEvent = function(room, track, pc) { track.addEventListener("ended", function() { //track ended, see if we need to cleanup let negotiate = false; for (const sender of pc.getSenders()) { if (sender.track === track) { pc.removeTrack(sender); //track found, set renegotiation flag negotiate = true; break; } } if (negotiate) { //kickoff renegotiation room.updateState(); } }); };
7. Добавление новой дорожки в PeerConnection
addTrackToPeerConnection() code
Вспомогательная функция, которая добавляет новую дорожку в PeerConnection для публикации
const addTrackToPeerConnection = function(pc, stream, track, encodings) { pc.addTransceiver(track, { direction: "sendonly", streams: [stream], sendEncodings: encodings ? encodings : [] //passing encoding types for video simulcast tracks }); }