diff options
| author | Astounds <kirito@disroot.org> | 2026-04-05 14:56:51 -0500 |
|---|---|---|
| committer | Astounds <kirito@disroot.org> | 2026-04-05 14:56:51 -0500 |
| commit | f0649be5dec84ce06a3164a2d9ee90f5385ac92f (patch) | |
| tree | 6dcae30ff3e0d66c895033aab9e92a4c9e4ed513 /youtube/static/js/watch.hls.js | |
| parent | 62a028968e6d9b4e821b6014d6658b8317328fcf (diff) | |
| download | yt-local-f0649be5dec84ce06a3164a2d9ee90f5385ac92f.tar.lz yt-local-f0649be5dec84ce06a3164a2d9ee90f5385ac92f.tar.xz yt-local-f0649be5dec84ce06a3164a2d9ee90f5385ac92f.zip | |
Add HLS support to multi-audio
Diffstat (limited to 'youtube/static/js/watch.hls.js')
| -rw-r--r-- | youtube/static/js/watch.hls.js | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/youtube/static/js/watch.hls.js b/youtube/static/js/watch.hls.js new file mode 100644 index 0000000..cedfbf3 --- /dev/null +++ b/youtube/static/js/watch.hls.js @@ -0,0 +1,323 @@ +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 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 = '<option value="">Select audio track</option>'; + 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); + } + } +})(); |
