diff options
| author | Astounds <kirito@disroot.org> | 2026-05-03 12:32:55 -0500 |
|---|---|---|
| committer | Astounds <kirito@disroot.org> | 2026-05-03 12:32:55 -0500 |
| commit | 8d66143c90c4b86e8ec8dfed67753bef2abf2114 (patch) | |
| tree | f7d73591c228cf52c7a4abd15c855d7d06e80ff8 /youtube/static | |
| parent | 50ad959a8051fec95f26b573f9fe067bdf3fdf6a (diff) | |
| download | yt-local-8d66143c90c4b86e8ec8dfed67753bef2abf2114.tar.lz yt-local-8d66143c90c4b86e8ec8dfed67753bef2abf2114.tar.xz yt-local-8d66143c90c4b86e8ec8dfed67753bef2abf2114.zip | |
fix: update innertube clients and fix HLS/DASH quality switching
- Update innertube client versions to match yt-dlp (android 21.02.35,
ios 21.02.3, web 2.20260114.08.00, android_vr 1.65.10)
- Remove obsolete clients (android-test-suite, ios_vr)
- Replace tv_embedded with TVHTML5_SIMPLY (cn 75)
- Add new clients: web_embedded, mweb, tv
- Fix HLS freeze on quality switch: use nextLevel instead of
currentLevel, handle bufferStalledError, stream proxy segments
instead of buffering in memory
- Populate DASH quality selector with actual sources (no Auto)
- Render quality-select empty in template, let JS populate per mode
Diffstat (limited to 'youtube/static')
| -rw-r--r-- | youtube/static/js/watch.dash.js | 27 | ||||
| -rw-r--r-- | youtube/static/js/watch.hls.js | 48 |
2 files changed, 72 insertions, 3 deletions
diff --git a/youtube/static/js/watch.dash.js b/youtube/static/js/watch.dash.js index cb02421..5bbc7a2 100644 --- a/youtube/static/js/watch.dash.js +++ b/youtube/static/js/watch.dash.js @@ -31,9 +31,34 @@ if (data.using_pair_sources) { avMerge = new AVMerge(video, srcPair, 0); } -// Quality selector +// Quality selector — populate with available sources const qs = document.getElementById('quality-select'); if (qs) { + // Clear the HLS-oriented "Auto" default; DASH has discrete sources + qs.innerHTML = ''; + + // Add pair_sources (video+audio, used by AVMerge) + if (data['pair_sources'] && data['pair_sources'].length) { + data['pair_sources'].forEach(function(src, i) { + let opt = document.createElement('option'); + opt.value = JSON.stringify({type: 'pair', index: i}); + opt.textContent = src.quality_string; + if (i === data['pair_idx']) opt.selected = true; + qs.appendChild(opt); + }); + } + + // Add uni_sources (integrated video+audio, single file) + if (data['uni_sources'] && data['uni_sources'].length) { + data['uni_sources'].forEach(function(src, i) { + let opt = document.createElement('option'); + opt.value = JSON.stringify({type: 'uni', index: i}); + opt.textContent = src.quality_string; + if (!data['pair_sources'].length && i === data['uni_idx']) opt.selected = true; + qs.appendChild(opt); + }); + } + qs.addEventListener('change', function(e) { changeQuality(JSON.parse(this.value)) }); diff --git a/youtube/static/js/watch.hls.js b/youtube/static/js/watch.hls.js index a924fdb..38e80a9 100644 --- a/youtube/static/js/watch.hls.js +++ b/youtube/static/js/watch.hls.js @@ -26,7 +26,17 @@ function initHLSNative(manifestUrl) { lowLatencyMode: false, maxBufferLength: 30, maxMaxBufferLength: 60, + maxBufferHole: 0.5, startLevel: -1, + // Prevent stalls on quality switch: nudge playback past small gaps + nudgeMaxRetry: 5, + // Allow more time for segments coming through our proxy + fragLoadingTimeOut: 30000, + fragLoadingMaxRetry: 5, + fragLoadingRetryDelay: 1000, + levelLoadingTimeOut: 15000, + levelLoadingMaxRetry: 4, + levelLoadingRetryDelay: 1000, }); window.hls = hls; @@ -89,15 +99,26 @@ function initHLSNative(manifestUrl) { console.error('HLS fatal error:', data.type, data.details); switch(data.type) { case Hls.ErrorTypes.NETWORK_ERROR: + console.warn('HLS network error, attempting recovery...'); hls.startLoad(); break; case Hls.ErrorTypes.MEDIA_ERROR: + console.warn('HLS media error, attempting recovery...'); hls.recoverMediaError(); break; default: hls.destroy(); break; } + } else { + // Non-fatal errors can still cause stalls, especially + // bufferStalledError after a quality switch through our proxy + console.warn('HLS non-fatal error:', data.type, data.details); + if (data.details === 'bufferStalledError') { + // Buffer ran dry — HLS.js is waiting for data. + // Nudge it to retry loading the current fragment. + hls.startLoad(); + } } }); @@ -122,13 +143,36 @@ function initPlayer() { initHLSNative(hls_manifest_url); const qualitySelect = document.getElementById('quality-select'); + // Set initial Auto option while manifest loads + if (qualitySelect) { + qualitySelect.innerHTML = '<option value="-1" selected>Auto</option>'; + } 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'); + const currentTime = video.currentTime; + const wasPaused = video.paused; + + // Use nextLevel for smoother transition: it waits for the + // current segment to finish before switching, avoiding an + // abrupt buffer flush that starves the player. + if (level === -1) { + // Back to auto — re-enable ABR + hls.currentLevel = -1; + console.log('Quality: Auto (ABR)'); + } else { + hls.nextLevel = level; + console.log('Quality: switching to', + hls.levels[level]?.height + 'p'); + } + + // If the video was already stalled, kick the loader + // so it starts fetching the new level immediately. + if (video.readyState < 3) { + hls.startLoad(currentTime); + } } }); } |
