...
Для анализа исходного кода возьмем версию модуля display.js, которая находится здесь и доступна в сборке 1.0.1.36
Захват и отображение локального видео
1. Инициализация
initLocalDisplay() code
Функция initLocalDisplay() возвращает объект для работы с HTML5 элементами захвата и отображения локального видео
...
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;
trackvideoElement.audioStateDisplay.addEventListener("endedclick", function() {
onMuteClick(videoElement.video.srcObject.removeTrack(trackaudioStateDisplay, stream, type);
videoElement.audioStateDisplay.innerHTML = "No audio"});
track.addEventListener("ended", //check video element has no tracks leftfunction() {
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
Здесь:
- создается контейнер для элементов отображения локального видео
- создается элемент для отображения информации о публикуемом видео
Code Block |
---|
|
const coreDisplay = document.createElement('div'createContainer(null);
coreDisplay.setAttribute("class","text-center");
coreDisplay.setAttribute("style","width: auto; height: auto;");
coreDisplay.id = stream.id;
const streamNameDisplaypublisherNameDisplay = document.createElement("div");
streamNameDisplay.innerHTML = "Name:createInfoDisplay(coreDisplay, name + " " + name;
streamNameDisplay.setAttribute("class","text-center");
streamNameDisplay.setAttribute("style","width: auto; height: auto;");
coreDisplay.appendChild(streamNameDisplay); |
...
2.3. Создание кнопки для включения/отключения локального аудио
add() code
Здесь:
- создается кнопка для включения/отключения локального аудио
- добавляется обработчик нажатия этой кнопки
Code Block |
---|
|
const audioStateDisplay = document.createElement("button");
audioStateDisplay.innerHTML = audioStateText(stream);
audioStateDisplay.addEventListener('click', function(){
coreDisplay.appendChild(audioStateDisplay); |
2.4. Создание элемента для отображения локального видео
add() code
Здесь:
- создается элемент-контейнер, размеры которого можно менять в зависимости от размеров родительского элемента
- создается HTML5
video
элемент, с учетом публикации в Safari
Code Block |
---|
|
const streamDisplay = createContainer(coreDisplay);
if (stream.getAudioTracks().length > 0) {
streamDisplay.id = "stream-" + id;
const video = streamdocument.getAudioTrackscreateElement()[0].enabled = !(stream.getAudioTracks()[0].enabled)"video");
video.muted = true;
if(Browser().isSafariWebRTC()) {
audioStateDisplay.innerHTML = audioStateText(streamvideo.setAttribute("playsinline", "");
} video.setAttribute("webkit-playsinline", "");
});
coreDisplaystreamDisplay.appendChild(audioStateDisplay)video);
video.srcObject = stream; |
2.
...
5. Создание
...
обработчиков событий video элемента
add() code
Здесь:
...
- запускается проигрывание локального видео
- настраивается обработчик события
onended
для видео дорожки - настраивается обработчик события
onresize
для локального видео, в котором размеры видео меняются под размеры контейнера
Code Block |
---|
|
constvideo.onloadedmetadata streamDisplay= =function document.createElement('div');(e) {
streamDisplay.id = "stream-" + idvideo.play();
streamDisplay.setAttribute("class","text-center")};
streamDisplay.setAttribute("style","width: auto; height: auto;");
stream.getTracks().forEach(function(track){
coreDisplay.appendChild(streamDisplay);track.addEventListener("ended", function() {
const video = document.createElement("video");
video.muted = truesrcObject.removeTrack(track);
if(Browser().isSafariWebRTC()) {
//check video element has video.setAttribute("playsinline", "");no tracks left
video.setAttribute("webkit-playsinline", "");
for (const [key, vTrack] of Object.entries(video.srcObject.getTracks())) {
if (vTrack.readyState !== "ended") {
}
streamDisplay.appendChild(video)return;
video.srcObject = stream; |
2.5. Создание обработчиков событий video элемента
add() code
Здесь:
- запускается проигрывание локального видео
- настраивается обработчик события
onended
для видео дорожки - настраивается обработчик события
onresize
для локального видео, в котором размеры видео меняются под размеры контейнера
Code Block |
---|
|
}
video.onloadedmetadata = function (e) {}
video.playremoveLocalDisplay(id);
});
stream.getTracks().forEach(function(track){});
track.addEventListener("ended", function(if (stream.getVideoTracks().length > 0) {
// Resize only if video.srcObject.removeTrack(track); displayed
video.addEventListener('resize', function (event) {
//check video element has no tracks left
publisherNameDisplay.innerHTML = name + " " + fortype (const [key, vTrack] of Object.entries(video.srcObject.getTracks())) {
+ " " + video.videoWidth + "x" + video.videoHeight;
if resizeVideo(vTrack.readyState !== "ended") {event.target);
});
} else {
return;
// Hide audio only container
}
hideItem(streamDisplay);
}
// Set up mute button for audio only stream
removeLocalDisplay(id);
audioStateDisplay.innerHTML = audioStateText(stream) + " " + })type;
});
videoaudioStateDisplay.addEventListener('resize'"click", function (event) {
streamNameDisplay.innerHTML = "Name: " + name + "<br/>Max.resolution: " + video.videoWidth + "x" + video.videoHeight onMuteClick(audioStateDisplay, stream, type);
resizeVideo(event.target});
}); |
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
Функция initLocalDisplayinitRemoteDisplay() возвращает объект для работы с HTML5 элементами отображения видео и аудио потоков, опубликованных в комнате
Code Block |
---|
|
const initRemoteDisplay = function(mainDiv, room, peerConnectionoptions) {
const constants = SFU.constants;
const remoteParticipants = {};
...
const createRemoteDisplay = function(id, name, mainDiv // Validate options first
if (!options.div) {
...
throw new Error("Main div }
to place all const stop = function(the media tag is not defined");
}
if (!options.room) {
throw new Error("Room is ...not defined");
}
peerConnection.ontrackif = ({transceiver}) =>(!options.peerConnection) {
throw ...
}
new Error("PeerConnection is not defined");
return {}
let mainDiv = options.div;
stop: stop
let }
} |
2. Обработка событий комнаты
2.1. ADD_TRACKS
initRemoteDisplay() code
Здесь:
- новый участник добавляется в список
- добавляется информация о качестве потоков
- создается элемент для отображения видео и аудио потоков участника
Code Block |
---|
|
room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e) {
console.log("Received ADD_TRACKS")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}) => {
...
}
return {
stop: stop
}
} |
2. Обработка событий комнаты
2.1. ADD_TRACKS
initRemoteDisplay() code
Здесь:
- новый участник добавляется в список
- добавляется информация о качестве потоков
- создается элемент для отображения видео и аудио потоков участника
Code Block |
---|
|
room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e) {
console.log("Received ADD_TRACKS");
let participant = remoteParticipants[e.info.nickName];
if (!participant) {
participant = {};
participant.nickName = e.info.nickName;
participant.tracks = [];
participant.displays = [];
remoteParticipants[participant.nickName] = participant;
}
participant.tracks.push.apply(participant.tracks, e.info.info);
for (const pTrack of e.info.info) {
let createDisplay = true;
for (let i = 0; i < participant.displays.length; i++) {
let display = participant.displays[i];
if (pTrack.type === "VIDEO") {
if (display.hasVideo()) {
continue;
}
display.videoMid = pTrack.mid;
display.setTrackInfo(pTrack);
createDisplay = false;
break;
} else if (pTrack.type === "AUDIO") {
if (display.hasAudio()) {
continue;
}
display.audioMid = pTrack.mid;
display.setTrackInfo(pTrack);
createDisplay = false;
break;
}
}
if (!createDisplay) {
continue;
}
let display = createRemoteDisplay(participant.nickName, participant.nickName, mainDiv, displayOptions);
participant.displays.push(display);
if (pTrack.type === "VIDEO") {
display.videoMid = pTrack.mid;
display.setTrackInfo(pTrack);
} else if (pTrack.type === "AUDIO") {
display.audioMid = pTrack.mid;
display.setTrackInfo(pTrack);
}
}
...
}); |
2.2. REMOVE_TRACKS
initRemoteDisplay() code
Здесь:
- удаляются элементы, в которых проигрывались потоки
- информация о потоках удаляется из списка
Code Block |
---|
|
room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e) {
...
}).on(constants.SFU_ROOM_EVENT.REMOVE_TRACKS, function(e) {
console.log("Received REMOVE_TRACKS");
const participant = remoteParticipants[e.info.nickName];
if (!participant) {
return;
}
for (const rTrack of e.info.info) {
for (let i = 0; i < participant.tracks.length; i++) {
if (rTrack.mid === participant.tracks[i].mid) {
participant.tracks.splice(i, 1);
break;
}
}
for (let i = 0; i < participant.displays.length; i++) {
let found = false;
const display = participant.displays[i];
if (display.audioMid === rTrack.mid) {
display.setAudio(null);
found = true;
} else if (display.videoMid === rTrack.mid) {
display.setVideo(null);
found = true;
}
if (found) {
if (!display.hasAudio() && !display.hasVideo()) {
display.dispose();
participant.displays.splice(i, 1);
}
break;
}
}
}
...
}); |
2.3. LEFT
initRemoteDisplay() code
Здесь:
- участник удаляется из списка
- удаляются элементы, в которых проигрывались потоки
Code Block |
---|
|
room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e) {
...
}).on(constants.SFU_ROOM_EVENT.LEFT, function(e) {
console.log("Received LEFT");
let participant = remoteParticipants[e.name];
if (!participant) {
return;
}
participant.displays.forEach(function(display){
display.dispose();
})
delete remoteParticipants[e.name];
...
}); |
2.4. TRACK_QUALITY_STATE
initRemoteDisplay() code
Здесь:
- обновляется информация о качествах потоков
Code Block |
---|
|
room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e) {
...
}).on(constants.SFU_ROOM_EVENT.TRACK_QUALITY_STATE, function(e){
console.log("Received track quality state");
const participant = remoteParticipants[e.info.nickName];
if (!participant) {
return;
}
for (const rTrack of e.info.tracks) {
const mid = rTrack.mid;
for (let i = 0; i < participant.displays.length; i++) {
const display = participant.displays[i];
if (display.videoMid === mid) {
display.updateQualityInfo(rTrack.quality);
break;
}
}
}
}); |
3. Создание элементов для отображения потоков участников
3.1. Создание контейнера
createRemoteDisplay() code
Здесь:
- настраиваются параметры отображения
- создается контейнер для потоков участника
- создается контейнер для конкретного потока
- создается контейнер для кнопок переключения качества
Code Block |
---|
|
const cell = document.createElement("div");
cell.setAttribute("class", "text-center");
cell.id = id;
mainDiv.appendChild(cell);
let publisherNameDisplay;
let currentQualityDisplay;
let videoTypeDisplay;
let abrQualityCheckPeriod = ABR_QUALITY_CHECK_PERIOD;
let abrKeepOnGoodQuality = ABR_KEEP_ON_QUALITY;
let abrTryForUpperQuality = ABR_TRY_UPPER_QUALITY;
if (displayOptions.abrQualityCheckPeriod !== undefined) {
abrQualityCheckPeriod = displayOptions.abrQualityCheckPeriod;
}
if (displayOptions.abrKeepOnGoodQuality !== undefined) {
abrKeepOnGoodQuality = displayOptions.abrKeepOnGoodQuality;
}
if (displayOptions.abrTryForUpperQuality !== undefined) {
abrTryForUpperQuality = displayOptions.abrTryForUpperQuality;
}
if (!displayOptions.abr) {
abrQualityCheckPeriod = 0;
abrKeepOnGoodQuality = 0;
abrTryForUpperQuality = 0;
}
if (displayOptions.publisher) {
publisherNameDisplay = createInfoDisplay(cell, "Published by: " + name);
}
if (displayOptions.quality) {
currentQualityDisplay = createInfoDisplay(cell, "");
}
if (displayOptions.type) {
videoTypeDisplay = createInfoDisplay(cell, "");
}
const qualitySwitchDisplay = createInfoDisplay(cell, "");
let qualityDivs = [];
let contentType = "";
const rootDisplay = createContainer(cell);
const streamDisplay = createContainer(rootDisplay);
const audioDisplay = createContainer(rootDisplay);
const audioTypeDisplay = createInfoDisplay(audioDisplay);
const audioTrackDisplay = createContainer(audioDisplay);
const audioStateButton = AudioStateButton();
hideItem(streamDisplay);
hideItem(audioDisplay);
hideItem(publisherNameDisplay);
hideItem(currentQualityDisplay);
hideItem(videoTypeDisplay);
hideItem(qualitySwitchDisplay); |
3.2. Инициализация ABR
createRemoteDisplay() code
Здесь задаются параметры качества получения потока от сервера, по которым отображение будет переключаться на более высокое или более низкое качество
Code Block |
---|
|
const abr = ABR(abrQualityCheckPeriod, [
{parameter: "nackCount", maxLeap: 10},
{parameter: "freezeCount", maxLeap: 10},
{parameter: "packetsLost", maxLeap: 10}
], abrKeepOnGoodQuality, abrTryForUpperQuality); |
3.3. Добавление видео элемента
setVideo() code
Code Block |
---|
|
setVideo: function(stream) {
if (video) {
video.remove();
}
if (stream == null) {
video = null;
this.videoMid = undefined;
qualityDivs.forEach(function(div) {
div.remove();
});
qualityDivs = [];
return;
}
showItem(streamDisplay);
video = document.createElement("video");
video.controls = "controls";
video.muted = true;
let participant video.autoplay = remoteParticipants[e.info.nickName]true;
if (!participant(Browser().isSafariWebRTC()) {
participant = {};
video.setAttribute("playsinline", "");
participant.nickName = e.info.nickName video.setAttribute("webkit-playsinline", "");
participant.tracks = [];
this.setWebkitEventHandlers(video);
participant.displays = [];
} else {
remoteParticipants[participant.nickName] = participant;
}
participant.tracks.push.apply(participant.tracks, e.info.infothis.setEventHandlers(video);
for (const pTrack of e.info.info) {
...}
let display = createRemoteDisplay(participant.nickName, participant.nickName, mainDiv streamDisplay.appendChild(video);
participant.displays.push(display) video.srcObject = stream;
if (pTrack.type === "VIDEO") { this.setResizeHandler(video);
display.videoMid = pTrack.mid;
abr.start();
}, |
3.4. Добавление аудио элемента
setAudio() code
Code Block |
---|
|
setAudio: display.setTrackInfofunction(pTrackstream); {
} else if (pTrack.type === "AUDIO" if (audio) {
display.audioMid = pTrackaudio.midremove();
}
}
if (!stream) {
...
}); |
2.2. REMOVE_TRACKS
initRemoteDisplay() code
Здесь:
- удаляются элементы, в которых проигрывались потоки
- информация о потоках удаляется из списка
Code Block |
---|
|
}).on(constants.SFU_ROOM_EVENT.REMOVE_TRACKS, function(e) { audio = null;
console.log("Received REMOVE_TRACKS");
const participantthis.audioMid = remoteParticipants[e.info.nickName]undefined;
if (!participant) {
return;
}
for (const rTrack of e.info.info) { showItem(audioDisplay);
for (let i = 0;audio i= < participant.tracks.length; i++) {document.createElement("audio");
if (rTrack.midaudio.controls === participant.tracks[i].mid) { "controls";
audio.muted participant.tracks.splice(i, 1)= true;
audio.autoplay = breaktrue;
}
if (Browser().isSafariWebRTC()) {
}
for (let i = 0; i < participant.displays.length; i++) {
audio.setAttribute("playsinline", "");
let found = false audio.setAttribute("webkit-playsinline", "");
const display = participant.displays[i]this.setWebkitEventHandlers(audio);
if (display.audioMid === rTrack.mid) } else {
displaythis.setAudiosetEventHandlers(nullaudio);
found = true;}
} else if (display.videoMid === rTrack.mid) {audioTrackDisplay.appendChild(audio);
display.setVideo(nullaudioStateButton.makeButton(audioTypeDisplay, audio);
audio.srcObject = stream;
found = true;
audio.onloadedmetadata = function (e) }{
if (foundaudio.play().then(function() {
if (Browser(!display).hasAudioisSafariWebRTC() && !display.hasVideoBrowser().isiOS()) {
display.dispose();
console.warn("Audio track should be manually unmuted in iOS Safari");
participant.displays.splice(i, 1);
} else {
}
audio.muted = breakfalse;
}
}audioStateButton.setButtonState();
}
}); |
2.3. LEFT
initRemoteDisplay() code
Здесь:
- участник удаляется из списка
- удаляются элементы, в которых проигрывались потоки
Code Block |
---|
|
}).on(constants.SFU_ROOM_EVENT.LEFT, function(e) {
console.log("Received LEFT");}
let participant = remoteParticipants[e.name];
if (!participant) {});
return;
};
}
participant.displays.forEach(function(display){
}, |
3.5. Настройка обработчиков событий для аудио и видео элементов
setResizeHandler(), setEventHandlers(), setWebkitEventHandlers() code
Code Block |
---|
|
setEventHandlers: display.disposefunction(video); {
})
delete remoteParticipants[e.name];
}); |
2.4. TRACK_QUALITY_STATE
initRemoteDisplay() code
Здесь:
- обновляется информация о качествах потоков
Code Block |
---|
|
}).on(constants.SFU_ROOM_EVENT.TRACK_QUALITY_STATE, function(e){
// Ignore play/pause button
video.addEventListener("pause", function () {
console.log("ReceivedMedia paused trackby quality stateclick, continue...");
const participant = remoteParticipants[e.info.nickName];
if video.play(!participant) {
;
return});
},
for (const rTrack of e.info.trackssetWebkitEventHandlers: function(video) {
const mid let needRestart = rTrack.midfalse;
for (let iisFullscreen = 0false;
i < participant.displays.length; i++) {
// Use webkitbeginfullscreen event to detect full screen constmode displayin = participant.displays[i];iOS Safari
if (display.videoMid === midvideo.addEventListener("webkitbeginfullscreen", function () {
display.updateQualityInfo(rTrack.quality)isFullscreen = true;
}); break;
}
video.addEventListener("pause", function () {
}
}
}); |
3. Создание элементов для отображения потоков участников
3.1. Создание контейнера
createRemoteDisplay() code
Здесь:
- создается контейнер для потоков участника
- создается контейнер для конкретного потока
- создается контейнер для кнопок переключения качества
Code Block |
---|
|
if (needRestart) {
const cell = document.createElement("div");
cellconsole.setAttributelog("class", "text-centerMedia paused after fullscreen, continue...");
cell.id = id;
mainDiv.appendChild(cell);
const streamNameDisplay = document.createElement("div"video.play();
streamNameDisplay.innerHTML = "Published by: " + name;
streamNameDisplay.setAttribute("style","width:auto; height:auto;");
needRestart = false;
streamNameDisplay.setAttribute("class","text-center");
cell.appendChild(streamNameDisplay);
} else {
const qualityDisplay = document.createElement("div");
qualityDisplay.setAttribute("style","width:auto; height:auto;");
qualityDisplayconsole.setAttributelog("class","text-centerMedia paused by click, continue...");
cell.appendChild(qualityDisplay);
let qualityDivs = [] video.play();
const rootDisplay = document.createElement("div");
rootDisplay.setAttribute("style","width:auto; height:auto;"); }
rootDisplay.setAttribute("class","text-center");
cell.appendChild(rootDisplay});
const streamDisplay = documentvideo.createElementaddEventListener("divwebkitendfullscreen");
, function () {
streamDisplay.setAttribute("style","width:auto; height:auto;");
streamDisplayvideo.setAttribute("class","text-center"play();
rootDisplay.appendChild(streamDisplay); |
3.2. Добавление видео элемента
setVideo() code
Code Block |
---|
|
setVideo: function(stream) {needRestart = true;
if (video) {
isFullscreen = false;
video.remove(});
}
}, |
3.6. Добавление информации о дорожке в ABR
setVideoABRTrack() code
Code Block |
---|
|
if (stream == nullsetVideoABRTrack: function(track) {
video = nullabr.setTrack(track);
this.videoMid = undefined;
}, |
3.7. Настройка переключения качества
setTrackInfo() code
Здесь:
- настраиваются кнопки переключения качества
- информация о заявленных качествах публикации добавляется в ABR
- скрываются или отображаются элементы для показа текущего качества и источника видео/аудио
Code Block |
---|
|
qualityDivs.forEach(setTrackInfo: function(divtrackInfo) {
if (trackInfo) {
div.remove();
if (trackInfo.quality) {
});
qualityDivs = [] showItem(qualitySwitchDisplay);
return;
if (abr.isEnabled()) {
}
const videoautoDiv = document.createElementcreateQualityButton("video");
video.muted = trueAuto", qualityDivs, qualitySwitchDisplay);
if(Browser().isSafariWebRTC()) {
autoDiv.style.color = QUALITY_COLORS.SELECTED;
video.setAttribute("playsinline", "");
videoautoDiv.setAttribute("webkit-playsinline", "");addEventListener('click', function() {
}
streamDisplay.appendChildsetQualityButtonsColor(videoqualityDivs);
video.srcObject = stream;
videoautoDiv.style.onloadedmetadatacolor = function (e) {
QUALITY_COLORS.SELECTED;
video.play().then(function() {
abr.setAuto();
video.muted = false;
});
};
}
video.addEventListener("resize", function (event) {
for (let i = 0; i < streamNameDisplay.innerHTML = "Published by: " + name + "<br/>Current resolution: " + video.videoWidth + "x" + video.videoHeight;
trackInfo.quality.length; i++) {
resizeVideoabr.addQuality(event.targettrackInfo.quality[i]);
} const qualityDiv = createQualityButton(trackInfo.quality[i], qualityDivs, qualitySwitchDisplay);
}, |
3.3. Добавление аудио элемента
setAudio() code
Code Block |
---|
|
setAudio:qualityDiv.addEventListener('click', function(stream) {
if (audio) {
console.log("Clicked on quality " + trackInfo.quality[i] + " trackId " + audiotrackInfo.remove(id);
}
if (!streamqualityDiv.style.color === QUALITY_COLORS.UNAVAILABLE) {
audio = null;
this.audioMid = undefinedreturn;
return;
}
audio = document.createElement("audio");
audio.controls = "controls"setQualityButtonsColor(qualityDivs);
audio.muted = true;
audioqualityDiv.style.autoplaycolor = trueQUALITY_COLORS.SELECTED;
cell.appendChild(audio);
audio.srcObject = streamabr.setManual();
audio.onloadedmetadata = function (e) {
audio.play().then(function() {
abr.setQuality(trackInfo.quality[i]);
audio.muted = false});
});
}
};
} }, |
3.4. Настройка переключения качества
setTrackInfo() code
Code Block |
---|
|
else {
setTrackInfo: function(trackInfo) {
hideItem(qualitySwitchDisplay);
if (trackInfo && trackInfo.quality) {
}
for (let i = 0; i <if (trackInfo.quality.length; i++type) {
constcontentType qualityDiv = document.createElement("button")trackInfo.contentType || "";
if (trackInfo.type == "VIDEO" && qualityDivs.push(qualityDiv);displayOptions.type && contentType !== "") {
qualityDiv.innerText = trackInfo.quality[i] showItem(videoTypeDisplay);
qualityDiv.setAttribute("style", "display:inline-block; border: solid; border-width: 1px") videoTypeDisplay.innerHTML = contentType;
qualityDiv.style.color = "red";
}
if qualityDiv.addEventListener('click', function(){
(trackInfo.type == "AUDIO") {
console.log("Clicked on quality " + trackInfo.quality[i] + " trackId " + trackInfoaudioStateButton.idsetContentType(contentType);
}
if (qualityDiv.style.color === "red") {
}
}
return;
}, |
3.8. Добавление информации о доступных качествах видео в ABR
updateQualityInfo() code
Code Block |
---|
|
updateQualityInfo: function(videoQuality) {
}
showItem(qualitySwitchDisplay);
for (const qualityInfo of videoQuality) {
for (let c = 0; c < qualityDivs.length; c++) {
let qualityColor = QUALITY_COLORS.UNAVAILABLE;
if (qualityDivs[c].style.color !qualityInfo.available === "red"true) {
qualityColor = QUALITY_COLORS.AVAILABLE;
qualityDivs[c].style.color = "gray";
}
for (const qualityDiv of qualityDivs) }{
if (qualityDiv.innerText }=== qualityInfo.quality){
qualityDiv.style.color = "blue"qualityColor;
room.changeQuality(trackInfo.id, trackInfo.quality[i]) break;
});
}
qualityDisplay.appendChild(qualityDiv);
}abr.setQualityAvailable(qualityInfo.quality, qualityInfo.available);
}
}, |
3.
...
9. Удаление контейнера с видео и аудио
dispose() code
Code Block |
---|
|
dispose: function() {
abr.stop();
cell.remove();
}, |
4. Подписка на добавление дорожек в WebRTC соединение
PeerConnection.ontrack(), setAudio(), setVideo(), setVideoABRTrack() code
Здесь:
- при получении видео или аудио потока, добавляется элемент для его проигрывания
Code Block |
---|
|
peerConnection.ontrack = ({transceiver}) => {
let rParticipant;
console.log("Attach remote track " + transceiver.receiver.track.id + " kind " + transceiver.receiver.track.kind + " mid " + transceiver.mid);
for (const [nickName, participant] of Object.entries(remoteParticipants)) {
for (const pTrack of participant.tracks) {
console.log("Participant " + participant.nickName + " track " + pTrack.id + " mid " + pTrack.mid);
if (pTrack.mid === transceiver.mid) {
rParticipant = participant;
break;
}
}
if (rParticipant) {
break;
}
}
if (rParticipant) {
for (const display of rParticipant.displays) {
if (transceiver.receiver.track.kind === "video") {
if (display.videoMid === transceiver.mid) {
let stream = new MediaStream();
stream.addTrack(transceiver.receiver.track);
display.setVideoABRTrack(transceiver.receiver.track);
display.setVideo(stream);
break;
}
} else if (transceiver.receiver.track.kind === "audio") {
if (display.audioMid === transceiver.mid) {
let stream = new MediaStream();
stream.addTrack(transceiver.receiver.track);
display.setAudio(stream);
break;
}
}
}
} else {
console.warn("Failed to find participant for track " + transceiver.receiver.track.id);
}
} |
5. Остановка воспроизведения
stop() code
Code Block |
---|
|
const stop = function() {
for (const [nickName, participant] of Object.entries(remoteParticipants)) {
participant.displays.forEach(function(display){
display.dispose();
});
delete remoteParticipants[nickName];
}
} |