aboutsummaryrefslogtreecommitdiffstats
path: root/youtube/static
diff options
context:
space:
mode:
authorAstounds <kirito@disroot.org>2026-05-03 12:32:55 -0500
committerAstounds <kirito@disroot.org>2026-05-03 12:32:55 -0500
commit8d66143c90c4b86e8ec8dfed67753bef2abf2114 (patch)
treef7d73591c228cf52c7a4abd15c855d7d06e80ff8 /youtube/static
parent50ad959a8051fec95f26b573f9fe067bdf3fdf6a (diff)
downloadyt-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.js27
-rw-r--r--youtube/static/js/watch.hls.js48
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);
+ }
}
});
}