...
Объявление локальных переменных для работы с константами, SFU SDK, для отображения локального видео и работы с конфигурацией клиента
code
Code Block |
---|
|
const constants = SFU.constants;
const sfu = SFU;
let localDisplay;
let cControls; |
...
Объявление конфигурации комнаты и публикации потоков по умолчанию, на случай, если нет файла конфигурации config.json
code
Клиент настраивается на соединение с сервером по WSS по адресу localhost для входа в комнату "ROOM1" с пин-кодом "1234" под именем "Alice". Секция media задает публикацию аудио и видео дорожек. Видео публикуется двумя дорожками с качествами high (h) и medium (m)
Code Block |
---|
|
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: "hm", active: true, maxBitrate: 300000, 900000scaleResolutionDownBy: 2},
{ rid: "mh", active: true, maxBitrate: 300000, scaleResolutionDownBy: 2 900000}
]
}
]
}
}
}; |
3. Инициализация
init() code
Функция init() вызывается после того, как страница загрузится. Функция загружает config.json или конфигурацию по умолчанию , создает элемент для отображения локального видео и открывает модальное окно входа
Code Block |
---|
|
/**
* 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);
});
//createopen local display to show local streamsentrance modal
localDisplay = initLocalDisplay(document.getElementById("localDisplay"));
//open entrance modal
$('#entranceModal').modal('show');
} |
...
$('#entranceModal').modal('show');
} |
4. Соединение с сервером и создание либо вход в комнату
connect() code
Функция вызывается по щелчку пользователя по кнопке Enter в модальном окне входа
Code Block |
---|
|
/**
* connect to server
*/
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();
roomConfig.pc = pc;
//kick off connect to server and local room creation
try {
const session = await sfu.createRoom(roomConfig);
session.on(constants.SFU_EVENT.CONNECTED, function(room) {
//connected to server // Now we connected to the server (if no exception was thrown)
const chatDiv = document.getElementById('messages');session.on(constants.SFU_EVENT.FAILED, function (e) {
const chatInput = document.getElementById('localMessage');
if (e.status && e.statusText) {
const chatButton = document.getElementById('sendMessage');
//create and bind chat to the new room
displayError("CONNECTION FAILED: " + e.status + " " + e.statusText);
createChat(room, chatDiv, chatInput, chatButton);
} else if room.on(constants.SFU_ROOM_EVENT.FAILED, function(e(e.type && e.info) {
const errField = document.getElementByIddisplayError("errorMsg"CONNECTION FAILED: " + e.info);
errField.style.color = "red";} else {
errField.innerText = e displayError("CONNECTION FAILED: " + e);
}
}).on(constants.SFU_ROOM_EVENT.OPERATION_FAILEDDISCONNECTED, function (e) {
displayError("DISCONNECTED. Refresh the page to constenter errFieldthe = document.getElementById("errorMsg"room again");
});
errField.style.color = "red";
const room = session.room();
errField.innerText = e.operation + " failed: " + e.errorroom.on(constants.SFU_ROOM_EVENT.FAILED, function (e) {
displayError(e);
})
.on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
//setup remote display for showing remote audio/video tracks
const remoteDisplay = document.getElementById("display" displayError(e.operation + " failed: " + e.error);
initRemoteDisplay(room, remoteDisplay, pc);})
//get configuredcreate local video display to show local streams
letlocalDisplay streams = cControlsinitLocalDisplay(document.getVideoStreamsgetElementById("localDisplay"));
//combine local display audio and video streams with audio streamscontrol tables
streams.push.apply(streams,await cControls.getAudioStreamsdisplayTables());
//add our local streams to the room (to PeerConnection)
cControls.onTrack(async function (s) {
await streams.forEach(function (s) {
publishNewTrack(room, pc, s);
});
//addcreate and localbind streamchat to localthe new displayroom
const chatDiv = localDisplaydocument.add(s.stream.id, "local", s.streamgetElementById('messages');
const chatInput = document.getElementById('localMessage');
//add each track to PeerConnection
const chatButton = document.getElementById('sendMessage');
s.stream.getTracks().forEach((track) => {createChat(room, chatDiv, chatInput, chatButton);
//setup remote display for showing remote addTrackToPeerConnection(pc, s.stream, track, s.encodings);
audio/video tracks
const remoteDisplay = document.getElementById("display");
subscribeTrackToEndedEventinitDefaultRemoteDisplay(room, trackremoteDisplay, pc);
{quality: true},{thresholds: [
});
{parameter: "nackCount", });
maxLeap: 10},
//add callback for the new local stream to the local controls{parameter: "freezeCount", maxLeap: 10},
cControls.onTrack(function (s) {
{parameter: "packetsLost", maxLeap: 10}
//add local stream to local display
], abrKeepOnGoodQuality: ABR_KEEP_ON_QUALITY, localDisplay.add(s.stream.id, "local", s.stream);
abrTryForUpperQuality: ABR_TRY_UPPER_QUALITY, interval: ABR_QUALITY_CHECK_PERIOD});
//get configured local video //addstreams
each track to PeerConnection
let streams = cControls.getVideoStreams();
s.stream.getTracks().forEach((track) => {
//combine local video streams with audio streams
addTrackToPeerConnection(pcstreams.push.apply(streams, s.stream, track, s.encodingscControls.getAudioStreams());
// Publish preconfigured streams
subscribeTrackToEndedEvent(room, track publishPreconfiguredStreams(room, pc, streams);
} });
//kickoff renegotiationcatch (e) {
room.updateState(console.error(e);
}displayError(e);
//join room
room.join();
});
} |
...
5. Подробнее о функции connect()
Скрытие модального окна входа и отключение полей ввода до установки соединения с сервером
code
Code Block |
---|
|
async function connect() {
// hide modal
$('#entranceModal').modal('hide');
// disable controls
cControls.muteInput();
...
} |
Создание объекта PeerConnection и подготовка объекта конфигурации комнаты
code
Code Block |
---|
|
//create peer connection
const pc = new RTCPeerConnectionasync function connect() {
...
//create peer connection
const pc = new RTCPeerConnection();
//get config object for room creation
const roomConfig = cControls.roomConfig();
roomConfig.pc = pc; ...
} |
Создание сессии и установка соединения с сервером
code
Code Block |
---|
|
const session = sfu.createRoom(roomConfig); |
Подписка на событие сессии "CONNECTED"
code
Code Block |
---|
|
session.on(constants.SFU_EVENT.CONNECTED, function(room) { |
Инициализация чата после установки соединения
code
Code Block |
---|
|
//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); |
...
async function connect() {
...
//kick off connect to server and local room creation
try {
const session = await sfu.createRoom(roomConfig);
...
} catch (e) {
console.error(e);
displayError(e);
}
} |
Подписка на события сессии
code
Code Block |
---|
|
room.on(constants.SFU_ROOM_EVENT.FAILED,async function connect(e) {
const errField = document.getElementById("errorMsg");...
errField.style.color = "red";
errField.innerText = e;
}).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e)//kick off connect to server and local room creation
try {
const errField = document.getElementById("errorMsg");
...
errField.style.color = "red";
errField.innerText = e.operation + " failed: " + e.error;
}) |
Инициализация объекта для отображения потоков от других участников
code
Code Block |
---|
|
//setup remote display for showing remote audio/video tracks
const remoteDisplay = document.getElementById("display");
initRemoteDisplay(room, remoteDisplay, pc); |
Получение настроек публикации локального медиа
code
Code Block |
---|
|
//get configured local video streams
let streams = cControls.getVideoStreams();
//combine local video streams with audio streams
streams.push.apply(streams, cControls.getAudioStreams()); |
Добавление каждого потока в объект localDisplay для отображения и в объект PeerConnection для публикации
code
Code Block |
---|
|
// Now we connected to the server (if no exception was thrown)
session.on(constants.SFU_EVENT.FAILED, function (e) {
if (e.status && e.statusText) {
//add our local streams to the room (to PeerConnection)
streams.forEach(function (s) {
displayError("CONNECTION FAILED: " + e.status + " " + e.statusText);
} //addelse local stream to local displayif (e.type && e.info) {
localDisplay.add(s.stream.id, "local", s.stream displayError("CONNECTION FAILED: " + e.info);
//add} each track to PeerConnectionelse {
s.stream.getTracks().forEach((track) => {
displayError("CONNECTION FAILED: " + e);
if (s.source === "screen") {}
}).on(constants.SFU_EVENT.DISCONNECTED, function (e) {
config[track.id] = s.source;
displayError("DISCONNECTED. Refresh the page to enter the room again");
});
...
} catch (e) addTrackToPeerConnection(pc, s.stream, track, s.encodings{
console.error(e);
displayError(e);
}
} |
Создание объекта комнаты и подписка на сообщения об ошибках
code
Code Block |
---|
|
async function connect() {
subscribeTrackToEndedEvent(room, track, pc); ...
//kick off connect to server and local room });creation
try {
}); |
Добавление слушателя, чтобы определить, когда клиент добавляет новые потоки для публикации. Получив новый поток, необходимо добавить его в localDisplay для отображения, добавить в PeerConnection для публикации и обновить состояние комнаты
code
Code Block |
---|
|
...
//add callback for the new localconst streamroom to the local controls= session.room();
cControlsroom.onTrack(on(constants.SFU_ROOM_EVENT.FAILED, function (se) {
let config = {}displayError(e);
}).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function //add local stream to local display(e) {
localDisplay.adddisplayError(s.stream.id, "local", s.streame.operation + " failed: " + e.error);
})
//add each track to PeerConnection...
} s.stream.getTracks().forEach((track) => catch (e) {
console.error(e);
if (s.source === "screen"displayError(e);
}
} |
Создание объекта для отображения локального видео
code
Code Block |
---|
|
async function connect() {
...
//kick off connect to server and local room creation
config[track.id] = s.source;try {
...
}
// create local display to show local streams
addTrackToPeerConnection(pc, s.stream, track, s.encodingslocalDisplay = initLocalDisplay(document.getElementById("localDisplay"));
// display audio and video subscribeTrackToEndedEvent(room, track, pc);control tables
}await cControls.displayTables();
cControls.onTrack(async function (s) {
//kickoff renegotiation
await publishNewTrack(room, room.updateState(configpc, s);
}); |
Настройка WebRTC соединения в комнате
code
Code Block |
---|
|
//join room
room.join(pc, null, config); |
6. Завершение публикации потока
subscribeTrackToEndedEvent() code
Вспомогательная функция, которая подписывается на событие "ended" для локального потока. При получении события поток удаляется из PeerConnection, и состояние комнаты обновляется.
Code Block |
---|
|
const subscribeTrackToEndedEvent = function(room, track, pc) {
track.addEventListener("ended", function() {
...
} catch (e) {
console.error(e);
displayError(e);
}
} |
Инициализация окна чата
code
Code Block |
---|
|
async function connect() {
...
//kick off connect to server and local room creation
try {
...
//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);
...
} catch (e) {
console.error(e);
displayError(e);
}
} |
Инициализация объекта для отображения потоков от других участников
code
Code Block |
---|
|
async function connect() {
...
//kick off connect to server and local room creation
try {
...
//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});
...
} catch (e) {
console.error(e);
displayError(e);
}
} |
Получение настроек и публикация локальных медиа потоков
code
Code Block |
---|
|
async function connect() {
...
//kick off connect to server and local room creation
try {
...
//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);
}
} |
6. Вход в комнату и публикация локальных потоков из файла конфигурации
publishPreconfiguredStreams(), Room.join() code
Code Block |
---|
|
const publishPreconfiguredStreams = async function (room, pc, streams) {
try {
const config = {};
//add our local streams to the room (to PeerConnection)
streams.forEach(function (s) {
let contentType = s.type || s.source;
//add each track to PeerConnection
s.stream.getTracks().forEach((track) => {
config[track.id] = contentType;
addTrackToPeerConnection(pc, s.stream, track, s.encodings);
subscribeTrackToEndedEvent(room, track, pc);
});
localDisplay.add(s.stream.id, "local", s.stream, contentType);
});
//join room
await room.join(pc, null, config, 10);
// Enable Delete button for each preconfigured stream #WCS-3689
streams.forEach(function (s) {
$('#' + s.stream.id + "-button").prop('disabled', false);
});
} catch (e) {
onOperationFailed("Failed to publish a preconfigured streams", e);
// Enable Delete button for each preconfigured stream #WCS-3689
streams.forEach(function (s) {
$('#' + s.stream.id + "-button").prop('disabled', false);
});
}
} |
7. Публикация дополнительных локальных потоков
publishNewTrack(), Room.updateState() code
Code Block |
---|
|
const publishNewTrack = async function (room, pc, media) {
try {
let config = {};
//add local stream to local display
let contentType = media.type || media.source;
localDisplay.add(media.stream.id, "local", media.stream, contentType);
//add each track to PeerConnection
media.stream.getTracks().forEach((track) => {
config[track.id] = contentType;
addTrackToPeerConnection(pc, media.stream, track, media.encodings);
subscribeTrackToEndedEvent(room, track, pc);
});
// Clean error message
displayError("");
//kickoff renegotiation
await room.updateState(config);
// Enable Delete button for a new stream #WCS-3689
$('#' + media.stream.id + "-button").prop('disabled', false);
} catch (e) {
onOperationFailed("Failed to publish a new track", e);
// Enable Delete button for a new stream #WCS-3689
$('#' + media.stream.id + "-button").prop('disabled', false);
}
} |
8. Завершение публикации потока
subscribeTrackToEndedEvent() code
Вспомогательная функция, которая подписывается на событие "ended" для локального потока. При получении события поток удаляется из PeerConnection, и состояние комнаты обновляется.
Code Block |
---|
|
const subscribeTrackToEndedEvent = function (room, track, pc) {
track.addEventListener("ended", async function () {
try {
//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;
//track ended, see if we need to cleanupbreak;
let negotiate = false;
}
for (const sender of pc.getSenders()) {
}
if (sender.track === track) {
// Clean error message
pc.removeTrack(senderdisplayError("");
//track found, set renegotiation flagif (negotiate) {
negotiate = true;//kickoff renegotiation
breakawait room.updateState();
}
}
if (negotiatecatch (e) {
//kickoff renegotiation
room.updateState(onOperationFailed("Failed to update room state", e);
}
});
}; |
...
9. Добавление новой дорожки в PeerConnection
addTrackToPeerConnection() code
Вспомогательная функция, которая добавляет новую дорожку в PeerConnection для публикации
...