Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Для анализа исходного кода возьмем версию модуля display.js, которая находится здесь

Захват и отображение локального видео

1. Инициализация

initLocalDisplay() code

Функция initLocalDisplay() возвращает объект для работы с HTML5 элементами захвата и отображения локального видео

...

2.1. Добавления аудио дорожки к HTML5 элементу

add() code

Здесь:

  • добавляется аудио дорожка к video элементу
  • создается обработчик события onended для аудио дорожкивидео дорожки
  • добавляется обработчик нажатия кнопки включения/отключения аудио
Code Block
languagejs
themeRDark
        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
languagejs
themeRDark
        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
languagejs
themeRDark
        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
languagejs
themeRDark
        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
languagejs
themeRDark
        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
languagejs
themeRDark
        localDisplays[id] = coreDisplay;
        localDisplayDiv.appendChild(coreDisplay);
        return coreDisplay;

3. Остановка захвата видео и аудио

stop() code

Code Block
languagejs
themeRDark
    const stop = function () {
        for (const [key, value] of Object.entries(localDisplays)) {
            removeLocalDisplay(value.id);
        }
    }

Отображение потоков, опубликованных в комнате

1. Инициализация

initRemoteDisplay() code

Функция initLocalDisplay() возвращает объект для работы с HTML5 элементами отображения видео и аудио потоков, опубликованных в комнате

Code Block
languagejs
themeRDark
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
languagejs
themeRDark
    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
languagejs
themeRDark
    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
languagejs
themeRDark
        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
languagejs
themeRDark
         localDisplays[id] = coreDisplay;
        localDisplayDivdisplay.appendChilddispose(coreDisplay);
              return coreDisplay;

3. Остановка захвата видео и аудио

stop() code

Code Block
languagejs
themeRDark
      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
languagejs
themeRDark
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
languagejs
themeRDark
    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
languagejs
themeRDark
    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
languagejs
themeRDark
        }).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
languagejs
themeRDark
        const abr =       break;ABR(abrQualityCheckPeriod, [
            {parameter: "nackCount", maxLeap:  10},
            }
        }
    });

2.3. LEFT

initRemoteDisplay() code

Здесь:

  • участник удаляется из списка
  • удаляются элементы, в которых проигрывались потоки
Code Block
languagejs
themeRDark
    }).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
languagejs
themeRDark
            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
languagejs
themeRDark
   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
languagejs
themeRDark
= 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
languagejs
themeRDark
            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
languagejs
themeRDark
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
languagejs
themeRDark
themeRDark
            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
languagejs
themeRDark
            setVideoABRTrack:      });function(track) {
                }abr.setTrack(track);
            },

3.

...

7. Настройка

...

setResizeHandler(), setEventHandlers(), setWebkitEventHandlers() code

переключения качества

setTrackInfo() code

Здесь:

  • настраиваются кнопки переключения качества
  • информация о заявленных качествах публикации добавляется в ABR
  • скрываются или отображаются элементы для показа текущего качества и источника видео/аудио
Code Block
languagejs
themeRDark
            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
languagejs
themeRDark
     } 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
languagejs
themeRDark
            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
languagejs
themeRDark
            dispose: function() {
                abr.stop();
                cell.remove();
            },

4. Подписка на добавление дорожек в WebRTC соединение

PeerConnection.ontrack(), setAudio(), setVideo(), setVideoABRTrack() code

Здесь:

  • при получении видео или аудио потока, добавляется элемент для его проигрывания
Code Block
languagejs
themeRDark
    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
languagejs
themeRDark
    const stop = function() {
        for (const [nickName, participant] of Object.entries(remoteParticipants)) {
            participant.displays.forEach(function(display){
                display.dispose();
            });
            delete remoteParticipants[nickName];
        }
    }