Initial Implementation of Dash Player

This commit is contained in:
PrestonN 2019-04-11 15:42:08 -04:00
parent df4b443ff4
commit 491180f762
18 changed files with 1886 additions and 27 deletions

View File

@ -15,6 +15,9 @@
<link rel="stylesheet" href="style/fontawesome-all.min.css">
<link rel="stylesheet" href="style/mediaelementplayer.css" />
<link rel="stylesheet" href="js/plugins/quality/quality.min.css" />
<link rel="stylesheet" href="js/plugins/loop/loop.css" />
<link rel="stylesheet" href="js/plugins/speed/speed.css" />
<link rel="stylesheet" href="js/plugins/context-menu/context-menu.css" />
<link rel="shortcut icon" href="icons/iconColor.ico" type="image/x-icon" />
<title>FreeTube Player</title>
</head>
@ -104,12 +107,6 @@
<i class="far fa-frown" style="font-size: 200px"></i>
</h2>
</div>
<div>
<video id='player' width="720" height="480">
<source data-quality='1080p' type="application/dash+xml" src="https://invidio.us/api/manifest/dash/[VIDEOID]" />
</video>
</div>
<div id='channelView'></div>
<div id='mainHeaderView'></div>
<div id='channelVideosView'></div>
@ -141,22 +138,14 @@
<script src="js/history.js"></script>
<script src="js/playlist.js"></script>
<script src="js/events.js"></script>
<script src="js/mediaelement-and-player.min.js"></script>
<script src="js/plugins/quality/quality.min.js"></script>
<script src="js/plugins/quality/quality-i18n.js"></script>
<script>
var player = new MediaElementPlayer('player', {
renderers: ['html5','native_dash'],
features: ['playpause', 'current', 'progress', 'duration', 'volume', 'loop', 'stop', 'contextmenu', 'fullscreen', 'quality'],
qualityText: 'Quality',
defaultQuality: '1080p',
// When using `MediaElementPlayer`, an `instance` argument
// is available in the `success` callback
success: function(mediaElement, originalNode, instance) {
console.log(mediaElement,originalNode,instance);
}
});
// "https://invidio.us/api/manifest/dash/3gbZJn8CHMk"
</script>
<script src="js/plugins/speed/speed.js"></script>
<script src="js/plugins/speed/speed-i18n.js"></script>
<script src="js/plugins/loop/loop.js"></script>
<script src="js/plugins/loop/loop-i18n.js"></script>
<script src="js/plugins/context-menu/context-menu.js"></script>
<script src="js/plugins/context-menu/context-menu-i18n.js"></script>
</html>

View File

@ -245,9 +245,9 @@ let fullscreenVideo = function (event) {
$(document).on('click', '#showComments', showComments);
$(document).on('click', '.videoPlayer', playPauseVideo);
// $(document).on('click', '.videoPlayer', playPauseVideo);
$(document).on('dblclick', '.videoPlayer', fullscreenVideo);
// $(document).on('dblclick', '.videoPlayer', fullscreenVideo);
$(document).on('keydown', videoShortcutHandler);

View File

@ -19,6 +19,8 @@
* File for functions related to videos.
*/
let checkedSettings = false;
/**
* Display the video player and play a video
*
@ -31,6 +33,7 @@ function playVideo(videoId, playlistId = '') {
let youtubedlFinished = false;
let invidiousFinished = false;
checkedSettings = false;
playerView.playerSeen = true;
playerView.firstLoad = true;
playerView.videoId = videoId;
@ -41,9 +44,11 @@ function playVideo(videoId, playlistId = '') {
playerView.video720p = undefined;
playerView.valid720p = true;
playerView.videoUrl = '';
playerView.videoDash = 'https://invidio.us/api/manifest/dash/' + videoId + '.mpd';
playerView.embededHtml = "<iframe width='560' height='315' src='https://www.youtube-nocookie.com/embed/" + videoId + "?rel=0' frameborder='0' allow='autoplay; encrypted-media' allowfullscreen></iframe>";
let videoHtml = '';
let player;
const checkSavedVideo = videoIsSaved(videoId);
@ -467,7 +472,30 @@ function clickMiniPlayer(videoId) {
}
function checkVideoSettings() {
let player = document.getElementById('videoPlayer');
//let player = document.getElementById('videoPlayer');
if (checkedSettings) {
return;
}
checkedSettings = true;
console.log('checking Settings');
let player = new MediaElementPlayer('player', {
features: ['playpause', 'current', 'progress', 'duration', 'volume', 'loop', 'stop', 'speed', 'quality', 'fullscreen'],
speeds: ['2', '1.75', '1.5', '1.25', '1', '0.75', '0.5', '0.25'],
defaultSpeed: '1',
qualityText: 'Quality',
defaultQuality: 'auto',
stretching: 'fill',
success: function(mediaElement, originalNode, instance) {
console.log(mediaElement,originalNode,instance);
}
});
return;
if (autoplay) {
player.play();

View File

@ -0,0 +1,107 @@
'use strict';
if (mejs.i18n.ca !== undefined) {
mejs.i18n.ca['mejs.fullscreen-off'] = 'Desconnectar pantalla completaa';
mejs.i18n.ca['mejs.fullscreen-on'] = 'Anar a pantalla completa';
mejs.i18n.ca['mejs.download-video'] = 'Descarregar vídeo';
}
if (mejs.i18n.cs !== undefined) {
mejs.i18n.cs['mejs.fullscreen-off'] = 'Vypnout režim celá obrazovka';
mejs.i18n.cs['mejs.fullscreen-on'] = 'Na celou obrazovku';
mejs.i18n.cs['mejs.download-video'] = 'Stáhnout video';
}
if (mejs.i18n.de !== undefined) {
mejs.i18n.de['mejs.fullscreen-off'] = 'Vollbildmodus beenden';
mejs.i18n.de['mejs.fullscreen-on'] = 'Vollbild';
mejs.i18n.de['mejs.download-video'] = 'Video herunterladen';
}
if (mejs.i18n.es !== undefined) {
mejs.i18n.es['mejs.fullscreen-off'] = 'Desconectar pantalla completa';
mejs.i18n.es['mejs.fullscreen-on'] = 'Ir a pantalla completa';
mejs.i18n.es['mejs.download-video'] = 'Descargar vídeo';
}
if (mejs.i18n.fa !== undefined) {
mejs.i18n.fa['mejs.fullscreen-off'] = 'تمام صفحه را خاموش کنید';
mejs.i18n.fa['mejs.fullscreen-on'] = 'برو تمام صفحه';
mejs.i18n.fa['mejs.download-video'] = 'دانلود فیلم';
}
if (mejs.i18n.fr !== undefined) {
mejs.i18n.fr['mejs.fullscreen-off'] = 'Quitter le mode plein écran';
mejs.i18n.fr['mejs.fullscreen-on'] = 'Afficher en plein écran';
mejs.i18n.fr['mejs.download-video'] = 'Télécharger la vidéo';
}
if (mejs.i18n.hr !== undefined) {
mejs.i18n.hr['mejs.fullscreen-off'] = 'Isključi puni zaslon';
mejs.i18n.hr['mejs.fullscreen-on'] = 'Uključi puni zaslon';
mejs.i18n.hr['mejs.download-video'] = 'Preuzmi video';
}
if (mejs.i18n.hu !== undefined) {
mejs.i18n.hu['mejs.fullscreen-off'] = 'Teljes képernyő kikapcsolása';
mejs.i18n.hu['mejs.fullscreen-on'] = 'Átlépés teljes képernyős módra';
mejs.i18n.hu['mejs.download-video'] = 'Videó letöltése';
}
if (mejs.i18n.it !== undefined) {
mejs.i18n.it['mejs.fullscreen-off'] = 'Disattivare lo schermo intero';
mejs.i18n.it['mejs.fullscreen-on'] = 'Attivare lo schermo intero';
mejs.i18n.it['mejs.download-video'] = 'Scaricare il video';
}
if (mejs.i18n.ja !== undefined) {
mejs.i18n.ja['mejs.fullscreen-off'] = '全画面をオフにする';
mejs.i18n.ja['mejs.fullscreen-on'] = '全画面にする';
mejs.i18n.ja['mejs.download-video'] = '動画をダウンロードする';
}
if (mejs.i18n.ko !== undefined) {
mejs.i18n.ko['mejs.fullscreen-off'] = '전체화면 해제';
mejs.i18n.ko['mejs.fullscreen-on'] = '전체화면 가기';
mejs.i18n.ko['mejs.download-video'] = '비디오 다운로드';
}
if (mejs.i18n.nl !== undefined) {
mejs.i18n.nl['mejs.fullscreen-off'] = 'Volledig scherm uitschakelen';
mejs.i18n.nl['mejs.fullscreen-on'] = 'Volledig scherm';
mejs.i18n.nl['mejs.download-video'] = 'Video downloaden';
}
if (mejs.i18n.pl !== undefined) {
mejs.i18n.pl['mejs.fullscreen-off'] = 'Wyłącz pełny ekran';
mejs.i18n.pl['mejs.fullscreen-on'] = 'Przejdź na pełny ekran';
mejs.i18n.pl['mejs.download-video'] = 'Pobierz wideo';
}
if (mejs.i18n.pt !== undefined) {
mejs.i18n.pt['mejs.fullscreen-off'] = 'Desligar ecrã completo';
mejs.i18n.pt['mejs.fullscreen-on'] = 'Ir para ecrã completo';
mejs.i18n.pt['mejs.download-video'] = 'Descarregar o vídeo';
}
if (mejs.i18n.ro !== undefined) {
mejs.i18n.ro['mejs.fullscreen-off'] = 'Opreşte ecranul complet';
mejs.i18n.ro['mejs.fullscreen-on'] = 'Treci la ecran complet';
mejs.i18n.ro['mejs.download-video'] = 'Descarcă fişierul video';
}
if (mejs.i18n.ru !== undefined) {
mejs.i18n.ru['mejs.fullscreen-off'] = 'Выключить полноэкранный режим';
mejs.i18n.ru['mejs.fullscreen-on'] = 'Перейти в полноэкранный режим';
mejs.i18n.ru['mejs.download-video'] = 'Скачать видео';
}
if (mejs.i18n.sk !== undefined) {
mejs.i18n.sk['mejs.fullscreen-off'] = 'Vypnúť celú obrazovku';
mejs.i18n.sk['mejs.fullscreen-on'] = 'Prejsť na celú obrazovku';
mejs.i18n.sk['mejs.download-video'] = 'Prevziať video';
}
if (mejs.i18n.sv !== undefined) {
mejs.i18n.sv['mejs.fullscreen-off'] = 'Stäng av Fullskärmläge';
mejs.i18n.sv['mejs.fullscreen-on'] = 'Visa i Fullskärmsläge';
mejs.i18n.sv['mejs.download-video'] = 'Ladda ner Video';
}
if (mejs.i18n.uk !== undefined) {
mejs.i18n.uk['mejs.fullscreen-off'] = 'Вимкнути повноекранний режим';
mejs.i18n.uk['mejs.fullscreen-on'] = 'Увійти в повноекранний режим';
mejs.i18n.uk['mejs.download-video'] = 'Скачати відео';
}
if (mejs.i18n.zh !== undefined) {
mejs.i18n.zh['mejs.fullscreen-off'] = '關閉全屏';
mejs.i18n.zh['mejs.fullscreen-on'] = '轉向全屏';
mejs.i18n.zh['mejs.download-video'] = '下載視頻';
}
if (mejs.i18n['zh-CN'] !== undefined) {
mejs.i18n['zh-CN']['mejs.fullscreen-off'] = '关闭全屏';
mejs.i18n['zh-CN']['mejs.fullscreen-on'] = '转向全屏';
mejs.i18n['zh-CN']['mejs.download-video'] = '下载视频';
}

View File

@ -0,0 +1,34 @@
.mejs__contextmenu,
.mejs-contextmenu {
background: #fff;
border: solid 1px #999;
border-radius: 4px;
left: 0;
padding: 10px;
position: absolute;
top: 0;
width: 150px;
z-index: 9999999999; /* make sure it shows on fullscreen */
}
.mejs__contextmenu-separator,
.mejs-contextmenu-separator {
background: #333;
font-size: 0;
height: 1px;
margin: 5px 6px;
}
.mejs__contextmenu-item,
.mejs-contextmenu-item {
color: #333;
cursor: pointer;
font-size: 12px;
padding: 4px 6px;
}
.mejs__contextmenu-item:hover,
.mejs-contextmenu-item:hover {
background: #2c7c91;
color: #fff;
}

View File

@ -0,0 +1,222 @@
'use strict';
// Translations (English required)
mejs.i18n.en['mejs.fullscreen-off'] = 'Turn off Fullscreen';
mejs.i18n.en['mejs.fullscreen-on'] = 'Go Fullscreen';
mejs.i18n.en['mejs.download-video'] = 'Download Video';
/*
* ContextMenu
*
*/
Object.assign(mejs.MepDefaults, {
contextMenuItems: [{
// demo of a fullscreen option
render (player) {
// check for fullscreen plugin
if (player.enterFullScreen === undefined) {
return null;
}
if (player.isFullScreen) {
return mejs.i18n.t('mejs.fullscreen-off');
} else {
return mejs.i18n.t('mejs.fullscreen-on');
}
},
click (player) {
if (player.isFullScreen) {
player.exitFullScreen();
} else {
player.enterFullScreen();
}
}
},
// demo of a mute/unmute button
{
render (player) {
if (player.media.muted) {
return mejs.i18n.t('mejs.unmute');
} else {
return mejs.i18n.t('mejs.mute');
}
},
click (player) {
if (player.media.muted) {
player.setMuted(false);
} else {
player.setMuted(true);
}
}
},
// separator
{
isSeparator: true
},
// demo of simple download video
{
render () {
return mejs.i18n.t('mejs.download-video');
},
click (player) {
window.location.href = player.media.currentSrc;
}
}]
}
);
Object.assign(MediaElementPlayer.prototype, {
isContextMenuEnabled: true,
contextMenuTimeout: null,
buildcontextmenu (player) {
if (!player.isVideo) {
return;
}
// create context menu
if (!document.querySelector(`.${player.options.classPrefix}contextmenu`)) {
player.contextMenu = document.createElement('div');
player.contextMenu.className = `${player.options.classPrefix}contextmenu`;
player.contextMenu.style.display = 'none';
document.body.appendChild(player.contextMenu);
}
// create events for showing context menu
player.container.addEventListener('contextmenu', (e) => {
if (player.isContextMenuEnabled && (e.keyCode === 3 || e.which === 3)) {
player.renderContextMenu(e);
e.preventDefault();
e.stopPropagation();
}
});
player.container.addEventListener('click', () => {
player.contextMenu.style.display = 'none';
});
player.contextMenu.addEventListener('mouseleave', () => {
player.startContextMenuTimer();
});
},
cleancontextmenu (player) {
player.contextMenu.remove();
},
enableContextMenu () {
this.isContextMenuEnabled = true;
},
disableContextMenu () {
this.isContextMenuEnabled = false;
},
startContextMenuTimer () {
const t = this;
t.killContextMenuTimer();
t.contextMenuTimer = setTimeout(() => {
t.hideContextMenu();
t.killContextMenuTimer();
}, 750);
},
killContextMenuTimer () {
let timer = this.contextMenuTimer;
if (timer !== null && timer !== undefined) {
clearTimeout(timer);
timer = null;
}
},
hideContextMenu () {
this.contextMenu.style.display = 'none';
},
renderContextMenu (event) {
// alway re-render the items so that things like "turn fullscreen on" and "turn fullscreen off" are always written correctly
let
t = this,
html = '',
items = t.options.contextMenuItems
;
for (let i = 0, total = items.length; i < total; i++) {
const item = items[i];
if (item.isSeparator) {
html += `<div class="${t.options.classPrefix}contextmenu-separator"></div>`;
} else {
const rendered = item.render(t);
// render can return null if the item doesn't need to be used at the moment
if (rendered !== null && rendered !== undefined) {
html += `<div class="${t.options.classPrefix}contextmenu-item" data-itemindex="${i}" id="element-${(Math.random() * 1000000)}">${rendered}</div>`;
}
}
}
// position and show the context menu
t.contextMenu.innerHTML = html;
const
width = t.contextMenu.offsetWidth,
height = t.contextMenu.offsetHeight,
x = event.pageX,
y = event.pageY,
doc = document.documentElement,
scrollLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0),
scrollTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0),
left = (x + width > window.innerWidth + scrollLeft) ? x - width : x,
top = (y + height > window.innerHeight + scrollTop) ? y - height : y
;
t.contextMenu.style.display = '';
t.contextMenu.style.left = `${left}px`;
t.contextMenu.style.top = `${top}px`;
// bind events
const contextItems = t.contextMenu.querySelectorAll(`.${t.options.classPrefix}contextmenu-item`);
for (let i = 0, total = contextItems.length; i < total; i++) {
// which one is this?
const
menuItem = contextItems[i],
itemIndex = parseInt(menuItem.getAttribute('data-itemindex'), 10),
item = t.options.contextMenuItems[itemIndex]
;
// bind extra functionality?
if (typeof item.show !== 'undefined') {
item.show(menuItem, t);
}
// bind click action
menuItem.addEventListener('click', () => {
// perform click action
if (typeof item.click !== 'undefined') {
item.click(t);
}
// close
t.contextMenu.style.display = 'none';
});
}
// stop the controls from hiding
setTimeout(() => {
t.killControlsTimer();
}, 100);
}
});

View File

@ -0,0 +1,65 @@
'use strict';
if (mejs.i18n.ca !== undefined) {
mejs.i18n.ca['mejs.loop'] = 'Commuta Bucle';
}
if (mejs.i18n.cs !== undefined) {
mejs.i18n.cs['mejs.loop'] = 'Přepnout smyčku';
}
if (mejs.i18n.de !== undefined) {
mejs.i18n.de['mejs.loop'] = 'Wiederholung (de-)aktivieren';
}
if (mejs.i18n.es !== undefined) {
mejs.i18n.es['mejs.loop'] = 'Alternar Repetición';
}
if (mejs.i18n.fa !== undefined) {
mejs.i18n.fa['mejs.loop'] = 'حلقه تعویض';
}
if (mejs.i18n.fr !== undefined) {
mejs.i18n.fr['mejs.loop'] = 'Répéter';
}
if (mejs.i18n.hr !== undefined) {
mejs.i18n.hr['mejs.loop'] = 'Uključi/isključi ponavljanje';
}
if (mejs.i18n.hu !== undefined) {
mejs.i18n.hu['mejs.loop'] = 'Húzza át a kapcsolót';
}
if (mejs.i18n.it !== undefined) {
mejs.i18n.it['mejs.loop'] = 'Passare il ciclo';
}
if (mejs.i18n.ja !== undefined) {
mejs.i18n.ja['mejs.loop'] = 'トグルループ';
}
if (mejs.i18n.ko !== undefined) {
mejs.i18n.ko['mejs.loop'] = '루프 토글';
}
if (mejs.i18n.nl !== undefined) {
mejs.i18n.nl['mejs.loop'] = 'Schakellus';
}
if (mejs.i18n.pl !== undefined) {
mejs.i18n.pl['mejs.loop'] = 'Zapętl';
}
if (mejs.i18n.pt !== undefined) {
mejs.i18n.pt['mejs.loop'] = 'Loop alternativo';
}
if (mejs.i18n.ro !== undefined) {
mejs.i18n.ro['mejs.loop'] = 'Comutați buclă';
}
if (mejs.i18n.ru !== undefined) {
mejs.i18n.ru['mejs.loop'] = 'Зациклить воспроизведение';
}
if (mejs.i18n.sk !== undefined) {
mejs.i18n.sk['mejs.loop'] = 'Prepínať slučku';
}
if (mejs.i18n.sv !== undefined) {
mejs.i18n.sv['mejs.loop'] = 'Repetera';
}
if (mejs.i18n.uk !== undefined) {
mejs.i18n.uk['mejs.loop'] = 'Повторювати';
}
if (mejs.i18n.zh !== undefined) {
mejs.i18n.zh['mejs.loop'] = '切換循環';
}
if (mejs.i18n['zh-CN'] !== undefined) {
mejs.i18n['zh-CN']['mejs.loop'] = '切换循环';
}

View File

@ -0,0 +1,13 @@
.mejs__loop-button > button,
.mejs-loop-button > button {
background: url('loop.svg') no-repeat transparent;
}
.mejs__loop-off > button,
.mejs-loop-off > button {
background-position: -20px 1px;
}
.mejs__loop-on > button,
.mejs-loop-on > button {
background-position: 0 1px;
}

View File

@ -0,0 +1,54 @@
'use strict';
/**
* Loop button
*
* This feature creates a loop button in the control bar to toggle its behavior. It will restart media once finished
* if activated
*/
// Translations (English required)
mejs.i18n.en['mejs.loop'] = 'Toggle Loop';
// Feature configuration
Object.assign(mejs.MepDefaults, {
/**
* @type {?String}
*/
loopText: null
});
Object.assign(MediaElementPlayer.prototype, {
/**
* Feature constructor.
*
* Always has to be prefixed with `build` and the name that will be used in MepDefaults.features list
* @param {MediaElementPlayer} player
*/
buildloop (player) {
const
t = this,
loopTitle = mejs.Utils.isString(t.options.loopText) ? t.options.loopText : mejs.i18n.t('mejs.loop'),
loop = document.createElement('div')
;
loop.className = `${t.options.classPrefix}button ${t.options.classPrefix}loop-button ${((player.options.loop) ? `${t.options.classPrefix}loop-on` : `${t.options.classPrefix}loop-off`)}`;
loop.innerHTML = `<button type="button" aria-controls="${t.id}" title="${loopTitle}" aria-label="${loopTitle}" tabindex="0"></button>`;
t.addControlElement(loop, 'loop');
// add a click toggle event
loop.addEventListener('click', () => {
player.options.loop = !player.options.loop;
if (player.options.loop) {
mejs.Utils.removeClass(loop, `${t.options.classPrefix}loop-off`);
mejs.Utils.addClass(loop, `${t.options.classPrefix}loop-on`);
} else {
mejs.Utils.removeClass(loop, `${t.options.classPrefix}loop-on`);
mejs.Utils.addClass(loop, `${t.options.classPrefix}loop-off`);
}
});
}
});

View File

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36.48 15.65" x="0" y="0" width="37" height="16"><defs><style>.cls-1{fill:#fff;}.cls-2{opacity:0.5;}</style></defs><title>4</title><g id="loop"><g id="loop_on" data-name="loop on"><path class="cls-1" d="M52.5,5.7a5.61,5.61,0,0,1-.77,7.9l-0.13.1a5.61,5.61,0,0,1-7.9-.77l-0.1-.13a5.63,5.63,0,0,1,.2-7.4L42.2,3.8A8,8,0,0,0,53.9,14.7a7.84,7.84,0,0,0,.2-10.6Z" transform="translate(-40.24 -1.7)"/><path class="cls-1" d="M49.6,2.5a0.64,0.64,0,0,1,.46-0.78l0.14,0h0.3L56,2.5c0.5,0.1.7,0.5,0.3,0.8l-5,5c-0.4.4-.8,0.3-0.9-0.3Z" transform="translate(-40.24 -1.7)"/></g><g id="loop_off" data-name="loop off" class="cls-2"><path class="cls-1" d="M72.7,5.9a5.61,5.61,0,0,1-.77,7.9l-0.13.1a5.7,5.7,0,0,1-8-.9A5.63,5.63,0,0,1,64,5.6L62.4,4.1A8,8,0,0,0,74.1,15a7.84,7.84,0,0,0,.2-10.6Z" transform="translate(-40.24 -1.7)"/><path class="cls-1" d="M69.8,2.8A0.64,0.64,0,0,1,70.26,2L70.4,2h0.3l5.5,0.8c0.5,0.1.7,0.5,0.3,0.8l-5,5c-0.4.4-.8,0.3-0.8-0.3Z" transform="translate(-40.24 -1.7)"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,250 @@
'use strict';
/**
* Preview feature
*
* This feature allows to create a preview effect on videos (playing on hover and with possibility of mute/fade-in/fade-out audio)
*/
// Feature configuration
Object.assign(mejs.MepDefaults, {
/**
* Media starts playing when users mouse hovers on it, and resets when leaving player area
* @type {Boolean}
*/
previewMode: false,
/**
* When playing on preview mode, turn on/off audio completely
* @type {Boolean}
*/
muteOnPreviewMode: true,
/**
* If fade in set in, time when it starts fading in
* @type {Number}
*/
fadeInAudioStart: 0,
/**
* When playing media, time interval to fade in audio (must be greater than zero)
* @type {Number}
*/
fadeInAudioInterval: 0,
/**
* If fade out set in, time when it starts fading out
* @type {Number}
*/
fadeOutAudioStart: 0,
/**
* When playing media, time interval to fade out audio (must be greater than zero)
* @type {Number}
*/
fadeOutAudioInterval: 0,
/**
* Percentage in decimals in which media will fade in/out (i.e., 0.02 = 2%)
* @type {Number}
*/
fadePercent: 0.02,
/**
* Whether reset or not the media
* @type {Boolean}
*/
pauseOnlyOnPreview: false,
/**
* Delay in milliseconds to start previewing media
* @type {Number}
*/
delayPreview: 0
});
Object.assign(MediaElementPlayer.prototype, {
/**
* Feature constructor.
*
* Always has to be prefixed with `build` and the name that will be used in MepDefaults.features list
* @param {MediaElementPlayer} player
*/
buildpreview (player) {
let
initFadeIn = false,
initFadeOut = false,
timeout,
mouseOver = false
;
const
t = this,
fadeInCallback = () => {
if (t.options.fadeInAudioInterval) {
if (Math.floor(t.media.currentTime) < t.options.fadeIntAudioStart) {
t.media.setVolume(0);
t.media.setMuted(true);
}
if (Math.floor(t.media.currentTime) === t.options.fadeInAudioStart) {
initFadeIn = true;
let
volume = 0,
audioInterval = t.options.fadeInAudioInterval,
interval = setInterval(() => {
// Increase volume by step as long as it is below 1
if (volume < 1) {
volume += t.options.fadePercent;
if (volume > 1) {
volume = 1;
}
// limit to 2 decimal places
t.media.setVolume(volume.toFixed(2));
} else {
// Stop firing interval when 1 is reached
clearInterval(interval);
interval = null;
t.media.setMuted(false);
setTimeout(() => {
initFadeIn = false;
}, 300);
}
}, audioInterval)
;
}
}
},
fadeOutCallback = () => {
if (t.options.fadeOutAudioInterval) {
if (Math.floor(t.media.currentTime) < t.options.fadeOutAudioStart) {
t.media.setVolume(1);
t.media.setMuted(false);
}
if (Math.floor(t.media.currentTime) === t.options.fadeOutAudioStart) {
initFadeOut = true;
let
volume = 1,
audioInterval = t.options.fadeOutAudioInterval,
interval = setInterval(() => {
// Increase volume by step as long as it is above 0
if (volume > 0) {
volume -= t.options.fadePercent;
if (volume < 0) {
volume = 0;
}
// limit to 2 decimal places
t.media.setVolume(volume.toFixed(2));
} else {
// Stop firing interval when 0 is reached
clearInterval(interval);
interval = null;
t.media.setMuted(false);
setTimeout(() => {
initFadeOut = false;
}, 300);
}
}, audioInterval)
;
}
}
}
;
if (t.options.muteOnPreviewMode || t.options.fadeInAudioInterval) {
t.media.setVolume(0);
t.media.setMuted(true);
} else if (t.options.fadeOutAudioInterval) {
t.media.setVolume(1);
t.media.setMuted(false);
}
// fade-in/out should be available for both video/audio
t.media.addEventListener('timeupdate', () => {
if (initFadeIn) {
t.media.removeEventListener('timeupdate', fadeInCallback);
return;
}
if (initFadeOut) {
t.media.removeEventListener('timeupdate', fadeOutCallback);
return;
}
fadeInCallback();
fadeOutCallback();
});
// preview is only for video
if (!player.isVideo) {
return;
}
// show/hide controls
document.body.addEventListener('mouseover', (e) => {
if (e.target === t.container || e.target.closest(`.${t.options.classPrefix}container`)) {
mouseOver = true;
t.container.querySelector(`.${t.options.classPrefix}overlay-loading`).parentNode.style.display = 'flex';
t.container.querySelector(`.${t.options.classPrefix}overlay-play`).style.display = 'none';
if (t.media.paused) {
timeout = setTimeout(() => {
if (mouseOver) {
t.media.play();
} else {
clearTimeout(timeout);
timeout = null;
}
t.container.querySelector(`.${t.options.classPrefix}overlay-loading`).parentNode.style.display = 'none';
}, t.options.delayPreview);
} else {
t.container.querySelector(`.${t.options.classPrefix}overlay-loading`).parentNode.style.display = 'none';
}
} else {
mouseOver = false;
clearTimeout(timeout);
timeout = null;
if (!t.media.paused) {
t.media.pause();
}
t.container.querySelector(`.${t.options.classPrefix}overlay-loading`).parentNode.style.display = 'none';
}
});
document.body.addEventListener('mouseout', (e) => {
if (!(e.target === t.container) && !(e.target.closest(`.${t.options.classPrefix}container`))) {
mouseOver = false;
t.container.querySelector(`.${t.options.classPrefix}overlay-loading`).parentNode.style.display = 'none';
if (!t.media.paused) {
t.media.pause();
if (!t.options.pauseOnlyOnPreview) {
t.media.setCurrentTime(0);
}
}
clearTimeout(timeout);
timeout = null;
}
});
window.addEventListener('scroll', () => {
mouseOver = false;
t.container.querySelector(`.${t.options.classPrefix}overlay-loading`).parentNode.style.display = 'none';
if (!t.media.paused) {
t.media.pause();
}
});
}
});

View File

@ -0,0 +1,65 @@
'use strict';
if (mejs.i18n.ca !== undefined) {
mejs.i18n.ca['mejs.speed-rate'] = 'Velocitat';
}
if (mejs.i18n.cs !== undefined) {
mejs.i18n.cs['mejs.speed-rate'] = 'Rychlost';
}
if (mejs.i18n.de !== undefined) {
mejs.i18n.de['mejs.speed-rate'] = 'Geschwindigkeitsrate';
}
if (mejs.i18n.es !== undefined) {
mejs.i18n.es['mejs.speed-rate'] = 'Velocidad';
}
if (mejs.i18n.fa !== undefined) {
mejs.i18n.fa['mejs.speed-rate'] = 'نرخ سرعت';
}
if (mejs.i18n.fr !== undefined) {
mejs.i18n.fr['mejs.speed-rate'] = 'Vitesse';
}
if (mejs.i18n.hr !== undefined) {
mejs.i18n.hr['mejs.speed-rate'] = 'Brzina reprodukcije';
}
if (mejs.i18n.hu !== undefined) {
mejs.i18n.hu['mejs.speed-rate'] = 'Sebesség';
}
if (mejs.i18n.it !== undefined) {
mejs.i18n.it['mejs.speed-rate'] = 'Velocità';
}
if (mejs.i18n.ja !== undefined) {
mejs.i18n.ja['mejs.speed-rate'] = '高速';
}
if (mejs.i18n.ko !== undefined) {
mejs.i18n.ko['mejs.speed-rate'] = '속도 속도';
}
if (mejs.i18n.nl !== undefined) {
mejs.i18n.nl['mejs.speed-rate'] = 'Snelheidsgraad';
}
if (mejs.i18n.pl !== undefined) {
mejs.i18n.pl['mejs.speed-rate'] = 'Prędkość';
}
if (mejs.i18n.pt !== undefined) {
mejs.i18n.pt['mejs.speed-rate'] = 'Taxa de velocidade';
}
if (mejs.i18n.ro !== undefined) {
mejs.i18n.ro['mejs.speed-rate'] = 'Viteză de viteză';
}
if (mejs.i18n.ru !== undefined) {
mejs.i18n.ru['mejs.speed-rate'] = 'Скорость воспроизведения';
}
if (mejs.i18n.sk !== undefined) {
mejs.i18n.sk['mejs.speed-rate'] = 'Rýchlosť';
}
if (mejs.i18n.sv !== undefined) {
mejs.i18n.sv['mejs.speed-rate'] = 'Hastighet';
}
if (mejs.i18n.uk !== undefined) {
mejs.i18n.uk['mejs.speed-rate'] = 'Швидкість відтворення';
}
if (mejs.i18n.zh !== undefined) {
mejs.i18n.zh['mejs.speed-rate'] = '速度';
}
if (mejs.i18n['zh-CN'] !== undefined) {
mejs.i18n['zh-CN']['mejs.speed-rate'] = '速度';
}

View File

@ -0,0 +1,94 @@
.mejs__speed-button,
.mejs-speed-button {
position: relative;
}
.mejs__speed-button > button,
.mejs-speed-button > button {
background: transparent;
color: #fff;
font-size: 11px;
line-height: normal;
margin: 11px 0 0;
width: 36px;
}
.mejs__speed-selector,
.mejs-speed-selector {
background: rgba(50, 50, 50, 0.7);
border: solid 1px transparent;
border-radius: 0;
height: 150px;
left: -10px;
overflow: hidden;
padding: 0;
position: absolute;
top: -100px;
visibility: hidden;
width: 60px;
}
.mejs__speed-selector,
.mejs-speed-selector {
visibility: visible;
}
.mejs__speed-selector-list,
.mejs-speed-selector-list {
display: block;
list-style-type: none !important;
margin: 0;
overflow: hidden;
padding: 0;
}
.mejs__speed-selector-list-item,
.mejs-speed-selector-list-item {
color: #fff;
display: block;
list-style-type: none !important;
margin: 0 0 6px;
overflow: hidden;
padding: 0 10px;
}
.mejs__speed-selector-list-item:hover,
.mejs-speed-selector-list-item:hover {
background-color: rgb(200, 200, 200) !important;
background-color: rgba(255, 255, 255, 0.4) !important;
}
.mejs__speed-selector-input,
.mejs-speed-selector-input {
clear: both;
float: left;
left: -1000px;
margin: 3px 3px 0 5px;
position: absolute;
}
.mejs__speed-selector-label,
.mejs-speed-selector-label {
color: white;
cursor: pointer;
float: left;
font-size: 11px;
line-height: 15px;
margin-left: 5px;
padding: 4px 0 0;
width: 60px;
}
.mejs__speed-selected,
.mejs-speed-selected {
color: rgba(33, 248, 248, 1);
}
.mejs__speed-selector,
.mejs-speed-selector {
visibility: hidden;
}
.mejs__speed-button:hover .mejs__speed-selector,
.mejs-speed-button:hover .mejs-speed-selector {
visibility: visible;
}

View File

@ -0,0 +1,221 @@
'use strict';
/**
* Speed button
*
* This feature creates a button to speed media in different levels.
*/
// Translations (English required)
mejs.i18n.en['mejs.speed-rate'] = 'Speed Rate';
// Feature configuration
Object.assign(mejs.MepDefaults, {
/**
* The speeds media can be accelerated
*
* Supports an array of float values or objects with format
* [{name: 'Slow', value: '0.75'}, {name: 'Normal', value: '1.00'}, ...]
* @type {{String[]|Object[]}}
*/
speeds: ['2.00', '1.50', '1.25', '1.00', '0.75'],
/**
* @type {String}
*/
defaultSpeed: '1.00',
/**
* @type {String}
*/
speedChar: 'x',
/**
* @type {?String}
*/
speedText: null
});
Object.assign(MediaElementPlayer.prototype, {
/**
* Feature constructor.
*
* Always has to be prefixed with `build` and the name that will be used in MepDefaults.features list
* @param {MediaElementPlayer} player
* @param {HTMLElement} controls
* @param {HTMLElement} layers
* @param {HTMLElement} media
*/
buildspeed (player, controls, layers, media) {
const
t = this,
isNative = t.media.rendererName !== null && /(native|html5)/i.test(t.media.rendererName)
;
if (!isNative) {
return;
}
const
speeds = [],
speedTitle = mejs.Utils.isString(t.options.speedText) ? t.options.speedText : mejs.i18n.t('mejs.speed-rate'),
getSpeedNameFromValue = (value) => {
for (let i = 0, total = speeds.length; i < total; i++) {
if (speeds[i].value === value) {
return speeds[i].name;
}
}
}
;
let
playbackSpeed,
defaultInArray = false
;
for (let i = 0, total = t.options.speeds.length; i < total; i++) {
const s = t.options.speeds[i];
if (typeof s === 'string') {
speeds.push({
name: `${s}${t.options.speedChar}`,
value: s
});
if (s === t.options.defaultSpeed) {
defaultInArray = true;
}
}
else {
speeds.push(s);
if (s.value === t.options.defaultSpeed) {
defaultInArray = true;
}
}
}
if (!defaultInArray) {
speeds.push({
name: t.options.defaultSpeed + t.options.speedChar,
value: t.options.defaultSpeed
});
}
speeds.sort((a, b) => {
return parseFloat(b.value) - parseFloat(a.value);
});
t.cleanspeed(player);
player.speedButton = document.createElement('div');
player.speedButton.className = `${t.options.classPrefix}button ${t.options.classPrefix}speed-button`;
player.speedButton.innerHTML = `<button type="button" aria-controls="${t.id}" title="${speedTitle}" ` +
`aria-label="${speedTitle}" tabindex="0">${getSpeedNameFromValue(t.options.defaultSpeed)}</button>` +
`<div class="${t.options.classPrefix}speed-selector ${t.options.classPrefix}offscreen">` +
`<ul class="${t.options.classPrefix}speed-selector-list"></ul>` +
`</div>`;
t.addControlElement(player.speedButton, 'speed');
for (let i = 0, total = speeds.length; i < total; i++) {
const inputId = `${t.id}-speed-${speeds[i].value}`;
player.speedButton.querySelector('ul').innerHTML += `<li class="${t.options.classPrefix}speed-selector-list-item">` +
`<input class="${t.options.classPrefix}speed-selector-input" type="radio" name="${t.id}_speed"` +
`disabled="disabled" value="${speeds[i].value}" id="${inputId}" ` +
`${(speeds[i].value === t.options.defaultSpeed ? ' checked="checked"' : '')}/>` +
`<label for="${inputId}" class="${t.options.classPrefix}speed-selector-label` +
`${(speeds[i].value === t.options.defaultSpeed ? ` ${t.options.classPrefix}speed-selected` : '')}">` +
`${speeds[i].name}</label>` +
`</li>`;
}
playbackSpeed = t.options.defaultSpeed;
player.speedSelector = player.speedButton.querySelector(`.${t.options.classPrefix}speed-selector`);
const
inEvents = ['mouseenter', 'focusin'],
outEvents = ['mouseleave', 'focusout'],
// Enable inputs after they have been appended to controls to avoid tab and up/down arrow focus issues
radios = player.speedButton.querySelectorAll('input[type="radio"]'),
labels = player.speedButton.querySelectorAll(`.${t.options.classPrefix}speed-selector-label`)
;
// hover or keyboard focus
for (let i = 0, total = inEvents.length; i < total; i++) {
player.speedButton.addEventListener(inEvents[i], () => {
mejs.Utils.removeClass(player.speedSelector, `${t.options.classPrefix}offscreen`);
player.speedSelector.style.height = player.speedSelector.querySelector('ul').offsetHeight;
player.speedSelector.style.top = `${(-1 * parseFloat(player.speedSelector.offsetHeight))}px`;
});
}
for (let i = 0, total = outEvents.length; i < total; i++) {
player.speedSelector.addEventListener(outEvents[i], function () {
mejs.Utils.addClass(this, `${t.options.classPrefix}offscreen`);
});
}
for (let i = 0, total = radios.length; i < total; i++) {
const radio = radios[i];
radio.disabled = false;
radio.addEventListener('click', function() {
const
self = this,
newSpeed = self.value
;
playbackSpeed = newSpeed;
media.playbackRate = parseFloat(newSpeed);
player.speedButton.querySelector('button').innerHTML = (getSpeedNameFromValue(newSpeed));
const selected = player.speedButton.querySelectorAll(`.${t.options.classPrefix}speed-selected`);
for (let i = 0, total = selected.length; i < total; i++) {
mejs.Utils.removeClass(selected[i], `${t.options.classPrefix}speed-selected`);
}
self.checked = true;
const siblings = mejs.Utils.siblings(self, (el) => mejs.Utils.hasClass(el, `${t.options.classPrefix}speed-selector-label`));
for (let j = 0, total = siblings.length; j < total; j++) {
mejs.Utils.addClass(siblings[j], `${t.options.classPrefix}speed-selected`);
}
});
}
for (let i = 0, total = labels.length; i < total; i++) {
labels[i].addEventListener('click', function () {
const
radio = mejs.Utils.siblings(this, (el) => el.tagName === 'INPUT')[0],
event = mejs.Utils.createEvent('click', radio)
;
radio.dispatchEvent(event);
});
}
//Allow up/down arrow to change the selected radio without changing the volume.
player.speedSelector.addEventListener('keydown', (e) => {
e.stopPropagation();
});
media.addEventListener('loadedmetadata', () => {
if (playbackSpeed) {
media.playbackRate = parseFloat(playbackSpeed);
}
});
},
/**
* Feature destructor.
*
* Always has to be prefixed with `clean` and the name that was used in MepDefaults.features list
* @param {MediaElementPlayer} player
*/
cleanspeed (player) {
if (player) {
if (player.speedButton) {
player.speedButton.parentNode.removeChild(player.speedButton);
}
if (player.speedSelector) {
player.speedSelector.parentNode.removeChild(player.speedSelector);
}
}
}
});

View File

@ -538,6 +538,7 @@ let playerView = new Vue({
firstLoad: true,
publishedDate: '',
videoUrl: '',
videoDash: '',
videoId: '',
channelId: '',
channelIcon: '',

708
src/js/videojs-dash.js Normal file
View File

@ -0,0 +1,708 @@
/*! @name videojs-contrib-dash @version 2.11.0 @license Apache-2.0 */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('dashjs'), require('video.js'), require('global/window'), require('global/document')) :
typeof define === 'function' && define.amd ? define(['dashjs', 'video.js', 'global/window', 'global/document'], factory) :
(global.videojsDash = factory(global.dashjs,global.videojs,global.window,global.document));
}(this, (function (dashjs,videojs,window,document) { 'use strict';
dashjs = dashjs && dashjs.hasOwnProperty('default') ? dashjs['default'] : dashjs;
videojs = videojs && videojs.hasOwnProperty('default') ? videojs['default'] : videojs;
window = window && window.hasOwnProperty('default') ? window['default'] : window;
document = document && document.hasOwnProperty('default') ? document['default'] : document;
/**
* Setup audio tracks. Take the tracks from dash and add the tracks to videojs. Listen for when
* videojs changes tracks and apply that to the dash player because videojs doesn't do this
* natively.
*
* @private
* @param {videojs} player the videojs player instance
* @param {videojs.tech} tech the videojs tech being used
*/
function handlePlaybackMetadataLoaded(player, tech) {
var mediaPlayer = player.dash.mediaPlayer;
var dashAudioTracks = mediaPlayer.getTracksFor('audio');
var videojsAudioTracks = player.audioTracks();
function generateIdFromTrackIndex(index) {
return "dash-audio-" + index;
}
function findDashAudioTrack(subDashAudioTracks, videojsAudioTrack) {
return subDashAudioTracks.find(function (_ref) {
var index = _ref.index;
return generateIdFromTrackIndex(index) === videojsAudioTrack.id;
});
} // Safari creates a single native `AudioTrack` (not `videojs.AudioTrack`) when loading. Clear all
// automatically generated audio tracks so we can create them all ourself.
if (videojsAudioTracks.length) {
tech.clearTracks(['audio']);
}
var currentAudioTrack = mediaPlayer.getCurrentTrackFor('audio');
dashAudioTracks.forEach(function (dashTrack) {
var localizedLabel;
if (Array.isArray(dashTrack.labels)) {
for (var i = 0; i < dashTrack.labels.length; i++) {
if (dashTrack.labels[i].lang && player.language().indexOf(dashTrack.labels[i].lang.toLowerCase()) !== -1) {
localizedLabel = dashTrack.labels[i];
break;
}
}
}
var label;
if (localizedLabel) {
label = localizedLabel.text;
} else if (Array.isArray(dashTrack.labels) && dashTrack.labels.length === 1) {
label = dashTrack.labels[0].text;
} else {
label = dashTrack.lang;
if (dashTrack.roles && dashTrack.roles.length) {
label += ' (' + dashTrack.roles.join(', ') + ')';
}
} // Add the track to the player's audio track list.
videojsAudioTracks.addTrack(new videojs.AudioTrack({
enabled: dashTrack === currentAudioTrack,
id: generateIdFromTrackIndex(dashTrack.index),
kind: dashTrack.kind || 'main',
label: label,
language: dashTrack.lang
}));
});
var audioTracksChangeHandler = function audioTracksChangeHandler() {
for (var i = 0; i < videojsAudioTracks.length; i++) {
var track = videojsAudioTracks[i];
if (track.enabled) {
// Find the audio track we just selected by the id
var dashAudioTrack = findDashAudioTrack(dashAudioTracks, track); // Set is as the current track
mediaPlayer.setCurrentTrack(dashAudioTrack); // Stop looping
continue;
}
}
};
videojsAudioTracks.addEventListener('change', audioTracksChangeHandler);
player.dash.mediaPlayer.on(dashjs.MediaPlayer.events.STREAM_TEARDOWN_COMPLETE, function () {
videojsAudioTracks.removeEventListener('change', audioTracksChangeHandler);
});
}
/*
* Call `handlePlaybackMetadataLoaded` when `mediaPlayer` emits
* `dashjs.MediaPlayer.events.PLAYBACK_METADATA_LOADED`.
*/
function setupAudioTracks(player, tech) {
// When `dashjs` finishes loading metadata, create audio tracks for `video.js`.
player.dash.mediaPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_METADATA_LOADED, handlePlaybackMetadataLoaded.bind(null, player, tech));
}
function find(l, f) {
for (var i = 0; i < l.length; i++) {
if (f(l[i])) {
return l[i];
}
}
}
/*
* Attach text tracks from dash.js to videojs
*
* @param {videojs} player the videojs player instance
* @param {array} tracks the tracks loaded by dash.js to attach to videojs
*
* @private
*/
function attachDashTextTracksToVideojs(player, tech, tracks) {
var trackDictionary = []; // Add remote tracks
var tracksAttached = tracks // Map input data to match HTMLTrackElement spec
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLTrackElement
.map(function (track) {
var localizedLabel;
if (Array.isArray(track.labels)) {
for (var i = 0; i < track.labels.length; i++) {
if (track.labels[i].lang && player.language().indexOf(track.labels[i].lang.toLowerCase()) !== -1) {
localizedLabel = track.labels[i];
break;
}
}
}
var label;
if (localizedLabel) {
label = localizedLabel.text;
} else if (Array.isArray(track.labels) && track.labels.length === 1) {
label = track.labels[0].text;
} else {
label = track.lang || track.label;
}
return {
dashTrack: track,
trackConfig: {
label: label,
language: track.lang,
srclang: track.lang,
kind: track.kind
}
};
}) // Add track to videojs track list
.map(function (_ref) {
var trackConfig = _ref.trackConfig,
dashTrack = _ref.dashTrack;
var remoteTextTrack = player.addRemoteTextTrack(trackConfig, false);
trackDictionary.push({
textTrack: remoteTextTrack.track,
dashTrack: dashTrack
}); // Don't add the cues becuase we're going to let dash handle it natively. This will ensure
// that dash handle external time text files and fragmented text tracks.
//
// Example file with external time text files:
// https://storage.googleapis.com/shaka-demo-assets/sintel-mp4-wvtt/dash.mpd
return remoteTextTrack;
});
/*
* Scan `videojs.textTracks()` to find one that is showing. Set the dash text track.
*/
function updateActiveDashTextTrack() {
var dashMediaPlayer = player.dash.mediaPlayer;
var textTracks = player.textTracks();
var activeTextTrackIndex = -1; // Iterate through the tracks and find the one marked as showing. If none are showing,
// `activeTextTrackIndex` will be set to `-1`, disabling text tracks.
var _loop = function _loop(i) {
var textTrack = textTracks[i];
if (textTrack.mode === 'showing') {
// Find the dash track we want to use
/* jshint loopfunc: true */
var dictionaryLookupResult = find(trackDictionary, function (track) {
return track.textTrack === textTrack;
});
/* jshint loopfunc: false */
var dashTrackToActivate = dictionaryLookupResult ? dictionaryLookupResult.dashTrack : null; // If we found a track, get it's index.
if (dashTrackToActivate) {
activeTextTrackIndex = tracks.indexOf(dashTrackToActivate);
}
}
};
for (var i = 0; i < textTracks.length; i += 1) {
_loop(i);
} // If the text track has changed, then set it in dash
if (activeTextTrackIndex !== dashMediaPlayer.getCurrentTextTrackIndex()) {
dashMediaPlayer.setTextTrack(activeTextTrackIndex);
}
} // Update dash when videojs's selected text track changes.
player.textTracks().on('change', updateActiveDashTextTrack); // Cleanup event listeners whenever we start loading a new source
player.dash.mediaPlayer.on(dashjs.MediaPlayer.events.STREAM_TEARDOWN_COMPLETE, function () {
player.textTracks().off('change', updateActiveDashTextTrack);
}); // Initialize the text track on our first run-through
updateActiveDashTextTrack();
return tracksAttached;
}
/*
* Wait for dash to emit `TEXT_TRACKS_ADDED` and then attach the text tracks loaded by dash if
* we're not using native text tracks.
*
* @param {videojs} player the videojs player instance
* @private
*/
function setupTextTracks(player, tech, options) {
// Clear VTTCue if it was shimmed by vttjs and let dash.js use TextTrackCue.
// This is necessary because dash.js creates text tracks
// using addTextTrack which is incompatible with vttjs.VTTCue in IE11
if (window.VTTCue && !/\[native code\]/.test(window.VTTCue.toString())) {
window.VTTCue = false;
} // Store the tracks that we've added so we can remove them later.
var dashTracksAttachedToVideoJs = []; // We're relying on the user to disable native captions. Show an error if they didn't do so.
if (tech.featuresNativeTextTracks) {
videojs.log.error('You must pass {html: {nativeCaptions: false}} in the videojs constructor ' + 'to use text tracks in videojs-contrib-dash');
return;
}
var mediaPlayer = player.dash.mediaPlayer; // Clear the tracks that we added. We don't clear them all because someone else can add tracks.
function clearDashTracks() {
dashTracksAttachedToVideoJs.forEach(player.removeRemoteTextTrack.bind(player));
dashTracksAttachedToVideoJs = [];
}
function handleTextTracksAdded(_ref2) {
var index = _ref2.index,
tracks = _ref2.tracks;
// Stop listening for this event. We only want to hear it once.
mediaPlayer.off(dashjs.MediaPlayer.events.TEXT_TRACKS_ADDED, handleTextTracksAdded); // Cleanup old tracks
clearDashTracks();
if (!tracks.length) {
// Don't try to add text tracks if there aren't any
return;
} // Save the tracks so we can remove them later
dashTracksAttachedToVideoJs = attachDashTextTracksToVideojs(player, tech, tracks, options);
} // Attach dash text tracks whenever we dash emits `TEXT_TRACKS_ADDED`.
mediaPlayer.on(dashjs.MediaPlayer.events.TEXT_TRACKS_ADDED, handleTextTracksAdded); // When the player can play, remove the initialization events. We might not have received
// TEXT_TRACKS_ADDED` so we have to stop listening for it or we'll get errors when we load new
// videos and are listening for the same event in multiple places, including cleaned up
// mediaPlayers.
mediaPlayer.on(dashjs.MediaPlayer.events.CAN_PLAY, function () {
mediaPlayer.off(dashjs.MediaPlayer.events.TEXT_TRACKS_ADDED, handleTextTracksAdded);
});
}
/**
* videojs-contrib-dash
*
* Use Dash.js to playback DASH content inside of Video.js via a SourceHandler
*/
var Html5DashJS =
/*#__PURE__*/
function () {
function Html5DashJS(source, tech, options) {
var _this = this;
// Get options from tech if not provided for backwards compatibility
options = options || tech.options_;
this.player = videojs(options.playerId);
this.player.dash = this.player.dash || {};
this.tech_ = tech;
this.el_ = tech.el();
this.elParent_ = this.el_.parentNode;
this.hasFiniteDuration_ = false; // Do nothing if the src is falsey
if (!source.src) {
return;
} // While the manifest is loading and Dash.js has not finished initializing
// we must defer events and functions calls with isReady_ and then `triggerReady`
// again later once everything is setup
tech.isReady_ = false;
if (Html5DashJS.updateSourceData) {
videojs.log.warn('updateSourceData has been deprecated.' + ' Please switch to using hook("updatesource", callback).');
source = Html5DashJS.updateSourceData(source);
} // call updatesource hooks
Html5DashJS.hooks('updatesource').forEach(function (hook) {
source = hook(source);
});
var manifestSource = source.src;
this.keySystemOptions_ = Html5DashJS.buildDashJSProtData(source.keySystemOptions);
this.player.dash.mediaPlayer = dashjs.MediaPlayer().create();
this.mediaPlayer_ = this.player.dash.mediaPlayer; // For whatever reason, we need to call setTextDefaultEnabled(false) to get
// VTT captions to show, even though we're doing virtually the same thing
// in setup-text-tracks.js
this.mediaPlayer_.setTextDefaultEnabled(false); // Log MedaPlayer messages through video.js
if (Html5DashJS.useVideoJSDebug) {
videojs.log.warn('useVideoJSDebug has been deprecated.' + ' Please switch to using hook("beforeinitialize", callback).');
Html5DashJS.useVideoJSDebug(this.mediaPlayer_);
}
if (Html5DashJS.beforeInitialize) {
videojs.log.warn('beforeInitialize has been deprecated.' + ' Please switch to using hook("beforeinitialize", callback).');
Html5DashJS.beforeInitialize(this.player, this.mediaPlayer_);
}
Html5DashJS.hooks('beforeinitialize').forEach(function (hook) {
hook(_this.player, _this.mediaPlayer_);
}); // Must run controller before these two lines or else there is no
// element to bind to.
this.mediaPlayer_.initialize(); // Retrigger a dash.js-specific error event as a player error
// See src/streaming/utils/ErrorHandler.js in dash.js code
// Handled with error (playback is stopped):
// - capabilityError
// - downloadError
// - manifestError
// - mediaSourceError
// - mediaKeySessionError
// Not handled:
// - timedTextError (video can still play)
// - mediaKeyMessageError (only fires under 'might not work' circumstances)
this.retriggerError_ = function (event) {
if (event.error === 'capability' && event.event === 'mediasource') {
// No support for MSE
_this.player.error({
code: 4,
message: 'The media cannot be played because it requires a feature ' + 'that your browser does not support.'
});
} else if (event.error === 'manifestError' && ( // Manifest type not supported
event.event.id === 'createParser' || // Codec(s) not supported
event.event.id === 'codec' || // No streams available to stream
event.event.id === 'nostreams' || // Error creating Stream object
event.event.id === 'nostreamscomposed' || // syntax error parsing the manifest
event.event.id === 'parse' || // a stream has multiplexed audio+video
event.event.id === 'multiplexedrep')) {
// These errors have useful error messages, so we forward it on
_this.player.error({
code: 4,
message: event.event.message
});
} else if (event.error === 'mediasource') {
// This error happens when dash.js fails to allocate a SourceBuffer
// OR the underlying video element throws a `MediaError`.
// If it's a buffer allocation fail, the message states which buffer
// (audio/video/text) failed allocation.
// If it's a `MediaError`, dash.js inspects the error object for
// additional information to append to the error type.
if (event.event.match('MEDIA_ERR_ABORTED')) {
_this.player.error({
code: 1,
message: event.event
});
} else if (event.event.match('MEDIA_ERR_NETWORK')) {
_this.player.error({
code: 2,
message: event.event
});
} else if (event.event.match('MEDIA_ERR_DECODE')) {
_this.player.error({
code: 3,
message: event.event
});
} else if (event.event.match('MEDIA_ERR_SRC_NOT_SUPPORTED')) {
_this.player.error({
code: 4,
message: event.event
});
} else if (event.event.match('MEDIA_ERR_ENCRYPTED')) {
_this.player.error({
code: 5,
message: event.event
});
} else if (event.event.match('UNKNOWN')) {
// We shouldn't ever end up here, since this would mean a
// `MediaError` thrown by the video element that doesn't comply
// with the W3C spec. But, since we should handle the error,
// throwing a MEDIA_ERR_SRC_NOT_SUPPORTED is probably the
// most reasonable thing to do.
_this.player.error({
code: 4,
message: event.event
});
} else {
// Buffer allocation error
_this.player.error({
code: 4,
message: event.event
});
}
} else if (event.error === 'capability' && event.event === 'encryptedmedia') {
// Browser doesn't support EME
_this.player.error({
code: 5,
message: 'The media cannot be played because it requires encryption ' + 'features that your browser does not support.'
});
} else if (event.error === 'key_session') {
// This block handles pretty much all errors thrown by the
// encryption subsystem
_this.player.error({
code: 5,
message: event.event
});
} else if (event.error === 'download') {
_this.player.error({
code: 2,
message: 'The media playback was aborted because too many consecutive ' + 'download errors occurred.'
});
} else if (event.error === 'mssError') {
_this.player.error({
code: 3,
message: event.event
});
} else {
// ignore the error
return;
} // only reset the dash player in 10ms async, so that the rest of the
// calling function finishes
setTimeout(function () {
_this.mediaPlayer_.reset();
}, 10);
};
this.mediaPlayer_.on(dashjs.MediaPlayer.events.ERROR, this.retriggerError_);
this.getDuration_ = function (event) {
var periods = event.data.Period_asArray;
var oldHasFiniteDuration = _this.hasFiniteDuration_;
if (event.data.mediaPresentationDuration || periods[periods.length - 1].duration) {
_this.hasFiniteDuration_ = true;
} else {
// in case we run into a weird situation where we're VOD but then
// switch to live
_this.hasFiniteDuration_ = false;
}
if (_this.hasFiniteDuration_ !== oldHasFiniteDuration) {
_this.player.trigger('durationchange');
}
};
this.mediaPlayer_.on(dashjs.MediaPlayer.events.MANIFEST_LOADED, this.getDuration_); // Apply all dash options that are set
if (options.dash) {
Object.keys(options.dash).forEach(function (key) {
var _this$mediaPlayer_;
var dashOptionsKey = 'set' + key.charAt(0).toUpperCase() + key.slice(1);
var value = options.dash[key];
if (_this.mediaPlayer_.hasOwnProperty(dashOptionsKey)) {
// Providing a key without `set` prefix is now deprecated.
videojs.log.warn('Using dash options in videojs-contrib-dash without the set prefix ' + ("has been deprecated. Change '" + key + "' to '" + dashOptionsKey + "'")); // Set key so it will still work
key = dashOptionsKey;
}
if (!_this.mediaPlayer_.hasOwnProperty(key)) {
videojs.log.warn("Warning: dash configuration option unrecognized: " + key);
return;
} // Guarantee `value` is an array
if (!Array.isArray(value)) {
value = [value];
}
(_this$mediaPlayer_ = _this.mediaPlayer_)[key].apply(_this$mediaPlayer_, value);
});
}
this.mediaPlayer_.attachView(this.el_); // Dash.js autoplays by default, video.js will handle autoplay
this.mediaPlayer_.setAutoPlay(false); // Setup audio tracks
setupAudioTracks.call(null, this.player, tech); // Setup text tracks
setupTextTracks.call(null, this.player, tech, options); // Attach the source with any protection data
this.mediaPlayer_.setProtectionData(this.keySystemOptions_);
this.mediaPlayer_.attachSource(manifestSource);
this.tech_.triggerReady();
}
/*
* Iterate over the `keySystemOptions` array and convert each object into
* the type of object Dash.js expects in the `protData` argument.
*
* Also rename 'licenseUrl' property in the options to an 'serverURL' property
*/
Html5DashJS.buildDashJSProtData = function buildDashJSProtData(keySystemOptions) {
var output = {};
if (!keySystemOptions || !Array.isArray(keySystemOptions)) {
return null;
}
for (var i = 0; i < keySystemOptions.length; i++) {
var keySystem = keySystemOptions[i];
var options = videojs.mergeOptions({}, keySystem.options);
if (options.licenseUrl) {
options.serverURL = options.licenseUrl;
delete options.licenseUrl;
}
output[keySystem.name] = options;
}
return output;
};
var _proto = Html5DashJS.prototype;
_proto.dispose = function dispose() {
if (this.mediaPlayer_) {
this.mediaPlayer_.off(dashjs.MediaPlayer.events.ERROR, this.retriggerError_);
this.mediaPlayer_.off(dashjs.MediaPlayer.events.MANIFEST_LOADED, this.getDuration_);
this.mediaPlayer_.reset();
}
if (this.player.dash) {
delete this.player.dash;
}
};
_proto.duration = function duration() {
if (this.mediaPlayer_.isDynamic() && !this.hasFiniteDuration_) {
return Infinity;
}
return this.mediaPlayer_.duration();
};
/**
* Get a list of hooks for a specific lifecycle
*
* @param {string} type the lifecycle to get hooks from
* @param {Function=|Function[]=} hook Optionally add a hook tothe lifecycle
* @return {Array} an array of hooks or epty if none
* @method hooks
*/
Html5DashJS.hooks = function hooks(type, hook) {
Html5DashJS.hooks_[type] = Html5DashJS.hooks_[type] || [];
if (hook) {
Html5DashJS.hooks_[type] = Html5DashJS.hooks_[type].concat(hook);
}
return Html5DashJS.hooks_[type];
};
/**
* Add a function hook to a specific dash lifecycle
*
* @param {string} type the lifecycle to hook the function to
* @param {Function|Function[]} hook the function or array of functions to attach
* @method hook
*/
Html5DashJS.hook = function hook(type, _hook) {
Html5DashJS.hooks(type, _hook);
};
/**
* Remove a hook from a specific dash lifecycle.
*
* @param {string} type the lifecycle that the function hooked to
* @param {Function} hook The hooked function to remove
* @return {boolean} True if the function was removed, false if not found
* @method removeHook
*/
Html5DashJS.removeHook = function removeHook(type, hook) {
var index = Html5DashJS.hooks(type).indexOf(hook);
if (index === -1) {
return false;
}
Html5DashJS.hooks_[type] = Html5DashJS.hooks_[type].slice();
Html5DashJS.hooks_[type].splice(index, 1);
return true;
};
return Html5DashJS;
}();
Html5DashJS.hooks_ = {};
var canHandleKeySystems = function canHandleKeySystems(source) {
// copy the source
source = JSON.parse(JSON.stringify(source));
if (Html5DashJS.updateSourceData) {
videojs.log.warn('updateSourceData has been deprecated.' + ' Please switch to using hook("updatesource", callback).');
source = Html5DashJS.updateSourceData(source);
} // call updatesource hooks
Html5DashJS.hooks('updatesource').forEach(function (hook) {
source = hook(source);
});
var videoEl = document.createElement('video');
if (source.keySystemOptions && !(window.navigator.requestMediaKeySystemAccess || // IE11 Win 8.1
videoEl.msSetMediaKeys)) {
return false;
}
return true;
};
videojs.DashSourceHandler = function () {
return {
canHandleSource: function canHandleSource(source) {
var dashExtRE = /\.mpd/i;
if (!canHandleKeySystems(source)) {
return '';
}
if (videojs.DashSourceHandler.canPlayType(source.type)) {
return 'probably';
} else if (dashExtRE.test(source.src)) {
return 'maybe';
}
return '';
},
handleSource: function handleSource(source, tech, options) {
return new Html5DashJS(source, tech, options);
},
canPlayType: function canPlayType(type) {
return videojs.DashSourceHandler.canPlayType(type);
}
};
};
videojs.DashSourceHandler.canPlayType = function (type) {
var dashTypeRE = /^application\/dash\+xml/i;
if (dashTypeRE.test(type)) {
return 'probably';
}
return '';
}; // Only add the SourceHandler if the browser supports MediaSourceExtensions
if (window.MediaSource) {
videojs.getTech('Html5').registerSourceHandler(videojs.DashSourceHandler(), 0);
}
videojs.Html5DashJS = Html5DashJS;
return Html5DashJS;
})));

View File

@ -204,7 +204,11 @@ iframe {
.videoPlayer {
width: 100%;
max-height: 1100px;
height: 100%;
}
#player {
min-height: 480px;
}
.statistics {

View File

@ -1,7 +1,10 @@
<div v-if='seen'>
<div v-if='playerSeen'>
<video class="videoPlayer" id='videoPlayer' type="application/x-mpegURL" object-fit='cover' onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" onloadstart='checkVideoSettings()' onvolumechange='updateVolume()' controls="" onended='playNextVideo()' :src='videoUrl' :poster="videoThumbnail" v-html="subtitleHtml">
<div v-if='playerSeen' class='videoPlayer'>
<video id='player' onloadstart='checkVideoSettings(); return;'>
<source data-quality='Auto' type="application/dash+xml" :src="videoDash" />
</video>
<!--<video class="videoPlayer" id='videoPlayer' type="application/x-mpegURL" object-fit='cover' onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" onloadstart='checkVideoSettings()' onvolumechange='updateVolume()' controls="" onended='playNextVideo()' :src='videoUrl' :poster="videoThumbnail" v-html="subtitleHtml">
</video>-->
</div>
<div v-else>
<span v-html="embededHtml"></span>