From 9e6bf5af73a9348b98db2a51ad826c57f5f177c6 Mon Sep 17 00:00:00 2001 From: WardPearce Date: Fri, 30 May 2025 22:30:52 +1200 Subject: [PATCH 1/5] Update 1.8.7 --- materialious/android/app/build.gradle | 4 +- .../electron/materialious.metainfo.xml | 6 +- materialious/electron/package.json | 4 +- materialious/package.json | 2 +- materialious/src/lib/api/index.ts | 125 +++++++++++++----- materialious/src/lib/components/Player.svelte | 48 +++++-- .../src/lib/css/shaka-player-theme.css | 6 +- materialious/src/lib/patches/youtubejs.ts | 8 +- materialious/src/lib/sabr/formatKeyUtils.ts | 2 +- materialious/src/lib/sabr/sabrUmpParser.ts | 3 +- materialious/src/lib/sabr/shakaHttpPlugin.ts | 6 +- update_versions.py | 2 +- 12 files changed, 156 insertions(+), 60 deletions(-) diff --git a/materialious/android/app/build.gradle b/materialious/android/app/build.gradle index f6402ca6..eaafb9e4 100644 --- a/materialious/android/app/build.gradle +++ b/materialious/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "us.materialio.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 110 - versionName "1.8.6" + versionCode 111 + versionName "1.8.7" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/materialious/electron/materialious.metainfo.xml b/materialious/electron/materialious.metainfo.xml index 5e98996c..92d89949 100644 --- a/materialious/electron/materialious.metainfo.xml +++ b/materialious/electron/materialious.metainfo.xml @@ -55,7 +55,11 @@ - + + + https://github.com/Materialious/Materialious/releases/tag/1.8.7 + + https://github.com/Materialious/Materialious/releases/tag/1.8.6 diff --git a/materialious/electron/package.json b/materialious/electron/package.json index 7baf0a34..37480b84 100644 --- a/materialious/electron/package.json +++ b/materialious/electron/package.json @@ -1,6 +1,6 @@ { "name": "Materialious", - "version": "1.8.6", + "version": "1.8.7", "description": "Modern material design for Invidious.", "author": { "name": "Ward Pearce", @@ -44,4 +44,4 @@ "capacitor", "electron" ] -} +} \ No newline at end of file diff --git a/materialious/package.json b/materialious/package.json index 45497fe3..911b0e87 100644 --- a/materialious/package.json +++ b/materialious/package.json @@ -1,6 +1,6 @@ { "name": "materialious", - "version": "1.8.6", + "version": "1.8.7", "private": true, "scripts": { "dev": "vite dev", diff --git a/materialious/src/lib/api/index.ts b/materialious/src/lib/api/index.ts index 40b6c905..f1b9911c 100644 --- a/materialious/src/lib/api/index.ts +++ b/materialious/src/lib/api/index.ts @@ -52,14 +52,14 @@ export async function fetchErrorHandle(response: Response): Promise { try { const json = await response.json(); message = 'errorBacktrace' in json ? json.errorBacktrace : json.error; - } catch { } + } catch {} throw Error(message); } return response; } -export function buildAuthHeaders(): { headers: { Authorization: string; }; } { +export function buildAuthHeaders(): { headers: { Authorization: string } } { return { headers: { Authorization: `Bearer ${get(authStore)?.token}` } }; } @@ -78,7 +78,11 @@ export async function getResolveUrl(url: string): Promise { return await resp.json(); } -export async function getVideo(videoId: string, local: boolean = false, fetchOptions?: RequestInit): Promise { +export async function getVideo( + videoId: string, + local: boolean = false, + fetchOptions?: RequestInit +): Promise { if (get(playerYouTubeJsAlways) && Capacitor.isNativePlatform()) { return await patchYoutubeJs(videoId); } @@ -93,7 +97,10 @@ export async function getVideo(videoId: string, local: boolean = false, fetchOpt return await resp.json(); } -export async function getDislikes(videoId: string, fetchOptions?: RequestInit): Promise { +export async function getDislikes( + videoId: string, + fetchOptions?: RequestInit +): Promise { const resp = await fetchErrorHandle( await fetch(`${get(returnYTDislikesInstanceStore)}/votes?videoId=${videoId}`, fetchOptions) ); @@ -123,8 +130,13 @@ export async function getComments( return await resp.json(); } -export async function getChannel(channelId: string, fetchOptions?: RequestInit): Promise { - const resp = await fetchErrorHandle(await fetch(buildPath(`channels/${channelId}`), fetchOptions)); +export async function getChannel( + channelId: string, + fetchOptions?: RequestInit +): Promise { + const resp = await fetchErrorHandle( + await fetch(buildPath(`channels/${channelId}`), fetchOptions) + ); return await resp.json(); } @@ -147,14 +159,17 @@ export async function getChannelContent( return await resp.json(); } -export async function getSearchSuggestions(search: string, fetchOptions?: RequestInit): Promise { +export async function getSearchSuggestions( + search: string, + fetchOptions?: RequestInit +): Promise { const path = buildPath('search/suggestions'); path.search = new URLSearchParams({ q: search }).toString(); const resp = await fetchErrorHandle(await fetch(path, fetchOptions)); return await resp.json(); } -export async function getHashtag(tag: string, page: number = 0): Promise<{ results: Video[]; }> { +export async function getHashtag(tag: string, page: number = 0): Promise<{ results: Video[] }> { const resp = await fetchErrorHandle(await fetch(buildPath(`hashtag/${tag}?page=${page}`))); return await resp.json(); } @@ -186,13 +201,19 @@ export async function getSearch( return await resp.json(); } -export async function getFeed(maxResults: number, page: number, fetchOptions: RequestInit = {}): Promise { +export async function getFeed( + maxResults: number, + page: number, + fetchOptions: RequestInit = {} +): Promise { const path = buildPath('auth/feed'); path.search = new URLSearchParams({ max_results: maxResults.toString(), page: page.toString() }).toString(); - const resp = await fetchErrorHandle(await fetch(path, { ...buildAuthHeaders(), ...fetchOptions })); + const resp = await fetchErrorHandle( + await fetch(path, { ...buildAuthHeaders(), ...fetchOptions }) + ); return await resp.json(); } @@ -203,11 +224,16 @@ export async function getSubscriptions(fetchOptions: RequestInit = {}): Promise< return await resp.json(); } -export async function amSubscribed(authorId: string, fetchOptions: RequestInit = {}): Promise { +export async function amSubscribed( + authorId: string, + fetchOptions: RequestInit = {} +): Promise { if (!get(authStore)) return false; try { - const subscriptions = (await getSubscriptions(fetchOptions)).filter((sub) => sub.authorId === authorId); + const subscriptions = (await getSubscriptions(fetchOptions)).filter( + (sub) => sub.authorId === authorId + ); return subscriptions.length === 1; } catch { return false; @@ -234,17 +260,24 @@ export async function deleteUnsubscribe(authorId: string, fetchOptions: RequestI ); } -export async function getHistory(page: number = 1, maxResults: number = 20, fetchOptions: RequestInit = {}): Promise { +export async function getHistory( + page: number = 1, + maxResults: number = 20, + fetchOptions: RequestInit = {} +): Promise { const resp = await fetchErrorHandle( - await fetch( - buildPath(`auth/history?page=${page}&max_results=${maxResults}`), - { ...buildAuthHeaders(), ...fetchOptions }, - ) + await fetch(buildPath(`auth/history?page=${page}&max_results=${maxResults}`), { + ...buildAuthHeaders(), + ...fetchOptions + }) ); return await resp.json(); } -export async function deleteHistory(videoId: string | undefined = undefined, fetchOptions: RequestInit = {}) { +export async function deleteHistory( + videoId: string | undefined = undefined, + fetchOptions: RequestInit = {} +) { let url = '/api/v1/auth/history'; if (typeof videoId !== 'undefined') { url += `/${videoId}`; @@ -269,11 +302,18 @@ export async function postHistory(videoId: string, fetchOptions: RequestInit = { ); } -export async function getPlaylist(playlistId: string, page: number = 1, fetchOptions: RequestInit = {}): Promise { +export async function getPlaylist( + playlistId: string, + page: number = 1, + fetchOptions: RequestInit = {} +): Promise { let resp; if (get(authStore)) { - resp = await fetch(buildPath(`auth/playlists/${playlistId}?page=${page}`), { ...buildAuthHeaders(), ...fetchOptions }); + resp = await fetch(buildPath(`auth/playlists/${playlistId}?page=${page}`), { + ...buildAuthHeaders(), + ...fetchOptions + }); } else { resp = await fetch(buildPath(`playlists/${playlistId}?page=${page}`), fetchOptions); } @@ -281,8 +321,12 @@ export async function getPlaylist(playlistId: string, page: number = 1, fetchOpt return await resp.json(); } -export async function getPersonalPlaylists(fetchOptions: RequestInit = {}): Promise { - const resp = await fetchErrorHandle(await fetch(buildPath('auth/playlists'), { ...buildAuthHeaders(), ...fetchOptions })); +export async function getPersonalPlaylists( + fetchOptions: RequestInit = {} +): Promise { + const resp = await fetchErrorHandle( + await fetch(buildPath('auth/playlists'), { ...buildAuthHeaders(), ...fetchOptions }) + ); return await resp.json(); } @@ -316,7 +360,11 @@ export async function postPersonalPlaylist( ); } -export async function addPlaylistVideo(playlistId: string, videoId: string, fetchOptions: RequestInit = {}) { +export async function addPlaylistVideo( + playlistId: string, + videoId: string, + fetchOptions: RequestInit = {} +) { const headers: Record> = buildAuthHeaders(); headers['headers']['Content-type'] = 'application/json'; @@ -332,7 +380,11 @@ export async function addPlaylistVideo(playlistId: string, videoId: string, fetc ); } -export async function removePlaylistVideo(playlistId: string, indexId: string, fetchOptions: RequestInit = {}) { +export async function removePlaylistVideo( + playlistId: string, + indexId: string, + fetchOptions: RequestInit = {} +) { await fetchErrorHandle( await fetch(buildPath(`auth/playlists/${playlistId}/videos/${indexId}`), { method: 'DELETE', @@ -349,7 +401,11 @@ export async function getDeArrow(videoId: string, fetchOptions?: RequestInit): P return await resp.json(); } -export async function getThumbnail(videoId: string, time: number, fetchOptions?: RequestInit): Promise { +export async function getThumbnail( + videoId: string, + time: number, + fetchOptions?: RequestInit +): Promise { const resp = await fetchErrorHandle( await fetch( `${get(deArrowThumbnailInstanceStore)}/api/v1/getThumbnail?videoID=${videoId}&time=${time}`, @@ -359,18 +415,25 @@ export async function getThumbnail(videoId: string, time: number, fetchOptions?: return URL.createObjectURL(await resp.blob()); } -export async function getVideoProgress(videoId: string, fetchOptions: RequestInit = {}): Promise { +export async function getVideoProgress( + videoId: string, + fetchOptions: RequestInit = {} +): Promise { const resp = await fetchErrorHandle( - await fetch( - `${get(synciousInstanceStore)}/video/${encodeURIComponent(videoId)}`, - { ...buildAuthHeaders(), ...fetchOptions } - ) + await fetch(`${get(synciousInstanceStore)}/video/${encodeURIComponent(videoId)}`, { + ...buildAuthHeaders(), + ...fetchOptions + }) ); return resp.json(); } -export async function saveVideoProgress(videoId: string, time: number, fetchOptions: RequestInit = {}) { +export async function saveVideoProgress( + videoId: string, + time: number, + fetchOptions: RequestInit = {} +) { const headers: Record> = buildAuthHeaders(); headers['headers']['Content-type'] = 'application/json'; diff --git a/materialious/src/lib/components/Player.svelte b/materialious/src/lib/components/Player.svelte index 415102e9..f9ee5fc2 100644 --- a/materialious/src/lib/components/Player.svelte +++ b/materialious/src/lib/components/Player.svelte @@ -66,6 +66,7 @@ let playerPosSet = false; let originalOrigination: ScreenOrientationResult | undefined; let watchProgressTimeout: NodeJS.Timeout; + let userWantsFullscreen = false; let player: shaka.Player; let shakaUi: shaka.ui.Overlay; @@ -308,11 +309,21 @@ if (!networkingEngine) return; + // Required to stop buttons from being still selected when fullscreening + document.addEventListener('fullscreenchange', async () => { + userWantsFullscreen = document.fullscreenElement !== null; + const buttons = document.querySelectorAll('.shaka-controls-button-panel button'); + buttons.forEach((button) => { + // Reset the button's focus and active states + (button as HTMLElement).blur(); // Remove focus from the button + button.removeAttribute('aria-pressed'); // Reset any ARIA attributes that might indicate selection + }); + }); + // Based off the following // https://github.com/FreeTubeApp/FreeTube/blob/d270c9e251a433f1e4246a3f6a37acef707d22aa/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js#L1206 // https://github.com/LuanRT/BgUtils/blob/6b121166be1ccb0b952dee1bdac488808365ae6b/examples/browser/web/src/main.ts#L293 // https://github.com/LuanRT/yt-sabr-shaka-demo/blob/main/src/components/VideoPlayer.vue - networkingEngine.registerRequestFilter(async (type, request, context) => { if (!player) return; @@ -506,10 +517,7 @@ request.method = 'POST'; if (!isSabr) { - if (request.method === 'POST') { - request.body = new Uint8Array([120, 0]); - } - + request.body = new Uint8Array([120, 0]); const poToken = get(poTokenCacheStore); if (poToken) { @@ -688,7 +696,7 @@ if (timestampIndex > 0) { player.addChaptersTrack( - URL.createObjectURL(new Blob([chapterWebVTT])), + `data:text/vtt;base64,${btoa(chapterWebVTT)}`, get(playerDefaultLanguage) ); } @@ -834,12 +842,20 @@ } else { playerElement.pause(); } + + if (!document.fullscreenElement && userWantsFullscreen) { + shakaUi.getControls()?.toggleFullScreen(); + } return false; }); Mousetrap.bind('right', () => { if (!playerElement) return; playerElement.currentTime = playerElement.currentTime + 10; + + if (!document.fullscreenElement && userWantsFullscreen) { + shakaUi.getControls()?.toggleFullScreen(); + } return false; }); @@ -847,6 +863,10 @@ if (!playerElement) return; playerElement.currentTime = playerElement.currentTime - 10; + + if (!document.fullscreenElement && userWantsFullscreen) { + shakaUi.getControls()?.toggleFullScreen(); + } return false; }); @@ -866,6 +886,10 @@ player.setTextTrackVisibility(true); } } + + if (!document.fullscreenElement && userWantsFullscreen) { + shakaUi.getControls()?.toggleFullScreen(); + } return false; }); @@ -873,9 +897,7 @@ if (document.fullscreenElement) { document.exitFullscreen(); } else { - if (!playerElement) return; - - playerElement.requestFullscreen(); + shakaUi.getControls()?.toggleFullScreen(); } return false; }); @@ -884,12 +906,20 @@ if (!playerElement) return; playerElement.playbackRate = playerElement.playbackRate - 0.25; + + if (!document.fullscreenElement && userWantsFullscreen) { + shakaUi.getControls()?.toggleFullScreen(); + } }); Mousetrap.bind('shift+right', () => { if (!playerElement) return; playerElement.playbackRate = playerElement.playbackRate + 0.25; + + if (!document.fullscreenElement && userWantsFullscreen) { + shakaUi.getControls()?.toggleFullScreen(); + } }); }); diff --git a/materialious/src/lib/css/shaka-player-theme.css b/materialious/src/lib/css/shaka-player-theme.css index 37cd5a59..b59a55c2 100644 --- a/materialious/src/lib/css/shaka-player-theme.css +++ b/materialious/src/lib/css/shaka-player-theme.css @@ -280,7 +280,7 @@ width: 100% !important; min-width: 100% !important; position: fixed !important; - top: 100% !important; + top: 70% !important; left: 50% !important; height: 50vh !important; transform: translate(-50%, -50%) !important; @@ -288,10 +288,10 @@ pointer-events: auto !important; opacity: 1 !important; visibility: visible !important; - padding: 1rem !important; + padding: 1rem 0.5rem !important; box-shadow: var(--elevate2); background-color: var(--surface-container); - z-index: 11; + z-index: 40; inset: auto auto 0 0; inline-size: 100%; max-block-size: 50vh; diff --git a/materialious/src/lib/patches/youtubejs.ts b/materialious/src/lib/patches/youtubejs.ts index edd9dfe0..dc366b72 100644 --- a/materialious/src/lib/patches/youtubejs.ts +++ b/materialious/src/lib/patches/youtubejs.ts @@ -67,8 +67,7 @@ export async function patchYoutubeJs(videoId: string): Promise { }); const rawNextResponse = await watchEndpoint.call(youtube.actions, { override_endpoint: '/next', - racyCheckOk: true, - contentCheckOk: true + ...extraArgs }); const video = new YT.VideoInfo( [rawPlayerResponse, rawNextResponse], @@ -83,11 +82,12 @@ export async function patchYoutubeJs(videoId: string): Promise { let dashUri: string | undefined; if (video.streaming_data) { - video.streaming_data.adaptive_formats.forEach((format) => { + video.streaming_data.adaptive_formats = video.streaming_data.adaptive_formats.map((format) => { const formatKey = fromFormat(format) || ''; format.url = `https://sabr?___key=${formatKey}`; format.signature_cipher = undefined; format.decipher = () => format.url || ''; + return format; }); if (video.basic_info.is_live) { @@ -99,7 +99,7 @@ export async function patchYoutubeJs(videoId: string): Promise { ? video.streaming_data.hls_manifest_url // HLS is preferred for DVR streams. : `${video.streaming_data.dash_manifest_url}/mpd_version/7`; } else { - dashUri = `data:application/dash+xml;base64,${btoa(await video.toDash(undefined, undefined, { captions_format: 'vtt' }))}`; + dashUri = `data:application/dash+xml;base64,${btoa(await video.toDash(undefined, undefined))}`; } } diff --git a/materialious/src/lib/sabr/formatKeyUtils.ts b/materialious/src/lib/sabr/formatKeyUtils.ts index 8bac92c4..b9df6a0f 100644 --- a/materialious/src/lib/sabr/formatKeyUtils.ts +++ b/materialious/src/lib/sabr/formatKeyUtils.ts @@ -58,7 +58,7 @@ export function createSegmentCacheKey( if (isInit && format) { return `${mediaHeader.itag}:${mediaHeader.xtags || ''}:${format.content_length || ''}:${format.mime_type || ''}`; } - /* @ts-ignore */ + // @ts-expect-error Invalid typing return `${mediaHeader.startRange || '0'}-${mediaHeader.itag}-${mediaHeader.xtags || ''}`; } diff --git a/materialious/src/lib/sabr/sabrUmpParser.ts b/materialious/src/lib/sabr/sabrUmpParser.ts index b4318990..2dcc5a8a 100644 --- a/materialious/src/lib/sabr/sabrUmpParser.ts +++ b/materialious/src/lib/sabr/sabrUmpParser.ts @@ -1,5 +1,4 @@ -import { GoogleVideo, Protos, concatenateChunks, PART } from 'googlevideo'; -import type { Part } from 'googlevideo'; +import { GoogleVideo, Protos, concatenateChunks, type Part, PART } from 'googlevideo'; import shaka from 'shaka-player/dist/shaka-player.ui'; import { cacheSegment } from './cacheHelper'; diff --git a/materialious/src/lib/sabr/shakaHttpPlugin.ts b/materialious/src/lib/sabr/shakaHttpPlugin.ts index 6ee23e8c..24dc1a03 100644 --- a/materialious/src/lib/sabr/shakaHttpPlugin.ts +++ b/materialious/src/lib/sabr/shakaHttpPlugin.ts @@ -6,7 +6,6 @@ import { retrieveCachedSegment } from './cacheHelper'; import { SabrUmpParser } from './sabrUmpParser'; import { CacheManager } from './cacheManager'; import { Capacitor } from '@capacitor/core'; -import { androidFetch } from '$lib/android/http/androidRequests'; export interface SabrStreamingContext { byteRange?: { start: number; end: number }; @@ -28,7 +27,7 @@ export interface SabrStreamingContext { } export class HttpFetchPlugin { - private static fetch_ = Capacitor.getPlatform() === 'android' ? androidFetch : window.fetch; + private static fetch_ = window.fetch; private static AbortController_ = window.AbortController; private static Headers_ = window.Headers; public static cacheManager = new CacheManager(); @@ -51,7 +50,7 @@ export class HttpFetchPlugin { const headers = new HttpFetchPlugin.Headers_(); HttpFetchPlugin.asMap(request.headers).forEach((value, key) => { - headers.append(key as string, value as string); + headers.append(key as string, value); }); let sabrStreamingContext: string | null = null; @@ -105,6 +104,7 @@ export class HttpFetchPlugin { abortStatus.timedOut = true; controller.abort(); }); + timer.tickAfter(timeoutMs / 1000); op.finally(() => timer.stop()); } diff --git a/update_versions.py b/update_versions.py index 706565a8..041fa243 100644 --- a/update_versions.py +++ b/update_versions.py @@ -3,7 +3,7 @@ import os import re from datetime import datetime -LATEST_VERSION = "1.8.6" +LATEST_VERSION = "1.8.7" RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D WORKING_DIR = os.path.join( From fd5c516b8084542f0f5db95dfbddbe668b326b56 Mon Sep 17 00:00:00 2001 From: WardPearce Date: Fri, 30 May 2025 23:22:12 +1200 Subject: [PATCH 2/5] Force SABR unless is live --- materialious/electron/package-lock.json | 4 +- materialious/package-lock.json | 16 ++-- materialious/package.json | 4 +- ...4.13.0.patch => shaka-player+4.11.7.patch} | 21 ++--- materialious/src/lib/components/Player.svelte | 88 +++++++------------ 5 files changed, 57 insertions(+), 76 deletions(-) rename materialious/patches/{shaka-player+4.13.0.patch => shaka-player+4.11.7.patch} (66%) diff --git a/materialious/electron/package-lock.json b/materialious/electron/package-lock.json index 357d004c..3effef41 100644 --- a/materialious/electron/package-lock.json +++ b/materialious/electron/package-lock.json @@ -1,12 +1,12 @@ { "name": "Materialious", - "version": "1.8.6", + "version": "1.8.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Materialious", - "version": "1.8.6", + "version": "1.8.7", "license": "MIT", "dependencies": { "@capacitor-community/electron": "^5.0.0", diff --git a/materialious/package-lock.json b/materialious/package-lock.json index a525dfcf..ea02225a 100644 --- a/materialious/package-lock.json +++ b/materialious/package-lock.json @@ -1,12 +1,12 @@ { "name": "materialious", - "version": "1.8.6", + "version": "1.8.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "materialious", - "version": "1.8.6", + "version": "1.8.7", "hasInstallScript": true, "dependencies": { "@capacitor-community/electron": "^5.0.1", @@ -32,7 +32,7 @@ "media-captions": "^1.0.4", "mousetrap": "^1.6.5", "peerjs": "^1.5.4", - "shaka-player": "^4.13.0", + "shaka-player": "^4.11.7", "sponsorblock-api": "^0.2.4", "svelte-i18n": "^4.0.1", "svelte-infinite-loading": "^1.4.0", @@ -11379,15 +11379,15 @@ } }, "node_modules/shaka-player": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/shaka-player/-/shaka-player-4.13.0.tgz", - "integrity": "sha512-FZToyYl7IXvt1oMEWVbU0tjWJpJ+oSYpUibNMxSqVQx2pLfSBDfEuodGJV+pRgiydYQ58+VFLftAylDx/Qvfdw==", + "version": "4.11.7", + "resolved": "https://registry.npmjs.org/shaka-player/-/shaka-player-4.11.7.tgz", + "integrity": "sha512-jNsdQd4pa24w2QwXHGNBs8MQ33YajYiZclWaSuGt+dLJ7/D9+NG1LJQGnTrGd0G1aDdbEzmEpppG3ic4OsGpyw==", "license": "Apache-2.0", "dependencies": { - "eme-encryption-scheme-polyfill": "^2.2.0" + "eme-encryption-scheme-polyfill": "^2.1.5" }, "engines": { - "node": ">=18" + "node": ">=14" } }, "node_modules/sharp": { diff --git a/materialious/package.json b/materialious/package.json index 911b0e87..ca71130b 100644 --- a/materialious/package.json +++ b/materialious/package.json @@ -63,7 +63,7 @@ "media-captions": "^1.0.4", "mousetrap": "^1.6.5", "peerjs": "^1.5.4", - "shaka-player": "^4.13.0", + "shaka-player": "^4.11.7", "sponsorblock-api": "^0.2.4", "svelte-i18n": "^4.0.1", "svelte-infinite-loading": "^1.4.0", @@ -71,4 +71,4 @@ "terser": "^5.34.1", "youtubei.js": "^13.4.0" } -} \ No newline at end of file +} diff --git a/materialious/patches/shaka-player+4.13.0.patch b/materialious/patches/shaka-player+4.11.7.patch similarity index 66% rename from materialious/patches/shaka-player+4.13.0.patch rename to materialious/patches/shaka-player+4.11.7.patch index 316e5cd5..359f2e50 100644 --- a/materialious/patches/shaka-player+4.13.0.patch +++ b/materialious/patches/shaka-player+4.11.7.patch @@ -1,31 +1,31 @@ diff --git a/node_modules/shaka-player/dist/shaka-player.compiled.d.ts b/node_modules/shaka-player/dist/shaka-player.compiled.d.ts -index 0971e58..9d5c515 100644 +index 19c0930..cc0a3fd 100644 --- a/node_modules/shaka-player/dist/shaka-player.compiled.d.ts +++ b/node_modules/shaka-player/dist/shaka-player.compiled.d.ts -@@ -5266,3 +5266,5 @@ declare namespace shaka.extern { +@@ -5117,3 +5117,5 @@ declare namespace shaka.extern { declare namespace shaka.extern { type TransmuxerPlugin = ( ) => shaka.extern.Transmuxer ; } + +export default shaka; diff --git a/node_modules/shaka-player/dist/shaka-player.ui.d.ts b/node_modules/shaka-player/dist/shaka-player.ui.d.ts -index 460e009..41bab00 100644 +index 1618ca0..a6076c6 100644 --- a/node_modules/shaka-player/dist/shaka-player.ui.d.ts +++ b/node_modules/shaka-player/dist/shaka-player.ui.d.ts -@@ -6011,3 +6011,5 @@ declare namespace shaka.extern { - declare namespace shaka.ui.Watermark { - type Options = { alpha : number , color : string , displayDuration : number , interval : number , position : string , size : number , skip : number , text : string , transitionDuration : number , type : string } ; +@@ -5830,3 +5830,5 @@ declare namespace shaka.extern { + declare namespace shaka.extern { + type UIVolumeBarColors = { base : string , level : string } ; } + +export default shaka; diff --git a/node_modules/shaka-player/index.d.ts b/node_modules/shaka-player/index.d.ts new file mode 100644 -index 0000000..79f4829 +index 0000000..3ebfd96 --- /dev/null +++ b/node_modules/shaka-player/index.d.ts @@ -0,0 +1,2 @@ -+ -+ ++/// ++/// \ No newline at end of file diff --git a/node_modules/shaka-player/ui.d.ts b/node_modules/shaka-player/ui.d.ts new file mode 100644 @@ -35,4 +35,5 @@ index 0000000..84a3be0 @@ -0,0 +1,3 @@ +import shaka from 'shaka-player/dist/shaka-player.ui' +export * from 'shaka-player/dist/shaka-player.ui' -+export default shaka; \ No newline at end of file ++export default shaka; +\ No newline at end of file \ No newline at end of file diff --git a/materialious/src/lib/components/Player.svelte b/materialious/src/lib/components/Player.svelte index f9ee5fc2..fd09788f 100644 --- a/materialious/src/lib/components/Player.svelte +++ b/materialious/src/lib/components/Player.svelte @@ -76,7 +76,6 @@ '' ); let isLive = false; - let isPostLiveDVR = false; let videoPlaybackUstreamerConfig: string | undefined; let serverAbrStreamingUrl: URL | undefined = undefined; let drmParams: string | undefined; @@ -186,16 +185,10 @@ enabled: true, restrictions: { maxWidth: 1920, - maxHeight: 1080, - maxBandwidth: data.video.ytjs - ? Number( - data.video.ytjs?.video.page[0].player_config?.stream_selection_config.max_bitrate - ) - : -1 + maxHeight: 1080 } }, streaming: { - autoLowLatencyMode: true, bufferingGoal: 120, rebufferingGoal: 0.01, bufferBehind: 300, @@ -262,7 +255,6 @@ if (data.video.fallbackPatch === 'youtubejs' && data.video.ytjs) { isLive = !!data.video.ytjs.video.basic_info.is_live; - isPostLiveDVR = !!data.video.ytjs.video.basic_info.is_post_live_dvr; if ( data.video.ytjs.rawApiResponse.data.streamingData && @@ -305,6 +297,11 @@ lastActionMs = Date.now(); }); + player.addEventListener('error', (event) => { + const error = (event as CustomEvent).detail as shaka.util.Error; + console.error('Player error:', error); + }); + const networkingEngine = player.getNetworkingEngine(); if (!networkingEngine) return; @@ -337,10 +334,7 @@ type === shaka.net.NetworkingEngine.RequestType.SEGMENT && url.pathname.includes('videoplayback') ) { - const isUmp = url.searchParams.get('ump') === '1'; - const isSabr = url.searchParams.get('sabr') === '1'; - - if (isSabr) { + if (!isLive) { const currentFormat = formatList.find( (format) => fromFormat(format) === (new URL(request.uris[0]).searchParams.get('___key') || '') @@ -493,18 +487,9 @@ // @NOTE: Not a real header. See the http plugin code for more info. request.headers['X-Streaming-Context'] = btoa(JSON.stringify(sabrStreamingContext)); delete headers.Range; - } else if (isUmp) { - if (!isLive && !isPostLiveDVR) { - url.searchParams.set('ump', '1'); - url.searchParams.set('srfvp', '1'); - if (headers.Range) { - url.searchParams.set('range', headers.Range?.split('=')[1]); - delete headers.Range; - } - } else { - url.pathname += '/ump/1'; - url.pathname += '/srfvp/1'; - } + } else { + url.pathname += '/ump/1'; + url.pathname += '/srfvp/1'; request.headers['X-Streaming-Context'] = btoa( JSON.stringify({ @@ -512,23 +497,11 @@ isSABR: false }) ); + + url.pathname += '/pot/' + get(poTokenCacheStore); } request.method = 'POST'; - - if (!isSabr) { - request.body = new Uint8Array([120, 0]); - const poToken = get(poTokenCacheStore); - - if (poToken) { - // Set Proof of Origin Token - if (isLive || isPostLiveDVR) { - url.pathname += '/pot/' + poToken; - } else { - url.searchParams.set('pot', poToken); - } - } - } } else if (type == shaka.net.NetworkingEngine.RequestType.LICENSE) { const wrapped = {} as Record; wrapped.context = data.video.ytjs?.innertube.session.context; @@ -567,7 +540,7 @@ const sabrStreamingContext = response.headers['X-Streaming-Context']; if (sabrStreamingContext) { - const { streamInfo, isSABR, format, byteRange } = JSON.parse( + const { streamInfo, format, byteRange } = JSON.parse( atob(sabrStreamingContext) ) as SabrStreamingContext; @@ -582,11 +555,8 @@ if (sabrRedirect?.url && !response.data.byteLength) { let redirectUrl = new URL(sabrRedirect.url); - // For SABR, create a fake URL so we can identify it in the request filter. - if (isSABR) { - serverAbrStreamingUrl = redirectUrl; - redirectUrl = new URL(`https://sabr?___key=${fromFormat(format) || ''}`); - } + serverAbrStreamingUrl = redirectUrl; + redirectUrl = new URL(`https://sabr?___key=${fromFormat(format) || ''}`); const retryParameters = player!.getConfiguration().streaming.retryParameters; @@ -596,7 +566,7 @@ ); // Keep range so we can slice the response (only if it's the init segment). - if (isSABR && byteRange) { + if (typeof byteRange !== 'undefined') { redirectRequest.headers['Range'] = `bytes=${byteRange.start}-${byteRange.end}`; } @@ -982,12 +952,6 @@ } onDestroy(async () => { - HttpFetchPlugin.cacheManager.clearCache(); - - if (playerElementResizeObserver) { - playerElementResizeObserver.disconnect(); - } - if (Capacitor.getPlatform() === 'android') { if (originalOrigination) { await StatusBar.setOverlaysWebView({ overlay: false }); @@ -1003,9 +967,25 @@ try { savePlayerPos(); } catch (error) {} - await player.destroy(); - await shakaUi.destroy(); playerPosSet = false; + HttpFetchPlugin.cacheManager.clearCache(); + + if (playerElementResizeObserver) { + playerElementResizeObserver.disconnect(); + } + + if (playerElement) { + playerElement.src = ''; + playerElement.load(); + } + + if (player) { + player.destroy(); + } + + if (shakaUi) { + shakaUi.destroy(); + } }); From 6b3ef8409168eb77879befd74b20d8a5fcab84db Mon Sep 17 00:00:00 2001 From: WardPearce Date: Fri, 30 May 2025 23:29:25 +1200 Subject: [PATCH 3/5] Add catch for handleMediaHeader --- materialious/src/lib/sabr/sabrUmpParser.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/materialious/src/lib/sabr/sabrUmpParser.ts b/materialious/src/lib/sabr/sabrUmpParser.ts index 2dcc5a8a..5b9b3d3f 100644 --- a/materialious/src/lib/sabr/sabrUmpParser.ts +++ b/materialious/src/lib/sabr/sabrUmpParser.ts @@ -120,7 +120,11 @@ export class SabrUmpParser { this.handleNextRequestPolicy(part); break; case PART.MEDIA_HEADER: - this.handleMediaHeader(part); + try { + this.handleMediaHeader(part); + } catch (error) { + console.error(); + } break; case PART.MEDIA: this.handleMedia(part); From 87cb9ca9a01a40dd608524dfd4f59672a0e586dc Mon Sep 17 00:00:00 2001 From: WardPearce Date: Fri, 30 May 2025 23:33:12 +1200 Subject: [PATCH 4/5] Minor formatting changed --- materialious/src/lib/components/Player.svelte | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/materialious/src/lib/components/Player.svelte b/materialious/src/lib/components/Player.svelte index fd09788f..33bbf8cd 100644 --- a/materialious/src/lib/components/Player.svelte +++ b/materialious/src/lib/components/Player.svelte @@ -488,8 +488,7 @@ request.headers['X-Streaming-Context'] = btoa(JSON.stringify(sabrStreamingContext)); delete headers.Range; } else { - url.pathname += '/ump/1'; - url.pathname += '/srfvp/1'; + url.pathname += `/ump/1/srfvp/1/pot/${get(poTokenCacheStore)}`; request.headers['X-Streaming-Context'] = btoa( JSON.stringify({ @@ -497,8 +496,6 @@ isSABR: false }) ); - - url.pathname += '/pot/' + get(poTokenCacheStore); } request.method = 'POST'; From 89c63fc3f47c3f0d32771e027953b7612869cf5a Mon Sep 17 00:00:00 2001 From: WardPearce Date: Fri, 30 May 2025 23:35:25 +1200 Subject: [PATCH 5/5] Fix error logging --- materialious/src/lib/sabr/sabrUmpParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/materialious/src/lib/sabr/sabrUmpParser.ts b/materialious/src/lib/sabr/sabrUmpParser.ts index 5b9b3d3f..c64aafda 100644 --- a/materialious/src/lib/sabr/sabrUmpParser.ts +++ b/materialious/src/lib/sabr/sabrUmpParser.ts @@ -123,7 +123,7 @@ export class SabrUmpParser { try { this.handleMediaHeader(part); } catch (error) { - console.error(); + console.error(error); } break; case PART.MEDIA: