diff --git a/materialious/src/lib/android/youtube/dash.ts b/materialious/src/lib/android/youtube/dash.ts new file mode 100644 index 00000000..1ba89832 --- /dev/null +++ b/materialious/src/lib/android/youtube/dash.ts @@ -0,0 +1,40 @@ +export async function dashManifestDomainInclusion(manifestUrl: string): Promise { + /* If on android, must manually format dash manifest to include URL. */ + + const response = await fetch(manifestUrl, { + method: 'GET', + redirect: 'follow' + }); + + if (!response.ok) { + throw Error('Unable to fetch manifest'); + } + + const finalUrl = response.url; + const manifestText = await response.text(); + + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(manifestText, 'application/xml'); + + // Find all BaseURL elements + const baseUrlElements = xmlDoc.getElementsByTagName('BaseURL'); + + for (let i = 0; i < baseUrlElements.length; i++) { + const baseUrlElement = baseUrlElements[i]; + const baseUrlValue = baseUrlElement.textContent; + + if (baseUrlValue && (baseUrlValue.startsWith('/') || !baseUrlValue.includes('://'))) { + const finalUrlObj = new URL(finalUrl); + const baseDomain = `${finalUrlObj.protocol}//${finalUrlObj.host}`; + const resolvedUrl = new URL(baseUrlValue, baseDomain).href; + baseUrlElement.textContent = resolvedUrl; + } + } + + const serializer = new XMLSerializer(); + const updatedManifest = serializer.serializeToString(xmlDoc); + + console.log(updatedManifest); + + return `data:application/dash+xml;base64,${btoa(updatedManifest)}`; +} diff --git a/materialious/src/lib/components/Player.svelte b/materialious/src/lib/components/Player.svelte index 74eaea6e..c5a89631 100644 --- a/materialious/src/lib/components/Player.svelte +++ b/materialious/src/lib/components/Player.svelte @@ -55,6 +55,7 @@ import { goto } from '$app/navigation'; import { unsafeRandomItem } from '$lib/misc'; import type { PlayerEvents } from '$lib/player'; + import { dashManifestDomainInclusion } from '$lib/android/youtube/dash'; interface Props { data: { video: VideoPlay; content: PhasedDescription; playlistId: string | null }; @@ -400,7 +401,12 @@ dashUrl += '?local=true'; } - await player.load(dashUrl, await getLastPlayPos()); + if (Capacitor.getPlatform() === 'android' && $playerProxyVideosStore) { + const manifest = await dashManifestDomainInclusion(dashUrl); + await player.load(manifest, await getLastPlayPos()); + } else { + await player.load(dashUrl, await getLastPlayPos()); + } } else { if (data.video.fallbackPatch === 'youtubejs') { if (!data.video.dashUrl) {