diff --git a/frontend/src/lib/components/StreamPlayer.svelte b/frontend/src/lib/components/StreamPlayer.svelte index 66cdcfb..f6e469d 100644 --- a/frontend/src/lib/components/StreamPlayer.svelte +++ b/frontend/src/lib/components/StreamPlayer.svelte @@ -147,12 +147,32 @@ enableWorker: true, lowLatencyMode: true, backBufferLength: 90, + // Increased retry settings for LLHLS resilience + fragLoadingMaxRetry: 6, + fragLoadingRetryDelay: 1000, + manifestLoadingMaxRetry: 4, + levelLoadingMaxRetry: 4, + maxBufferLength: 30, + maxBufferHole: 0.5, xhrSetup: function(xhr, url) { - // Only add token if not already present (segments don't have it) - if (viewerToken && url.includes('/app/') && !url.includes('token=')) { - const separator = url.includes('?') ? '&' : '?'; - xhr.open('GET', url + separator + 'token=' + encodeURIComponent(viewerToken), true); + let finalUrl = url; + + // Use URL API for proper parameter handling to avoid encoding issues + try { + const urlObj = new URL(url); + if (viewerToken && url.includes('/app/') && !urlObj.searchParams.has('token')) { + urlObj.searchParams.set('token', viewerToken); + finalUrl = urlObj.toString(); + } + } catch (e) { + // Fallback for relative URLs + if (viewerToken && url.includes('/app/') && !url.includes('token=')) { + const separator = url.includes('?') ? '&' : '?'; + finalUrl = url + separator + 'token=' + encodeURIComponent(viewerToken); + } } + + xhr.open('GET', finalUrl, true); xhr.withCredentials = true; } } diff --git a/frontend/src/lib/components/StreamTileOverlay.svelte b/frontend/src/lib/components/StreamTileOverlay.svelte index 85eee67..f0d42ae 100644 --- a/frontend/src/lib/components/StreamTileOverlay.svelte +++ b/frontend/src/lib/components/StreamTileOverlay.svelte @@ -162,11 +162,32 @@ enableWorker: true, lowLatencyMode: true, backBufferLength: 30, + // Increased retry settings for LLHLS resilience + fragLoadingMaxRetry: 6, + fragLoadingRetryDelay: 1000, + manifestLoadingMaxRetry: 4, + levelLoadingMaxRetry: 4, + maxBufferLength: 30, + maxBufferHole: 0.5, xhrSetup: function(xhr, url) { - if (token && url.includes('/app/') && !url.includes('token=')) { - const separator = url.includes('?') ? '&' : '?'; - xhr.open('GET', url + separator + 'token=' + encodeURIComponent(token), true); + let finalUrl = url; + + // Use URL API for proper parameter handling to avoid encoding issues + try { + const urlObj = new URL(url); + if (token && url.includes('/app/') && !urlObj.searchParams.has('token')) { + urlObj.searchParams.set('token', token); + finalUrl = urlObj.toString(); + } + } catch (e) { + // Fallback for relative URLs + if (token && url.includes('/app/') && !url.includes('token=')) { + const separator = url.includes('?') ? '&' : '?'; + finalUrl = url + separator + 'token=' + encodeURIComponent(token); + } } + + xhr.open('GET', finalUrl, true); xhr.withCredentials = true; } } diff --git a/frontend/src/routes/[realm]/live/+page.svelte b/frontend/src/routes/[realm]/live/+page.svelte index 7756515..a8ba888 100644 --- a/frontend/src/routes/[realm]/live/+page.svelte +++ b/frontend/src/routes/[realm]/live/+page.svelte @@ -356,13 +356,32 @@ enableWorker: true, lowLatencyMode: true, backBufferLength: 90, + // Increased retry settings for LLHLS resilience + fragLoadingMaxRetry: 6, + fragLoadingRetryDelay: 1000, + manifestLoadingMaxRetry: 4, + levelLoadingMaxRetry: 4, + maxBufferLength: 30, + maxBufferHole: 0.5, xhrSetup: function(xhr, url) { - // Add viewer token to HLS segment requests (not playlists, which already have it) - // Only add if token is not already present in the URL - if (viewerToken && url.includes('/app/') && !url.includes('token=')) { - const separator = url.includes('?') ? '&' : '?'; - xhr.open('GET', url + separator + 'token=' + encodeURIComponent(viewerToken), true); + let finalUrl = url; + + // Use URL API for proper parameter handling to avoid encoding issues + try { + const urlObj = new URL(url); + if (viewerToken && url.includes('/app/') && !urlObj.searchParams.has('token')) { + urlObj.searchParams.set('token', viewerToken); + finalUrl = urlObj.toString(); + } + } catch (e) { + // Fallback for relative URLs + if (viewerToken && url.includes('/app/') && !url.includes('token=')) { + const separator = url.includes('?') ? '&' : '?'; + finalUrl = url + separator + 'token=' + encodeURIComponent(viewerToken); + } } + + xhr.open('GET', finalUrl, true); xhr.withCredentials = true; } }