mirror of https://github.com/FreeTubeApp/FreeTube
Initial Implementation of Dash Player
This commit is contained in:
parent
df4b443ff4
commit
491180f762
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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'] = '下载视频';
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
});
|
|
@ -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'] = '切换循环';
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -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 |
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
|
@ -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'] = '速度';
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -538,6 +538,7 @@ let playerView = new Vue({
|
|||
firstLoad: true,
|
||||
publishedDate: '',
|
||||
videoUrl: '',
|
||||
videoDash: '',
|
||||
videoId: '',
|
||||
channelId: '',
|
||||
channelIcon: '',
|
||||
|
|
|
@ -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;
|
||||
|
||||
})));
|
|
@ -204,7 +204,11 @@ iframe {
|
|||
|
||||
.videoPlayer {
|
||||
width: 100%;
|
||||
max-height: 1100px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#player {
|
||||
min-height: 480px;
|
||||
}
|
||||
|
||||
.statistics {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue