(function main() { 'use strict'; console.log('Plyr start script loaded'); // Captions let captionsActive = false; if (typeof data !== 'undefined' && (data.settings.subtitles_mode === 2 || (data.settings.subtitles_mode === 1 && data.has_manual_captions))) { captionsActive = true; } // AutoPlay let autoplayActive = typeof data !== 'undefined' && data.settings.autoplay_videos || false; // Quality map: label -> hls level index window.hlsQualityMap = {}; let plyrInstance = null; let currentQuality = 'auto'; let hls = null; window.hls = null; /** * Get start level from settings (highest quality <= target) */ function getStartLevel(levels) { if (typeof data === 'undefined' || !data.settings) return -1; const defaultRes = data.settings.default_resolution; if (defaultRes === 'auto' || !defaultRes) return -1; const target = parseInt(defaultRes); // Find the level with the highest height that is still <= target let bestLevel = -1; let bestHeight = 0; for (let i = 0; i < levels.length; i++) { const h = levels[i].height; if (h <= target && h > bestHeight) { bestHeight = h; bestLevel = i; } } return bestLevel; } /** * Initialize HLS */ function initHLS(manifestUrl) { return new Promise((resolve, reject) => { if (!manifestUrl) { reject('No HLS manifest URL provided'); return; } console.log('Initializing HLS for Plyr:', manifestUrl); if (hls) { hls.destroy(); hls = null; } hls = new Hls({ enableWorker: true, lowLatencyMode: false, maxBufferLength: 30, maxMaxBufferLength: 60, startLevel: -1, }); window.hls = hls; const video = document.getElementById('js-video-player'); if (!video) { reject('Video element not found'); return; } hls.loadSource(manifestUrl); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, function(event, data) { console.log('HLS manifest parsed, levels:', hls.levels?.length); // Set initial quality from settings const startLevel = getStartLevel(hls.levels); if (startLevel !== -1) { hls.currentLevel = startLevel; const level = hls.levels[startLevel]; currentQuality = level.height + 'p'; console.log('Starting at resolution:', currentQuality); } resolve(hls); }); 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: reject(data); break; } } }); }); } /** * Change HLS quality */ function changeHLSQuality(quality) { if (!hls) { console.error('HLS not available'); return; } console.log('Changing HLS quality to:', quality); if (quality === 'auto') { hls.currentLevel = -1; currentQuality = 'auto'; console.log('HLS quality set to Auto'); const qualityBtnText = document.getElementById('plyr-quality-text'); if (qualityBtnText) { qualityBtnText.textContent = 'Auto'; } } else { const levelIndex = window.hlsQualityMap[quality]; if (levelIndex !== undefined) { hls.currentLevel = levelIndex; currentQuality = quality; console.log('HLS quality set to:', quality); const qualityBtnText = document.getElementById('plyr-quality-text'); if (qualityBtnText) { qualityBtnText.textContent = quality; } } } } /** * Create custom quality control in Plyr controls */ function addCustomQualityControl(player, qualityLabels) { player.on('ready', () => { console.log('Adding custom quality control...'); const controls = player.elements.container.querySelector('.plyr__controls'); if (!controls) { console.error('Controls not found'); return; } if (document.getElementById('plyr-quality-container')) { console.log('Quality control already exists'); return; } const qualityContainer = document.createElement('div'); qualityContainer.id = 'plyr-quality-container'; qualityContainer.className = 'plyr__control plyr__control--custom'; const qualityButton = document.createElement('button'); qualityButton.type = 'button'; qualityButton.className = 'plyr__control'; qualityButton.setAttribute('data-plyr', 'quality-custom'); qualityButton.setAttribute('aria-label', 'Quality'); qualityButton.innerHTML = ` ${currentQuality === 'auto' ? 'Auto' : currentQuality} `; const dropdown = document.createElement('div'); dropdown.className = 'plyr-quality-dropdown'; qualityLabels.forEach(label => { const option = document.createElement('div'); option.className = 'plyr-quality-option'; option.textContent = label === 'auto' ? 'Auto' : label; if (label === currentQuality) { option.setAttribute('data-active', 'true'); } option.addEventListener('click', (e) => { e.stopPropagation(); changeHLSQuality(label); dropdown.querySelectorAll('.plyr-quality-option').forEach(opt => { opt.removeAttribute('data-active'); }); option.setAttribute('data-active', 'true'); dropdown.style.display = 'none'; }); dropdown.appendChild(option); }); qualityButton.addEventListener('click', (e) => { e.stopPropagation(); const isVisible = dropdown.style.display === 'block'; document.querySelectorAll('.plyr-quality-dropdown, .plyr-audio-dropdown').forEach(d => { d.style.display = 'none'; }); dropdown.style.display = isVisible ? 'none' : 'block'; }); document.addEventListener('click', (e) => { if (!qualityContainer.contains(e.target)) { dropdown.style.display = 'none'; } }); qualityContainer.appendChild(qualityButton); qualityContainer.appendChild(dropdown); const settingsBtn = controls.querySelector('[data-plyr="settings"]'); if (settingsBtn) { settingsBtn.insertAdjacentElement('beforebegin', qualityContainer); } else { controls.appendChild(qualityContainer); } console.log('Custom quality control added'); }); } /** * Create custom audio tracks control in Plyr controls */ function addCustomAudioTracksControl(player, hlsInstance) { player.on('ready', () => { console.log('Adding custom audio tracks control...'); const controls = player.elements.container.querySelector('.plyr__controls'); if (!controls) { console.error('Controls not found'); return; } if (document.getElementById('plyr-audio-container')) { console.log('Audio tracks control already exists'); return; } const audioContainer = document.createElement('div'); audioContainer.id = 'plyr-audio-container'; audioContainer.className = 'plyr__control plyr__control--custom'; const audioButton = document.createElement('button'); audioButton.type = 'button'; audioButton.className = 'plyr__control'; audioButton.setAttribute('data-plyr', 'audio-custom'); audioButton.setAttribute('aria-label', 'Audio Track'); audioButton.innerHTML = ` Audio `; const audioDropdown = document.createElement('div'); audioDropdown.className = 'plyr-audio-dropdown'; function updateAudioDropdown() { if (!hlsInstance || !hlsInstance.audioTracks) return; audioDropdown.innerHTML = ''; if (hlsInstance.audioTracks.length === 0) { const noTrackMsg = document.createElement('div'); noTrackMsg.className = 'plyr-audio-no-tracks'; noTrackMsg.textContent = 'No audio tracks'; audioDropdown.appendChild(noTrackMsg); return; } hlsInstance.audioTracks.forEach((track, idx) => { const option = document.createElement('div'); option.className = 'plyr-audio-option'; option.textContent = track.name || track.lang || `Track ${idx + 1}`; if (hlsInstance.audioTrack === idx) { option.setAttribute('data-active', 'true'); } option.addEventListener('click', (e) => { e.stopPropagation(); hlsInstance.audioTrack = idx; console.log('Audio track changed to:', track.name || track.lang || idx); const audioText = document.getElementById('plyr-audio-text'); if (audioText) { const trackName = track.name || track.lang || `Track ${idx + 1}`; audioText.textContent = trackName.length > 8 ? trackName.substring(0, 6) + '...' : trackName; } audioDropdown.querySelectorAll('.plyr-audio-option').forEach(opt => { opt.removeAttribute('data-active'); }); option.setAttribute('data-active', 'true'); audioDropdown.style.display = 'none'; }); audioDropdown.appendChild(option); }); } audioButton.addEventListener('click', (e) => { e.stopPropagation(); updateAudioDropdown(); const isVisible = audioDropdown.style.display === 'block'; document.querySelectorAll('.plyr-quality-dropdown, .plyr-audio-dropdown').forEach(d => { d.style.display = 'none'; }); audioDropdown.style.display = isVisible ? 'none' : 'block'; }); document.addEventListener('click', (e) => { if (!audioContainer.contains(e.target)) { audioDropdown.style.display = 'none'; } }); audioContainer.appendChild(audioButton); audioContainer.appendChild(audioDropdown); const qualityContainer = document.getElementById('plyr-quality-container'); if (qualityContainer) { qualityContainer.insertAdjacentElement('beforebegin', audioContainer); } else { const settingsBtn = controls.querySelector('[data-plyr="settings"]'); if (settingsBtn) { settingsBtn.insertAdjacentElement('beforebegin', audioContainer); } else { controls.appendChild(audioContainer); } } if (hlsInstance && hlsInstance.audioTracks && hlsInstance.audioTracks.length > 0) { // Prefer "original" audio track const originalIdx = hlsInstance.audioTracks.findIndex(t => (t.name || '').toLowerCase().includes('original') ); if (originalIdx !== -1) { hlsInstance.audioTrack = originalIdx; console.log('Selected original audio track:', hlsInstance.audioTracks[originalIdx].name); } const currentTrack = hlsInstance.audioTracks[hlsInstance.audioTrack]; if (currentTrack) { const audioText = document.getElementById('plyr-audio-text'); if (audioText) { const trackName = currentTrack.name || currentTrack.lang || 'Audio'; audioText.textContent = trackName.length > 8 ? trackName.substring(0, 6) + '...' : trackName; } } } hlsInstance.on(Hls.Events.AUDIO_TRACKS_UPDATED, () => { console.log('Audio tracks updated, count:', hlsInstance.audioTracks?.length); if (hlsInstance.audioTracks?.length > 0) { updateAudioDropdown(); const currentTrack = hlsInstance.audioTracks[hlsInstance.audioTrack]; if (currentTrack) { const audioText = document.getElementById('plyr-audio-text'); if (audioText) { const trackName = currentTrack.name || currentTrack.lang || 'Audio'; audioText.textContent = trackName.length > 8 ? trackName.substring(0, 6) + '...' : trackName; } } } }); console.log('Custom audio tracks control added'); }); } /** * Initialize Plyr with HLS quality options */ function initPlyrWithQuality(hlsInstance) { const video = document.getElementById('js-video-player'); if (!hlsInstance || !hlsInstance.levels || hlsInstance.levels.length === 0) { console.error('HLS not ready'); return; } if (!video) { console.error('Video element not found'); return; } console.log('HLS levels available:', hlsInstance.levels.length); const sortedLevels = [...hlsInstance.levels].sort((a, b) => b.height - a.height); const seenHeights = new Set(); const uniqueLevels = []; sortedLevels.forEach((level) => { if (!seenHeights.has(level.height)) { seenHeights.add(level.height); uniqueLevels.push(level); } }); const qualityLabels = ['auto']; uniqueLevels.forEach((level) => { const originalIndex = hlsInstance.levels.indexOf(level); const label = level.height + 'p'; if (!window.hlsQualityMap[label]) { qualityLabels.push(label); window.hlsQualityMap[label] = originalIndex; } }); console.log('Quality labels:', qualityLabels); const playerOptions = { autoplay: autoplayActive, disableContextMenu: false, captions: { active: captionsActive, language: typeof data !== 'undefined' ? data.settings.subtitles_language : 'en', }, controls: [ 'play-large', 'play', 'progress', 'current-time', 'duration', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen', ], iconUrl: '/youtube.com/static/modules/plyr/plyr.svg', blankVideo: '/youtube.com/static/modules/plyr/blank.webm', debug: false, storage: { enabled: false }, previewThumbnails: { enabled: typeof storyboard_url !== 'undefined' && storyboard_url !== null, src: typeof storyboard_url !== 'undefined' && storyboard_url !== null ? [storyboard_url] : [], }, settings: ['captions', 'speed', 'loop'], tooltips: { controls: true, }, }; console.log('Creating Plyr...'); try { plyrInstance = new Plyr(video, playerOptions); console.log('Plyr instance created'); window.plyrInstance = plyrInstance; addCustomQualityControl(plyrInstance, qualityLabels); addCustomAudioTracksControl(plyrInstance, hlsInstance); if (plyrInstance.eventListeners) { plyrInstance.eventListeners.forEach(function(eventListener) { if(eventListener.type === 'dblclick') { eventListener.element.removeEventListener(eventListener.type, eventListener.callback, eventListener.options); } }); } plyrInstance.started = false; plyrInstance.once('playing', function(){this.started = true}); if (typeof data !== 'undefined' && data.time_start != 0) { video.addEventListener('loadedmetadata', function() { video.currentTime = data.time_start; }); } console.log('Plyr init complete'); } catch (e) { console.error('Failed to initialize Plyr:', e); } } /** * Main initialization */ async function start() { console.log('Starting Plyr with HLS...'); if (typeof hls_manifest_url === 'undefined' || !hls_manifest_url) { console.error('No HLS manifest URL available'); return; } try { const hlsInstance = await initHLS(hls_manifest_url); initPlyrWithQuality(hlsInstance); } catch (error) { console.error('Failed to initialize:', error); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', start); } else { start(); } })();