Samsung TV fix: detect Tizen, lock to 720p, smaller buffer, better error recovery; revert 480p enable

This commit is contained in:
Sebastjan 2026-04-25 15:10:39 +02:00
parent d33d772b2f
commit b3ec537687

View File

@ -643,6 +643,14 @@
function showMsg(html) { msg.innerHTML = html; msg.classList.add('visible'); }
function hideMsg() { msg.classList.remove('visible'); }
// Device detection — Samsung Tizen, LG webOS, and other smart TVs need
// smaller buffers and lower resolution caps to avoid stalling.
const UA = navigator.userAgent || '';
const IS_SAMSUNG_TV = /Tizen|SMART-TV|SmartTV|SamsungBrowser/i.test(UA);
const IS_LG_TV = /webOS|Web0S|LG SmartTV/i.test(UA);
const IS_TV = IS_SAMSUNG_TV || IS_LG_TV || /Hisense|Bravia|Roku|AppleTV|GoogleTV|AndroidTV/i.test(UA);
const IS_LOW_POWER = IS_TV;
function attach() {
try { hls && hls.destroy(); } catch (e) {}
hls = null;
@ -655,7 +663,31 @@
}
if (window.Hls && window.Hls.isSupported()) {
hls = new Hls({
// TV-tuned settings (smaller buffers, 720p cap, more aggressive recovery)
const cfg = IS_LOW_POWER ? {
liveDurationInfinity: true,
liveSyncDurationCount: 3,
liveMaxLatencyDurationCount: 8,
backBufferLength: 10, // Tizen has tight memory
maxBufferLength: 20,
maxMaxBufferLength: 30,
maxBufferSize: 30 * 1000 * 1000, // 30 MB buffer cap (Tizen-friendly)
manifestLoadingTimeOut: 15000,
manifestLoadingMaxRetry: 8,
manifestLoadingRetryDelay: 1000,
levelLoadingTimeOut: 15000,
levelLoadingMaxRetry: 8,
fragLoadingTimeOut: 25000,
fragLoadingMaxRetry: 8,
fragLoadingRetryDelay: 1000,
// Lock to 720p on TVs — Tizen decoder struggles with 1080p HLS.js
startLevel: -1,
capLevelToPlayerSize: false,
abrEwmaDefaultEstimate: 2000000,
testBandwidth: true,
abrBandWidthFactor: 0.9,
abrBandWidthUpFactor: 0.6,
} : {
liveDurationInfinity: true,
liveSyncDurationCount: 4,
backBufferLength: 30,
@ -664,27 +696,61 @@
manifestLoadingMaxRetry: 6,
fragLoadingTimeOut: 20000,
fragLoadingMaxRetry: 6,
// ABR (Adaptive Bitrate) — let player auto-switch between qualities
startLevel: -1, // auto: start with whatever bandwidth allows
capLevelToPlayerSize: false, // don't cap to viewport size
abrEwmaDefaultEstimate: 3000000, // realistic 3 Mbps starting estimate
testBandwidth: true, // probe bandwidth on startup
abrBandWidthFactor: 0.95, // 95% of measured BW → safety margin
abrBandWidthUpFactor: 0.7, // need 70% headroom before upgrading
});
startLevel: -1,
capLevelToPlayerSize: false,
abrEwmaDefaultEstimate: 3000000,
testBandwidth: true,
abrBandWidthFactor: 0.95,
abrBandWidthUpFactor: 0.7,
};
hls = new Hls(cfg);
hls.loadSource(HLS_URL);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
// ABR enabled across ALL variants (1080p / 720p / 480p)
// Player will auto-pick best quality based on bandwidth.
try {
if (hls.levels && hls.levels.length) {
if (IS_LOW_POWER) {
// On Samsung TV / smart TV: lock to 720p (720p only, disable both 1080p and 480p).
// 1080p stalls on Tizen decoder, 480p is desynced from Rok's encoder.
hls.levels.forEach((lvl) => {
lvl.enabled = (lvl.height === 720);
});
// Also force start at 720p
const idx720 = hls.levels.findIndex(l => l.height === 720);
if (idx720 >= 0) {
hls.currentLevel = idx720;
hls.loadLevel = idx720;
}
} else {
// Desktop/mobile: only disable 480p (desynced); allow 1080p ↔ 720p ABR.
hls.levels.forEach((lvl) => {
if (lvl.height && lvl.height <= 480) {
lvl.enabled = false;
}
});
}
}
} catch (e) {}
hideMsg();
video.play().catch(() => setTimeout(() => video.play().catch(() => {}), 200));
});
hls.on(Hls.Events.ERROR, (_e, data) => {
if (!data.fatal) return;
// Try in-place recovery first (faster than full reattach, less disruptive on TV)
if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
try { hls.startLoad(); return; } catch (e) {}
}
if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
try { hls.recoverMediaError(); return; } catch (e) {}
}
// Last resort: full reattach
showMsg('<div class="spinner"></div><div>Verbindung wird wiederhergestellt…</div>');
setTimeout(() => { attach(); }, 3000);
});
// Buffer stall recovery — esp. needed on Samsung TV
hls.on(Hls.Events.BUFFER_STALLED_ERROR, () => {
try { hls.startLoad(); } catch (e) {}
});
} else {
showMsg('<div>HLS wird von diesem Browser nicht unterstützt.</div>');
}