Versions Compared

Key

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

...

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

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

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

initLocalDisplay() code

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

Code Block
languagejs
themeRDark
const initLocalDisplay = function (localDisplayElement) {
    const localDisplayDiv = localDisplayElement;
    const localDisplays = {};

    const removeLocalDisplay = function (id) {
        ...
    }

    const getAudioContainer = function () {
        ...
    }

    const onMuteClick = function (button, stream, type) {
        ...
    };

    const add = function (id, name, stream, type) {
        ...
    }

    const stop = function () {
        ...
    }

    const audioStateText = function (stream) {
        ...
    }

    return {
        add: add,
        stop: stop
    }
}

...

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

Здесь:

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

...

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

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

Code Block
languagejs
themeRDark
const initRemoteDisplay = function(options) {
/*
display options:
autoAbr     - choose abr by default
quality     const- constantsshow = SFU.constants;
    const remoteParticipants = {};quality buttons
showAudio   - show audio elements
 */
const initRemoteDisplay = function (room, div, displayOptions, abrOptions, meetingController, meetingModel, meetingView, participantFactory) {
    // 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.roomconst dOptions = displayOptions || {quality: true, type: true, showAudio: false};
    let abrFactory;
    if (abrOptions) {
        abrFactory = abrManagerFactory(room, abrOptions);
    }
    participantFactory.abrFactory = abrFactory;
    let peerConnectionparticipantFactory.displayOptions = options.peerConnectiondOptions;
    let displayOptions = options.displayOptions || {publisher: true, quality: true, type: true};
    ...return meetingController(room, meetingModel(meetingView(div), participantFactory));
}

2. Создание фабрики объектов для управления проигрыванием WebRTC ABR потока

abrManagerFactory() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
    const createRemoteDisplay = function(id, name, mainDiv, displayOptions createAbrManager: function () {
            ...
    }

     const stop = function() {return abr;
        ...}
    }

    peerConnection.ontrack = ({transceiver}) => {
        ...
    }

    return {
        stop: stop
    }
}

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

2.1. ADD_TRACKS

initRemoteDisplay() code

Здесь:

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

2.1. Инициализация объекта управления ABR

createAbrManager() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e(room, abrOptions) {
    return {
   console.log("Received ADD_TRACKS");
     createAbrManager: function () {
  let participant = remoteParticipants[e.info.nickName];
       let if (!participant)abr = {
            participant = {};
  track: null,
         participant.nickName = e.info.nickName;
     interval: abrOptions.interval,
      participant.tracks = [];
        thresholds: abrOptions.thresholds,
   participant.displays = [];
           qualities: remoteParticipants[participant.nickName],
 = participant;
        }
        participant.tracks.push.apply(participant.tracks, e.info.info);currentQualityName: null,
        for (const pTrack of e.info.info) {
   statTimer: null,
        let createDisplay = true;
     paused: false,
      for (let i = 0; i < participant.displays.length; i++) {
 manual: false,
              let display =keepGoodTimeout: participant.displays[i];abrOptions.abrKeepOnGoodQuality,
                if (pTrack.type === "VIDEO") {keepGoodTimer: null,
                    if (display.hasVideo()) {
tryUpperTimeout: abrOptions.abrTryForUpperQuality,
                tryUpperTimer: null,
        continue;
        ...
            }
            return abr;
       display.videoMid = pTrack.mid;}
    }
}

2.2. Запуск автоматического выбора качества ABR

abr.start() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
     display.setTrackInfo(pTrack);
   createAbrManager: function () {
            let  createDisplayabr = false;{
                    break;...
                } else if (pTrack.type === "AUDIO"start: function () {
                    if (display.hasAudiothis.stop()) {;
                    console.log("Start abr interval")
  continue;
                  if (abr.interval) }{
                    display.audioMid = pTrack.mid;
  const thresholds = Thresholds();
               display.setTrackInfo(pTrack);
         for (const threshold of abr.thresholds) {
       createDisplay = false;
                    break;
thresholds.add(threshold.parameter, threshold.maxLeap);
                        }
            }
            ifabr.statsTimer = setInterval(!createDisplay() => {
                continue;
            if (abr.track) }{
              let  display = createRemoteDisplay(participant.nickName, participant.nickName, mainDiv, displayOptions);
            participant.displays.push(display);room.getStats(abr.track.track, constants.SFU_RTC_STATS_TYPE.INBOUND, (stats) => {
            if (pTrack.type === "VIDEO") {
                       display.videoMid =if pTrack.mid;(thresholds.isReached(stats)) {
                display.setTrackInfo(pTrack);
            } else if (pTrack.type === "AUDIO") {
      abr.shiftDown();
          display.audioMid = pTrack.mid;
                display.setTrackInfo(pTrack);
        } else {
  }
        }
        ...
    });

2.2. REMOVE_TRACKS

initRemoteDisplay() code

Здесь:

  • удаляются элементы, в которых проигрывались потоки
  • информация о потоках удаляется из списка
Code Block
languagejs
themeRDark
    room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e) {
        ...
    })abr.on(constants.SFU_ROOM_EVENT.REMOVE_TRACKS, function(e) {
useGoodQuality();
         console.log("Received REMOVE_TRACKS");
        const participant = remoteParticipants[e.info.nickName];
        if (!participant) {
            return;     }
                                });
                            }
                        }, abr.interval);
                    }
                },
                ...
            }
            return abr;
        }
    }
}

2.3. Остановка автоматического выбора качества ABR

abr.stop() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                stop: function () {
                    console.log("Stop abr interval")
                    abr.stopKeeping();
                    abr.stopTrying();
                    if (abr.statsTimer) {
                        clearInterval(abr.statsTimer);
                        abr.statsTimer = null;
                    }
                },
                ...
            }
            return abr;
        }
    }
}

2.4. Добавление информации о видеодорожке

abr.setTrack() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                setTrack: function (track) {
                    abr.track = track;
                },
                ...
            }
            return abr;
        }
    }
}

2.5. Добавление описания ABR качества в список для выбора

abr.addQuality() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                addQuality: function (name) {
                    abr.qualities.push({name: name, available: false, good: true});
                },
                ...
            }
            return abr;
        }
    }
}

2.6. Установка признака доступности качества для проигрывания

abr.setQualityAvailable() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                setQualityAvailable: function (name, available) {
                    for (let i = 0; i < abr.qualities.length; i++) {
                        if (name === abr.qualities[i].name) {
                            abr.qualities[i].available = available;
                        }
                    }
                },
                ...
            }
            return abr;
        }
    }
}

2.7. Установка признака хорошего качества для текущего состояния канала

abr.setQualityGood() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                setQualityGood: function (name, good) {
                    if (name) {
                        for (let i = 0; i < abr.qualities.length; i++) {
                            if (name === abr.qualities[i].name) {
                                abr.qualities[i].good = good;
                            }
                        }
                    }
                },
                ...
            }
            return abr;
        }
    }
}

2.8. Получение первого доступного качества

abr.getFirstAvailableQuality() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                getFirstAvailableQuality: function () {
                    for (let i = 0; i < abr.qualities.length; i++) {
                        if (abr.qualities[i].available) {
                            return abr.qualities[i];
                        }
                    }
                    return null;
                },
                ...
            }
            return abr;
        }
    }
}

2.9. Получение более низкого качества

abr.getLowerQuality() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                getLowerQuality: function (name) {
                    let quality = null;
                    if (!name) {
                        // There were no switching yet, return a first available quality
                        return abr.getFirstAvailableQuality();
                    }
                    let currentIndex = abr.qualities.map(item => item.name).indexOf(name);
                    for (let i = 0; i < currentIndex; i++) {
                        if (abr.qualities[i].available) {
                            quality = abr.qualities[i];
                        }
                    }
                    return quality;
                },
                ...
            }
            return abr;
        }
    }
}

2.10. Получение более высокого качества

abr.getUpperQuality() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                getUpperQuality: function (name) {
                    let quality = null;
                    if (!name) {
                        // There were no switching yet, return a first available quality
                        return abr.getFirstAvailableQuality();
                    }
                    let currentIndex = abr.qualities.map(item => item.name).indexOf(name);
                    for (let i = currentIndex + 1; i < abr.qualities.length; i++) {
                        if (abr.qualities[i].available) {
                            quality = abr.qualities[i];
                            break;
                        }
                    }
                    return quality;
                },
                ...
            }
            return abr;
        }
    }
}

2.11. Переключение качества вниз

add.shiftDown() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                shiftDown: function () {
                    if (!abr.manual && !abr.paused) {
                        abr.stopKeeping();
                        abr.setQualityGood(abr.currentQualityName, false);
                        let quality = abr.getLowerQuality(abr.currentQualityName);
                        if (quality) {
                            console.log("Switching down to " + quality.name + " quality");
                            abr.setQuality(quality.name);
                        }
                    }
                },
                ...
            }
            return abr;
        }
    }
}

2.12. Переключение качества вверх

abr.shiftUp() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                shiftUp: function () {
                    if (!abr.manual && !abr.paused) {
                        let quality = abr.getUpperQuality(abr.currentQualityName);
                        if (quality) {
                            if (quality.good) {
                                console.log("Switching up to " + quality.name + " quality");
                                abr.setQuality(quality.name);
                            } else {
                                abr.tryUpper();
                            }
                        }
                    }
                },
                ...
            }
            return abr;
        }
    }
}

2.13. Использовать текущее качество как хорошее

abr.useGoodQuality() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                useGoodQuality: function () {
                    if (!abr.manual && !abr.paused) {
                        if (!abr.currentQualityName) {
                            let quality = abr.getFirstAvailableQuality();
                            abr.currentQualityName = quality.name;
                        }
                        abr.setQualityGood(abr.currentQualityName, true);
                        abr.keepGoodQuality();
                    }
                },
                ...
            }
            return abr;
        }
    }
}

2.14. Установить таймер использования текущего качества

abr.keepGoodQuality() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                keepGoodQuality: function () {
                    if (abr.keepGoodTimeout && !abr.keepGoodTimer && abr.getUpperQuality(abr.currentQualityName)) {
                        console.log("start keepGoodTimer");
                        abr.keepGoodTimer = setTimeout(() => {
                            abr.shiftUp();
                            abr.stopKeeping();
                        }, abr.keepGoodTimeout);
                    }
                },
                ...
            }
            return abr;
        }
    }
}

2.15. Сбросить таймер использования текущего качества

abr.stopKeeping() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                stopKeeping: function () {
                    if (abr.keepGoodTimer) {
                        clearTimeout(abr.keepGoodTimer);
                        abr.keepGoodTimer = null;
                    }
                },
                ...
            }
            return abr;
        }
    }
}

2.16. Переключиться на более высокое качество на заданное время

abr.tryUpper() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                tryUpper: function () {
                    let quality = abr.getUpperQuality(abr.currentQualityName);
                    if (abr.tryUpperTimeout && !abr.tryUpperTimer && quality) {
                        abr.tryUpperTimer = setTimeout(() => {
                            abr.setQualityGood(quality.name, true);
                            abr.stopTrying();
                        }, abr.tryUpperTimeout);
                    }
                },
                ...
            }
            return abr;
        }
    }
}

2.17. Остановить таймер тестирования более высокого качества

abr.stopTrying() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                stopTrying: function () {
                    if (abr.tryUpperTimer) {
                        clearTimeout(abr.tryUpperTimer);
                        abr.tryUpperTimer = null;
                    }
                },
                ...
            }
            return abr;
        }
    }
}

2.18. Переключиться на указанное качество

abr.setQuality() code

Code Block
languagejs
themeRDark
const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                setQuality: async function (name) {
                    console.log("set quality name");
                    // Pause switching until a new quality is received
                    abr.pause();
                    abr.currentQualityName = name;
                    abr.track.setPreferredQuality(abr.currentQualityName);
                }
            }
            return abr;
        }
    }
}

3. Создание объекта для управления комнатой

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
    ...

    return {
        stop: stop
    }
}

3.1. Обработка события PARTICIPANT_LIST

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
    const constants = SFU.constants;
    room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, async function (e) {
        for (const idName of e.participants) {
            meetingModel.addParticipant(idName.userId, idName.name);
        }
        ...
    });
    ...
}

3.2. Обработка события JOINED

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
    const constants = SFU.constants;
    room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, async function (e) {
        ...
    }).on(constants.SFU_ROOM_EVENT.JOINED, async function (e) {
        meetingModel.addParticipant(e.userId, e.name);
        ...
    });
    ...
}

3.3. Обработка события LEFT

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
    const constants = SFU.constants;
    room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, async function (e) {
        ...
    }).on(constants.SFU_ROOM_EVENT.LEFT, function (e) {
        meetingModel.removeParticipant(e.userId);
        ...
    });
    ...
}

3.4. Обработка события ADD_TRACKS

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
    const constants = SFU.constants;
    room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, async function (e) {
        ...
    }).on(constants.SFU_ROOM_EVENT.ADD_TRACKS, async function (e) {
        meetingModel.addTracks(e.info.userId, e.info.info);
        ...
    });
    ...
}

3.4. Обработка события REMOVE_TRACKS

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
    const constants = SFU.constants;
    room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, async function (e) {
        ...
    }).on(constants.SFU_ROOM_EVENT.REMOVE_TRACKS, async function (e) {
        meetingModel.removeTracks(e.info.userId, e.info.info);
        ...
    });
    ...
}

3.4. Обработка события TRACK_QUALITY_STATE

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
    const constants = SFU.constants;
    room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, async function (e) {
        ...
    }).on(constants.SFU_ROOM_EVENT.TRACK_QUALITY_STATE, async function (e) {
        meetingModel.updateQualityInfo(e.info.userId, e.info.tracks);
        ...
    });
    ...
}

3.5. Обработка события ENDED

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
    const constants = SFU.constants;
    room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, async function (e) {
        ...
    }).on(constants.SFU_ROOM_EVENT.ENDED, function (e) {
        meetingModel.end();
    });
    ...
}

3.6. Остановка комнаты

createDefaultMeetingController() code

Code Block
languagejs
themeRDark
const createDefaultMeetingController = function (room, meetingModel) {
    ...
    const stop = function () {
        meetingModel.end();
    };

    return {
        stop: stop
    }
}

4. Создание модели комнаты

createDefaultMeetingModel() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    ...
}

4.1. Добавление участника

addParticipant() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    return {
        ...
        addParticipant: function (userId, participantName) {
            if (this.participants.get(userId)) {
                return;
            }
            const [participantModel, participantView, participant] = participantFactory.createParticipant(userId, participantName, displayOptions, abrFactory);
            this.participants.set(userId, participant);
            meetingView.addParticipant(userId, participantName, participantView.rootDiv);
        },
        ...
    }
}

4.2. Удаление участника

removeParticipant() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    return {
        ...
        removeParticipant: function (userId) {
            const participant = this.participants.get(userId);
            if (participant) {
                this.participants.delete(userId);
                meetingView.removeParticipant(userId);
                participant.dispose();
            }
        },
        ...
    }
}

4.3. Переименование участника

renameParticipant() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    return {
        ...
        renameParticipant: function (userId, newNickname) {
            const participant = this.participants.get(userId);
            if (participant) {
                participant.setNickname(newNickname);
            }
        },
        ...
    }
}

4.4. Добавление треков участника для отображения

addTracks() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    return {
        ...
        addTracks: function (userId, tracks) {
            const participant = this.participants.get(userId);
            if (!participant) {
                return;
            }

            for (const track of tracks) {
                if (track.type === "VIDEO") {
                    participant.addVideoTrack(track);
                } else if (track.type === "AUDIO") {
                    participant.addAudioTrack(track);
                }
            }
        },
        ...
    }
}

4.5. Удаление отображаемых треков участника

removeTracks() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    return {
        ...
        removeTracks: function (userId, tracks) {
            const participant = this.participants.get(userId);
            if (!participant) {
                return;
            }
            for (const track of tracks) {
                if (track.type === "VIDEO") {
                    participant.removeVideoTrack(track);
                } else if (track.type === "AUDIO") {
                    participant.removeAudioTrack(track);
                }
            }
        },
        ...
    }
}

4.6. Обновление информации о качестве треков участника

updateQualityInfo() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    return {
        ...
        updateQualityInfo: function (userId, tracksInfo) {
            const participant = this.participants.get(userId);
            if (!participant) {
                return;
            }
            participant.updateQualityInfo(tracksInfo);
        },
        ...
    }
}

4.7. Завершение отображения участников при остановке комнаты

end() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    return {
        ...
        end: function () {
            console.log("Meeting " + this.meetingName + " ended")
            meetingView.end();
            this.participants.forEach((participant, id) => {
                participant.dispose();
            });
            this.participants.clear();
        },
        ...
    }
}

4.8. Переименование комнаты

setMeetingName() code

Code Block
languagejs
themeRDark
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    return {
        ...
        setMeetingName: function (id) {
            this.meetingName = id;
            meetingView.setMeetingName(id);
        }
    }
}

5. Создание объекта для отображения потоков в комнате

createDefaultMeetingView() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = function (entryPoint) {
    ...
}

5.1. Инициализация HTML5 элементов

createDefaultMeetingView() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = function (entryPoint) {
    const rootDiv = document.createElement("div");
    rootDiv.setAttribute("class", "grid-item");
    entryPoint.appendChild(rootDiv);
    const title = document.createElement("label");
    title.setAttribute("style", "display:block; border: solid; border-width: 1px");
    rootDiv.appendChild(title);
    return {
        ...
    }
}

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

participantViews() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = function (entryPoint) {
    ...
    return {
        participantViews: new Map(),
        ...
    }
}

5.3. Отображение имени комнаты

setMeetingName() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = function (entryPoint) {
    ...
    return {
        ...
        setMeetingName: function (id) {
            title.innerText = "Meeting: " + id;
        },
        ...
    }
}

5.4. Добавление элементов для отображения участника

addParticipant() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = function (entryPoint) {
    ...
    return {
        ...
        addParticipant: function (userId, participantName, cell) {
            const participantDiv = createContainer(rootDiv);
            participantDiv.appendChild(cell);
            this.participantViews.set(userId, participantDiv);
        },
        ...
    }
}

5.5. Удаление элементов участника

removeParticipant() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = function (entryPoint) {
    ...
    return {
        ...
        removeParticipant: function (userId) {
            const cell = this.participantViews.get(userId);
            if (cell) {
                this.participantViews.delete(userId);
                cell.remove();
            }
        },
        ...
    }
}

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

end() code

Code Block
languagejs
themeRDark
const createDefaultMeetingView = function (entryPoint) {
    ...
    return {
        ...
        end: function () {
            rootDiv.remove();
        }
    }
}

6. Создание фабрики объектов участников

createParticipantFactory() code

Здесь содаются объекты модели, управления и отображения участника комнаты

Code Block
languagejs
themeRDark
const createParticipantFactory = function (remoteTrackFactory, createParticipantView, createParticipantModel) {
    return {
        displayOptions: null,
        abrFactory: null,
        createParticipant: function (userId, nickname) {
            const view = createParticipantView();
            const model = createParticipantModel(userId, nickname, view, remoteTrackFactory, this.abrFactory, this.displayOptions);
            const controller = createParticipantController(model);
            return [model, view, controller];
        }
    }
}

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

createParticipantController() code

Объект вызывает соответствующие методы модели участника

Code Block
languagejs
themeRDark
const createParticipantController = function (model) {
    return {
        addVideoTrack: function (track) {
            model.addVideoTrack(track);
        },
        removeVideoTrack: function (track) {
            model.removeVideoTrack(track);
        },
        addAudioTrack: function (track) {
            model.addAudioTrack(track);
        },
        removeAudioTrack: function (track) {
            model.removeAudioTrack(track);
        },
        updateQualityInfo: function (qualityInfo) {
            model.updateQualityInfo(qualityInfo);
        },
        setNickname: function (nickname) {
            model.setNickname(nickname);
        },
        dispose: function () {
            model.dispose();
        }
    }
}

8. Создание объекта модели участника в комнате с двумя участниками

createOneToOneParticipantModel() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        userId: userId,
        nickname: nickname,
        remoteVideoTracks: new Map(),
        remoteAudioTracks: new Map(),
        audioTracks: new Map(),
        videoTracks: new Map(),
        abrManagers: new Map(),
        disposed: false,
        dispose: async function () {
            ...
        },
        addVideoTrack: function (track) {
            ...
        },
        removeVideoTrack: function (track) {
            ...
        },
        addAudioTrack: function (track) {
            ...
        },
        removeAudioTrack: function (track) {
            ...
        },
        setUserId: function (userId) {
            ...
        },
        setNickname: function (nickname) {
            ...
        },
        updateQualityInfo: function (remoteTracks) {
            ...
        },
        requestVideoTrack: async function (track, remoteTrack) {
            ...
        },
        pickQuality: async function (track, qualityName) {
            ...
        },
        muteVideo: async function (track) {
            ...
        },
        unmuteVideo: async function (track) {
            ...
        }
    };
    instance.setUserId(userId);
    instance.setNickname(nickname);
    return instance;
}

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

addVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        addVideoTrack: function (track) {
            this.videoTracks.set(track.mid, track);
            if (!track.quality) {
                track.quality = [];
            }
            participantView.addVideoTrack(track);
            const self = this;
            remoteTrackFactory.getVideoTrack().then((remoteTrack) => {
                if (remoteTrack) {
                    if (self.disposed || !self.videoTracks.get(track.mid)) {
                        remoteTrack.dispose();
                        return;
                    }

                    participantView.addVideoSource(remoteTrack.track, track, () => {
                        const abrManager = self.abrManagers.get(track.id);
                        if (!abrManager) {
                            return;
                        }
                        if (abrManager.isAuto()) {
                            abrManager.resume();
                        }
                    }, (mute) => {
                        if (mute) {
                            return self.muteVideo(track);
                        } else {
                            return self.unmuteVideo(track);
                        }
                    });
                    self.requestVideoTrack(track, remoteTrack).then(() => {
                        participantView.showVideoTrack(track);
                    }, (ex) => {
                        participantView.removeVideoSource(track);
                        remoteTrack.dispose();
                    });
                }
            }, (ex) => {
                console.log("Failed to get remote track " + ex);
            });
        },
        ...
    };
    ...
    return instance;
}

8.2. Удаление отображаемой видео дорожки

removeVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        removeVideoTrack: function (track) {
            if (this.videoTracks.delete(track.mid)) {
                const remoteTrack = this.remoteVideoTracks.get(track.mid);
                if (remoteTrack) {
                    this.remoteVideoTracks.delete(track.mid);
                    remoteTrack.dispose();
                }
                participantView.removeVideoTrack(track);

                const abrManager = this.abrManagers.get(track.id);
                if (abrManager) {
                    this.abrManagers.delete(track.id);
                    abrManager.clearQualityState();
                    abrManager.stop();
                }
            }
        },
        ...
    };
    ...
    return instance;
}

8.3. Добавление аудио дорожки для отображения

addAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        addAudioTrack: function (track) {
            this.audioTracks.set(track.mid, track);
            const self = this;
            remoteTrackFactory.getAudioTrack().then((remoteTrack) => {
                if (remoteTrack) {
                    if (self.disposed || !self.audioTracks.get(track.mid)) {
                        remoteTrack.dispose();
                        return;
                    }
                    this.remoteAudioTracks.set(track.mid, remoteTrack);
                    remoteTrack.demandTrack(track.id).then(() => {
                        if (!self.audioTracks.get(track.mid)) {
                            remoteTrack.dispose();
                            self.remoteAudioTracks.delete(track.mid);
                            return;
                        }
                        participantView.addAudioTrack(track, remoteTrack.track, displayOptions.showAudio);
                    }, (ex) => {
                        console.log("Failed demand track " + ex);
                        remoteTrack.dispose();
                        self.remoteAudioTracks.delete(track.mid);
                    });
                }
            }, (ex) => {
                console.log("Failed to get audio track " + ex);
            });
        },
        ...
    };
    ...
    return instance;
}

8.4. Удаление отображаемой аудио дорожки

removeAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        removeAudioTrack: function (track) {
            if (!this.audioTracks.delete(track.mid)) {
                return
            }

            participantView.removeAudioTrack(track);
            const remoteTrack = this.remoteAudioTracks.get(track.mid);
            if (remoteTrack) {
                this.remoteAudioTracks.delete(track.mid);
                remoteTrack.dispose();
            }
        },
        ...
    };
    ...
    return instance;
}

8.5. Запрос видео дорожки для отображения

requestVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        requestVideoTrack: async function (track, remoteTrack) {
            return new Promise((resolve, reject) => {
                if (!remoteTrack || !track) {
                    reject(new Error("Remote and local track must be defined"));
                    return;
                }
                const self = this;
                remoteTrack.demandTrack(track.id).then(() => {
                    if (!self.videoTracks.get(track.mid)) {
                        reject(new Error("Video track already removed from model"));
                        return;
                    }
                    let abrManager = self.abrManagers.get(track.id);

                    if (abrManager) {
                        abrManager.clearQualityState();
                    } else if (abrFactory) {
                        abrManager = abrFactory.createAbrManager();
                        self.abrManagers.set(track.id, abrManager);
                    }

                    if (abrManager) {
                        abrManager.setTrack(remoteTrack);
                        abrManager.stop();
                        if (track.quality.length > 0) {
                            participantView.addQuality(track, "Auto", true, async () => {
                                const manager = self.abrManagers.get(track.id);
                                if (!manager) {
                                    return;
                                }
                                manager.start();
                                manager.setAuto();
                                participantView.pickQuality(track, "Auto");
                            });
                            if (displayOptions.autoAbr) {
                                abrManager.setAuto();
                                abrManager.start();
                                participantView.pickQuality(track, "Auto");
                            }
                        }
                    }
                    for (const qualityDescriptor of track.quality) {
                        if (abrManager) {
                            abrManager.addQuality(qualityDescriptor.quality);
                            abrManager.setQualityAvailable(qualityDescriptor.quality, qualityDescriptor.available);
                        }
                        if (displayOptions.quality) {
                            participantView.addQuality(track, qualityDescriptor.quality, qualityDescriptor.available, async () => {
                                const manager = self.abrManagers.get(track.id);
                                if (manager) {
                                    manager.setManual();
                                    manager.setQuality(qualityDescriptor.quality);
                                }
                                return self.pickQuality(track, qualityDescriptor.quality);
                            });
                        }
                    }
                    self.remoteVideoTracks.delete(track.mid);
                    self.remoteVideoTracks.set(track.mid, remoteTrack);
                    resolve();
                }, (ex) => {
                    reject(ex);
                });
            });
        },
        ...
    };
    ...
    return instance;
}

8.6. Обновление информации о доступных качествах потока

updateQualityInfo() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        updateQualityInfo: function (remoteTracks) {
            for (const remoteTrackQuality of remoteTracks) {
                const track = this.videoTracks.get(remoteTrackQuality.mid);
                if (!track) {
                    continue;
                }
                if (!this.remoteVideoTracks.get(track.mid)) {
                    // update model and return, view not changed
                    for (const remoteQualityInfo of remoteTrackQuality.quality) {
                        const quality = track.quality.find((q) => q.quality === remoteQualityInfo.quality);
                        if (quality) {
                            quality.available = remoteQualityInfo.available;
                        } else {
                            track.quality.push(remoteQualityInfo);
                        }
                    }
                    return;
                }
                let abrManager = this.abrManagers.get(track.id);
                if (abrManager && track.quality.length === 0 && remoteTrackQuality.quality.length > 0) {
                    const self = this;
                    participantView.addQuality(track, "Auto", true, async () => {
                        const manager = self.abrManagers.get(track.id);
                        if (!manager) {
                            return;
                        }
                        manager.start();
                        manager.setAuto();
                        participantView.pickQuality(track, "Auto");
                    })
                    if (displayOptions.autoAbr) {
                        abrManager.setAuto();
                        abrManager.start();
                        participantView.pickQuality(track, "Auto");
                    }
                }
                for (const remoteQualityInfo of remoteTrackQuality.quality) {
                    const localQuality = track.quality.find((q) => q.quality === remoteQualityInfo.quality);
                    if (localQuality) {
                        localQuality.available = remoteQualityInfo.available;
                        if (abrManager) {
                            abrManager.setQualityAvailable(remoteQualityInfo.quality, remoteQualityInfo.available);
                        }
                        if (displayOptions.quality) {
                            participantView.updateQuality(track, localQuality.quality, localQuality.available);
                        }
                    } else {
                        track.quality.push(remoteQualityInfo);
                        if (abrManager) {
                            abrManager.addQuality(remoteQualityInfo.quality);
                            abrManager.setQualityAvailable(remoteQualityInfo.quality, remoteQualityInfo.available)
                        }
                        if (displayOptions.quality) {
                            const self = this;
                            participantView.addQuality(track, remoteQualityInfo.quality, remoteQualityInfo.available, async () => {
                                const manager = self.abrManagers.get(track.id);
                                if (manager) {
                                    manager.setManual();
                                    manager.setQuality(remoteQualityInfo.quality);
                                }
                                return self.pickQuality(track, remoteQualityInfo.quality);
                            });
                        }
                    }
                }
            }

        },
        ...
    };
    ...
    return instance;
}

8.7. Выбор качества для проигрывания

pickQuality() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        pickQuality: async function (track, qualityName) {
            let remoteVideoTrack = this.remoteVideoTracks.get(track.mid);
            if (remoteVideoTrack) {
                return remoteVideoTrack.setPreferredQuality(qualityName).then(() => {
                    participantView.pickQuality(track, qualityName);
                });
            }
        },
        ...
    };
    ...
    return instance;
}

8.8. Заглушить видео участника

muteVideo() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        muteVideo: async function (track) {
            const remoteTrack = this.remoteVideoTracks.get(track.mid);
            if (remoteTrack) {
                return remoteTrack.mute();
            } else {
                return new Promise((resolve, reject) => {
                    reject(new Error("Remote track not defined"));
                });
            }
        },
        ...
    };
    ...
    return instance;
}

8.9. Возобновить видео участника

unmuteVideo() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        unmuteVideo: async function (track) {
            const remoteTrack = this.remoteVideoTracks.get(track.mid);
            if (remoteTrack) {
                return remoteTrack.unmute();
            } else {
                return new Promise((resolve, reject) => {
                    reject(new Error("Remote track not defined"));
                });
            }
        }
    };
    ...
    return instance;
}

8.10. Завершение работы

dispose() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        dispose: async function () {
            this.disposed = true;
            participantView.dispose();
            this.remoteVideoTracks.forEach((track, id) => {
                track.dispose();
            })
            this.remoteVideoTracks.clear();

            this.remoteAudioTracks.forEach((track, id) => {
                track.dispose();
            })
            this.remoteAudioTracks.clear();

            this.abrManagers.forEach((abrManager, id) => {
                abrManager.stop();
            })
            this.abrManagers.clear();

        },
    };
    ...
    return instance;
}

9. Создание объекта модели участника в комнате со многими участниками

createOneToManyParticipantModel() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        userId: userId,
        nickname: nickname,
        videoEnabled: false,
        currentTrack: null,
        remoteVideoTrack: null,
        remoteAudioTracks: new Map(),
        audioTracks: new Map(),
        videoTracks: new Map(),
        abr: null,
        disposed: false,
        dispose: async function () {
            ...
        },
        addVideoTrack: function (track) {
            ...
        },
        removeVideoTrack: function (track) {
            ...
        },
        addAudioTrack: function (track) {
            ...
        },
        removeAudioTrack: function (track) {
            ...
        },
        setUserId: function (userId) {
            ...
        },
        setNickname: function (nickname) {
            ...
        },
        updateQualityInfo: function (remoteTracks) {
            ...
        },
        requestVideoTrack: async function (track, remoteTrack) {
            ...
        },
        pickQuality: async function (track, qualityName) {
            ...
        },
        muteVideo: async function (track) {
            ...
        },
        unmuteVideo: async function (track) {
            ...
        }
    };
    instance.setUserId(userId);
    instance.setNickname(nickname);
    if (abrFactory) {
        instance.abr = abrFactory.createAbrManager();
    }
    return instance;
}

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

addVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        addVideoTrack: function (track) {
            this.videoTracks.set(track.mid, track);
            if (!track.quality) {
                track.quality = [];
            }
            const self = this;
            participantView.addVideoTrack(track, () => {
                if (self.disposed) {
                    return new Promise((resolve, reject) => {
                        reject(new Error("Model disposed"));
                    });
                }

                if (self.remoteVideoTrack) {
                    return new Promise((resolve, reject) => {
                        self.requestVideoTrack(track, self.remoteVideoTrack).then(() => {
                            resolve();
                        }, (ex) => {
                            reject(ex);
                        });
                    });
                } else {
                    return new Promise((resolve, reject) => {
                        reject(new Error("Remote track is null"));
                        requestTrackAndPick(self, track);
                    });
                }
            });
            requestTrackAndPick(this, track);
        },
        ...
    };
    ...
    return instance;
}

9.2. Удаление отображаемой видео дорожки

removeVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        removeVideoTrack: function (track) {
            this.videoTracks.delete(track.mid);
            participantView.removeVideoTrack(track);
            if (this.currentTrack && this.currentTrack.mid === track.mid) {
                repickTrack(this, track);
            }
        },
        ...
    };
    ...
    return instance;
}

9.3. Добавление аудио дорожки для отображения

addAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        addAudioTrack: async function (track) {
            this.audioTracks.set(track.mid, track);
            const self = this;
            remoteTrackFactory.getAudioTrack().then((remoteTrack) => {
                if (!remoteTrack) {
                    return;
                }
                if (self.disposed || !self.audioTracks.get(track.mid)) {
                    remoteTrack.dispose();
                    return;
                }
                this.remoteAudioTracks.set(track.mid, remoteTrack);
                remoteTrack.demandTrack(track.id).then(() => {
                    if (!self.audioTracks.get(track.mid)) {
                        remoteTrack.dispose();
                        self.remoteAudioTracks.delete(track.mid);
                        return;
                    }
                    participantView.addAudioTrack(track, remoteTrack.track, displayOptions.showAudio);
                }, (ex) => {
                    console.log("Failed demand track " + ex);
                    remoteTrack.dispose();
                    self.remoteAudioTracks.delete(track.mid);
                });
            }, (ex) => {
                console.log("Failed to get audio track " + ex);
            });

        },
        ...
    };
    ...
    return instance;
}

9.4. Удаление отображаемой аудио дорожки

removeAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        removeAudioTrack: function (track) {
            if (!this.audioTracks.delete(track.mid)) {
                return
            }

            participantView.removeAudioTrack(track);
            const remoteTrack = this.remoteAudioTracks.get(track.mid);
            if (remoteTrack) {
                this.remoteAudioTracks.delete(track.mid);
                remoteTrack.dispose();
            }

        },
        ...
    };
    ...
    return instance;
}

9.5. Запрос видео дорожки для отображения

requestVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        requestVideoTrack: async function (track, remoteTrack) {
            return new Promise((resolve, reject) => {
                if (!remoteTrack || !track) {
                    reject(new Error("Remote and local track must be defined"));
                    return;
                }
                const self = this;
                remoteTrack.demandTrack(track.id).then(() => {
                    // channels reordering case, must be removed after channels unification
                    if (!self.videoTracks.get(track.mid)) {
                        reject(new Error("Video track already removed from model"));
                        return;
                    }
                    self.currentTrack = track;
                    participantView.clearQualityState(track);
                    if (self.abr) {
                        self.abr.stop();
                        self.abr.clearQualityState();
                        self.abr.setTrack(remoteTrack);

                        if (track.quality.length > 0) {
                            participantView.addQuality(track, "Auto", true, async () => {
                                if (!self.abr) {
                                    return;
                                }
                                self.abr.start();
                                self.abr.setAuto();
                                participantView.pickQuality(track, "Auto");
                            })
                        }
                        if (displayOptions.autoAbr) {
                            self.abr.setAuto();
                            self.abr.start();
                            participantView.pickQuality(track, "Auto");
                        }
                    }
                    for (const qualityDescriptor of track.quality) {
                        if (self.abr) {
                            self.abr.addQuality(qualityDescriptor.quality);
                            self.abr.setQualityAvailable(qualityDescriptor.quality, qualityDescriptor.available);
                        }
                        if (displayOptions.quality) {
                            participantView.addQuality(track, qualityDescriptor.quality, qualityDescriptor.available, async () => {
                                if (self.abr) {
                                    self.abr.setManual();
                                    self.abr.setQuality(qualityDescriptor.quality);
                                }
                                return self.pickQuality(track, qualityDescriptor.quality);
                            });
                        }
                    }
                    resolve();
                }, (ex) => reject(ex));
            });
        },
        ...
    };
    ...
    return instance;
}

9.6. Обновление информации о доступных качествах потока

updateQualityInfo() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        updateQualityInfo: function (remoteTracks) {
            for (const remoteTrackQuality of remoteTracks) {
                const track = this.videoTracks.get(remoteTrackQuality.mid);
                if (!track) {
                    continue;
                }
                if (!this.currentTrack || this.currentTrack.mid !== track.mid) {
                    // update model and return, view not changed
                    for (const remoteQualityInfo of remoteTrackQuality.quality) {
                        const quality = track.quality.find((q) => q.quality === remoteQualityInfo.quality);
                        if (quality) {
                            quality.available = remoteQualityInfo.available;
                        } else {
                            track.quality.push(remoteQualityInfo);
                        }
                    }
                    return;
                }
                if (this.abr && track.quality.length === 0 && remoteTrackQuality.quality.length > 0) {
                    const self = this;
                    participantView.addQuality(track, "Auto", true, async () => {
                        if (!self.abr) {
                            return;
                        }
                        self.abr.start();
                        self.abr.setAuto();
                        participantView.pickQuality(track, "Auto");
                    })
                    if (displayOptions.autoAbr && this.abr) {
                        this.abr.setAuto();
                        this.abr.start();
                        participantView.pickQuality(track, "Auto");
                    }
                }
                for (const remoteQualityInfo of remoteTrackQuality.quality) {
                    const localQuality = track.quality.find((q) => q.quality === remoteQualityInfo.quality);
                    if (localQuality) {
                        localQuality.available = remoteQualityInfo.available;
                        if (this.abr) {
                            this.abr.setQualityAvailable(remoteQualityInfo.quality, remoteQualityInfo.available)
                        }
                        if (displayOptions.quality) {
                            participantView.updateQuality(track, localQuality.quality, localQuality.available);
                        }
                    } else {
                        track.quality.push(remoteQualityInfo);
                        if (this.abr) {
                            this.abr.addQuality(remoteQualityInfo.quality);
                            this.abr.setQualityAvailable(remoteQualityInfo.quality, remoteQualityInfo.available)
                        }
                        if (displayOptions.quality) {
                            const self = this;
                            participantView.addQuality(track, remoteQualityInfo.quality, remoteQualityInfo.available, async () => {
                                if (self.abr) {
                                    self.abr.setManual();
                                    self.abr.setQuality(remoteQualityInfo.quality);
                                }
                                return self.pickQuality(track, remoteQualityInfo.quality);
                            });
                        }
                    }
                }
            }
        },
        ...
    };
    ...
    return instance;
}

9.7. Выбор качества для проигрывания

pickQuality() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        pickQuality: async function (track, qualityName) {
            if (this.remoteVideoTrack) {
                return this.remoteVideoTrack.setPreferredQuality(qualityName).then(() => participantView.pickQuality(track, qualityName));
            }
        },
        ...
    };
    ...
    return instance;
}

9.8. Заглушить видео участника

muteVideo() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        muteVideo: async function (track) {
            if (this.remoteVideoTrack) {
                return this.remoteVideoTrack.mute();
            } else {
                return new Promise((resolve, reject) => {
                    reject(new Error("Remote track not defined"));
                });
            }
        },
        ...
    };
    ...
    return instance;
}

9.9. Возобновить видео участника

unmuteVideo() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    ...
    const instance = {
        ...
        unmuteVideo: async function (track) {
            if (this.remoteVideoTrack) {
                return this.remoteVideoTrack.unmute();
            } else {
                return new Promise((resolve, reject) => {
                    reject(new Error("Remote track not defined"));
                });
            }
        }
    };
    ...
    return instance;
}

9.10. Завершение работы

dispose() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
    const instance = {
        ...
        dispose: async function () {
            this.disposed = true;
            participantView.dispose();
            this.remoteVideoTracks.forEach((track, id) => {
                track.dispose();
            })
            this.remoteVideoTracks.clear();

            this.remoteAudioTracks.forEach((track, id) => {
                track.dispose();
            })
            this.remoteAudioTracks.clear();

            this.abrManagers.forEach((abrManager, id) => {
                abrManager.stop();
            })
            this.abrManagers.clear();

        },
    };
    ...
    return instance;
}

10. Создание объекта отображения участника в комнате с двумя участниками

createOneToOneParticipantView() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {

    const participantDiv = createContainer(null);

    const audioDisplay = createContainer(participantDiv);

    const participantNicknameDisplay = createInfoDisplay(participantDiv, "Name: ")

    const videoPlayers = new Map();
    const audioElements = new Map();

    return {
        rootDiv: participantDiv,
        dispose: function () {
            ...
        },
        addVideoTrack: function (track) {
            ...
        },
        removeVideoTrack: function (track) {
            ...
        },
        addVideoSource: function (remoteVideoTrack, track, onResize, muteHandler) {
            ...
        },
        removeVideoSource: function (track) {
            ...
        },
        showVideoTrack: function (track) {
            ...
        },
        addAudioTrack: function (track, audioTrack, show) {
            ...
        },
        removeAudioTrack: function (track) {
            ...
        },
        setNickname: function (userId, nickname) {
            ...
        },
        updateQuality: function (track, qualityName, available) {
            ...
        },
        addQuality: function (track, qualityName, available, onQualityPick) {
            ...
        },
        pickQuality: function (track, qualityName) {
            ...
        }
    }
}

10.1. Добавление видео дорожки

addVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        addVideoTrack: function (track) {
            const player = createVideoPlayer(participantDiv);
            videoPlayers.set(track.mid, player);
        },
        ...
    }
}

10.2. Удаление видео дорожки

removeVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        removeVideoTrack: function (track) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.dispose();
            }
        },
        ...
    }
}

10.3. Добавление источника к видео элементу

addVideoSource() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        addVideoSource: function (remoteVideoTrack, track, onResize, muteHandler) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.setVideoSource(remoteVideoTrack, onResize, muteHandler);
            }
        },
        ...
    }
}

10.4. Удаление источника из видео элемента

removeVideoSource() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        removeVideoSource: function (track) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.removeVideoSource();
            }
        },
        ...
    }
}

10.5. Показать видео дорожку

showVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        showVideoTrack: function (track) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.showVideoTrack(track);
            }
        },
        ...
    }
}

10.6. Добавление аудио дорожки

addAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        addAudioTrack: function (track, audioTrack, show) {
            const stream = new MediaStream();
            stream.addTrack(audioTrack);
            const audioElement = document.createElement("audio");
            if (!show) {
                hideItem(audioElement);
            }
            audioElement.controls = "controls";
            audioElement.muted = true;
            audioElement.autoplay = true;
            audioElement.onloadedmetadata = function (e) {
                audioElement.play().then(function () {
                    if (Browser().isSafariWebRTC() && Browser().isiOS()) {
                        console.warn("Audio track should be manually unmuted in iOS Safari");
                    } else {
                        audioElement.muted = false;
                    }
                });
            };
            audioElements.set(track.mid, audioElement);
            audioDisplay.appendChild(audioElement);
            audioElement.srcObject = stream;
        },
        ...
    }
}

10.7. Удаление аудио дорожки

removeAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        removeAudioTrack: function (track) {
            const audioElement = audioElements.get(track.mid);
            if (audioElement) {
                audioElement.remove();
                audioElements.delete(track.mid);
            }
        },
        ...
    }
}

10.8. Отображение имени участника

setNickname() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        setNickname: function (userId, nickname) {
            const additionalUserId = userId ? "#" + getShortUserId(userId) : "";
            participantNicknameDisplay.innerText = "Name: " + nickname + additionalUserId;
        },
        ...
    }
}

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

updateQuality() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        updateQuality: function (track, qualityName, available) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.updateQuality(qualityName, available);
            }
        },
        ...
    }
}

10.10. Добавление информации о качестве

addQuality() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        addQuality: function (track, qualityName, available, onQualityPick) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.addQuality(qualityName, available, onQualityPick);
            }
        },
        ...
    }
}

10.11. Выбор качества

pickQuality() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        pickQuality: function (track, qualityName) {
            const player = videoPlayers.get(track.mid);
            if (player) {
                player.pickQuality(qualityName);
            }
        }
        ...
    }
}

10.12. Завершение отображения

dispose() code

Code Block
languagejs
themeRDark
const createOneToOneParticipantView = function () {
    ...
    return {
        ...
        dispose: function () {
            for (const player of videoPlayers.values()) {
                player.dispose();
            }
            videoPlayers.clear();
            for (const element of audioElements.values()) {
                element.remove();
            }
            audioElements.clear();
        },
        ...
    }
}

11. Создание объекта отображения участника в комнате с несколькими участниками

createOneToManyParticipantView() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {

    const participantDiv = createContainer(null);

    const audioDisplay = createContainer(participantDiv);

    const participantNicknameDisplay = createInfoDisplay(participantDiv, "Name: ")

    const audioElements = new Map();
    const player = createVideoPlayer(participantDiv);

    return {
        rootDiv: participantDiv,
        currentTrack: null,
        dispose: function () {
            ...
        },
        addVideoTrack: function (track) {
            ...
        },
        removeVideoTrack: function (track) {
            ...
        },
        addVideoSource: function (remoteVideoTrack, track, onResize, muteHandler) {
            ...
        },
        removeVideoSource: function (track) {
            ...
        },
        showVideoTrack: function (track) {
            ...
        },
        addAudioTrack: function (track, audioTrack, show) {
            ...
        },
        removeAudioTrack: function (track) {
            ...
        },
        setNickname: function (userId, nickname) {
            ...
        },
        updateQuality: function (track, qualityName, available) {
            ...
        },
        addQuality: function (track, qualityName, available, onQualityPick) {
            ...
        },
        clearQualityState: function (track) {
            ...
        },
        pickQuality: function (track, qualityName) {
            ...
        }
    }
}

11.1. Добавление видео дорожки

addVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        addVideoTrack: function (track, requestVideoTrack) {
            player.addVideoTrack(track, async () => {
                return requestVideoTrack();
            });
        },
        ...
    }
}

11.2. Удаление видео дорожки

removeVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        removeVideoTrack: function (track) {
            player.removeVideoTrack(track);
        },
        ...
    }
}

11.3. Добавление источника к видео элементу

addVideoSource() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        addVideoSource: function (remoteVideoTrack, track, onResize, muteHandler) {
            this.currentTrack = track;
            player.setVideoSource(remoteVideoTrack, onResize, muteHandler);
        },
        ...
    }
}

11.4. Удаление источника из видео элемента

removeVideoSource() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        removeVideoSource: function (track) {
            if (this.currentTrack && this.currentTrack.mid === track.mid) {
                player.removeVideoSource();
            }
        },
        ...
    }
}

11.5. Показать видео дорожку

showVideoTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        showVideoTrack: function (track) {
            player.showVideoTrack(track);
        },
        ...
    }
}

11.6. Добавление аудио дорожки

addAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        ...
        addAudioTrack: function (track, audioTrack, show) {
            const stream = new MediaStream();
            stream.addTrack(audioTrack);
            const audioElement = document.createElement("audio");
            if (!show) {
                hideItem(audioElement);
            }
            audioElement.controls = "controls";
            audioElement.muted = true;
            audioElement.autoplay = true;
            audioElement.onloadedmetadata = function (e) {
                audioElement.play().then(function () {
                    if (Browser().isSafariWebRTC() && Browser().isiOS()) {
                        console.warn("Audio track should be manually unmuted in iOS Safari");
                    } else {
                        audioElement.muted = false;
                    }
                });
            };
            audioElements.set(track.mid, audioElement);
            audioDisplay.appendChild(audioElement);
            audioElement.srcObject = stream;
        },
        ...
    }
}

11.7. Удаление аудио дорожки

removeAudioTrack() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
        }...
        forremoveAudioTrack: function (const rTrack of e.info.infotrack) {
            for (let iconst audioElement = 0; i < participant.tracks.length; i++) {
   audioElements.get(track.mid);
             if (rTrack.mid === participant.tracks[i].midaudioElement) {
                    participant.tracks.splice(i, 1audioElement.remove();
                    breakaudioElements.delete(track.mid);
                }
            },
        ...
    }
}

11.8. Отображение имени участника

setNickname() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = forfunction (let) i{
 = 0; i < participant..displays.length; i++).
    return {
        ...
        setNickname: letfunction found = false;(userId, nickname) {
                const displayadditionalUserId = participant.displays[i];
      userId ? "#" + getShortUserId(userId) : "";
           if (displayparticipantNicknameDisplay.audioMidinnerText === rTrack.mid) {
    "Name: " + nickname + additionalUserId;
        },
        ...
    display.setAudio(null);
     }
}

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

updateQuality() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
      found = true;...
        updateQuality: function (track, qualityName, available) {
    } else if (display.videoMid === rTrack.mid) {
  player.updateQuality(qualityName, available);
        },
        ...
    display.setVideo(null);
     }
}

11.10. Добавление информации о качестве

addQuality() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
      found = true;...
        addQuality: function (track, qualityName, available, onQualityPick)   }{
            player.addQuality(qualityName,    if (found) {available, onQualityPick);
        },
        ...
       if (!display.hasAudio() && !display.hasVideo()}
}

11.11. Выбор качества

pickQuality() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
               display.dispose();...
        pickQuality: function (track, qualityName) {
             participant.displays.splice(i, 1player.pickQuality(qualityName);
        }
        ...
       }
     }
}

11.12. Очистка отображаемого списка качеств

clearQualityState() code

Code Block
languagejs
themeRDark
const createOneToManyParticipantView = function () {
    ...
    return {
      break;
  ...
        clearQualityState: function     }(track) {
            }player.clearQualityState();
        },
           ...
    }
});

...

11.

...

13.

...

Завершение отображения

initRemoteDisplaydispose() code

Здесь:

  • участник удаляется из списка
  • удаляются элементы, в которых проигрывались потоки
room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e) { ... }).on(constants.SFU_ROOM_EVENT.LEFT, function(e
Code Block
languagejs
themeRDark
RDark
const createOneToManyParticipantView = function () {
    ...
    console.log("Received LEFT");return {
        let participant = remoteParticipants[e.name];
...
        dispose: iffunction (!participant) {
            returnplayer.dispose();
        }
    for (const element of participantaudioElements.displays.forEach(function(display){
values()) {
                displayelement.disposeremove();
            })
           delete remoteParticipants[e.name]audioElements.clear();
        },
        ...
    }
});

2.4. TRACK_QUALITY_STATE

...

12. Создание объекта видео плеера

createVideoPlayer() code

Здесь:

  • обновляется информация о качествах потоков
Code Block
languagejs
themeRDark
const createVideoPlayer = function room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e) {(participantDiv) {

    const streamDisplay = createContainer(participantDiv);

    const resolutionLabel =  ... createInfoDisplay(streamDisplay, "0x0");
    }).on(constants.SFU_ROOM_EVENT.TRACK_QUALITY_STATE, function(e){hideItem(resolutionLabel);

    const trackNameDisplay =  console.log("Received createInfoDisplay(streamDisplay, "track qualitynot stateset");
    hideItem(trackNameDisplay);

    const participantvideoMuteDisplay = remoteParticipants[e.info.nickName]createContainer(streamDisplay);

    const qualityDisplay =  if (!participant) {createContainer(streamDisplay);

    const trackDisplay       return;= createContainer(streamDisplay);

        }let videoElement;

    const trackButtons = new for (const rTrack of e.info.tracks) {Map();
    const qualityButtons = new Map();

    const midlock = rTrack.mid; function () {
        ...
    for}

 (let i = 0;const iunlock < participant.displays.length; i++= function () {
        ...
    }

    const displaysetWebkitEventHandlers = participant.displays[i]; function (video) {
        ...
    }
    if (display.videoMidconst setEventHandlers === mid function (video) {
        ...
    }

    const repickQuality = function display.updateQualityInfo(rTrack.quality);(qualityName) {
        ...
    }

    return {
   break;
     rootDiv: streamDisplay,
        muteButton:  }null,
            }autoButton: null,
        }
dispose:    });

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

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

createRemoteDisplay() code

Здесь:

  • настраиваются параметры отображения
  • создается контейнер для потоков участника
  • создается контейнер для конкретного потока
  • создается контейнер для кнопок переключения качества
Code Block
languagejs
themeRDark
function () {
         const cell = document.createElement("div");...
        cell.setAttribute("class", "text-center");
},
        clearQualityState: cell.id = id;
function () {
           mainDiv.appendChild(cell); ...
        let publisherNameDisplay;
},
         let currentQualityDisplay;addVideoTrack: function (track, asyncCallback) {
        let videoTypeDisplay;
   ...
     let abrQualityCheckPeriod = ABR_QUALITY_CHECK_PERIOD; },
        letremoveVideoTrack: abrKeepOnGoodQualityfunction = ABR_KEEP_ON_QUALITY;
(track) {
            ...
    let  abrTryForUpperQuality = ABR_TRY_UPPER_QUALITY; },
        setVideoSource: iffunction (displayOptions.abrQualityCheckPeriod !== undefinedremoteVideoTrack, onResize, onMute) {
            abrQualityCheckPeriod = displayOptions.abrQualityCheckPeriod;...
        },
        if (displayOptions.abrKeepOnGoodQuality !== undefinedremoveVideoSource: function () {
            abrKeepOnGoodQuality = displayOptions.abrKeepOnGoodQuality;...
        },
        ifshowVideoTrack: function (displayOptions.abrTryForUpperQuality !== undefinedtrack) {
            abrTryForUpperQuality = displayOptions.abrTryForUpperQuality;...
        },
        ifupdateQuality: function (!displayOptions.abrqualityName, available) {
            abrQualityCheckPeriod = 0;
...
        },
        abrKeepOnGoodQuality = 0;
  addQuality: function (qualityName, available, onPickQuality) {
          abrTryForUpperQuality = 0;...
        },
        pickQuality: iffunction (displayOptions.publisherqualityName) {
            publisherNameDisplay = createInfoDisplay(cell, "Published by: " + name);...
        }
    }
}

12.1. Блокировка и разблокировка кнопок плеера для асинхронных операций

lock(), unlock() code

Code Block
languagejs
themeRDark
const createVideoPlayer =  iffunction (displayOptions.quality) {
   participantDiv) {
    ...
    const currentQualityDisplaylock = function createInfoDisplay(cell, "");) {
        }
for (const btn      if (displayOptions.typeof trackButtons.values()) {
            videoTypeDisplaybtn.disabled = createInfoDisplay(cell, "")true;
        }
        for (const qualitySwitchDisplaystate =of createInfoDisplay(cell, "");

qualityButtons.values()) {
           let qualityDivsstate.btn.disabled = []true;
        let}
  contentType = "";}

        const rootDisplayunlock = function createContainer(cell); {
        for (const streamDisplaybtn =of createContainertrackButtons.values(rootDisplay)); {
        const audioDisplay = createContainer(rootDisplay);
        const audioTypeDisplay btn.disabled = createInfoDisplay(audioDisplay)false;
        const audioTrackDisplay}
 = createContainer(audioDisplay);
      for  (const audioStateButtonstate =of AudioStateButtonqualityButtons.values();) {

        hideItem(streamDisplay);
    state.btn.disabled    hideItem(audioDisplay)= false;
        hideItem(publisherNameDisplay);}
    }
    hideItem(currentQualityDisplay);...
    return {
   hideItem(videoTypeDisplay);
     ...
    hideItem(qualitySwitchDisplay);

...

}
}

12.2.

...

Настройка обработчиков событий плеера для Safari

setWebkitEventHandlers() code

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

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    ...
    const abrsetWebkitEventHandlers = ABR(abrQualityCheckPeriod, [
     function (video) {
        let needRestart = false;
        let isFullscreen = false;
        // Use webkitbeginfullscreen event to detect full screen mode in iOS Safari
       {parameter: "nackCount video.addEventListener("webkitbeginfullscreen", maxLeap: 10},function () {
            {parameter: "freezeCount", maxLeap: 10},
      isFullscreen = true;
      {parameter: "packetsLost", maxLeap: 10});
        ]video.addEventListener("pause", abrKeepOnGoodQuality, abrTryForUpperQuality);

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

setVideo() code

Code Block
languagejs
themeRDark
function () {
            setVideo:if function(streamneedRestart) {
                if (video) {
   console.log("Media paused after fullscreen, continue...");
                 video.removeplay();
                needRestart = false;
            } else {

                 if (stream == null) {
    console.log("Media paused by click, continue...");
                video = null.play();
            }
        this.videoMid = undefined;
});
        video.addEventListener("webkitendfullscreen", function () {
            qualityDivsvideo.forEach(functionplay(div) {;
            needRestart = true;
          div.remove();
  isFullscreen = false;
        });
    }
    });...
    return {
        ...
    }
}

12.3. Настройка обработчиков событий плеера для других браузеров

setEventHandlers() code

Code Block
languagejs
themeRDark
const createVideoPlayer = qualityDivsfunction = [];(participantDiv) {
    ...
    const setEventHandlers = function (video) {
       return;
 // Ignore play/pause button
        video.addEventListener("pause", function ()  }{
            console.log("Media paused by  showItem(streamDisplayclick, continue...");
                video = document.createElementplay("video");
        });
    }
    video.controls = "controls";...
    return {
        ...
   video.muted = true;
      }
}

12.4. Перерисовка кнопок переключения качества

repickQuality() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    ...
    const repickQuality = video.autoplay = true;function (qualityName) {
        for (const [quality, state]     if (Browser().isSafariWebRTCof qualityButtons.entries()) {
            if (quality ===      video.setAttribute("playsinline", "");qualityName) {
                state.btn.style.color    video.setAttribute("webkit-playsinline", "")= QUALITY_COLORS.SELECTED;
            } else if (state.btn.style.color     this.setWebkitEventHandlers(video);=== QUALITY_COLORS.SELECTED) {
                } elseif (state.available) {
                    state.btn.style.color = this.setEventHandlers(video)QUALITY_COLORS.AVAILABLE;
                } else {
                streamDisplay.appendChild(video)    state.btn.style.color = QUALITY_COLORS.UNAVAILABLE;
                video.srcObject}
 = stream;
          }
      this.setResizeHandler(video);
  }
    }
    ...
      abr.start();return {
        ...
    }
},

...

12.

...

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

clearQualityState() code

Code Block
languagejs
themeRDark
const            setAudio: function(stream) {
                if (audiocreateVideoPlayer = function (participantDiv) {
    
                audio.remove();...
    return {
           }...
        clearQualityState:        if (!streamfunction () {
            qualityButtons.forEach((state, qName) => {
     audio = null;
         state.btn.remove();
           this.audioMid = undefined });
            qualityButtons.clear();
        return;},
        ...
    }
}

12.6. Добавление видео дорожки

addVideoTrack() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv)  }{
    
    ...
        showItem(audioDisplay);return {
        ...
        audioaddVideoTrack: =function document.createElement("audio");(track, asyncCallback) {
                audio.controlsconst trackButton = document.createElement("controlsbutton");
            trackButtons.set(track.mid, trackButton);
   audio.muted  = true;
      trackButton.innerText = "Track №" + track.mid + ": " + audio.autoplay = truetrack.contentType;
            trackButton.setAttribute("style", "display:inline-block; border:  if (Browser().isSafariWebRTC()) {solid; border-width: 1px");
            trackButton.style.color = QUALITY_COLORS.AVAILABLE;
      audio.setAttribute("playsinline", "");
     const self = this;
            audiotrackButton.setAttribute("webkit-playsinline", "");
addEventListener('click', async function () {
                console.log("Clicked on track button track.mid " + thistrack.setWebkitEventHandlers(audiomid);
                if  } else(trackButton.style.color === QUALITY_COLORS.SELECTED) {
                    this.setEventHandlers(audio);return
                }

                audioTrackDisplay.appendChildlock(audio);
                audioStateButton.makeButton(audioTypeDisplay, audio);asyncCallback().then(() => {
                audio.srcObject   = stream self.showVideoTrack(track);
                audio.onloadedmetadata}).finally(() => function (e) {
                    audio.playunlock().then(function() {
;
                });
         if (Browser().isSafariWebRTC() && Browser().isiOS()) {});
            trackDisplay.appendChild(trackButton);
        },
        console.warn("Audio track should be manually unmuted in iOS Safari");
         ...
    }
}

12.7. Удаление видео дорожки

removeVideoTrack() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
    return {
  } else {
    ...
        removeVideoTrack: function (track) {
            const audio.mutedtrackButton = falsetrackButtons.get(track.mid);
            if (trackButton) {
                audioStateButtontrackButton.setButtonStateremove();
                        }trackButtons.delete(track.mid);
            }
        });,
        ...
        };
            },

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

...

   }
}

12.8. Добавление видео элемента и назначение видео дорожки как источника проигрывания

setVideoSource() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
   setEventHandlers: function(video) return {
        ...
        //setVideoSource: Ignore play/pause button
    function (remoteVideoTrack, onResize, onMute) {
            video.addEventListener("pause", functionif (!this.muteButton) {
                const newVideoMuteBtn =  consoledocument.logcreateElement("Media paused by click, continue...button");
                this.muteButton    video.play()= newVideoMuteBtn;
                })newVideoMuteBtn.innerText = "mute";
            },
    newVideoMuteBtn.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
   setWebkitEventHandlers: function(video) {
           newVideoMuteBtn.addEventListener('click', async function () {
 let needRestart = false;
                let isFullscreennewVideoMuteBtn.disabled = falsetrue;
                // Use webkitbeginfullscreen event to detect full screen mode in iOS Safari
    try {
                        if video.addEventListener("webkitbeginfullscreen", function () {
(newVideoMuteBtn.innerText === "mute") {
                           isFullscreen =await onMute(true);
                  });          newVideoMuteBtn.innerText = "unmute";
     
                   } else if video.addEventListener("pause", function ((newVideoMuteBtn.innerText === "unmute") {
                        if    await onMute(needRestartfalse) {;
                        console.log("Media paused after fullscreen, continue...")newVideoMuteBtn.innerText = "mute";
                        video.play();}
                    } finally {
  needRestart = false;
                    }newVideoMuteBtn.disabled else= {false;
                    }
    console.log("Media paused by click, continue...");
        });
                videovideoMuteDisplay.playappendChild(newVideoMuteBtn);
                    }

            if    });(videoElement) {
                videovideoElement.addEventListener("webkitendfullscreen", function () {
remove();
                videoElement = null;
        video.play();
    }

            if (!remoteVideoTrack) {
  needRestart = true;
            return;
        isFullscreen = false;
  }

            videoElement  }= document.createElement("video");
            },

3.6. Добавление информации о дорожке в ABR

setVideoABRTrack() code

Code Block
languagejs
themeRDark
hideItem(videoElement);
             setVideoABRTrack: function(track) {videoElement.setAttribute("style", "display:none; border: solid; border-width: 1px");

            const stream = new abr.setTrackMediaStream(track);

            },

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

setTrackInfo() code

Здесь:

  • настраиваются кнопки переключения качества
  • информация о заявленных качествах публикации добавляется в ABR
  • скрываются или отображаются элементы для показа текущего качества и источника видео/аудио
Code Block
languagejs
themeRDark
streamDisplay.appendChild(videoElement);
            videoElement.srcObject = stream;
            videoElement.onloadedmetadata setTrackInfo:= function (trackInfoe) {
                if videoElement.play(trackInfo);
   { 
        };
            ifvideoElement.addEventListener("resize", function (trackInfo.qualityevent) {
                showItem(resolutionLabel);
        showItem(qualitySwitchDisplay);
        if (videoElement) {
              if (abr.isEnabled()) {
    resolutionLabel.innerText = videoElement.videoWidth + "x" + videoElement.videoHeight;
                  const autoDiv = createQualityButton("Auto", qualityDivs, qualitySwitchDisplayresizeVideo(event.target);
                    onResize();
        autoDiv.style.color = QUALITY_COLORS.SELECTED;
        }
            });
            autoDivstream.addEventListener('click', function() {addTrack(remoteVideoTrack);
               if (Browser().isSafariWebRTC()) {
                 setQualityButtonsColor(qualityDivsvideoElement.setAttribute("playsinline", "");
                videoElement.setAttribute("webkit-playsinline", "");
               autoDiv.style.color = QUALITY_COLORS.SELECTED setWebkitEventHandlers(videoElement);
            } else {
                  abr.setAutosetEventHandlers(videoElement);
            }
        },
        ...
    }
});
     

12.9. Удаление видео элемента

removeVideoSource() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
    return {
        }...
        removeVideoSource: function () {
            if for (letvideoElement) i{
 = 0; i < trackInfo.quality.length; i++) {
         videoElement.remove();
                videoElement   abr.addQuality(trackInfo.quality[i]);
   = null;
            }
            if const qualityDiv = createQualityButton(trackInfo.quality[i], qualityDivs, qualitySwitchDisplay);
(this.muteButton) {
                this.muteButton.remove();
                qualityDiv.addEventListener('click', function() {this.muteButton = null;
            }
            hideItem(resolutionLabel);
        console.log("Clicked on quality " + trackInfo.quality[i] +trackNameDisplay.innerText = "track trackIdnot set" + trackInfo.id);
        },
        ...
    }
}

12.10. Отображение видео элемента и информации о видео дорожке

showVideoTrack() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
     if (qualityDiv.style.color === QUALITY_COLORS.UNAVAILABLE) {
    return {
        ...
        showVideoTrack: function    (track) {
          return;
  if (videoElement) {
                showItem(videoElement);
            }
            for (const [mid, btn] of trackButtons.entries()) {
              setQualityButtonsColor(qualityDivs);
  if (mid === track.mid) {
                          qualityDivbtn.style.color = QUALITY_COLORS.SELECTED;
                } else if (btn.style.color === QUALITY_COLORS.SELECTED) {
          abr.setManual();
          btn.style.color = QUALITY_COLORS.AVAILABLE;
                }
    abr.setQuality(trackInfo.quality[i]);
        }
            trackNameDisplay.innerText = "Current video track: "   })+ track.mid;
            showItem(trackNameDisplay);
        },
    }    ...
    }
}

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

updateQuality() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    ...
     }return else {
        ...
        updateQuality: function (qualityName,      hideItem(qualitySwitchDisplay);available) {
            const value       }= qualityButtons.get(qualityName);
                    if (trackInfo.typevalue) {
                const qualityButton = value.btn;
     contentType = trackInfo.contentType || "";
       value.available = available;
                if (trackInfoqualityButton.style.typecolor == "VIDEO" && displayOptions.type && contentType !== ""= QUALITY_COLORS.SELECTED) {
                            showItem(videoTypeDisplay)return;
                            videoTypeDisplay.innerHTML = contentType;}
                if        }
     (available) {
                   if (trackInfo.type == "AUDIO") {qualityButton.style.color = QUALITY_COLORS.AVAILABLE;
                } else {
          audioStateButton.setContentType(contentType);
          qualityButton.style.color = QUALITY_COLORS.UNAVAILABLE;
            }
        }
            }
                },
        ...
      }
},

...

12.

...

12. Добавление

...

кнопки выбора качества

addQuality() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
    updateQualityInfo: function(videoQuality)...
    return {
        ...
        addQuality: function showItem(qualitySwitchDisplay);
     (qualityName, available, onPickQuality) {
            const qualityButton = document.createElement("button");
           for (const qualityInfo of videoQuality) {qualityButtons.set(qualityName, {btn: qualityButton, available: available});
            qualityButton.innerText = qualityName;
      let qualityColor = QUALITY_COLORS.UNAVAILABLE;
   qualityButton.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
            if (qualityInfo.available === true) {
                        qualityColor qualityButton.style.color = QUALITY_COLORS.AVAILABLE;
            } else {
      }
          qualityButton.style.color = QUALITY_COLORS.UNAVAILABLE;
        for (const qualityDiv of qualityDivs) {}
            qualityDisplay.appendChild(qualityButton);
            if (qualityDiv.innerText === qualityInfo.quality)qualityButton.addEventListener('click', async function () {
                console.log("Clicked on quality button " + qualityName);
                if (qualityButton.style.color === QUALITY_COLORS.SELECTED || qualityDivqualityButton.style.color == qualityColor;
   = QUALITY_COLORS.UNAVAILABLE || !videoElement) {
                         breakreturn;
                }
        }
        lock();
            }
    onPickQuality().finally(() => unlock());
              abr.setQualityAvailable(qualityInfo.quality, qualityInfo.available});
                }},
        ...
      }
},

...

12.

...

13. Нажатие на кнопку выбора качества

pickQuality() code

Code Block
languagejs
themeRDark
const createVideoPlayer = function (participantDiv) {
    
   dispose: function() ...
    return {
        ...
        abr.stop();pickQuality: function (qualityName) {
            repickQuality(qualityName);
      cell.remove();
  }
        ...
   },

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

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

Здесь:

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

12.14. Завершение работы плеера

dispose() code

Code Block
languagejs
themeRDark
const createVideoPlayer   peerConnection.ontrack == function ({transceiver}) =>participantDiv) {
    
    let rParticipant;...
    return {
   console.log("Attach remote track " + transceiver.receiver.track.id + " kind " + transceiver.receiver.track.kind + " mid " + transceiver.mid);.
        dispose: function () {
            streamDisplay.remove();
        },
        for (const [nickName, participant] of Object.entries(remoteParticipants))...
    }
}

13. Получение дорожки из комнаты для отображения

remoteTrackProvider() code

Code Block
languagejs
themeRDark
const remoteTrackProvider = function (room) {
    return {
       for (constgetVideoTrack: pTrackasync offunction participant.tracks() {
            return await   consoleroom.loggetRemoteTrack("Participant VIDEO" + participant.nickName + " track " + pTrack.id + " mid " + pTrack.mid, false);
        },
        if (pTrack.mid === transceiver.midgetAudioTrack: async function () {
            return await room.getRemoteTrack("AUDIO", true);
      rParticipant = participant;}
    }
}

14. Вспомогательные функции

14.1. Изменение размера видео под размеры плеера

resizeVideo(), downScaleToFitSize() code

Code Block
languagejs
themeRDark
const resizeVideo = function (video, width, height) {
    // TODO: fix
   break;
 if (video) {
        return;
     }
      if (!video.parentNode) {
    }
    return;
    }
    if (rParticipantvideo instanceof HTMLCanvasElement) {
        video.videoWidth = video.width;
      break;
    video.videoHeight =  video.height;
     }
    const display   }= video.parentNode;
    const parentSize = {
 if (rParticipant) {
     w: display.parentNode.clientWidth,
      for  (consth: display of rParticipant.displays) {.parentNode.clientHeight
    };
    let newSize;
       if (transceiver.receiver.track.kind === "video"width && height) {
        newSize = downScaleToFitSize(width, height, parentSize.w, parentSize.h);
       if (display.videoMid === transceiver.mid)} else {
        newSize = downScaleToFitSize(video.videoWidth, video.videoHeight,    parentSize.w, parentSize.h);
    }
    display.style.width let= streamnewSize.w = new MediaStream()+ "px";
    display.style.height = newSize.h + "px";

    //vertical align
    let margin      stream.addTrack(transceiver.receiver.track)= 0;
    if (parentSize.h - newSize.h > 1) {
        margin = Math.floor((parentSize.h - newSize.h)  display.setVideoABRTrack(transceiver.receiver.track/ 2);
    }
    display.style.margin = margin + "px auto";
           display.setVideo(stream);
                        break;
                    }
                } else if (transceiver.receiver.track.kind === "audio"console.log("Resize from " + video.videoWidth + "x" + video.videoHeight + " to " + display.offsetWidth + "x" + display.offsetHeight);
}

const downScaleToFitSize = function (videoWidth, videoHeight, dstWidth, dstHeight) {
    var newWidth, newHeight;
    var videoRatio = videoWidth / videoHeight;
    var dstRatio = dstWidth / dstHeight;
    if (dstRatio > videoRatio) {
        newHeight = dstHeight;
        newWidth = if (display.audioMid === transceiver.mid) {Math.floor(videoRatio * dstHeight);
    } else {
        newWidth = dstWidth;
        letnewHeight stream = new MediaStream(= Math.floor(dstWidth / videoRatio);
    }
    return {
        w: newWidth,
      stream.addTrack(transceiver.receiver.track);
  h: newHeight
    };
}

14.2. Создание элемента для отображения текстовой информации

createInfoDisplay() code

Code Block
languagejs
themeRDark
const createInfoDisplay = function       (parent, text) {
    const div = displaydocument.setAudiocreateElement(stream"div");
    if (text) {
        div.innerHTML = text;
    }
    div.setAttribute("style",  break"width:auto; height:30px;");
    div.setAttribute("class", "text-center");
    if (parent) {
         }parent.appendChild(div);
    }
    return div;
}

14.3. Создание элемента-контейнера

createContainer() code

Code Block
languagejs
themeRDark
const createContainer = function (parent) {
  }
  const div = document.createElement("div");
    div.setAttribute("style",   }"width:auto; height:auto;");
        } else {div.setAttribute("class", "text-center");
    if (parent) {
      console.warn("Failed to find participant for track " + transceiver.receiver.track.id parent.appendChild(div);
        }
    return div;
}

5. Остановка воспроизведения

...

14.4. Скрытие и отображение элемента на странице

showItem(), hideItem() code

Code Block
languagejs
themeRDark
    const stopshowItem = function (tag) {
        for (const [nickName, participant] of Object.entries(remoteParticipants)if (tag) {
            participant.displays.forEach(function(display){
  tag.style.display = "block";
    }
}

const hideItem = function (tag) {
    if display.dispose(tag);
            }); {
        tag.style.display    delete remoteParticipants[nickName]= "none";
        }
    }