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 = ''; 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 trackId = this.value; if (hls && hls.audioTracks) { const index = hls.audioTracks.findIndex(t => t.lang === trackId || t.name === trackId ); if (index !== -1) { hls.audioTrack = index; console.log('Audio track changed to:', index); } } }); } 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 = ''; data.audioTracks.forEach(track => { const option = document.createElement('option'); option.value = track.lang || track.name; option.textContent = track.name || track.lang || `Track ${track.id}`; audioTrackSelect.appendChild(option); }); audioTrackSelect.disabled = false; } }); } }); // ============ // 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); } } })();