Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 4 Next »

В модуль display.js вынесены функции создания и удаления HTML5 элементов для захвата и отображения видео

Исходный код модуля

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

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

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

initLocalDisplay() code

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

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. Добавление элементов для захвата и отображения локального видео/аудио

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

add() code

Здесь:

  • добавляется аудио дорожка к video элементу
  • создается обработчик события onended для видео дорожки
  • добавляется обработчик нажатия кнопки включения/отключения аудио
        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

Здесь:

  • создается контейнер для элементов отображения локального видео
  • создается элемент для отображения информации о публикуемом видео
        const coreDisplay = createContainer(null);
        coreDisplay.id = stream.id;
        const publisherNameDisplay = createInfoDisplay(coreDisplay, name + " " + type);

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

add() code

Здесь:

  • создается кнопка для включения/отключения локального аудио
        const audioStateDisplay = document.createElement("button");
        coreDisplay.appendChild(audioStateDisplay);

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

add() code

Здесь:

  • создается элемент-контейнер, размеры которого можно менять в зависимости от размеров родительского элемента
  • создается HTML5 video  элемент, с учетом публикации в Safari
        const streamDisplay = createContainer(coreDisplay);
        streamDisplay.id = "stream-" + id;
        const video = document.createElement("video");
        video.muted = true;
        if(Browser().isSafariWebRTC()) {
            video.setAttribute("playsinline", "");
            video.setAttribute("webkit-playsinline", "");
        }
        streamDisplay.appendChild(video);
        video.srcObject = stream;

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

add() code

Здесь:

  • запускается проигрывание локального видео
  • настраивается обработчик события onended  для видео дорожки
  • настраивается обработчик события onresize  для локального видео, в котором размеры видео меняются под размеры контейнера
        video.onloadedmetadata = function (e) {
            video.play();
        };
        stream.getTracks().forEach(function(track){
            track.addEventListener("ended", function() {
                video.srcObject.removeTrack(track);
                //check video element has no tracks left
                for (const [key, vTrack] of Object.entries(video.srcObject.getTracks())) {
                    if (vTrack.readyState !== "ended") {
                        return;
                    }
                }
                removeLocalDisplay(id);
            });
        });
        if (stream.getVideoTracks().length > 0) {
            // Resize only if video displayed
            video.addEventListener('resize', function (event) {
                publisherNameDisplay.innerHTML = name + " " + type + " " + video.videoWidth + "x" + video.videoHeight;
                resizeVideo(event.target);
            });
        } else {
            // Hide audio only container
            hideItem(streamDisplay);
            // Set up mute button for audio only stream
            audioStateDisplay.innerHTML = audioStateText(stream) + " " + type;
            audioStateDisplay.addEventListener("click", function() {
                onMuteClick(audioStateDisplay, stream, type);
            });
        }

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

add() code

        localDisplays[id] = coreDisplay;
        localDisplayDiv.appendChild(coreDisplay);
        return coreDisplay;

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

stop() code

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

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

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

initRemoteDisplay() code

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

/*
display options:
autoAbr     - choose abr by default
quality     - show quality buttons
showAudio   - show audio elements
 */
const initRemoteDisplay = function (room, div, displayOptions, abrOptions, meetingController, meetingModel, meetingView, participantFactory) {
    // Validate options first
    if (!div) {
        throw new Error("Main div to place all the media tag is not defined");
    }
    if (!room) {
        throw new Error("Room is not defined");
    }

    const dOptions = displayOptions || {quality: true, type: true, showAudio: false};
    let abrFactory;
    if (abrOptions) {
        abrFactory = abrManagerFactory(room, abrOptions);
    }
    participantFactory.abrFactory = abrFactory;
    participantFactory.displayOptions = dOptions;
    return meetingController(room, meetingModel(meetingView(div), participantFactory));
}

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

abrManagerFactory() code

const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            ...
            return abr;
        }
    }
}

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

createAbrManager() code

const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                track: null,
                interval: abrOptions.interval,
                thresholds: abrOptions.thresholds,
                qualities: [],
                currentQualityName: null,
                statTimer: null,
                paused: false,
                manual: false,
                keepGoodTimeout: abrOptions.abrKeepOnGoodQuality,
                keepGoodTimer: null,
                tryUpperTimeout: abrOptions.abrTryForUpperQuality,
                tryUpperTimer: null,
                ...
            }
            return abr;
        }
    }
}

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

abr.start() code

const abrManagerFactory = function (room, abrOptions) {
    return {
        createAbrManager: function () {
            let abr = {
                ...
                start: function () {
                    this.stop();
                    console.log("Start abr interval")
                    if (abr.interval) {
                        const thresholds = Thresholds();
                        for (const threshold of abr.thresholds) {
                            thresholds.add(threshold.parameter, threshold.maxLeap);
                        }
                        abr.statsTimer = setInterval(() => {
                            if (abr.track) {
                                room.getStats(abr.track.track, constants.SFU_RTC_STATS_TYPE.INBOUND, (stats) => {
                                    if (thresholds.isReached(stats)) {
                                        abr.shiftDown();
                                    } else {
                                        abr.useGoodQuality();
                                    }
                                });
                            }
                        }, abr.interval);
                    }
                },
                ...
            }
            return abr;
        }
    }
}

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

abr.stop() code

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

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

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

abr.addQuality() code

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

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

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

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

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

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

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

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

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

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

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

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

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

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

const createDefaultMeetingController = function (room, meetingModel) {
    ...

    return {
        stop: stop
    }
}

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

createDefaultMeetingController() code

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

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

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

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

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

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

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

const createDefaultMeetingController = function (room, meetingModel) {
    ...
    const stop = function () {
        meetingModel.end();
    };

    return {
        stop: stop
    }
}

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

createDefaultMeetingModel() code

const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    ...
}

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

addParticipant() code

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

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

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

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

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

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

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

const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
    return {
        ...
        setMeetingName: function (id) {
            this.meetingName = id;
            meetingView.setMeetingName(id);
        }
    }
}
  • No labels