controls.js¶
Данный модуль содержит код для управления локальными медиа потоками и конфигурацией комнаты, включая создание новых дорожек, остановку существующих дорожек и подготовку конфигурации комнаты перед установкой соединения с сервером.
1. Обертка для кода¶
createControls() code
Функция-обертка для вызова из основной логики, ограничивает область видимости
2. Создание объекта controls¶
Создание объекта controls, содержащего все HTML элементы для настройки. Здесь же инициализируются таблицы для настройки публикации дорожек
const controls = {
entrance: {
url: document.getElementById("url"),
roomName: document.getElementById("roomName"),
roomPin: document.getElementById("roomPin"),
nickName: document.getElementById("nickName"),
enter: document.getElementById("startButton")
},
addVideoTrack: {
source: document.getElementById("addVideoTrackSource"),
width: document.getElementById("addVideoTrackWidth"),
height: document.getElementById("addVideoTrackHeight"),
codec: document.getElementById("addVideoTrackCodec")
},
addAudioTrack: {
source: document.getElementById("addAudioTrackSource"),
channels: document.getElementById("addAudioTrackChannels")
},
addVideoEncoding: {
rid: document.getElementById("addVideoTrackEncodingRid"),
active: document.getElementById("addVideoTrackEncodingActive"),
maxBitrate: document.getElementById("addVideoTrackEncodingMaxBitrate"),
resolutionScale: document.getElementById("addVideoTrackEncodingResolutionScale")
},
tables: {
video: $('#videoTracksTable').DataTable({
"sDom": 't',
"columns": [
{
"className": 'details-control',
"orderable": false,
"data": null,
"defaultContent": ''
},
{"data": "source"},
{"data": "width"},
{"data": "height"},
{"data": "codec"},
{"data": "action"}
]
}),
audio: $('#audioTracksTable').DataTable({
"sDom": 't',
"columns": [
{"data": "source"},
{"data": "channels"},
{"data": "action"}
]
}),
encodings: $('#videoTrackEncodingsTable').DataTable({
"sDom": 't',
"columns": [
{"data": "rid"},
{"data": "active"},
{"data": "maxBitrate"},
{"data": "resolutionScale"},
{"data": "action"}
]
})
}
}
3. Заполнение полей модального окна входа¶
Заполнение полей модального окна входа в соответствии с текущей конфигурацией
//apply room config
controls.entrance.url.value = config.room.url;
controls.entrance.roomName.value = config.room.name;
controls.entrance.roomPin.value = config.room.pin;
controls.entrance.nickName.value = config.room.nickName;
4. Добавление новых аудио дорожек в объект controls¶
addAudioTrackRow() code
Добавление новых аудиодорожек и оповещение основного модуля
const addAudioTrackRow = async function(track) {
const stream = await getMedia([track]);
let button = '<button id="' + stream.id + '-button" class="btn btn-primary">Delete</button>';
const row = controls.tables.audio.row.add({
source: track.source,
channels: track.channels,
action: button,
stream: stream
}).node();
controls.tables.audio.draw();
$('#' + stream.id + "-button").on('click', function(){
//terminate stream
console.log("terminate audio stream " + stream.id);
let track = stream.getAudioTracks()[0];
track.stop();
track.dispatchEvent(new Event("ended"));
}).prop('disabled', true);
stream.getTracks()[0].onended = function() {
controls.tables.audio.row(row).remove().draw();
}
trackCallback({
stream: stream,
encodings: track.encodings,
source: track.source,
type: track.type
});
}
Запрос локального медиа через WebRTC API
Добавление аудио дорожки в таблицу дорожек
let button = '<button id="' + stream.id + '-button" class="btn btn-primary">Delete</button>';
const row = controls.tables.audio.row.add({
source: track.source,
channels: track.channels,
action: button,
stream: stream
}).node();
controls.tables.audio.draw();
Подписка на событие "click". По нажатию кнопки "Delete" дорожка останавливается, генерируется событие "ended"
$('#' + stream.id + "-button").on('click', function(){
//terminate stream
console.log("terminate audio stream " + stream.id);
let track = stream.getAudioTracks()[0];
track.stop();
track.dispatchEvent(new Event("ended"));
}).prop('disabled', true);
Подписка на событие "ended" и очистка таблицы при получении данного события
Оповещение основного модуля о том, что добавлена новая дорожка
trackCallback({
stream: stream,
encodings: track.encodings,
source: track.source,
type: track.type
});
5. Добавление новых видео дорожек¶
addVideoTrackRow() code
Добавление новых видео дорожек и оповещение основного модуля, аналогично функции addAudioTrackRow
const addVideoTrackRow = async function(track) {
const stream = await getMedia([track]);
let button = '<button id="' + stream.id + '-button" class="btn btn-primary">Delete</button>';
const row = controls.tables.video.row.add({
source: track.source,
width: track.width,
height: track.height,
codec: track.codec,
action: button,
stream: stream,
encodings: track.encodings,
}).node();
controls.tables.video.draw();
$('#' + stream.id + "-button").on('click', function(){
//terminate stream
console.log("terminate video stream " + stream.id);
let track = stream.getVideoTracks()[0];
track.stop();
track.dispatchEvent(new Event("ended"));
}).prop('disabled', true);
stream.getTracks()[0].addEventListener("ended", function() {
controls.tables.video.row(row).remove().draw();
});
trackCallback({
stream: stream,
encodings: track.encodings,
source: track.source
});
}
6. Форматирование настроек кодирования видео¶
format() code
Вспомогательная функция форматирует настройки кодирования видео для их отображения в таблице
const format = function(d) {
if (!d.encodings) {
return;
}
let details = '<table cellpadding="5" cellspacing="0" border="0" style="padding-left:50px;">';
d.encodings.forEach(function(encoding){
details += '<tr>';
for (const [key, value] of Object.entries(encoding)) {
details += '<td>'+ key + '</td>'+
'<td>'+ value + '</td>';
}
details += '</tr>';
});
details +='</table>';
return details;
}
7. Отображение таблиц аудио и видео дорожек¶
displayTables() code
const displayTables = async function() {
// Add event listener for opening and closing details
$('#videoTracksTableBody').on('click', 'td.details-control', function () {
let tr = $(this).closest('tr');
let row = controls.tables.video.row(tr);
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
} else {
// Open this row
row.child(format(row.data())).show();
tr.addClass('shown');
}
});
// Add preconfigured audio and video tracks
for (const track of config.media.audio.tracks) {
await addAudioTrackRow(track);
}
for (const track of config.media.video.tracks) {
await addVideoTrackRow(track);
}
// Click event listener to add a new video track
document.getElementById("addVideoTrack").addEventListener("click", function(e){
let encodings = [];
controls.tables.encodings.rows().every(function() {
let encoding = this.data();
encodings.push({
rid: encoding.rid,
active: encoding.active,
maxBitrate: encoding.maxBitrate,
scaleResolutionDownBy: encoding.resolutionScale
})
});
let track = {
source: controls.addVideoTrack.source.value,
width: controls.addVideoTrack.width.value,
height: controls.addVideoTrack.height.value,
codec: controls.addVideoTrack.codec.value,
encodings: encodings
}
addVideoTrackRow(track);
});
// Click event listener to remove video quality
$("#videoTrackEncodingsTable").on("click", ".remove", function(){
controls.tables.encodings.row($(this).parents('tr')).remove().draw();
});
// Click event listener to add video quality
document.getElementById("addVideoTrackEncoding").addEventListener("click", function(){
let button = '<button class="btn btn-primary remove">Delete</button>';
controls.tables.encodings.row.add({
rid: controls.addVideoEncoding.rid.value,
active: controls.addVideoEncoding.active.value,
maxBitrate: controls.addVideoEncoding.maxBitrate.value,
resolutionScale: controls.addVideoEncoding.resolutionScale.value,
action: button
}).draw();
});
// Click event listener to add a new audio track
document.getElementById("addAudioTrack").addEventListener("click", function(e){
let encodings = [];
let track = {
source: controls.addAudioTrack.source.value,
channels: controls.addAudioTrack.channels.value,
encodings: encodings
}
addAudioTrackRow(track);
});
}
7.1. Добавление обработчика для отображения/скрытия данных дорожки¶
Добавление обработчика для того, чтобы показать или спрятать данные видео дорожки
$('#videoTracksTableBody').on('click', 'td.details-control', function () {
let tr = $(this).closest('tr');
let row = controls.tables.video.row(tr);
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
} else {
// Open this row
row.child(format(row.data())).show();
tr.addClass('shown');
}
});
7.2. Добавление аудио и видео дорожек из файла конфигурации в таблицу¶
Добавление всех настроенных аудио и видео дорожек в таблицу
// Add preconfigured audio and video tracks
for (const track of config.media.audio.tracks) {
await addAudioTrackRow(track);
}
for (const track of config.media.video.tracks) {
await addVideoTrackRow(track);
}
7.3. Добавление новых видео дорожек в таблицу¶
Добавление всех настроенных видео дорожек в таблицу
// Click event listener to add a new video track
document.getElementById("addVideoTrack").addEventListener("click", function(e){
let encodings = [];
controls.tables.encodings.rows().every(function() {
let encoding = this.data();
encodings.push({
rid: encoding.rid,
active: encoding.active,
maxBitrate: encoding.maxBitrate,
scaleResolutionDownBy: encoding.resolutionScale
})
});
let track = {
source: controls.addVideoTrack.source.value,
width: controls.addVideoTrack.width.value,
height: controls.addVideoTrack.height.value,
codec: controls.addVideoTrack.codec.value,
encodings: encodings
}
addVideoTrackRow(track);
});
7.4. Добавление новых аудио дорожек в таблицу¶
Добавление всех настроенных видео дорожек в таблицу
// Click event listener to add a new audio track
document.getElementById("addAudioTrack").addEventListener("click", function(e){
let encodings = [];
let track = {
source: controls.addAudioTrack.source.value,
channels: controls.addAudioTrack.channels.value,
encodings: encodings
}
addAudioTrackRow(track);
});
8. Отключение элементов формы¶
muteForm() code
Вспомогательная функция, отключающая все элементы указанной формы
const muteForm = function(form) {
for (const [key, value] of Object.entries(form)) {
value.disabled = true;
}
}
9. Включение элементов формы¶
unmuteForm() code
Вспомогательная функция, включающая все элементы указанной формы
const unmuteForm = function(form) {
for (const [key, value] of Object.entries(form)) {
value.disabled = false;
}
}
10. Отключение полей ввода модального окна входа¶
muteInput() code
Вспомогательная функция, отключающая поля ввода в модальном окне
11. Создание объекта конфигурации комнаты¶
roomConfig() code
Функция собирает объект конфигурации комнаты
const roomConfig = function() {
let roomConfig = {
url: controls.entrance.url.value,
roomName: controls.entrance.roomName.value,
pin: controls.entrance.roomPin.value,
nickname: controls.entrance.nickName.value
};
if (config.room.failedProbesThreshold !== undefined) {
roomConfig.failedProbesThreshold = config.room.failedProbesThreshold;
}
if (config.room.pingInterval !== undefined) {
roomConfig.pingInterval = config.room.pingInterval;
}
return roomConfig;
}
12. Получение локальных видео дорожек¶
getVideoStreams() code
Функция возвращает все локальные видео дорожки
const getVideoStreams = function() {
let streams = [];
controls.tables.video.rows().every(function(rowIdx, tableLoop, rowLoop) {
let data = this.data();
streams.push({
stream: data.stream,
encodings: data.encodings,
source: data.source,
type: data.type
});
});
return streams;
}
13. Получение локальных аудио дорожек¶
getAudioStreams() code
Функция возвращает все локальные аудио дорожки
const getAudioStreams = function() {
let streams = [];
controls.tables.audio.rows().every(function(rowIdx, tableLoop, rowLoop) {
let data = this.data();
streams.push({
stream: data.stream,
encodings: [],
source: data.source
});
});
return streams;
}
14. Передача callback функции новым дорожкам¶
Функция передает указанную callback функцию новым дорожкам
15. Экспорт функций¶
Экспорт функций для использования в основном модуле
return {
muteInput: muteInput,
roomConfig: roomConfig,
displayTables: displayTables,
getAudioStreams: getAudioStreams,
getVideoStreams: getVideoStreams,
onTrack: onTrack,
cleanTables: cleanTables
}
16. Получение медиа потоков из WebRTC API¶
getMedia() code
Запрос списка локальных медиа потоков от WebRTC API
const getMedia = async function(tracks) {
//convert to constraints
let screen = false;
const constraints= {};
tracks.forEach(function(track){
if (track.source === "mic") {
//audio
constraints.audio = {};
if (track.constraints) {
constraints.audio = track.constraints;
}
constraints.audio.stereo = track.channels !== 1
if (track.channels && track.channels === 2) {
constraints.audio.echoCancellation = false;
constraints.audio.googEchoCancellation = false;
}
} else if (track.source === "camera") {
constraints.video = {};
if (track.constraints) {
constraints.video = track.constraints;
}
constraints.video.width = track.width;
constraints.video.height = track.height;
} else if (track.source === "screen") {
constraints.video = {};
if (track.constraints) {
constraints.video = track.constraints;
}
constraints.video.width = track.width;
constraints.video.height = track.height;
screen = true;
}
});
//get access to a/v
let stream;
if (screen) {
stream = await navigator.mediaDevices.getDisplayMedia(constraints);
} else {
stream = await navigator.mediaDevices.getUserMedia(constraints);
}
return stream;
}