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
При этом воспроизведение потока будет запущено автоматически, с отключенным звуком. Для включения звука зритель должен использовать кнопку регулятора громкости в интерфейсе плеера.
Код примера¶
Код данного примера находится на сервере по следующему пути:
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-плеера
Тестировать данный пример можно по следующему адресу:
Здесь host
- адрес вашего WCS-сервера.
Работа с кодом примера¶
Для разбора кода возьмем версию файла hls-player.js
с хешем b19f637
, которая находится здесь и доступна для скачивания в соответствующей сборке 2.0.248.
1. Загрузка страницы плеера¶
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¶
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¶
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¶
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-страницы плеера¶
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. Очистка списка качеств после остановки плеера¶
const disposeQualityLevels = function() {
qualityLevels.forEach(level => {
if (level.button) {
level.button.remove();
}
});
qualityLevels = [];
}
17. Создание нового div элемента и плеера в нем после остановки предыдущего плеера¶
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;
}