fix: update innertube clients and fix HLS/DASH quality switching
All checks were successful
CI / test (push) Successful in 53s
All checks were successful
CI / test (push) Successful in 53s
- 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
This commit is contained in:
@@ -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))
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user