Перейти к содержанию

HLS VideoJS Player

Описание

Данный пример демонстрирует возможности WCS по преобразованию опубликованного на сервере потока в HLS и воспроизведению его в браузере при помощи библиотеки VideoJS. Нарезка потока в HLS запускается автоматически, при обращении к потоку, опубликованному на сервере, по HLS URL, например, для потока на скриншоте ниже https://test1.flashphoner.com:8445/test/test.m3u8

Пример также позволяет выбрать версию VideoJS для проигрывания. Версия 8, в отличие от версии 7, поддерживает Low Latency HLS, если сервер отдает поток в этом формате

Начиная со сборки 2.0.244, пример поддерживает следующие параметры:

  • version - используемая версия VideoJS: videojs7 или videojs8
  • src - полный HLS URL потока для проигрывания, должен быть закодирован при помощи URI encode, например https%3A%2F%2Ftest1.flashphoner.com%3A8445%2Ftest%2Ftest.m3u8
  • autoplay - автоматически запустить проигрывание указанного HLS URL, при этом все поля ввода и кнопки скрываются: false (по умолчанию) или true

Пример открытия плеера с параметрами, как на скриншоте выше (ссылка в поле Permalink)

https://test1.flashphoner.com:8444/client2/examples/demo/streaming/hls-player/hls-player.html?version=videojs7&src=https%3A%2F%2Ftest1.flashphoner.com%3A8445%2Ftest-HLS-ABR-STREAM%2Ftest-HLS-ABR-STREAM.m3u8

Пример вызова плеера с автозапуском

https://test1.flashphoner.com:8444/client2/examples/demo/streaming/hls-player/hls-player.html?version=videojs7&src=https%3A%2F%2Ftest1.flashphoner.com%3A8445%2Ftest-HLS-ABR-STREAM%2Ftest-HLS-ABR-STREAM.m3u8&autoplay=true

При этом воспроизведение потока будет запущено автоматически, с отключенным звуком. Для включения звука зритель должен использовать кнопку регулятора громкости в интерфейсе плеера.

Код примера

Код данного примера находится на сервере по следующему пути:

/usr/local/FlashphonerWebCallServer/client2/examples/demo/streaming/hls-player
  • hls-player.css - файл стилей страницы с плеером
  • hls-player.html - страница с плеером
  • hls-player.js - скрипт, обеспечивающий запуск плеера
  • player-page.html - общие элементы страницы плеера для трех примеров воспроизведения HLS

В подкаталогах videojs7 и videojs8 находятся, соответственно, две версии VideoJS:

  • video.js - скрипт, обеспечивающий работу плеера (http://videojs.com/, Apache License Version 2.0)
  • video.min.js - скрипт, обеспечивающий работу плеера (минимизированная версия)
  • videojs-contrib-quality-levels.js - плагин для переключения качества (только VideoJS 7)
  • video-js.css - файл стилей HLS-плеера

Тестировать данный пример можно по следующему адресу:

https://host:8444/client2/examples/demo/streaming/hls-player/hls-player.html

Здесь host - адрес вашего WCS-сервера.

Работа с кодом примера

Для разбора кода возьмем версию файла hls-player.js с хешем b19f637, которая находится здесь и доступна для скачивания в соответствующей сборке 2.0.248.

1. Загрузка страницы плеера

code

const loadPlayerPage = function() {
    if (videojsVersion) {
        hideItem("videojsInputForm");
        loadVideoJS(videojsVersion);
    } else {
        if (autoplay) {
            console.warn("No VideoJS version set, autoplay disabled");
            autoplay = false;
        }
        let videojsInput = document.getElementById("videojsInput");
        for (videojsType in VIDEOJS_VERSION_TYPE) {
            let option = document.createElement("option");
            let videojsFolder = "";
            switch (videojsType) {
                case 'VIDEOJS7':
                    videojsFolder = VIDEOJS_VERSION_TYPE.VIDEOJS7;
                    break;
                case 'VIDEOJS8':
                    videojsFolder = VIDEOJS_VERSION_TYPE.VIDEOJS8;
                    break;
            }
            option.text = videojsFolder;
            option.value = videojsFolder;
            videojsInput.appendChild(option);
        }

        setHandler("videojsBtn", "click", onVideojsBtnClick);
    }
}

2. Загрузка выбранной версии VideoJS

2. Loading the VideoJS chosen version

code

const loadVideoJS = function(version) {
    if (version) {
        videojsVersion = version;
        let playerPage = document.getElementById("playerPage");
        loadFile(version + "/video.js", "text/javascript").then( data  => {
            console.log("HLS library loaded successfully", data);
            loadStyles(version, playerPage);
        }).catch( err => {
            setText("videojsError", "Can't load VideoJS library");
            console.error(err);
        });
    }
}

3. Загрузка стилей для выбранной версии VideoJS

code

const loadStyles = function(version, playerPage) {
    if (version) {
        loadFile(version + "/video-js.css", "stylesheet").then ( data => {
            console.log("HLS library stylesheet loaded successfully", data);
            if (version === VIDEOJS_VERSION_TYPE.VIDEOJS7) {
                loadQualityPlugin(version, playerPage);
            } else {
                hideItem("videojsInputForm");
                loadPage("player-page.html", "playerPage", initPage);
            }
        }).catch( err => {
            playerPage.innerHTML = "Can't load VideoJS library stylesheet";
            playerPage.setAttribute("class", "text-danger");
            console.error(err);
        });
    }
}

4. Загрузка плагина переключения качества для VideoJS 7

code

const loadQualityPlugin = function(version, playerPage) {
    if (version) {
        loadFile(version + "/videojs-contrib-quality-levels.js", "text/javascript").then( data => {
            console.log("HLS quality levels plugin loaded successfully", data);
            hideItem("videojsInputForm");
            loadPage("player-page.html", "playerPage", initPage);
        }).catch( err => {
            setText("videojsError", "Can't load VideoJS quality levels plugin");
            console.error(err);
        });
    }
}

5. Инициализация HTML-страницы плеера

code

const initPage = function() {
    if (playSrc) {
        setValue("fullLink", decodeURIComponent(playSrc));
    } else if (autoplay) {
        console.warn("No HLS URL set, autoplay disabled");
        autoplay = false;
    }
    let remoteVideo = document.getElementById('remoteVideo');
    if (autoplay) {
        // There should not be any visible item on the page unless player
        hideAllToAutoplay();
        // The player should use all available page width
        setUpPlayerItem(true);
        // The player should be muted to automatically start playback
        player = initVideoJsPlayer(remoteVideo, true);
        playBtnClick();
    } else {
        // No autoplay, all the forms and buttons should be visible
        setText("header", "HLS VideoJS Player Minimal");
        displayCommonItems();
        setUpButtons();
        enablePlaybackStats();
        // The player should have a maximum fixed size
        setUpPlayerItem(false);
        // The player can be unmuted because user should click Play button
        player = initVideoJsPlayer(remoteVideo, false);
    }
}

6. Инициализация плеера

videojs() code

Плееру передаются следующие параметры:

  • video - div-элемент, в котором должен быть проигран поток
  • playsinline: true - проигрывать видео на странице, не переключаясь в полноэкранный режим (игнорируется в iOS Safari)
  • playbackRates - список скоростей воспроизведения
  • liveui: true - включает интерфейс для перемотки (DVR)
  • liveTracker - настройка границы проигрывания живого потока
  • fill: true - масштабировать плеер под размеры div элемента
  • muted - включить или выключить (для autoplay) звук
  • html5.vhs.limitRenditionByPlayerDimensions: false - не ограничивать загружаемое качество размерами div элемента
const initVideoJsPlayer = function(video, muted) {
    let videoJsPlayer = null;
    if (video) {
        video.className = "video-js vjs-default-skin";
        videoJsPlayer = videojs(video, {
            playsinline: true,
            playbackRates: [0.1, 0.25, 0.5, 1, 1.5, 2],
            liveui: true,
            liveTracker: {
                trackingThreshold: LIVE_THRESHOLD,
                liveTolerance: LIVE_TOLERANCE
            },
            fill: true,
            muted: muted,
            html5: {
                vhs: {
                    limitRenditionByPlayerDimensions: false
                }
            }
        });
        console.log("Using VideoJs " + videojs.VERSION);
        if (Browser.isSafariWebRTC() && Browser.isiOS()) {
            // iOS hack when using standard controls to leave fullscreen mode
            let videoTag = getActualVideoTag();
            if(videoTag) {
                setWebkitFullscreenHandlers(videoTag, false);
            }
        }
    }
    return videoJsPlayer;
}

7. Формирование URL HLS-потока

encodeURIComponent() code

Если указаны ключ и токен авторизации, они будут включены в URL потока

const getVideoSrc = function(src) {
    let videoSrc = src;
    if (validateForm()) {
        let streamName = getValue('playStream');
        streamName = encodeURIComponent(streamName);
        videoSrc = getValue("urlServer") + '/' + streamName + '/' + streamName + '.m3u8';
        let key = getValue('key');
        let token = getValue("token");
        if (key.length > 0 && token.length > 0) {
            videoSrc += "?" + key + "=" + token;
        }
    }
    setValue("fullLink", videoSrc);
    return videoSrc;
}

8. Запуск плеера

player.on(), player.play() code

const playBtnClick = function() {
    let videoSrc = getVideoSrc(getValue("fullLink"));
    if (videoSrc) {
        player.on('loadedmetadata', function() {
            console.log("Play with VideoJs");
            player.play();
        });
        ...
        player.src({
            src: videoSrc,
            type: "application/vnd.apple.mpegurl"
        });
        onStarted();
    }
}

9. Обработка события playing

player.on() code

const playBtnClick = function() {
     let videoSrc = getVideoSrc(getValue("fullLink"));
     if (videoSrc) {
         ...
         player.on('playing', function() {
            console.log("playing event fired");
            displayPermalink(videoSrc);
            if (player.liveTracker) {
                if (!player.liveTracker.isLive()) {
                    // A cratch to display live UI for the first subscriber
                    liveUIDisplay();
                }
                if (player.liveTracker.atLiveEdge()) {
                    // Unlock backward buttons when seeked to live edge
                    toggleBackButtons(true);
                    // Stop live UI startup timer
                    stopLiveUITimer();
                }
            }
            initQualityLevels(player);
            displayQualitySwitch();
        });
        ...
    }
}

10. Включение интерфейса перемотки (DVR) для первого подписчика

player.liveTracker.seekToLiveEdge() code

const liveUIDisplay = function() {
    stopLiveUITimer()
    if (player && player.liveTracker) {
        liveUITimer = setInterval(function() {
            if (!player.liveTracker.isLive() && player.liveTracker.liveWindow() > LIVE_THRESHOLD) {
                // Live UI is not displayed yet, seek to live edge to display
                player.liveTracker.seekToLiveEdge();
            }
        }, LIVE_UI_INTERVAL)
    }
}

11. Получение списка доступных качеств

player.qualityLevels() code

const initQualityLevels = function(player) {
    if (player && !qualityLevels.length) {
        let playerQualityLevels = player.qualityLevels();
        if (playerQualityLevels) {
            let qualityDiv = document.getElementById("qualityBtns");
            let qualityLevel;
            for (let i = 0; i < playerQualityLevels.length; i++) {
                qualityLevel = QualityLevel(playerQualityLevels, playerQualityLevels[i].height, i, qualityDiv);
                qualityLevels.push(qualityLevel);
            }
            if (qualityLevels.length) {
                qualityLevel = QualityLevel(playerQualityLevels, QUALITY_AUTO, -1, qualityDiv);
                qualityLevels.push(qualityLevel);
            }
        }
    }
}

12. Действия по нажатию на кнопку перемотки назад

player.seekable(), player.currentTime() code

const backBtnClick = function(event) {
    if (player != null && player.liveTracker) {
        toggleBackButtons(false);
        let seekable = player.seekable();
        let backTime = -1;
        if (event.target.id.indexOf("10") !== -1) {
            backTime = player.currentTime() - 10;
        } else if (event.target.id.indexOf("30") !== -1) {
            backTime = player.currentTime() - 30;
        }
        if (backTime < 0) {
            backTime = seekable ? seekable.start(0) : player.currentTime();
        }
        player.currentTime(backTime);
    }
}

13. Действия по нажатию на кнопку Live

player.liveTracker.seekToLiveEdge() code

const liveBtnClick = function() {
    if (player != null && player.liveTracker) {
        player.liveTracker.seekToLiveEdge();
        toggleBackButtons(true);
    }
}

14. Действия по нажатию на кнопку выбора качества

player.qualityLevels().selectedIndex_, player.qualityLevels().trigger() code

const qualityBtnClick = function(button, playerQualityLevels, index) {
    if (playerQualityLevels && playerQualityLevels.length) {
        let currentIndex = playerQualityLevels.selectedIndex_;
        for (let i = 0; i < playerQualityLevels.length; i++) {
            let qualityLevel = playerQualityLevels[i];
            if (index === -1 || i === index) {
                qualityLevel.enabled = true;
            } else if (i === index) {
                qualityLevel.enabled = true;
                currentIndex = index;
            } else {
                qualityLevel.enabled = false;
            }
        }
        playerQualityLevels.selectedIndex_ = currentIndex;
        playerQualityLevels.trigger({ type: 'change', selectedIndex: currentIndex });
    }
    button.style.color = QUALITY_COLORS.SELECTED;
    qualityLevels.forEach(item => {
        if (item.button.id !== button.id) {
            item.button.style.color = QUALITY_COLORS.AVAILABLE
        }
    });
}

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

player.dispose() code

This method removes the div container tag where player was initialized from the page

const stopBtnClick = function() {
    if (player != null) {
        console.log("Stop VideoJS player");
        stopLiveUITimer();
        player.dispose();
    }
    onStopped();
}

16. Очистка списка качеств после остановки плеера

code

const disposeQualityLevels = function() {
    qualityLevels.forEach(level => {
        if (level.button) {
            level.button.remove();
        }
    });
    qualityLevels = [];
}

17. Создание нового div элемента и плеера в нем после остановки предыдущего плеера

code

const createRemoteVideo = function(parent) {
    let remoteVideo = document.createElement("video");
    remoteVideo.id = "remoteVideo";
    remoteVideo.controls="controls";
    remoteVideo.autoplay="autoplay";
    remoteVideo.type="application/vnd.apple.mpegurl";
    remoteVideo.className = "video-js vjs-default-skin";
    remoteVideo.setAttribute("playsinline","");
    remoteVideo.setAttribute("webkit-playsinline","");
    parent.appendChild(remoteVideo);
    player = initVideoJsPlayer(remoteVideo, autoplay);
}

18. Получение доступной статистики воспроизведения из HTML5 video элемента

HTML5Stats code

const PlaybackStats = function(interval) {
    const playbackStats = {
        interval: interval || STATS_INTERVAL,
        timer: null,
        stats: null,
        start: function() {
            let video = getActualVideoTag();

            playbackStats.stop();
            stats = HTML5Stats(video);
            playbackStats.timer = setInterval(playbackStats.displayStats, playbackStats.interval);
            setText("videoWidth", "N/A");
            setText("videoHeight", "N/A");
            setText("videoRate", "N/A");
            setText("videoFps", "N/A");
            showItem("stats");
        },
        stop: function() {
            if (playbackStats.timer) {
                clearInterval(playbackStats.timer);
                playbackStats.timer = null;
            }
            playbackStats.stats = null;
            hideItem("stats");
        },
        displayStats: function() {
            if (stats.collect()) {
                let width = stats.getWidth();
                let height = stats.getHeight();
                let bitrate = stats.getBitrate();
                let fps = stats.getFps();

                setText("videoWidth", width);
                setText("videoHeight", height);

                if (bitrate !== undefined) {
                    setText("videoRate", Math.round(bitrate));
                }
                if (fps !== undefined) {
                    setText("videoFps", fps.toFixed(1));
                }
            }
        }
    };
    return playbackStats;
}