From e658d13ec5c003e72aaeb54cf2a8277c5a7fce6c Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Fri, 24 Oct 2025 19:23:48 -0600 Subject: [PATCH] attempt to fix airplay and add m3u8 proxy tool --- src/components/player/display/base.ts | 75 ++++++++++++++++++++++++++- src/components/player/utils/proxy.ts | 58 +++++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 src/components/player/utils/proxy.ts diff --git a/src/components/player/display/base.ts b/src/components/player/display/base.ts index c5703ae1..29f7f0aa 100644 --- a/src/components/player/display/base.ts +++ b/src/components/player/display/base.ts @@ -12,6 +12,11 @@ import { } from "@/components/player/display/displayInterface"; import { handleBuffered } from "@/components/player/utils/handleBuffered"; import { getMediaErrorDetails } from "@/components/player/utils/mediaErrorDetails"; +import { + createM3U8ProxyUrl, + createMP4ProxyUrl, + isUrlAlreadyProxied, +} from "@/components/player/utils/proxy"; import { useLanguageStore } from "@/stores/language"; import { LoadableSource, @@ -579,7 +584,75 @@ export function makeVideoElementDisplayInterface(): DisplayInterface { }, startAirplay() { const videoPlayer = videoElement as any; - if (videoPlayer && videoPlayer.webkitShowPlaybackTargetPicker) { + if (!videoPlayer || !videoPlayer.webkitShowPlaybackTargetPicker) return; + + if (!source) { + // No source loaded, just trigger Airplay + videoPlayer.webkitShowPlaybackTargetPicker(); + return; + } + + // Store the original URL to restore later + const originalUrl = + source?.type === "hls" ? hls?.url || source.url : videoPlayer.src; + + let proxiedUrl: string | null = null; + + if (source?.type === "hls") { + // Only proxy HLS streams if they need it: + // 1. Not already proxied AND + // 2. Has headers (either preferredHeaders or headers) + const allHeaders = { + ...source.preferredHeaders, + ...source.headers, + }; + const hasHeaders = Object.keys(allHeaders).length > 0; + + // Don't create proxy URL if it's already using the proxy + if (!isUrlAlreadyProxied(source.url) && hasHeaders) { + proxiedUrl = createM3U8ProxyUrl(source.url, allHeaders); + } else { + proxiedUrl = source.url; // Already proxied or no headers needed + } + } else if (source?.type === "mp4") { + // TODO: Implement MP4 proxy for protected streams + const hasHeaders = + source.headers && Object.keys(source.headers).length > 0; + if (hasHeaders) { + // Use MP4 proxy for streams with headers + proxiedUrl = createMP4ProxyUrl(source.url, source.headers || {}); + } else { + proxiedUrl = source.url; + } + } + + if (proxiedUrl && proxiedUrl !== originalUrl) { + // Temporarily set the proxied URL for Airplay + if (source?.type === "hls") { + if (hls) { + hls.loadSource(proxiedUrl); + } + } else { + videoPlayer.src = proxiedUrl; + } + + // Small delay to ensure the URL is set before triggering Airplay + setTimeout(() => { + videoPlayer.webkitShowPlaybackTargetPicker(); + + // Restore original URL after a short delay + setTimeout(() => { + if (source?.type === "hls") { + if (hls && originalUrl) { + hls.loadSource(originalUrl); + } + } else if (originalUrl) { + videoPlayer.src = originalUrl; + } + }, 1000); + }, 100); + } else { + // No proxying needed, just trigger Airplay videoPlayer.webkitShowPlaybackTargetPicker(); } }, diff --git a/src/components/player/utils/proxy.ts b/src/components/player/utils/proxy.ts new file mode 100644 index 00000000..649a2db0 --- /dev/null +++ b/src/components/player/utils/proxy.ts @@ -0,0 +1,58 @@ +import { getLoadbalancedM3U8ProxyUrl } from "@/backend/providers/fetchers"; +import { getM3U8ProxyUrls } from "@/utils/proxyUrls"; + +/** + * Creates a proxied M3U8 URL for HLS streams using a random proxy from config + * @param url - The original M3U8 URL to proxy + * @param headers - Headers to include with the request + * @returns The proxied M3U8 URL + */ +export function createM3U8ProxyUrl( + url: string, + headers: Record = {}, +): string { + // Get a random M3U8 proxy URL from the configuration + const proxyBaseUrl = getLoadbalancedM3U8ProxyUrl(); + + if (!proxyBaseUrl) { + console.warn("No M3U8 proxy URLs available in configuration"); + return url; // Fallback to original URL + } + + const encodedUrl = encodeURIComponent(url); + const encodedHeaders = encodeURIComponent(JSON.stringify(headers)); + return `${proxyBaseUrl}/m3u8-proxy?url=${encodedUrl}${headers ? `&headers=${encodedHeaders}` : ""}`; +} + +/** + * TODO: Creates a proxied MP4 URL for MP4 streams + * @param url - The original MP4 URL to proxy + * @param headers - Headers to include with the request + * @returns The proxied MP4 URL + */ +export function createMP4ProxyUrl( + url: string, + _headers: Record = {}, +): string { + // TODO: Implement MP4 proxy for protected streams + // This would need a separate MP4 proxy service that can handle headers + // For now, return the original URL + console.warn("MP4 proxy not yet implemented - using original URL"); + return url; +} + +/** + * Checks if a URL is already using one of the configured M3U8 proxy services + * @param url - The URL to check + * @returns True if the URL is already proxied, false otherwise + */ +export function isUrlAlreadyProxied(url: string): boolean { + // Check if URL contains the m3u8-proxy pattern + if (url.includes("/m3u8-proxy?url=")) { + return true; + } + + // Also check if URL starts with any of the configured proxy URLs + const proxyUrls = getM3U8ProxyUrls(); + return proxyUrls.some((proxyUrl) => url.startsWith(proxyUrl)); +}