From da1514b29576bb705b5e086942ca95dbce4e6eac Mon Sep 17 00:00:00 2001 From: absidue <48293849+absidue@users.noreply.github.com> Date: Tue, 10 Sep 2024 23:55:45 +0200 Subject: [PATCH] Proxy live streams when "Proxy Videos Through Invidious" is enabled (#5649) --- .../ft-shaka-video-player.js | 41 +++++++++++++++++++ src/renderer/views/Watch/Watch.js | 22 +++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js b/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js index 563fb37da207e..7e1d4722e2a99 100644 --- a/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js +++ b/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js @@ -38,6 +38,7 @@ const HTTP_IN_HEX = 0x68747470 const USE_OVERFLOW_MENU_WIDTH_THRESHOLD = 600 const RequestType = shaka.net.NetworkingEngine.RequestType +const AdvancedRequestType = shaka.net.NetworkingEngine.AdvancedRequestType const TrackLabelFormat = shaka.ui.Overlay.TrackLabelFormat /** @type {Map} */ @@ -1232,7 +1233,47 @@ export default defineComponent({ response.data = new TextEncoder().encode(cleaned).buffer } } + } else if (type === RequestType.MANIFEST && context.type === AdvancedRequestType.MEDIA_PLAYLIST) { + const url = new URL(response.uri) + + // Fixes proxied HLS manifests, as Invidious replaces the path parameters with query parameters, + // so shaka-player isn't able to infer the mime type from the `/file/seg.ts` part like it does for non-proxied HLS manifests. + // Shaka-player does attempt to detect it with HEAD request but the `Content-Type` header is `application/octet-stream`, + // which still doesn't tell shaka-player how to handle the stream because that's the equivalent of saying "binary data". + if (url.searchParams.has('local')) { + const stringBody = new TextDecoder().decode(response.data) + const fixed = stringBody.replaceAll(/https?:\/\/.+$/gm, hlsProxiedUrlReplacer) + + response.data = new TextEncoder().encode(fixed).buffer + } + } + } + + /** + * @param {string} match + */ + function hlsProxiedUrlReplacer(match) { + const url = new URL(match) + + let fileValue + for (const [key, value] of url.searchParams) { + if (key === 'file') { + fileValue = value + continue + } else if (key === 'hls_chunk_host') { + // Add the host parameter so some Invidious instances stop complaining about the missing host parameter + // Replace .c.youtube.com with .googlevideo.com as the built-in Invidious video proxy only accepts host parameters with googlevideo.com + url.pathname += `/host/${encodeURIComponent(value.replace('.c.youtube.com', '.googlevideo.com'))}` + } + + url.pathname += `/${key}/${encodeURIComponent(value)}` } + + // This has to be right at the end so that shaka-player can read the file extension + url.pathname += `/file/${encodeURIComponent(fileValue)}` + + url.search = '' + return url.toString() } // #endregion request/response filters diff --git a/src/renderer/views/Watch/Watch.js b/src/renderer/views/Watch/Watch.js index f25e22cd92de0..30e87e82610ce 100644 --- a/src/renderer/views/Watch/Watch.js +++ b/src/renderer/views/Watch/Watch.js @@ -493,7 +493,16 @@ export default defineComponent({ // this.manifestSrc = src // this.manifestMimeType = MANIFEST_TYPE_DASH // } else { - this.manifestSrc = result.streaming_data.hls_manifest_url + let hlsManifestUrl = result.streaming_data.hls_manifest_url + + if (this.proxyVideos) { + const url = new URL(hlsManifestUrl) + url.searchParams.set('local', 'true') + + hlsManifestUrl = url.toString().replace(url.origin, this.currentInvidiousInstanceUrl) + } + + this.manifestSrc = hlsManifestUrl this.manifestMimeType = MANIFEST_TYPE_HLS // } } @@ -808,7 +817,16 @@ export default defineComponent({ // // Proxying doesn't work for live or post live DVR DASH, so use HLS instead // // https://github.com/iv-org/invidious/pull/4589 // if (this.proxyVideos) { - this.manifestSrc = result.hlsUrl + + let hlsManifestUrl = result.hlsUrl + + if (this.proxyVideos) { + const url = new URL(hlsManifestUrl) + url.searchParams.set('local', 'true') + hlsManifestUrl = url.toString() + } + + this.manifestSrc = hlsManifestUrl this.manifestMimeType = MANIFEST_TYPE_HLS // The HLS manifests only contain combined audio+video streams, so we can't do audio only