...
Для анализа исходного кода возьмем версию модуля display.js, которая находится здесь
Захват и отображение локального видео
1. Инициализация
initLocalDisplay() code
Функция initLocalDisplay() возвращает объект для работы с HTML5 элементами захвата и отображения локального видео
Code Block | ||||
---|---|---|---|---|
| ||||
const initLocalDisplay = function (localDisplayElement) { const localDisplayDiv = localDisplayElement; const localDisplays = {}; const removeLocalDisplay = function (id) { ... } const getAudioContainer = function () { ... }; const onMuteClick = function (button, stream, type) { ... } const add = function (id, name, stream, type) { ... } const stop = function () { ... } const audioStateText = function (stream) { ... } return { add: add, stop: stop } } |
...
2.1. Добавления аудио дорожки к HTML5 элементу
add() code
Здесь:
- добавляется аудио дорожка к video элементу
- создается обработчик события
onended
для видео дорожки - добавляется обработчик нажатия кнопки включения/отключения аудио
Code Block | ||||
---|---|---|---|---|
| ||||
if (stream.getAudioTracks().length > 0) { let videoElement = getAudioContainer(); if (videoElement) { let track = stream.getAudioTracks()[0]; videoElement.video.srcObject.addTrack(track); videoElement.audioStateDisplay.innerHTML = audioStateText(stream) + " " + type; videoElement.audioStateDisplay.addEventListener("click", function () { onMuteClick(videoElement.audioStateDisplay, stream, type); }); track.addEventListener("ended", function () { videoElement.video.srcObject.removeTrack(track); videoElement.audioStateDisplay.innerHTML = "No audio"; //check video element has no tracks left for (const [key, vTrack] of Object.entries(videoElement.video.srcObject.getTracks())) { if (vTrack.readyState !== "ended") { return; } } removeLocalDisplay(videoElement.id); }); return; } } |
2.2. Создание контейнера для отображения локального видео
add() code
Здесь:
- создается контейнер для элементов отображения локального видео
- создается элемент для отображения информации о публикуемом видео
...
2.3. Создание кнопки для включения/отключения локального аудио
add() code
Здесь:
- создается кнопка для включения/отключения локального аудио
...
2.4. Создание элемента для отображения локального видео
add() code
Здесь:
- создается элемент-контейнер, размеры которого можно менять в зависимости от размеров родительского элемента
- создается HTML5
video
элемент, с учетом публикации в Safari
...
2.5. Создание обработчиков событий video элемента
add() code
Здесь:
- запускается проигрывание локального видео
- настраивается обработчик события
onended
для видео дорожки - настраивается обработчик события
onresize
для локального видео, в котором размеры видео меняются под размеры контейнера
...
2.6. Добавление видео контейнера в элемент HTML страницы
add() code
Code Block | ||||
---|---|---|---|---|
| ||||
localDisplays[id] = coreDisplay; localDisplayDiv.appendChild(coreDisplay); return coreDisplay; |
3. Остановка захвата видео и аудио
stop() code
Code Block | ||||
---|---|---|---|---|
| ||||
const stop = function () { for (const [key, value] of Object.entries(localDisplays)) { removeLocalDisplay(value.id); } } |
...
1. Инициализация
initRemoteDisplay() code
Функция initRemoteDisplay() возвращает объект для работы с HTML5 элементами отображения видео и аудио потоков, опубликованных в комнате
Code Block | ||||
---|---|---|---|---|
| ||||
const initRemoteDisplay = function(options) { const constants = SFU.constants; /* display options: autoAbr - choose abr by default quality const- remoteParticipantsshow =quality {};buttons showAudio - //show Validateaudio optionselements first*/ const initRemoteDisplay = function if (!options.div) { (room, div, displayOptions, abrOptions, meetingController, meetingModel, meetingView, participantFactory) { // Validate options first if (!div) { throw new Error("Main div to place all the media tag is not defined"); } if (!options.room) { throw new Error("Room is not defined"); } if (!options.peerConnection) { throw new Error("PeerConnection is not defined"); } let mainDiv = options.div; let room = options.room; let peerConnection = options.peerConnection; let displayOptions = options.displayOptions || {publisher: true, quality: true, type: true}; ... const createRemoteDisplay = function(id, name, mainDiv, displayOptions) { ... } const stop = function() { ... } peerConnection.ontrack = ({transceiver}) => { const dOptions = displayOptions || {quality: true, type: true, showAudio: false}; let abrFactory; if (abrOptions) { abrFactory = abrManagerFactory(room, abrOptions); } participantFactory.abrFactory = abrFactory; participantFactory.displayOptions = dOptions; return meetingController(room, meetingModel(meetingView(div), participantFactory)); } |
2. Создание фабрики объектов для управления проигрыванием WebRTC ABR потока
abrManagerFactory() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
...
return abr;
}
}
} |
2.1. Инициализация объекта управления ABR
createAbrManager() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
let abr = {
track: null,
interval: abrOptions.interval,
thresholds: abrOptions.thresholds,
qualities: [],
currentQualityName: null,
statTimer: null,
paused: false,
manual: false,
keepGoodTimeout: abrOptions.abrKeepOnGoodQuality,
keepGoodTimer: null,
tryUpperTimeout: abrOptions.abrTryForUpperQuality,
tryUpperTimer: null,
...
}
return abr;
}
}
} |
2.2. Запуск автоматического выбора качества ABR
abr.start() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
let abr = {
...
start: function () {
this.stop();
console.log("Start abr interval")
if (abr.interval) {
const thresholds = Thresholds();
for (const threshold of abr.thresholds) {
thresholds.add(threshold.parameter, threshold.maxLeap);
}
abr.statsTimer = setInterval(() => {
if (abr.track) {
room.getStats(abr.track.track, constants.SFU_RTC_STATS_TYPE.INBOUND, (stats) => {
if (thresholds.isReached(stats)) {
abr.shiftDown();
} else {
abr.useGoodQuality();
}
});
}
}, abr.interval);
}
},
...
}
return abr;
}
}
} |
2.3. Остановка автоматического выбора качества ABR
abr.stop() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
let abr = {
...
stop: function () {
console.log("Stop abr interval")
abr.stopKeeping();
abr.stopTrying();
if (abr.statsTimer) {
clearInterval(abr.statsTimer);
abr.statsTimer = null;
}
},
...
}
return abr;
}
}
} |
2.4. Добавление информации о видеодорожке
abr.setTrack() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
let abr = {
...
setTrack: function (track) {
abr.track = track;
},
...
}
return abr;
}
}
} |
2.5. Добавление описания ABR качества в список для выбора
abr.addQuality() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
let abr = {
...
addQuality: function (name) {
abr.qualities.push({name: name, available: false, good: true});
},
...
}
return abr;
}
}
} |
2.6. Установка признака доступности качества для проигрывания
abr.setQualityAvailable() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
let abr = {
...
setQualityAvailable: function (name, available) {
for (let i = 0; i < abr.qualities.length; i++) {
if (name === abr.qualities[i].name) {
abr.qualities[i].available = available;
}
}
},
...
}
return abr;
}
}
} |
2.7. Установка признака хорошего качества для текущего состояния канала
abr.setQualityGood() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
let abr = {
...
setQualityGood: function (name, good) {
if (name) {
for (let i = 0; i < abr.qualities.length; i++) {
if (name === abr.qualities[i].name) {
abr.qualities[i].good = good;
}
}
}
},
...
}
return abr;
}
}
} |
2.8. Получение первого доступного качества
abr.getFirstAvailableQuality() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
let abr = {
...
getFirstAvailableQuality: function () {
for (let i = 0; i < abr.qualities.length; i++) {
if (abr.qualities[i].available) {
return abr.qualities[i];
}
}
return null;
},
...
}
return abr;
}
}
} |
2.9. Получение более низкого качества
abr.getLowerQuality() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
let abr = {
...
getLowerQuality: function (name) {
let quality = null;
if (!name) {
// There were no switching yet, return a first available quality
return abr.getFirstAvailableQuality();
}
let currentIndex = abr.qualities.map(item => item.name).indexOf(name);
for (let i = 0; i < currentIndex; i++) {
if (abr.qualities[i].available) {
quality = abr.qualities[i];
}
}
return quality;
},
...
}
return abr;
}
}
} |
2.10. Получение более высокого качества
abr.getUpperQuality() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
let abr = {
...
getUpperQuality: function (name) {
let quality = null;
if (!name) {
// There were no switching yet, return a first available quality
return abr.getFirstAvailableQuality();
}
let currentIndex = abr.qualities.map(item => item.name).indexOf(name);
for (let i = currentIndex + 1; i < abr.qualities.length; i++) {
if (abr.qualities[i].available) {
quality = abr.qualities[i];
break;
}
}
return quality;
},
...
}
return abr;
}
}
} |
2.11. Переключение качества вниз
add.shiftDown() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
let abr = {
...
shiftDown: function () {
if (!abr.manual && !abr.paused) {
abr.stopKeeping();
abr.setQualityGood(abr.currentQualityName, false);
let quality = abr.getLowerQuality(abr.currentQualityName);
if (quality) {
console.log("Switching down to " + quality.name + " quality");
abr.setQuality(quality.name);
}
}
},
...
}
return abr;
}
}
} |
2.12. Переключение качества вверх
abr.shiftUp() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
let abr = {
...
shiftUp: function () {
if (!abr.manual && !abr.paused) {
let quality = abr.getUpperQuality(abr.currentQualityName);
if (quality) {
if (quality.good) {
console.log("Switching up to " + quality.name + " quality");
abr.setQuality(quality.name);
} else {
abr.tryUpper();
}
}
}
},
...
}
return abr;
}
}
} |
2.13. Использовать текущее качество как хорошее
abr.useGoodQuality() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) {
return {
createAbrManager: function () {
let abr = {
...
useGoodQuality: function () {
if (!abr.manual && !abr.paused) {
if (!abr.currentQualityName) {
let quality = abr.getFirstAvailableQuality();
abr.currentQualityName = quality.name;
}
abr.setQualityGood(abr.currentQualityName, true);
abr.keepGoodQuality();
}
},
...
}
return abr;
}
}
} |
2.14. Установить таймер использования текущего качества
abr.keepGoodQuality() code
Code Block | ||||
---|---|---|---|---|
| ||||
const abrManagerFactory = function (room, abrOptions) { return { createAbrManager: function () { let abr = { ... keepGoodQuality: function () { if (abr.keepGoodTimeout && !abr.keepGoodTimer && abr.getUpperQuality(abr.currentQualityName)) { console.log("start keepGoodTimer"); abr.keepGoodTimer = setTimeout(() => { abr.shiftUp(); abr.stopKeeping(); }, abr.keepGoodTimeout); } }, ... } return {abr; stop: stop} } } |
2.15. Сбросить таймер использования текущего качества
2. Обработка событий комнаты
...