463 lines
16 KiB
JavaScript
463 lines
16 KiB
JavaScript
// Plyr Player
|
|
import Plyr from 'plyr/dist/plyr.polyfilled.min.js';
|
|
import 'plyr/dist/plyr.css';
|
|
|
|
// Vidstack Player
|
|
import 'vidstack/player/styles/default/theme.css';
|
|
import 'vidstack/player/styles/default/layouts/video.css';
|
|
import { VidstackPlayer, VidstackPlayerLayout } from 'vidstack/global/player';
|
|
|
|
// Dash Support
|
|
import dashjs from 'dashjs';
|
|
|
|
// Subtitle Support
|
|
import SubtitlesOctopus from '@jellyfin/libass-wasm';
|
|
|
|
// Custom JS
|
|
import { initMobileWidescreen } from './player-mobile';
|
|
import { mobileDoubleClick } from './player-mobile'
|
|
import { playNextPlaylistVideo } from './playlist';
|
|
import { addVideoTracks } from './player-data';
|
|
import { addSubtitleTracks } from './player-data';
|
|
import { serverSelectMenuItem, serverSelectSubmenu, serverSelectMenuClickToggle } from './player-server-select';
|
|
import { isIOS } from './detect-ios';
|
|
|
|
// Variables
|
|
var player = null;
|
|
var av1Supported = (!!document.createElement('video').canPlayType('video/webm; codecs="av01.0.05M.08, opus"'));
|
|
var dashSupported = dashjs.supportsMediaSource();
|
|
var apiResponse = {};
|
|
var volume = 0.5;
|
|
var captions = true;
|
|
var lastTime = 0.0;
|
|
var streamServer = '';
|
|
var streamServers = [];
|
|
var streamServerIndex = 0;
|
|
var streamServerCount = 0;
|
|
var ambientMode = true;
|
|
var serverFallback = false;
|
|
var saveInterval;
|
|
|
|
var subtitleInstance = null;
|
|
|
|
var controls = [
|
|
'play-large', // The large play button in the center
|
|
'play', // Play/pause playback
|
|
'progress', // The progress bar and scrubber for playback and buffering
|
|
'current-time', // The current time of playback
|
|
'duration', // The full duration of the media
|
|
'mute', // Toggle mute
|
|
'volume', // Volume control
|
|
'captions', // Toggle captions
|
|
'settings', // Settings menu
|
|
'fullscreen', // Toggle fullscreen
|
|
];
|
|
|
|
// Load Volume from LocalStorage
|
|
if (localStorage.hstreamVolume) {
|
|
volume = parseFloat(localStorage.getItem('hstreamVolume')).toFixed(2);
|
|
console.log('Loaded Audio Volume from Local Storage: ' + volume);
|
|
}
|
|
|
|
// Load Captions from LocalStorage
|
|
if (localStorage.hstreamCaptions) {
|
|
captions = (localStorage.getItem('hstreamCaptions') == 'true');
|
|
console.log('Loaded Captions Status from Local Storage: ' + captions);
|
|
}
|
|
|
|
// Asia Server Fallback
|
|
if (localStorage.hstreamServerFallback) {
|
|
serverFallback = (localStorage.getItem('hstreamServerFallback') == 'true');
|
|
console.log('Loaded Captions Status from Local Storage: ' + captions);
|
|
}
|
|
|
|
// Alert User when AV1 is not supported
|
|
if (!av1Supported) {
|
|
document.getElementById("av1-unsupported").classList.remove("hidden");
|
|
}
|
|
|
|
function initDash(data, player) {
|
|
const video = document.querySelector('video');
|
|
|
|
data.forEach(function (el) {
|
|
if (el.mode === 'mpd' && el.size === player.config.quality.selected) {
|
|
const dash = dashjs.MediaPlayer().create();
|
|
dash.initialize(video, el.src, true);
|
|
// Expose player and dash so they can be used from the console
|
|
window.player = player;
|
|
window.dash = dash;
|
|
}
|
|
});
|
|
}
|
|
|
|
function setCanvasDimension(canvas, video) {
|
|
canvas.height = video.offsetHeight;
|
|
canvas.width = video.offsetWidth;
|
|
}
|
|
|
|
function paintStaticVideo(ctx, video) {
|
|
if (localStorage.theme == 'light') {
|
|
return;
|
|
}
|
|
if (!ambientMode) {
|
|
return;
|
|
}
|
|
ctx.drawImage(video, 0, 0, video.offsetWidth, video.offsetHeight);
|
|
}
|
|
|
|
function toggleAmbientMode() {
|
|
let canvas = document.getElementById("ambientVideo"), ctx = canvas.getContext("2d"), video = document.getElementsByTagName('video')[0];
|
|
if (ambientMode) {
|
|
ambientMode = false;
|
|
localStorage.ambientMode = 'false';
|
|
setCanvasDimension(canvas, video);
|
|
document.getElementById('ambient-mode-toggle').innerHTML = '<span>Ambient Mode<span class="plyr__menu__value">Off</span></span>';
|
|
} else {
|
|
ambientMode = true;
|
|
localStorage.ambientMode = 'true';
|
|
setCanvasDimension(canvas, video);
|
|
paintStaticVideo(ctx, video);
|
|
document.getElementById('ambient-mode-toggle').innerHTML = '<span>Ambient Mode<span class="plyr__menu__value">On</span></span>';
|
|
}
|
|
}
|
|
|
|
function toggleAsiaServer() {
|
|
if (serverFallback) {
|
|
serverFallback = false;
|
|
localStorage.hstreamServerFallback = 'false';
|
|
document.getElementById('server-fallback-toggle').innerHTML = '<span>Fallback Server<span class="plyr__menu__value">Off</span></span>';
|
|
streamServers = apiResponse.stream_domains;
|
|
} else {
|
|
serverFallback = true;
|
|
localStorage.hstreamServerFallback = 'true';
|
|
document.getElementById('server-fallback-toggle').innerHTML = '<span>Fallback Server<span class="plyr__menu__value">On</span></span>';
|
|
streamServers = apiResponse.asia_stream_domains;
|
|
}
|
|
|
|
streamServerCount = streamServers.length;
|
|
streamServerIndex = Math.floor(Math.random() * streamServerCount);
|
|
streamServer = streamServers[streamServerIndex];
|
|
console.log('Selected Server: ' + streamServer);
|
|
|
|
if (player) {
|
|
clearInterval(saveInterval);
|
|
player.destroy();
|
|
}
|
|
initPlayer();
|
|
}
|
|
|
|
function initSubtitles(lang) {
|
|
if (isIOS()) {
|
|
return;
|
|
}
|
|
|
|
// Dispose old instance
|
|
if (subtitleInstance != null && subtitleInstance instanceof SubtitlesOctopus) {
|
|
subtitleInstance.dispose();
|
|
}
|
|
|
|
let newSubUrl = streamServer + '/' + apiResponse.stream_url + '/';
|
|
|
|
if (lang != 'en') {
|
|
newSubUrl += 'autotrans/' + lang + '.ass';
|
|
}
|
|
else {
|
|
newSubUrl += 'eng.ass'
|
|
}
|
|
|
|
let subFont = '/fonts/Figtree-ExtraBold.woff2';
|
|
// Hindi font
|
|
if (lang == 'hi') {
|
|
subFont = '/fonts/Hind-SemiBold.ttf';
|
|
}
|
|
|
|
// Subtitles
|
|
var options = {
|
|
video: document.getElementsByTagName('video')[0], // HTML5 video element
|
|
subUrl: newSubUrl, // Link to subtitles
|
|
workerUrl: '/build/js/subtitles-octopus-worker.js', // Link to WebAssembly-based file "libassjs-worker.js"
|
|
legacyWorkerUrl: '/build/js/subtitles-octopus-worker-legacy.js', // Link to non-WebAssembly worker
|
|
fonts: [subFont],
|
|
renderMode: 'wasm-blend',
|
|
};
|
|
|
|
subtitleInstance = new SubtitlesOctopus(options);
|
|
}
|
|
|
|
function initPlayer() {
|
|
player = new Plyr('#player', {
|
|
controls,
|
|
quality: {
|
|
default: 720,
|
|
options: [2161, 2160, 1081, 1080, 720]
|
|
},
|
|
i18n: {
|
|
qualityLabel: {
|
|
2161: "2160p48",
|
|
2160: "2160p",
|
|
1081: "1080p48",
|
|
1080: "1080p",
|
|
720: "720p"
|
|
},
|
|
qualityBadge: {
|
|
2161: "UHD@48",
|
|
1081: "FHD@48",
|
|
1080: "FHD",
|
|
},
|
|
},
|
|
fullscreen: { enabled: true, fallback: true, iosNative: true }
|
|
});
|
|
|
|
// Player Track Data
|
|
var data = addVideoTracks(streamServer, apiResponse, av1Supported, dashSupported);
|
|
|
|
player.source = {
|
|
type: 'video',
|
|
title: apiResponse.title,
|
|
poster: apiResponse.poster,
|
|
previewThumbnails: {
|
|
enabled: true,
|
|
src: streamServer + '/' + apiResponse.stream_url + '/thumbs.vtt',
|
|
},
|
|
sources: data,
|
|
tracks: addSubtitleTracks(streamServer, apiResponse)
|
|
};
|
|
|
|
player.volume = volume;
|
|
//player.captions.languages = ['en'];
|
|
player.captions.language = 'en';
|
|
player.captions.active = captions;
|
|
|
|
if (dashSupported && !apiResponse.legacy) {
|
|
player.on('qualitychange', () => {
|
|
initDash(data, player);
|
|
});
|
|
|
|
initDash(data, player);
|
|
}
|
|
|
|
// Ambient Mode
|
|
let canvas = document.getElementById("ambientVideo"), ctx = canvas.getContext("2d"), video = document.getElementsByTagName('video')[0];
|
|
setCanvasDimension(canvas, video);
|
|
paintStaticVideo(ctx, video);
|
|
|
|
var allItems = document.getElementsByClassName('plyr__control--forward');
|
|
var lastItem = allItems[allItems.length - 1];
|
|
lastItem.insertAdjacentHTML('afterend', '<button id="ambient-mode-toggle" type="button" class="plyr__control" role="menuitem" aria-haspopup="true"><span>Ambient Mode<span class="plyr__menu__value">On</span></span></button>');
|
|
document.getElementById('ambient-mode-toggle').addEventListener('click', toggleAmbientMode);
|
|
|
|
if (localStorage.ambientMode == 'false') {
|
|
toggleAmbientMode();
|
|
}
|
|
|
|
// Server select (Asia)
|
|
lastItem = allItems[allItems.length - 1];
|
|
let value = 'Off';
|
|
if (serverFallback) { value = 'On'; }
|
|
lastItem.insertAdjacentHTML('afterend', '<button id="server-fallback-toggle" type="button" class="plyr__control" role="menuitem" aria-haspopup="true"><span>Fallback Server<span class="plyr__menu__value">' + value + '</span></span></button>');
|
|
document.getElementById('server-fallback-toggle').addEventListener('click', toggleAsiaServer);
|
|
|
|
var clickedPlay = false;
|
|
|
|
player.on('play', () => {
|
|
if (!clickedPlay) {
|
|
player.stop();
|
|
console.log("Stopped video, because user didn't click play.")
|
|
}
|
|
|
|
setCanvasDimension(canvas, video);
|
|
console.log('Play => Function Loop()');
|
|
var $this = video;
|
|
(function loop() {
|
|
if (!player.paused && !player.ended && localStorage.theme == 'dark' && ambientMode) {
|
|
ctx.drawImage($this, 0, 0, $this.offsetWidth, $this.offsetHeight);
|
|
setTimeout(loop, 24000 / 1001); // drawing at 30fps
|
|
}
|
|
})();
|
|
});
|
|
|
|
player.on('seeked', () => {
|
|
paintStaticVideo(ctx, video);
|
|
if (player.currentTime > 0) {
|
|
lastTime = player.currentTime;
|
|
}
|
|
console.log('Seeked => paintStaticVideo() at ' + player.currentTime);
|
|
});
|
|
|
|
window.addEventListener("resize", () => {
|
|
setCanvasDimension(canvas, video);
|
|
if (player.paused) {
|
|
paintStaticVideo(ctx, video);
|
|
}
|
|
});
|
|
|
|
player.on('captionsenabled', () => {
|
|
document.getElementsByClassName('libassjs-canvas-parent')[0].style.visibility = 'visible';
|
|
localStorage.setItem('hstreamCaptions', 'true');
|
|
console.log('Set Captions Status to Local Storage: true');
|
|
});
|
|
|
|
player.on('captionsdisabled', () => {
|
|
document.getElementsByClassName('libassjs-canvas-parent')[0].style.visibility = 'hidden';
|
|
localStorage.setItem('hstreamCaptions', 'false');
|
|
console.log('Set Captions Status to Local Storage: false');
|
|
});
|
|
|
|
player.on('volumechange', () => {
|
|
console.log('Saving Audio Volume to Local Storage: ' + player.volume);
|
|
localStorage.setItem('hstreamVolume', player.volume.toString())
|
|
});
|
|
|
|
player.on('ended', () => {
|
|
playNextPlaylistVideo();
|
|
});
|
|
|
|
player.on('languagechange', (event) => {
|
|
let lang = event.detail.plyr.captions.language;
|
|
|
|
console.log('Subtitle Event ' + lang);
|
|
initSubtitles(lang);
|
|
});
|
|
|
|
function playerPlayTemp() {
|
|
clickedPlay = true;
|
|
}
|
|
|
|
document.querySelectorAll('[data-plyr="play"]').forEach(play =>
|
|
play.addEventListener('click', playerPlayTemp)
|
|
);
|
|
|
|
document.getElementsByClassName('plyr--video')[0].addEventListener('click', playerPlayTemp);
|
|
|
|
initMobileWidescreen();
|
|
|
|
// Start time
|
|
setTimeout(function () {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const time = parseInt(params.get("t"));
|
|
if (!isNaN(time)) {
|
|
player.currentTime = time;
|
|
console.log("Skipping to " + time)
|
|
}
|
|
if (lastTime > 0) {
|
|
player.currentTime = lastTime;
|
|
console.log("Skipping to " + lastTime)
|
|
}
|
|
}, 500);
|
|
|
|
player.on('ready', () => {
|
|
mobileDoubleClick(player);
|
|
});
|
|
|
|
// Server Select
|
|
// I hate this...
|
|
var settingElements = document.getElementsByClassName('plyr__control--forward');
|
|
if (settingElements.length == 3) {
|
|
settingElements[2].insertAdjacentHTML('afterend', serverSelectMenuItem(streamServerIndex));
|
|
|
|
var settingNodes = document.getElementsByClassName('plyr__menu__container')[0].childNodes[0].childNodes;
|
|
if (settingNodes.length == 4) {
|
|
document.getElementsByClassName('plyr__menu__container')[0].childNodes[0].childNodes[3].insertAdjacentHTML('afterend', serverSelectSubmenu(streamServerIndex, streamServerCount));
|
|
}
|
|
|
|
// Event Listeners
|
|
document.getElementById('server-select').addEventListener('click', serverSelectMenuClickToggle);
|
|
document.getElementById('server-select-list-back-btn').addEventListener('click', serverSelectMenuClickToggle);
|
|
let serverSelects = document.getElementsByClassName('change_server');
|
|
for (let i = 0; i < serverSelects.length; i++) {
|
|
serverSelects[i].addEventListener('click', function() {
|
|
streamServerIndex = Number(this.value);
|
|
streamServer = streamServers[streamServerIndex];
|
|
console.log('Selected Server: ' + streamServer);
|
|
|
|
if (player) {
|
|
clearInterval(saveInterval);
|
|
player.destroy();
|
|
}
|
|
initPlayer();
|
|
});
|
|
}
|
|
}
|
|
|
|
// Periodically save last timestamp
|
|
saveInterval = setInterval(function () {
|
|
lastTime = player.currentTime;
|
|
console.log("Last Player Position: " + lastTime);
|
|
}, 10000);
|
|
}
|
|
|
|
async function initVidstackPlayer() {
|
|
const videoSource = streamServer + '/' + apiResponse.stream_url + '/x264.720p.mp4';
|
|
const videoThumbs = streamServer + '/' + apiResponse.stream_url + '/thumbs.vtt';
|
|
const videoCaption = streamServer + '/' + apiResponse.stream_url + '/eng.vtt';
|
|
|
|
player = await VidstackPlayer.create({
|
|
target: '#player',
|
|
title: apiResponse.title,
|
|
src: videoSource,
|
|
poster: apiResponse.poster,
|
|
layout: new VidstackPlayerLayout({
|
|
thumbnails: videoThumbs,
|
|
}),
|
|
tracks: [
|
|
{
|
|
src: videoCaption,
|
|
label: 'English',
|
|
language: 'en-US',
|
|
kind: 'subtitles',
|
|
type: 'vtt',
|
|
default: true,
|
|
}
|
|
]
|
|
});
|
|
|
|
// Ambient Mode
|
|
let canvas = document.getElementById("ambientVideo"), ctx = canvas.getContext("2d"), video = document.getElementsByTagName('video')[0];
|
|
setCanvasDimension(canvas, video);
|
|
paintStaticVideo(ctx, video);
|
|
|
|
player.addEventListener('play', () => {
|
|
setCanvasDimension(canvas, video);
|
|
console.log('Play => Function Loop()');
|
|
var $this = video;
|
|
(function loop() {
|
|
if (!player.paused && !player.ended && localStorage.theme == 'dark' && ambientMode) {
|
|
ctx.drawImage($this, 0, 0, $this.offsetWidth, $this.offsetHeight);
|
|
setTimeout(loop, 24000 / 1001); // drawing at 30fps
|
|
}
|
|
})();
|
|
});
|
|
}
|
|
|
|
// Get Data from API
|
|
window.axios.post('/player/api', {
|
|
episode_id: document.getElementById('e_id').value
|
|
}).then(function (response) {
|
|
if (response.status == 200) {
|
|
apiResponse = response.data;
|
|
streamServers = apiResponse.stream_domains;
|
|
|
|
if (serverFallback) {
|
|
streamServers = apiResponse.asia_stream_domains;
|
|
}
|
|
|
|
streamServerCount = streamServers.length;
|
|
streamServerIndex = Math.floor(Math.random() * streamServerCount);
|
|
streamServer = streamServers[streamServerIndex];
|
|
console.log('Selected Server: ' + streamServer + ' with Index: ' + streamServerIndex);
|
|
|
|
if (!isIOS()) {
|
|
initPlayer();
|
|
}
|
|
else {
|
|
console.log("Detected Apple Shit. Using different player.")
|
|
initVidstackPlayer();
|
|
}
|
|
|
|
}
|
|
}).catch(function (error) {
|
|
var alert = document.getElementById("player-alert");
|
|
alert.innerText = 'The player encountered a problem: ' + error;
|
|
alert.classList.remove("hidden");
|
|
});
|