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: "h", active: true, maxBitrate: 900000 }, { rid: "m", active: true, maxBitrate: 300000, scaleResolutionDownBy: 2 } ] } ] } } };
3. Инициализация
init() code
Функция init() вызывается после того, как страница загрузится. Функция загружает config.json или конфигурацию по умолчанию, создает элемент для отображения локального видео и открывает модальное окно входа
/** * load config 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); }); //create local display to show local streams localDisplay = initLocalDisplay(document.getElementById("localDisplay")); //open entrance modal $('#entranceModal').modal('show'); }
4. Соединение с сервером и создание либо вход в комнату
connect() code
Функция вызывается по щелчку пользователя по кнопке Enter в модальном окне входа
/** * connect to server */ 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(); roomConfig.pc = pc; //kick off connect to server and local room creation const session = sfu.createRoom(roomConfig); session.on(constants.SFU_EVENT.CONNECTED, function(room) { //connected to server const chatDiv = document.getElementById('messages'); const chatInput = document.getElementById('localMessage'); const chatButton = document.getElementById('sendMessage'); //create and bind chat to the new room createChat(room, chatDiv, chatInput, chatButton); room.on(constants.SFU_ROOM_EVENT.FAILED, function(e) { const errField = document.getElementById("errorMsg"); errField.style.color = "red"; errField.innerText = e; }).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) { const errField = document.getElementById("errorMsg"); errField.style.color = "red"; errField.innerText = e.operation + " failed: " + e.error; }) //setup remote display for showing remote audio/video tracks const remoteDisplay = document.getElementById("display"); initRemoteDisplay(room, remoteDisplay, pc); //get configured local video streams let streams = cControls.getVideoStreams(); //combine local video streams with audio streams streams.push.apply(streams, cControls.getAudioStreams()); //add our local streams to the room (to PeerConnection) streams.forEach(function (s) { //add local stream to local display localDisplay.add(s.stream.id, "local", s.stream); //add each track to PeerConnection s.stream.getTracks().forEach((track) => { addTrackToPeerConnection(pc, s.stream, track, s.encodings); subscribeTrackToEndedEvent(room, track, pc); }); }); //add callback for the new local stream to the local controls cControls.onTrack(function (s) { //add local stream to local display localDisplay.add(s.stream.id, "local", s.stream); //add each track to PeerConnection s.stream.getTracks().forEach((track) => { addTrackToPeerConnection(pc, s.stream, track, s.encodings); subscribeTrackToEndedEvent(room, track, pc); }); //kickoff renegotiation room.updateState(); }); //join room room.join(); }); }
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 = sfu.createRoom(roomConfig);
Подписка на событие сессии "CONNECTED"
session.on(constants.SFU_EVENT.CONNECTED, function(room) {
Инициализация чата после установки соединения
//connected to server const chatDiv = document.getElementById('messages'); const chatInput = document.getElementById('localMessage'); const chatButton = document.getElementById('sendMessage'); //create and bind chat to the new room createChat(room, chatDiv, chatInput, chatButton);
Подписка на сообщения об ошибках комнаты
room.on(constants.SFU_ROOM_EVENT.FAILED, function(e) { const errField = document.getElementById("errorMsg"); errField.style.color = "red"; errField.innerText = e; }).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) { const errField = document.getElementById("errorMsg"); errField.style.color = "red"; errField.innerText = e.operation + " failed: " + e.error; })
Инициализация объекта для отображения потоков от других участников
//setup remote display for showing remote audio/video tracks const remoteDisplay = document.getElementById("display"); initRemoteDisplay(room, remoteDisplay, pc);
Получение настроек публикации локального медиа
//get configured local video streams let streams = cControls.getVideoStreams(); //combine local video streams with audio streams streams.push.apply(streams, cControls.getAudioStreams());
Добавление каждого потока в объект localDisplay для отображения и в объект PeerConnection для публикации
//add our local streams to the room (to PeerConnection) streams.forEach(function (s) { //add local stream to local display localDisplay.add(s.stream.id, "local", s.stream); //add each track to PeerConnection s.stream.getTracks().forEach((track) => { if (s.source === "screen") { config[track.id] = s.source; } addTrackToPeerConnection(pc, s.stream, track, s.encodings); subscribeTrackToEndedEvent(room, track, pc); }); });
Добавление слушателя, чтобы определить, когда клиент добавляет новые потоки для публикации. Получив новый поток, необходимо добавить его в localDisplay для отображения, добавить в PeerConnection для публикации и обновить состояние комнаты
//add callback for the new local stream to the local controls cControls.onTrack(function (s) { let config = {}; //add local stream to local display localDisplay.add(s.stream.id, "local", s.stream); //add each track to PeerConnection s.stream.getTracks().forEach((track) => { if (s.source === "screen") { config[track.id] = s.source; } addTrackToPeerConnection(pc, s.stream, track, s.encodings); subscribeTrackToEndedEvent(room, track, pc); }); //kickoff renegotiation room.updateState(config); });
Настройка WebRTC соединения в комнате
//join room room.join(pc, null, config);
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 }); }