Versions Compared

Key

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

...

1. Функция-обертка

initLocalDisplay() code

Обертка для кода отображения локального медиа

Code Block
languagejs
themeRDark
const initLocalDisplay = function(localDisplayElement){

2. Локальные переменные

code

Объявление локальных переменных

...

3. Удаление элемента локального отображения

removeLocalDisplay() code

Удаление локального элемента отображения при завершении публикации дорожки

...

4. Поиск видео элемента без аудио

getAudioContainer() code

Поиск видео элемента без аудио дорожки

...

5. Добавление дорожки для локального отображения

add() code

Функция добавляет дорожку для локального отображения

...

Проверка, какая дорожка добавляется. Если аудио, поиск ближайшего видео элемента без аудио, и добавление аудио дорожки к нему

code

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 = "Audio state: " + stream.getAudioTracks()[0].enabled;
        track.addEventListener("ended", function() {
            videoElement.video.srcObject.removeTrack(track);
            videoElement.audioStateDisplay.innerHTML = "Audio state: " + false;
            //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;
    }
}

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

code

Code Block
languagejs
themeRDark
const coreDisplay = document.createElement('div');
coreDisplay.setAttribute("style","width:200px; height:auto; border: solid; border-width: 1px");
coreDisplay.id = stream.id;

Создание контейнера для отображения имени

code

Code Block
languagejs
themeRDark
const streamNameDisplay = document.createElement("div");
streamNameDisplay.innerHTML = "Name: " + name;
streamNameDisplay.setAttribute("style","width:auto; height:30px");
coreDisplay.appendChild(streamNameDisplay);

Создание элемента для отображения состояния аудио. Подписка на событие "click" для отключения/включения аудио.

code

Code Block
languagejs
themeRDark
const audioStateDisplay = document.createElement("button");
audioStateDisplay.setAttribute("style","width:auto; height:30px");
audioStateDisplay.innerHTML = "Audio state: " + (stream.getAudioTracks().length > 0 ? stream.getAudioTracks()[0].enabled : false);
audioStateDisplay.addEventListener('click', function(){
    if (stream.getAudioTracks().length > 0) {
        stream.getAudioTracks()[0].enabled = !(stream.getAudioTracks()[0].enabled);
        audioStateDisplay.innerHTML = "Audio state: " + stream.getAudioTracks()[0].enabled;
    }
});
coreDisplay.appendChild(audioStateDisplay);

Создание контейнера для отображения видео потока

code

Code Block
languagejs
themeRDark
const streamDisplay = document.createElement('div');
streamDisplay.id = id;
streamDisplay.setAttribute("style","width:auto; height:auto");
coreDisplay.appendChild(streamDisplay);

Создание видео элемента и добавление его в контейнер

code

Code Block
languagejs
themeRDark
const video = document.createElement("video");
streamDisplay.appendChild(video);
video.srcObject = stream;
video.muted = true;
video.onloadedmetadata = function (e) {
    video.play();
};

Подписка на событие "ended" для дорожки. Если дорожка завершилась, и не осталось ни одной активной дорожки, удаление контейнера

code

Code Block
languagejs
themeRDark
stream.getTracks().forEach(function(track){
    track.addEventListener("ended", function() {
        video.srcObject.removeTrack(track);
        //check video element has no tracks left
        for (const [key, vTrack] of Object.entries(video.srcObject.getTracks())) {
            if (vTrack.readyState !== "ended") {
                return;
            }
        }
        removeLocalDisplay(id);
    });
});

Подписка на событие 'resize', чтобы масштабировать видео под размеры контейнера.

code

Code Block
languagejs
themeRDark
video.addEventListener('resize', function (event) {
    streamNameDisplay.innerHTML = "Name: " + name + " " + video.videoWidth + "x" + video.videoHeight;
    resizeVideo(event.target);
});

Сохранение контейнера, обновление отображения и возврат нового контейнера.

code

Code Block
languagejs
themeRDark
localDisplays[id] = coreDisplay;
reassembleLocalLayout();
return coreDisplay; 

6. Обновление локального отображения на странице

reassembleLocalLayout() code

Вспомогательная функция пересчитывает сетку локальных контейнеров и перерисовывает их на странице

...

7. Экспорт функции для использования в основном коде

code

Code Block
languagejs
themeRDark
return {
    add: add
}

...

1. Функция-обертка

initRemoteDisplay() code

Обертка для кода отображения получаемого медиа

Code Block
languagejs
themeRDark
const initRemoteDisplay = function(room, mainDiv, peerConnection) {

2. Локальные переменные

code

Объявление локальных переменных

...

3. Подписка на события комнаты

code

Подписка на необходимые события комнаты

...

Поиск участника. Если не найден, создание нового участника

code

Code Block
languagejs
themeRDark
let participant = remoteParticipants[e.info.nickName];
if (!participant) {
    participant = {};
    participant.nickName = e.info.nickName;
    participant.tracks = [];
    participant.displays = [];
    remoteParticipants[participant.nickName] = participant;
}

Добавление новых дорожек для этого участника

code

Code Block
languagejs
themeRDark
participant.tracks.push.apply(participant.tracks, e.info.info);

Создание контейнера для каждой дорожки

code

Code Block
languagejs
themeRDark
        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;
                    createDisplay = false;
                    break;
                }
            }
            if (!createDisplay) {
                continue;
            }
            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;
            }
        }
    }

...

Поиск участника. Если не найден, возврат

code

Code Block
languagejs
themeRDark
const participant = remoteParticipants[e.info.nickName];
if (!participant) {
    return;
}

Перебор дорожек участника

code

Code Block
languagejs
themeRDark
for (const rTrack of e.info.info) {

Поиск и удаление дорожки с таким же mid, какой был получен в событии

code

Code Block
languagejs
themeRDark
for (let i = 0; i < participant.tracks.length; i++) {
    if (rTrack.mid === participant.tracks[i].mid) {
        participant.tracks.splice(i, 1);
        break;
    }
}

Поиск контейнера, в котором проигрывается дорожка, и удаление дорожки. Если в контейнере не осталось дорожек, он также удаляется

code

Code Block
languagejs
themeRDark
            for (let i = 0; i < participant.displays.length; i++) {
                let found = false;
                const display = participant.displays[i];
                if (display.mids.audio.includes(audioMid === rTrack.mid)) {
         //remove from mids array
        display.mids.audio.splice(display.mids.audio.indexOf(rTrack.mid), 1);
setAudio(null);
         //stop track and remove stream
        display.audioStreams[rTrack.mid].getAudioTracks()[0].stop()found = true;
        delete display.audioStreams[rTrack.mid];
        //remove audio element
} else if (display.videoMid === rTrack.mid) {
                    display.display.removeChild(display.audioElements[rTrack.mid]setVideo(null);
        delete display.audioElements[rTrack.mid];
            found = true;
    } else if (display.mids.video === rTrack.mid) {            }
        display.mids.video = undefined;
       if display.mediaStream.getVideoTracks()[0].stop();
found) {
          found = true;
    }
    if (!display.mids.audio.length === 0hasAudio() && !display.mids.video === undefinedhasVideo()) {
         const     video = display.display.getElementsByTagName("video")[0]
        videodisplay.pausedispose();
        video.srcObject = null;
        display.display.remove();
        participant.displays.splice(i, 1);
                    }
      if (found) {
              break;
                }
            }

SFU_ROOM_EVENT.LEFT

Поиск и удаление участника

code

Code Block
languagejs
themeRDark
        let participant = remoteParticipants[e.name];
        if (!participant) {
            return;
        }
        participant.displays.forEach(function(display){
            display.dispose();
        })
        delete remoteParticipants[e.name];

...

Поиск участника. Если не найден, возврат

code

Code Block
languagejs
themeRDark
console.log("Received track quality state");
const participant = remoteParticipants[e.info.nickName];
if (!participant) {
    return;
}

Перебор дорожек участника

code

Code Block
languagejs
themeRDark
for (const rTrack of e.info.tracks) {

Поиск соответствующего контейнера и обновление качества

code

Code Block
languagejs
themeRDark
            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;
                }
            }

4. Создание контейнера для отображения принимаемых потоков

createRemoteDisplay() code

Вспомогательная функция создает контейнер на основе данных о потоке и дорожках

...

Удаление контейнера

dispose() code

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

Скрытие контейнера

hide() code

Code Block
languagejs
themeRDark
            hide: function(value) {
                if (value) {
                    cell.style.display = "none";
                } else {
                    cell.style.display = "block";
                }
            }

Создание аудио элемента

setAudio() code

Code Block
languagejs
themeRDark
            setAudio: function(stream) {
                if (audio) {
                    audio.remove();
                }
                if (!stream) {
                    audio = null;
                    this.audioMid = undefined;
                    return;
                }
                audio = document.createElement("audio");
                audio.controls = "controls";
                cell.appendChild(audio);
                audio.srcObject = stream;
                audio.play();
            }

Создание видео элемента

setVideo() code

Code Block
languagejs
themeRDark
            setVideo: function(stream) {
                if (video) {
                    video.remove();
                }

                if (stream == null) {
                    video = null;
                    this.videoMid = undefined;
                    qualityDivs.forEach(function(div) {
                        div.remove();
                    });
                    qualityDivs = [];
                    tidDivs.forEach(function(div) {
                        div.remove();
                    });
                    tidDivs = [];
                    return;
                }
                video = document.createElement("video");
                streamDisplay.appendChild(video);
                video.srcObject = stream;
                video.onloadedmetadata = function (e) {
                    video.play();
                };
                video.addEventListener("resize", function (event) {
                    streamNameDisplay.innerHTML = "Name: " + name + " " + video.videoWidth + "x" + video.videoHeight;
                    resizeVideo(event.target);
                });
            }

Отображение информации о качестве

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);
                    }
                    for (let i = 0; i < 3; i++) {
                        const tidDiv = document.createElement("button");
                        tidDivs.push(tidDiv);
                        tidDiv.innerText = "TID"+i;
                        tidDiv.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
                        tidDiv.style.color = "gray";
                        tidDiv.addEventListener('click', function(){
                            console.log("Clicked on TID " + i + " trackId " + trackInfo.id);
                            for (let c = 0; c < tidDivs.length; c++) {
                                tidDivs[c].style.color = "gray";
                            }
                            tidDiv.style.color = "blue";
                            room.changeQuality(trackInfo.id, null, i);
                        });
                        tidDisplay.appendChild(tidDiv);
                    }
                }
            }

Обновление качетсва

updateQualityInfo() code

Code Block
languagejs
themeRDark
            updateQualityInfo: function(videoQuality) {
                for (const qualityInfo of videoQuality) {
                    for (const qualityDiv of qualityDivs) {
                        if (qualityDiv.innerText === qualityInfo.quality){
                            if (qualityInfo.available === true) {
                                qualityDiv.style.color = "gray";
                            } else {
                                qualityDiv.style.color = "red";
                            }
                            break;
                        }
                    }
                }
            }

5. Работа с PeerConnection

code

Подписка на событие PeerConnection "ontrack".

...

Поиск участника на основе mid дорожки

code

Code Block
languagejs
themeRDark
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;
    }
}

Поиск соответствующего контейнера и добавление к нему дорожки

code

Code Block
languagejs
themeRDark
            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.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;
                    }
                }
            }