- Auto-select "Original" audio track by default in both native and Plyr HLS players - Fix native HLS audio selector to use numeric indices instead of string matching - Robustly detect "original" track by checking both `name` and `lang` attributes - Fix audio track change handler to correctly switch between available tracks
330 lines
10 KiB
JavaScript
330 lines
10 KiB
JavaScript
const video = document.getElementById('js-video-player');
|
|
|
|
window.hls = null;
|
|
let hls = null;
|
|
|
|
// ===========
|
|
// HLS NATIVE
|
|
// ===========
|
|
function initHLSNative(manifestUrl) {
|
|
if (!manifestUrl) {
|
|
console.error('No HLS manifest URL provided');
|
|
return;
|
|
}
|
|
|
|
console.log('Initializing native HLS player with manifest:', manifestUrl);
|
|
|
|
if (hls) {
|
|
window.hls = null;
|
|
hls.destroy();
|
|
hls = null;
|
|
}
|
|
|
|
if (Hls.isSupported()) {
|
|
hls = new Hls({
|
|
enableWorker: true,
|
|
lowLatencyMode: false,
|
|
maxBufferLength: 30,
|
|
maxMaxBufferLength: 60,
|
|
startLevel: -1,
|
|
});
|
|
|
|
window.hls = hls;
|
|
|
|
hls.loadSource(manifestUrl);
|
|
hls.attachMedia(video);
|
|
|
|
hls.on(Hls.Events.MANIFEST_PARSED, function(event, data) {
|
|
console.log('Native manifest parsed');
|
|
console.log('Levels:', data.levels.length);
|
|
|
|
const qualitySelect = document.getElementById('quality-select');
|
|
|
|
if (qualitySelect && data.levels?.length) {
|
|
qualitySelect.innerHTML = '<option value="-1">Auto</option>';
|
|
|
|
const sorted = [...data.levels].sort((a, b) => b.height - a.height);
|
|
const seen = new Set();
|
|
|
|
sorted.forEach(level => {
|
|
if (!seen.has(level.height)) {
|
|
seen.add(level.height);
|
|
|
|
const i = data.levels.indexOf(level);
|
|
const opt = document.createElement('option');
|
|
|
|
opt.value = i;
|
|
opt.textContent = level.height + 'p';
|
|
|
|
qualitySelect.appendChild(opt);
|
|
}
|
|
});
|
|
|
|
// Set initial quality from settings
|
|
if (typeof window.data !== 'undefined' && window.data.settings) {
|
|
const defaultRes = window.data.settings.default_resolution;
|
|
if (defaultRes !== 'auto' && defaultRes) {
|
|
const target = parseInt(defaultRes);
|
|
let bestLevel = -1;
|
|
let bestHeight = 0;
|
|
for (let i = 0; i < hls.levels.length; i++) {
|
|
const h = hls.levels[i].height;
|
|
if (h <= target && h > bestHeight) {
|
|
bestHeight = h;
|
|
bestLevel = i;
|
|
}
|
|
}
|
|
if (bestLevel !== -1) {
|
|
hls.currentLevel = bestLevel;
|
|
qualitySelect.value = bestLevel;
|
|
console.log('Starting at resolution:', bestHeight + 'p');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
hls.on(Hls.Events.ERROR, function(_, data) {
|
|
if (data.fatal) {
|
|
console.error('HLS fatal error:', data.type, data.details);
|
|
switch(data.type) {
|
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
|
hls.startLoad();
|
|
break;
|
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
|
hls.recoverMediaError();
|
|
break;
|
|
default:
|
|
hls.destroy();
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
|
video.src = manifestUrl;
|
|
} else {
|
|
console.error('HLS not supported');
|
|
}
|
|
}
|
|
|
|
// ======
|
|
// INIT
|
|
// ======
|
|
function initPlayer() {
|
|
console.log('Init native player');
|
|
|
|
if (typeof hls_manifest_url === 'undefined' || !hls_manifest_url) {
|
|
console.error('No manifest URL');
|
|
return;
|
|
}
|
|
|
|
initHLSNative(hls_manifest_url);
|
|
|
|
const qualitySelect = document.getElementById('quality-select');
|
|
if (qualitySelect) {
|
|
qualitySelect.addEventListener('change', function () {
|
|
const level = parseInt(this.value);
|
|
|
|
if (hls) {
|
|
hls.currentLevel = level;
|
|
console.log('Quality:', level === -1 ? 'Auto' : hls.levels[level]?.height + 'p');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// DOM READY
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initPlayer);
|
|
} else {
|
|
initPlayer();
|
|
}
|
|
|
|
// =============
|
|
// AUDIO TRACKS
|
|
// =============
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const audioTrackSelect = document.getElementById('audio-track-select');
|
|
|
|
if (audioTrackSelect) {
|
|
audioTrackSelect.addEventListener('change', function() {
|
|
const trackIdx = parseInt(this.value);
|
|
|
|
if (!isNaN(trackIdx) && hls && hls.audioTracks && trackIdx >= 0 && trackIdx < hls.audioTracks.length) {
|
|
hls.audioTrack = trackIdx;
|
|
console.log('Audio track changed to:', hls.audioTracks[trackIdx].name || trackIdx);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (hls) {
|
|
hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, (_, data) => {
|
|
console.log('Audio tracks:', data.audioTracks);
|
|
|
|
// Populate audio track select if needed
|
|
if (audioTrackSelect && data.audioTracks.length > 0) {
|
|
audioTrackSelect.innerHTML = '<option value="">Select audio track</option>';
|
|
let originalIdx = -1;
|
|
data.audioTracks.forEach((track, idx) => {
|
|
// Find "original" track
|
|
if (originalIdx === -1 && (track.name || '').toLowerCase().includes('original')) {
|
|
originalIdx = idx;
|
|
}
|
|
const option = document.createElement('option');
|
|
option.value = String(idx);
|
|
option.textContent = track.name || track.lang || `Track ${idx}`;
|
|
audioTrackSelect.appendChild(option);
|
|
});
|
|
audioTrackSelect.disabled = false;
|
|
|
|
// Auto-select "original" audio track
|
|
if (originalIdx !== -1) {
|
|
hls.audioTrack = originalIdx;
|
|
audioTrackSelect.value = String(originalIdx);
|
|
console.log('Auto-selected original audio track:', data.audioTracks[originalIdx].name);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// ============
|
|
// START TIME
|
|
// ============
|
|
if (typeof data !== 'undefined' && data.time_start != 0 && video) {
|
|
video.addEventListener('loadedmetadata', function() {
|
|
video.currentTime = data.time_start;
|
|
});
|
|
}
|
|
|
|
// ==============
|
|
// SPEED CONTROL
|
|
// ==============
|
|
let speedInput = document.getElementById('speed-control');
|
|
|
|
if (speedInput) {
|
|
speedInput.addEventListener('keyup', (event) => {
|
|
if (event.key === 'Enter') {
|
|
let speed = parseFloat(speedInput.value);
|
|
if(!isNaN(speed)){
|
|
video.playbackRate = speed;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// =========
|
|
// Autoplay
|
|
// =========
|
|
(function() {
|
|
if (typeof data === 'undefined' || (data.settings.related_videos_mode === 0 && data.playlist === null)) {
|
|
return;
|
|
}
|
|
|
|
let playability_error = !!data.playability_error;
|
|
let isPlaylist = false;
|
|
if (data.playlist !== null && data.playlist['current_index'] !== null)
|
|
isPlaylist = true;
|
|
|
|
// read cookies on whether to autoplay
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie
|
|
let cookieValue;
|
|
let playlist_id;
|
|
if (isPlaylist) {
|
|
// from https://stackoverflow.com/a/6969486
|
|
function escapeRegExp(string) {
|
|
// $& means the whole matched string
|
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
}
|
|
playlist_id = data.playlist['id'];
|
|
playlist_id = escapeRegExp(playlist_id);
|
|
|
|
cookieValue = document.cookie.replace(new RegExp(
|
|
'(?:(?:^|.*;\\s*)autoplay_'
|
|
+ playlist_id + '\\s*\\=\\s*([^;]*).*$)|^.*$'
|
|
), '$1');
|
|
} else {
|
|
cookieValue = document.cookie.replace(new RegExp(
|
|
'(?:(?:^|.*;\\s*)autoplay\\s*\\=\\s*([^;]*).*$)|^.*$'
|
|
),'$1');
|
|
}
|
|
|
|
let autoplayEnabled = 0;
|
|
if(cookieValue.length === 0){
|
|
autoplayEnabled = 0;
|
|
} else {
|
|
autoplayEnabled = Number(cookieValue);
|
|
}
|
|
|
|
// check the checkbox if autoplay is on
|
|
let checkbox = document.querySelector('.autoplay-toggle');
|
|
if(autoplayEnabled){
|
|
checkbox.checked = true;
|
|
}
|
|
|
|
// listen for checkbox to turn autoplay on and off
|
|
let cookie = 'autoplay'
|
|
if (isPlaylist)
|
|
cookie += '_' + playlist_id;
|
|
|
|
checkbox.addEventListener( 'change', function() {
|
|
if(this.checked) {
|
|
autoplayEnabled = 1;
|
|
document.cookie = cookie + '=1; SameSite=Strict';
|
|
} else {
|
|
autoplayEnabled = 0;
|
|
document.cookie = cookie + '=0; SameSite=Strict';
|
|
}
|
|
});
|
|
|
|
if(!playability_error){
|
|
// play the video if autoplay is on
|
|
if(autoplayEnabled){
|
|
video.play().catch(function(e) {
|
|
// Autoplay blocked by browser - ignore silently
|
|
console.log('Autoplay blocked:', e.message);
|
|
});
|
|
}
|
|
}
|
|
|
|
// determine next video url
|
|
let nextVideoUrl;
|
|
if (isPlaylist) {
|
|
let currentIndex = data.playlist['current_index'];
|
|
if (data.playlist['current_index']+1 == data.playlist['items'].length)
|
|
nextVideoUrl = null;
|
|
else
|
|
nextVideoUrl = data.playlist['items'][data.playlist['current_index']+1]['url'];
|
|
|
|
// scroll playlist to proper position
|
|
// item height + gap == 100
|
|
let pl = document.querySelector('.playlist-videos');
|
|
pl.scrollTop = 100*currentIndex;
|
|
} else {
|
|
if (data.related.length === 0)
|
|
nextVideoUrl = null;
|
|
else
|
|
nextVideoUrl = data.related[0]['url'];
|
|
}
|
|
let nextVideoDelay = 1000;
|
|
|
|
// go to next video when video ends
|
|
// https://stackoverflow.com/a/2880950
|
|
if (nextVideoUrl) {
|
|
if(playability_error){
|
|
videoEnded();
|
|
} else {
|
|
video.addEventListener('ended', videoEnded, false);
|
|
}
|
|
function nextVideo(){
|
|
if(autoplayEnabled){
|
|
window.location.href = nextVideoUrl;
|
|
}
|
|
}
|
|
function videoEnded(e) {
|
|
window.setTimeout(nextVideo, nextVideoDelay);
|
|
}
|
|
}
|
|
})();
|