...
Для анализа исходного кода возьмем версию модуля display.js, которая находится здесь
Захват и отображение локального видео
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; 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
Здесь:
- создается контейнер для элементов отображения локального видео
- создается элемент для отображения информации о публикуемом видео
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); |
...
type); |
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; stream.getAudioTracks()[0].enabledconst video = !(streamdocument.getAudioTrackscreateElement()[0].enabled)"video"); video.muted = true; audioStateDisplay.innerHTML = audioStateText(streamif(Browser().isSafariWebRTC()) { video.setAttribute("playsinline", ""); }video.setAttribute("webkit-playsinline", ""); }); coreDisplaystreamDisplay.appendChild(audioStateDisplay)video); video.srcObject = stream; |
2.
...
5. Создание
...
обработчиков событий video элемента
add() code
Здесь:
- создается элемент-контейнер, размеры которого можно менять в зависимости от размеров родительского элемента
- создается HTML5
video
элемент, с учетом публикации в Safariзапускается проигрывание локального видео - настраивается обработчик события
onended
для видео дорожки - настраивается обработчик события
onresize
для локального видео, в котором размеры видео меняются под размеры контейнера
Code Block | ||||
---|---|---|---|---|
| ||||
constvideo.onloadedmetadata streamDisplay= =function document.createElement('div'); (e) { streamDisplay.id = "stream-" + id video.play(); }; streamDisplay.setAttribute("class","text-center"); stream.getTracks().forEach(function(track){ streamDisplay.setAttribute track.addEventListener("styleended","width: auto; height: auto;"); function() { coreDisplayvideo.srcObject.appendChildremoveTrack(streamDisplaytrack); const video = document.createElement("video"); //check video element has video.mutedno =tracks true;left if(Browser().isSafariWebRTC()) { for (const [key, vTrack] of Object.entries(video.setAttribute("playsinline", "");srcObject.getTracks())) { video.setAttribute("webkit-playsinline", "") if (vTrack.readyState !== "ended") { return; } } streamDisplay.appendChild(video); removeLocalDisplay(id); }); }); if (stream.getVideoTracks().length > 0) { // Resize only if video displayed video.addEventListener('resize', function (event) { publisherNameDisplay.innerHTML = name + " " + type + " " + video.videoWidth + "x" + video.videoHeight; resizeVideo(event.target); }); } else { // Hide audio only container hideItem(streamDisplay); // Set up mute button for audio only stream audioStateDisplay.innerHTML = audioStateText(stream) + " " + type; audioStateDisplay.addEventListener("click", function() { onMuteClick(audioStateDisplay, stream, type); }); } |
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
Функция initLocalDisplay() возвращает объект для работы с HTML5 элементами отображения видео и аудио потоков, опубликованных в комнате
Code Block | ||||
---|---|---|---|---|
| ||||
const initRemoteDisplay = function(options) {
const constants = SFU.constants;
const remoteParticipants = {};
// Validate options first
if (!options.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}) => {
...
}
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) { video.srcObject = stream; |
2.5. Создание обработчиков событий video элемента
add() code
Здесь:
- запускается проигрывание локального видео
- настраивается обработчик события
onended
для видео дорожки - настраивается обработчик события
onresize
для локального видео, в котором размеры видео меняются под размеры контейнера
Code Block | ||||
---|---|---|---|---|
| ||||
video.onloadedmetadata = function (e) { participant.tracks.splice(i, 1); video.play(); }break; stream.getTracks().forEach(function(track){ } track.addEventListener("ended", function() {} for (let i video.srcObject.removeTrack(track); = 0; i < participant.displays.length; i++) { //check video elementlet hasfound no tracks left = false; for (const [key, vTrack] of Object.entries(video.srcObject.getTracks())) {display = participant.displays[i]; if (vTrackdisplay.readyStateaudioMid !=== "ended"rTrack.mid) { display.setAudio(null); return; found = true; } } else }if (display.videoMid === rTrack.mid) { removeLocalDisplay(id display.setVideo(null); }); found = })true; video.addEventListener('resize', function (event) { } streamNameDisplay.innerHTML = "Name: " +if name + "<br/>Max.resolution: " + video.videoWidth + "x" + video.videoHeight; (found) { if resizeVideo(event!display.targethasAudio(); && !display.hasVideo()) { }); |
2.6. Добавление видео контейнера в элемент HTML страницы
add() code
Code Block | ||||
---|---|---|---|---|
| ||||
localDisplays[id] = coreDisplay; localDisplayDivdisplay.appendChilddispose(coreDisplay); return coreDisplay; |
3. Остановка захвата видео и аудио
stop() code
Code Block | ||||
---|---|---|---|---|
| ||||
const stop = function participant.displays.splice() {i, 1); for (const [key, value] of Object.entries(localDisplays)) { } removeLocalDisplay(value.id); } } |
Отображение потоков, опубликованных в комнате
1. Инициализация
initRemoteDisplay() code
Функция initLocalDisplay() возвращает объект для работы с HTML5 элементами отображения видео и аудио потоков, опубликованных в комнате
Code Block | ||||
---|---|---|---|---|
| ||||
const initRemoteDisplay = function(options) { break; const constants = SFU.constants; const remoteParticipants = {}; // Validate options first if (!options.div) { } throw} new Error("Main div to place all the media... tag is not defined"); } if (!options.room}); |
2.3. LEFT
initRemoteDisplay() code
Здесь:
- участник удаляется из списка
- удаляются элементы, в которых проигрывались потоки
Code Block | ||||
---|---|---|---|---|
| ||||
room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e) { throw new Error("Room is not defined");... } if (!options.peerConnection).on(constants.SFU_ROOM_EVENT.LEFT, function(e) { throw new Errorconsole.log("PeerConnection is not definedReceived LEFT"); } ... let participant = remoteParticipants[e.name]; const createRemoteDisplay = function(id, name, mainDiv if (!participant) { ... } return; const stop = function() {} participant...displays.forEach(function(display){ } peerConnection.ontrack = display.dispose({transceiver}) => {); ...}) } return {delete remoteParticipants[e.name]; stop: stop... } }); |
2.
...
4. TRACK_QUALITY_STATE
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; }).on(constants.SFU_ROOM_EVENT.TRACK_QUALITY_STATE, function(e){ console.log("Received track participant.tracks = []quality state"); const participant.displays = remoteParticipants[e.info.nickName]; if (!participant) { remoteParticipants[participant.nickName] = participant; }return; participant.tracks.push.apply(participant.tracks, e.info.info);} for (const pTrackrTrack of e.info.infotracks) { ... const mid = rTrack.mid; let display = createRemoteDisplay(participant.nickName, participant.nickName, mainDiv); for (let i = 0; i < participant.displays.push(display);length; i++) { if (pTrack.type const display === "VIDEO") { participant.displays[i]; if (display.videoMid === pTrack.mid;) { display.setTrackInfoupdateQualityInfo(pTrackrTrack.quality); } else if (pTrack.type === "AUDIO") { break; display.audioMid = pTrack.mid; } } } ...} }); |
2.2. REMOVE_TRACKS
...
3. Создание элементов для отображения потоков участников
3.1. Создание контейнера
createRemoteDisplay() code
Здесь:
...
- настраиваются параметры отображения
- создается контейнер для потоков участника
- создается контейнер для конкретного потока
- создается контейнер для кнопок переключения качества
Code Block | ||||
---|---|---|---|---|
| ||||
}).on(constants.SFU_ROOM_EVENT.REMOVE_TRACKS, function(e) {const cell = document.createElement("div"); consolecell.logsetAttribute("Received REMOVE_TRACKSclass", "text-center"); const participantcell.id = remoteParticipants[e.info.nickName]id; if (!participant) {mainDiv.appendChild(cell); let publisherNameDisplay; return; let currentQualityDisplay; } let videoTypeDisplay; for (const rTrack of e.info.info) { let abrQualityCheckPeriod = ABR_QUALITY_CHECK_PERIOD; for (let iabrKeepOnGoodQuality = 0; i < participant.tracks.length; i++) { ABR_KEEP_ON_QUALITY; let abrTryForUpperQuality = ABR_TRY_UPPER_QUALITY; if (rTrackdisplayOptions.midabrQualityCheckPeriod !=== participant.tracks[i].mid undefined) { abrQualityCheckPeriod = displayOptions.abrQualityCheckPeriod; participant.tracks.splice(i, 1); } if (displayOptions.abrKeepOnGoodQuality !== undefined) { break abrKeepOnGoodQuality = displayOptions.abrKeepOnGoodQuality; } }if (displayOptions.abrTryForUpperQuality !== undefined) { } abrTryForUpperQuality = displayOptions.abrTryForUpperQuality; } for (let i = 0; i < participant.displays.length; i++if (!displayOptions.abr) { abrQualityCheckPeriod = 0; let found = false; abrKeepOnGoodQuality = 0; const displayabrTryForUpperQuality = participant.displays[i]0; } if (display.audioMid === rTrack.middisplayOptions.publisher) { publisherNameDisplay = createInfoDisplay(cell, "Published by: " display.setAudio(null+ name); } if (displayOptions.quality) { found = true; currentQualityDisplay = createInfoDisplay(cell, ""); } else if (display.videoMid === rTrack.mid if (displayOptions.type) { videoTypeDisplay = createInfoDisplay(cell, ""); display.setVideo(null);} const qualitySwitchDisplay = createInfoDisplay(cell, ""); foundlet qualityDivs = true[]; let contentType = ""; } const rootDisplay = createContainer(cell); const streamDisplay if= createContainer(foundrootDisplay) {; const audioDisplay = createContainer(rootDisplay); const if (!display.hasAudio() && !display.hasVideo()) {audioTypeDisplay = createInfoDisplay(audioDisplay); const audioTrackDisplay = createContainer(audioDisplay); const audioStateButton = display.disposeAudioStateButton(); hideItem(streamDisplay); hideItem(audioDisplay); participant.displays.splice(i, 1hideItem(publisherNameDisplay); hideItem(currentQualityDisplay); hideItem(videoTypeDisplay); } hideItem(qualitySwitchDisplay); |
3.2. Инициализация ABR
createRemoteDisplay() code
Здесь задаются параметры качества получения потока от сервера, по которым отображение будет переключаться на более высокое или более низкое качество
Code Block | ||||
---|---|---|---|---|
| ||||
const abr = break;ABR(abrQualityCheckPeriod, [ {parameter: "nackCount", maxLeap: 10}, } } }); |
2.3. LEFT
initRemoteDisplay() code
Здесь:
- участник удаляется из списка
- удаляются элементы, в которых проигрывались потоки
Code Block | ||||
---|---|---|---|---|
| ||||
}).on(constants.SFU_ROOM_EVENT.LEFT, function(e) { {parameter: "freezeCount", maxLeap: 10}, console.log("Received LEFT"); {parameter: "packetsLost", maxLeap: 10} let participant = remoteParticipants[e.name]; ], abrKeepOnGoodQuality, abrTryForUpperQuality); |
3.3. Добавление видео элемента
setVideo() code
Code Block | ||||
---|---|---|---|---|
| ||||
ifsetVideo: function(!participantstream) { return; if (video) }{ participant.displays.forEach(function(display){ displayvideo.disposeremove(); }) delete remoteParticipants[e.name]; }); |
2.4. TRACK_QUALITY_STATE
initRemoteDisplay() code
Здесь:
- обновляется информация о качествах потоков
Code Block | ||||
---|---|---|---|---|
| ||||
if }).on(constants.SFU_ROOM_EVENT.TRACK_QUALITY_STATE, function(e)(stream == null) { console.log("Received track quality state"); const participantvideo = remoteParticipants[e.info.nickName]null; if (!participant) { this.videoMid = returnundefined; } for (const rTrack of e.info.tracksqualityDivs.forEach(function(div) { const mid = rTrack.mid div.remove(); for (let i = 0; i < participant.displays.length; i++) { }); const displayqualityDivs = participant.displays[i]; return; if (display.videoMid === mid) { } display.updateQualityInfo(rTrack.qualityshowItem(streamDisplay); video break= document.createElement("video"); } video.controls = "controls"; } video.muted } }); |
3. Создание элементов для отображения потоков участников
3.1. Создание контейнера
createRemoteDisplay() code
Здесь:
- создается контейнер для потоков участника
- создается контейнер для конкретного потока
- создается контейнер для кнопок переключения качества
Code Block | ||||
---|---|---|---|---|
| ||||
= true; const cellvideo.autoplay = document.createElement("div")true; cell.setAttribute("class", "text-center"); if (Browser().isSafariWebRTC()) { cell.id = id; mainDivvideo.appendChild(cellsetAttribute("playsinline", ""); let publisherNameDisplay; video.setAttribute("webkit-playsinline", ""); let currentQualityDisplay; if (displayOptions.publisher) {this.setWebkitEventHandlers(video); publisherNameDisplay = document.createElement("div"); } else { publisherNameDisplay.innerHTML = "Published by: " + name; this.setEventHandlers(video); publisherNameDisplay.setAttribute("style","width:auto; height:30px;"); } publisherNameDisplay.setAttribute("class","text-center"); cellstreamDisplay.appendChild(publisherNameDisplayvideo); } if (displayOptions.quality) { video.srcObject = stream; currentQualityDisplay = documentthis.createElementsetResizeHandler("div"video); currentQualityDisplay.innerHTML = "" abr.start(); currentQualityDisplay.setAttribute("style","width:auto; height:30px;"); }, |
3.4. Добавление аудио элемента
setAudio() code
Code Block | ||||
---|---|---|---|---|
| ||||
setAudio: currentQualityDisplay.setAttribute("class","text-center");function(stream) { cell.appendChild(currentQualityDisplay); if (audio) { } const qualitySwitchDisplay = documentaudio.createElementremove("div"); qualitySwitchDisplay.setAttribute("style","width:auto; height:30px;"); } qualitySwitchDisplay.setAttribute("class","text-center"); cell.appendChild(qualitySwitchDisplay); if (!stream) { let qualityDivs = []; const rootDisplayaudio = document.createElement("div")null; rootDisplay.setAttribute("style","width:auto; height:auto;"); rootDisplay.setAttribute("class","text-center"); this.audioMid = undefined; cell.appendChild(rootDisplay); const streamDisplay = document.createElement("div") return; streamDisplay.setAttribute("style","width:auto; height:auto;"); } streamDisplay.setAttribute("class","text-center"); rootDisplay.appendChildshowItem(streamDisplay); |
3.2. Добавление видео элемента
setVideo() code
Code Block | ||||
---|---|---|---|---|
| ||||
audioDisplay); audio setVideo: function(stream) {= document.createElement("audio"); if (video) {audio.controls = "controls"; audio.muted = true; video.remove(); audio.autoplay = } true; if (stream == null(Browser().isSafariWebRTC()) { video = nullaudio.setAttribute("playsinline", ""); this.videoMid = undefinedaudio.setAttribute("webkit-playsinline", ""); qualityDivsthis.forEachsetWebkitEventHandlers(function(div) {audio); } div.remove();else { }this.setEventHandlers(audio); qualityDivs = [];} returnaudioTrackDisplay.appendChild(audio); }audioStateButton.makeButton(audioTypeDisplay, audio); videoaudio.srcObject = document.createElement("video")stream; videoaudio.controlsonloadedmetadata = "controls"; function (e) { video.muted = true; audio.play().then(function() { video.autoplay = true; if (Browser().isSafariWebRTC() && Browser().isiOS()) { videoconsole.setAttribute("playsinline", "warn("Audio track should be manually unmuted in iOS Safari"); video.setAttribute("webkit-playsinline", ""); } else { this.setWebkitEventHandlers(video); audio.muted } else {= false; this.setEventHandlers(video); audioStateButton.setButtonState(); } streamDisplay.appendChild(video); } video.srcObject = stream}); this.setResizeHandler(video)}; }, |
3.
...
5. Настройка обработчиков событий для аудио и видео элементов
setResizeHandler(), setEventHandlers(), setWebkitEventHandlers() code
Code Block | |||||||
---|---|---|---|---|---|---|---|
| |||||||
setEventHandlers: function(video) { // Ignore play/pause button setAudio:video.addEventListener("pause", function (stream) { if (audio) {console.log("Media paused by click, continue..."); audiovideo.removeplay(); }); }, ifsetWebkitEventHandlers: function(!streamvideo) { let needRestart audio = nullfalse; let this.audioMid isFullscreen = undefinedfalse; // Use webkitbeginfullscreen event return; }to detect full screen mode in iOS Safari audio = document.createElementvideo.addEventListener("audiowebkitbeginfullscreen"); , function () { audio.controls = "controls"; audio.muted isFullscreen = true; audio.autoplay = true; }); if (Browser().isSafariWebRTC()) { audio.setAttributevideo.addEventListener("playsinlinepause", function ""(); { if audio.setAttribute("webkit-playsinline", ""); (needRestart) { this console.setWebkitEventHandlers(audio); log("Media paused after fullscreen, continue..."); } else { video.play(); this.setEventHandlers(audio); needRestart = }false; cell.appendChild(audio); } else { audio.srcObject = stream; console.log("Media paused by click, audio.onloadedmetadata = function (e) { continue..."); audiovideo.play().then(function() { ; } if (Browser().isSafariWebRTC() && Browser().isiOS()) { }); consolevideo.warnaddEventListener("Audio track should be manually unmuted in iOS Safari"); webkitendfullscreen", function () { video.play(); } else { needRestart = true; audio.mutedisFullscreen = false; }); } }, |
3.6. Добавление информации о дорожке в ABR
setVideoABRTrack() code
Code Block | ||||
---|---|---|---|---|
| ||||
setVideoABRTrack: });function(track) { }abr.setTrack(track); }, |
3.
...
7. Настройка
...
setResizeHandler(), setEventHandlers(), setWebkitEventHandlers() code
переключения качества
setTrackInfo() code
Здесь:
- настраиваются кнопки переключения качества
- информация о заявленных качествах публикации добавляется в ABR
- скрываются или отображаются элементы для показа текущего качества и источника видео/аудио
Code Block | ||||
---|---|---|---|---|
| ||||
setResizeHandler: function(video) { video.addEventListener("resize", function (event setTrackInfo: function(trackInfo) { if (displayOptions.publishertrackInfo) { if (trackInfo.quality) { publisherNameDisplay.innerHTML = "Published by: " + name; showItem(qualitySwitchDisplay); } if (displayOptionsabr.qualityisEnabled()) { currentQualityDisplay.innerHTML = video.videoWidth + "x" + video.videoHeight const autoDiv = createQualityButton("Auto", qualityDivs, qualitySwitchDisplay); } autoDiv.style.color resizeVideo(event.target)= QUALITY_COLORS.SELECTED; }); },autoDiv.addEventListener('click', function() { setEventHandlers: function(video) { // Ignore play/pause button setQualityButtonsColor(qualityDivs); video.addEventListener("pause", function () { autoDiv.style.color = QUALITY_COLORS.SELECTED; console.log("Media paused by click, continue..."); videoabr.playsetAuto(); }); },); setWebkitEventHandlers: function(video) { } let needRestart = false; for (let isFullscreeni = false0; i < trackInfo.quality.length; i++) { // Use webkitbeginfullscreen event to detect full screen mode in iOS Safari abr.addQuality(trackInfo.quality[i]); video.addEventListener("webkitbeginfullscreen", function () { const qualityDiv = createQualityButton(trackInfo.quality[i], isFullscreen = truequalityDivs, qualitySwitchDisplay); }); qualityDiv.addEventListener('click', function() { video.addEventListener("pause", function () { console.log("Clicked on quality " + trackInfo.quality[i] + " trackId " if (needRestart) { + trackInfo.id); console.log("Media paused after fullscreen,if continue(qualityDiv.style..");color === QUALITY_COLORS.UNAVAILABLE) { video.play(); return; needRestart = false; } } else { console.log("Media paused by click, continue..." setQualityButtonsColor(qualityDivs); video.play(); qualityDiv.style.color = QUALITY_COLORS.SELECTED; } }); abr.setManual(); video.addEventListener("webkitendfullscreen", function () { videoabr.play(setQuality(trackInfo.quality[i]); needRestart = true; }); isFullscreen = false; } }); }, |
3.5. Настройка переключения качества
setTrackInfo() code
Code Block | ||||
---|---|---|---|---|
| ||||
} else { setTrackInfo: function(trackInfo) { if (trackInfo && trackInfo.quality) { hideItem(qualitySwitchDisplay); } for (let i = 0; i <if (trackInfo.quality.length; i++type) { const qualityDivcontentType = document.createElement("button"); trackInfo.contentType || ""; if (trackInfo.type == "VIDEO" && displayOptions.type && contentType !== "") { qualityDivs.push(qualityDiv); 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 " + trackInfo.id audioStateButton.setContentType(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]; } } |