Versions Compared

Key

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

...

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

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

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

initLocalDisplay() code

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

...

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

add() code

Здесь:

  • добавляется аудио дорожка к video элементу
  • создается обработчик события onended для аудио дорожки

...

2.2. Создание контейнера для отображения локального видео

add() code

Здесь:

  • создается контейнер для элементов отображения локального видео
  • создается элемент для отображения информации о публикуемом видео

...

2.3. Создание кнопки для включения/отключения локального аудио

add() code

Здесь:

  • создается кнопка для включения/отключения локального аудио
  • добавляется обработчик нажатия этой кнопки

...

2.4. Создание элемента для отображения локального видео

add() code

Здесь:

  • создается элемент-контейнер, размеры которого можно менять в зависимости от размеров родительского элемента
  • создается HTML5 video  элемент, с учетом публикации в Safari

...

2.5. Создание обработчиков событий video элемента

add() code

Здесь:

  • запускается проигрывание локального видео
  • настраивается обработчик события onended  для видео дорожки
  • настраивается обработчик события onresize  для локального видео, в котором размеры видео меняются под размеры контейнера

...

2.6. Добавление видео контейнера в элемент HTML страницы

add() code

Code Block
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(mainDiv, room, peerConnectionoptions) {
    const constants = SFU.constants;
    const remoteParticipants = {};
     ...
  // Validate options first
  const createRemoteDisplay =if function(id, name, mainDiv(!options.div) {
        ...
    }

    const stop = function() {
    throw new Error("Main div to place all the media tag is not defined");
    }
    if ...(!options.room) {
    }

    peerConnection.ontrackthrow =new ({transceiver}) => {
  Error("Room is not defined");
    }
    if (!options.peerConnection) {
        throw new Error("PeerConnection is not defined");
    }
    ...
    const }

createRemoteDisplay = function(id, name, returnmainDiv) {
        stop: stop...
    }
}

2. Обработка событий комнаты

...


    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 display = createRemoteDisplay(participant.nickName, participant.nickName, mainDiv);
            participant.displays.push(display);
            if (pTrack.type === "VIDEO") {
                display.videoMid = pTrack.mid;
                display.setTrackInfo(pTrack);
            } else if (pTrack.type === "AUDIO") {
                display.audioMid = pTrack.mid;
            }
        }
        ...
    });

2.2. REMOVE_TRACKS

initRemoteDisplay() code

Здесь:

  • удаляются элементы, в которых проигрывались потоки
  • информация о потоках удаляется из списка
Code Block
languagejs
themeRDark
    }).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
languagejs
themeRDark
    }).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
languagejs
themeRDark
    room}).on(constants.SFU_ROOM_EVENT.ADDTRACK_QUALITY_TRACKSSTATE, function(e) {
        console.log("Received ADD_TRACKStrack quality state");
        letconst participant = remoteParticipants[e.info.nickName];
        if (!participant) {
participant) {
            return;
     participant  = {};

        for (const   participant.nickName =rTrack of e.info.nickName;tracks) {
            participant.tracksconst mid = []rTrack.mid;
            for (let participant.displaysi = []0;
 i           remoteParticipants[participant.nickName] = participant;< participant.displays.length; i++) {
        }
        const  participant.tracks.push.apply(participant.tracks, e.info.info)display = participant.displays[i];
        for (const pTrack of e.info.info) {
   if (display.videoMid === mid) {
     ...
            let display = createRemoteDisplaydisplay.updateQualityInfo(participant.nickName, participant.nickName, mainDivrTrack.quality);
            participant.displays.push(display);
          break;
  if (pTrack.type === "VIDEO") {
          }
      display.videoMid = pTrack.mid;
    }
        }
    display.setTrackInfo(pTrack);
});

3. Создание элементов для отображения потоков участников

3.1. Создание контейнера

createRemoteDisplay() code

Здесь:

  • создается контейнер для потоков участника
  • создается контейнер для конкретного потока
  • создается контейнер для кнопок переключения качества
Code Block
languagejs
themeRDark
        const cell = document.createElement("div");
   } else if (pTrack.type === "AUDIO") { cell.setAttribute("class", "text-center");
        cell.id = id;
      display.audioMid = pTrack.midmainDiv.appendChild(cell);
            }let publisherNameDisplay;
        }
        ...let currentQualityDisplay;
    });

2.2. REMOVE_TRACKS

initRemoteDisplay() code

Здесь:

  • удаляются элементы, в которых проигрывались потоки
  • информация о потоках удаляется из списка
Code Block
languagejs
themeRDark
    }).on(constants.SFU_ROOM_EVENT.REMOVE_TRACKS, function(eif (displayOptions.publisher) {
         console.log("Received REMOVE_TRACKS   publisherNameDisplay = document.createElement("div");
        const participant = remoteParticipants[e.info.nickName]    publisherNameDisplay.innerHTML = "Published by: " + name;
            publisherNameDisplay.setAttribute("style","width:auto; height:30px;");
        if (!participant) {    publisherNameDisplay.setAttribute("class","text-center");
            returncell.appendChild(publisherNameDisplay);
        }
        forif (const rTrack of e.info.infodisplayOptions.quality) {
            currentQualityDisplay for= (let i = 0; i < participant.tracks.length; i++) {
     document.createElement("div");
            currentQualityDisplay.innerHTML = "";
           if (rTrack.mid === participant.tracks[i].mid) {
currentQualityDisplay.setAttribute("style","width:auto; height:30px;");
            currentQualityDisplay.setAttribute("class","text-center");
            participantcell.tracks.splice(i, 1appendChild(currentQualityDisplay);
        }
        const qualitySwitchDisplay   break= document.createElement("div");
                }qualitySwitchDisplay.setAttribute("style","width:auto; height:30px;");
        qualitySwitchDisplay.setAttribute("class","text-center");
    }
    cell.appendChild(qualitySwitchDisplay);

        for (let iqualityDivs = 0; i < participant.displays.length; i++) {[];

                let foundconst rootDisplay = falsedocument.createElement("div");
        rootDisplay.setAttribute("style","width:auto; height:auto;");
       const display = participant.displays[i]rootDisplay.setAttribute("class","text-center");
        cell.appendChild(rootDisplay);
        if (display.audioMid ==const streamDisplay = rTrack.mid) {document.createElement("div");
        streamDisplay.setAttribute("style","width:auto; height:auto;");
           display.setAudio(nullstreamDisplay.setAttribute("class","text-center");
        rootDisplay.appendChild(streamDisplay);

3.2. Добавление видео элемента

setVideo() code

Code Block
languagejs
themeRDark
            setVideo:     found = true;
  function(stream) {
              } else if (display.videoMid === rTrack.midvideo) {
                    displayvideo.setVideoremove(null);
                    found = true;
                }}

                if (foundstream == null) {
                    if (!display.hasAudio() && !display.hasVideo()) {video = null;
                    this.videoMid    display.dispose()= undefined;
                    qualityDivs.forEach(function(div) {
   participant.displays.splice(i, 1);
                    }div.remove();
                    break});
                    qualityDivs = }[];
            }
        }return;
    });

2.3. LEFT

initRemoteDisplay() code

Здесь:

  • участник удаляется из списка
  • удаляются элементы, в которых проигрывались потоки
Code Block
languagejs
themeRDark
    }).on(constants.SFU_ROOM_EVENT.LEFT, function(e) {
            }
                video = consoledocument.logcreateElement("Received LEFTvideo");
           let participant = remoteParticipants[e.name];
  video.controls = "controls";
    if (!participant) {
          video.muted = returntrue;
        }
        participant.displays.forEach(function(display){video.autoplay = true;
            display.dispose();
    if (Browser().isSafariWebRTC()) {
           })
        delete remoteParticipants[e.name]video.setAttribute("playsinline", "");
    });

2.4. TRACK_QUALITY_STATE

initRemoteDisplay() code

Здесь:

  • обновляется информация о качествах потоков
Code Block
languagejs
themeRDark
    }).on(constants.SFU_ROOM_EVENT.TRACK_QUALITY_STATE, function(e){
             consolevideo.logsetAttribute("webkit-playsinline"Received track quality state, "");
        const participant = remoteParticipants[e.info.nickName]            this.setWebkitEventHandlers(video);
        if (!participant)        } else {
                    returnthis.setEventHandlers(video);
                }

            for (const rTrack of e.info.tracks) {
streamDisplay.appendChild(video);
               const midvideo.srcObject = rTrack.midstream;
            for (let i = 0; i < participant.displays.length; i++) {
 this.setResizeHandler(video);
            },

3.3. Добавление аудио элемента

setAudio() code

Code Block
languagejs
themeRDark
         const display = participant.displays[i];
   setAudio: function(stream) {
              if (display.videoMid === mid if (audio) {
                    displayaudio.updateQualityInforemove(rTrack.quality);
                }
    break;
            if (!stream) {
  }
            }
      audio = }null;
    });

3. Создание элементов для отображения потоков участников

3.1. Создание контейнера

createRemoteDisplay() code

Здесь:

  • создается контейнер для потоков участника
  • создается контейнер для конкретного потока
  • создается контейнер для кнопок переключения качества
Code Block
languagejs
themeRDark
               const cell = document.createElement("div");this.audioMid = undefined;
                    return;
                }
        cell.setAttribute("class", "text-center"        audio = document.createElement("audio");
        cell.id = id;
      audio.controls  mainDiv.appendChild(cell)= "controls";
         const streamNameDisplay = document.createElement("div");
    audio.muted = true;
  streamNameDisplay.innerHTML = "Published by: " + name;
        streamNameDisplay.setAttribute("style","width:auto; height:auto;");audio.autoplay = true;
        streamNameDisplay.setAttribute("class","text-center");
        if cell.appendChild(streamNameDisplay);(Browser().isSafariWebRTC()) {
        const  qualityDisplay = document.createElement("div");
        qualityDisplayaudio.setAttribute("styleplaysinline","width:auto; height:auto; "");
                    qualityDisplayaudio.setAttribute("classwebkit-playsinline", "text-center");
        cell.appendChild(qualityDisplay            this.setWebkitEventHandlers(audio);

               let qualityDivs} =else [];

{
                 const rootDisplay = documentthis.createElementsetEventHandlers("div"audio);
               rootDisplay.setAttribute("style","width:auto; height:auto;"); }
        rootDisplay.setAttribute("class","text-center");
        cell.appendChild(rootDisplayaudio);
              const  streamDisplayaudio.srcObject = document.createElement("div")stream;
         streamDisplay.setAttribute("style","width:auto; height:auto;");
       audio.onloadedmetadata = function (e) {
            streamDisplay.setAttribute("class","text-center");
        rootDisplayaudio.appendChildplay(streamDisplay);

3.2. Добавление видео элемента

setVideo() code

Code Block
languagejs
themeRDark
.then(function() {
                         setVideo: function(streamif (Browser().isSafariWebRTC() && Browser().isiOS()) {
                if (video) {
          console.warn("Audio track   should be manually unmuted in  video.remove(iOS Safari");
                }

        } else   {
    if (stream == null) {
                    videoaudio.muted = nullfalse;
                     this.videoMid  = undefined;}
                    qualityDivs.forEach(function(div) {
});
                };
            div.remove();
},

3.4. Настройка обработчиков событий для аудио и видео элементов

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

Code Block
languagejs
themeRDark
            setResizeHandler: function(video) {
      });
          video.addEventListener("resize", function (event) {
        qualityDivs = [];
          if (displayOptions.publisher) {
        return;
                }
publisherNameDisplay.innerHTML = "Published by: " + name;
          video = document.createElement("video");
        }
        video.muted = true;
          if (displayOptions.quality) {
    if(Browser().isSafariWebRTC()) {
                   currentQualityDisplay.innerHTML = video.setAttribute("playsinline", "");
videoWidth + "x" + video.videoHeight;
                     video.setAttribute("webkit-playsinline", "");
}
                    }resizeVideo(event.target);
                streamDisplay.appendChild(video});
            },
    video.srcObject = stream;
            setEventHandlers: function(video) {
            video.onloadedmetadata = function (e) {
     // Ignore play/pause button
                video.play().then(functionaddEventListener("pause", function () {
                    console.log("Media paused by click, video.muted = falsecontinue...");
                    }video.play();
                });
            },
            video.addEventListener("resize",setWebkitEventHandlers: function (eventvideo) {
                let needRestart = false;
          streamNameDisplay.innerHTML = "Published by: " + namelet + "<br/>Current resolution: " + video.videoWidth + "x" + video.videoHeight;
isFullscreen = false;
                // Use webkitbeginfullscreen event to detect full screen mode in  resizeVideo(event.target);iOS Safari
                });
   video.addEventListener("webkitbeginfullscreen", function () {
         },

3.3. Добавление аудио элемента

setAudio() code

Code Block
languagejs
themeRDark
            setAudio: function(stream) {
isFullscreen = true;
                }); if (audio) {
             
       audio.remove();
         video.addEventListener("pause", function () {
    }
                if (!streamneedRestart) {
                    audio = null;
  console.log("Media paused after fullscreen, continue...");
              this.audioMid = undefined;
        video.play();
            return;
            needRestart = false;
  }
                audio = document.createElement("audio");
   } else {
              audio.controls = "controls";
        console.log("Media paused by click, continue...");
    audio.muted = true;
                audio.autoplay = truevideo.play();
                cell.appendChild(audio);
    }
              audio.srcObject = stream});
                audio.onloadedmetadata =video.addEventListener("webkitendfullscreen", function (e) {
                    audiovideo.play().then(function() {;
                    needRestart    audio.muted = falsetrue;
                    })isFullscreen = false;
                });
            },

3.

...

5. Настройка переключения качества

setTrackInfo() code

Code Block
languagejs
themeRDark
            setTrackInfo: function(trackInfo) {
                if (trackInfo && trackInfo.quality) {
                    for (let i = 0; i < trackInfo.quality.length; i++) {
                        const qualityDiv = document.createElement("button");
                        qualityDivs.push(qualityDiv);
                        qualityDiv.innerText = trackInfo.quality[i];
                        qualityDiv.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
                        qualityDiv.style.color = "red";
                        qualityDiv.addEventListener('click', function(){
                            console.log("Clicked on quality " + trackInfo.quality[i] + " trackId " + trackInfo.id);
                            if (qualityDiv.style.color === "red") {
                                return;
                            }
                            for (let c = 0; c < qualityDivs.length; c++) {
                                if (qualityDivs[c].style.color !== "red") {
                                    qualityDivs[c].style.color = "gray";
                                }
                            }
                            qualityDiv.style.color = "blue";
                            room.changeQuality(trackInfo.id, trackInfo.quality[i]);
                        });
                        qualityDisplay.appendChild(qualityDiv);
                    }
                }
            },

3.

...

6. Удаление контейнера с видео и аудио

dispose() code

Code Block
languagejs
themeRDark
            dispose: function() {
                cell.remove();
            },

...

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

Здесь:

  • при получении видео или аудио потока, добавляется элемент для его проигрывания

...

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];
        }
    }