Merge pull request #927 from Materialious/update/1.8.7

Update/1.8.7
This commit is contained in:
Ward
2025-05-30 23:43:07 +12:00
committed by GitHub
15 changed files with 214 additions and 136 deletions
+2 -2
View File
@@ -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.
@@ -55,7 +55,11 @@
<releases>
<release version="1.8.6" date="2025-5-29">
<release version="1.8.7" date="2025-5-30">
<url>https://github.com/Materialious/Materialious/releases/tag/1.8.7</url>
</release>
<release version="1.8.6" date="2025-5-29">
<url>https://github.com/Materialious/Materialious/releases/tag/1.8.6</url>
</release>
<release version="1.8.5" date="2025-5-29">
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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"
]
}
}
+8 -8
View File
@@ -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": {
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.8.6",
"version": "1.8.7",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -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"
}
}
}
@@ -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 @@
+<reference path="./dist/shaka-player.compiled.d.ts" />
+<reference path="./dist/shaka-player.ui.d.ts" />
+/// <reference path="./dist/shaka-player.compiled.d.ts" />
+/// <reference path="./dist/shaka-player.ui.d.ts" />
\ 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;
+export default shaka;
\ No newline at end of file
+94 -31
View File
@@ -52,14 +52,14 @@ export async function fetchErrorHandle(response: Response): Promise<Response> {
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<ResolvedUrl> {
return await resp.json();
}
export async function getVideo(videoId: string, local: boolean = false, fetchOptions?: RequestInit): Promise<VideoPlay> {
export async function getVideo(
videoId: string,
local: boolean = false,
fetchOptions?: RequestInit
): Promise<VideoPlay> {
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<ReturnYTDislikes> {
export async function getDislikes(
videoId: string,
fetchOptions?: RequestInit
): Promise<ReturnYTDislikes> {
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<ChannelPage> {
const resp = await fetchErrorHandle(await fetch(buildPath(`channels/${channelId}`), fetchOptions));
export async function getChannel(
channelId: string,
fetchOptions?: RequestInit
): Promise<ChannelPage> {
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<SearchSuggestion> {
export async function getSearchSuggestions(
search: string,
fetchOptions?: RequestInit
): Promise<SearchSuggestion> {
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<Feed> {
export async function getFeed(
maxResults: number,
page: number,
fetchOptions: RequestInit = {}
): Promise<Feed> {
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<boolean> {
export async function amSubscribed(
authorId: string,
fetchOptions: RequestInit = {}
): Promise<boolean> {
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<string[]> {
export async function getHistory(
page: number = 1,
maxResults: number = 20,
fetchOptions: RequestInit = {}
): Promise<string[]> {
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<PlaylistPage> {
export async function getPlaylist(
playlistId: string,
page: number = 1,
fetchOptions: RequestInit = {}
): Promise<PlaylistPage> {
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<PlaylistPage[]> {
const resp = await fetchErrorHandle(await fetch(buildPath('auth/playlists'), { ...buildAuthHeaders(), ...fetchOptions }));
export async function getPersonalPlaylists(
fetchOptions: RequestInit = {}
): Promise<PlaylistPage[]> {
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<string, Record<string, string>> = 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<string> {
export async function getThumbnail(
videoId: string,
time: number,
fetchOptions?: RequestInit
): Promise<string> {
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<SynciousProgressModel[]> {
export async function getVideoProgress(
videoId: string,
fetchOptions: RequestInit = {}
): Promise<SynciousProgressModel[]> {
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<string, Record<string, string>> = buildAuthHeaders();
headers['headers']['Content-type'] = 'application/json';
+69 -62
View File
@@ -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;
@@ -75,7 +76,6 @@
''
);
let isLive = false;
let isPostLiveDVR = false;
let videoPlaybackUstreamerConfig: string | undefined;
let serverAbrStreamingUrl: URL | undefined = undefined;
let drmParams: string | undefined;
@@ -185,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,
@@ -261,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 &&
@@ -304,15 +297,30 @@
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;
// 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;
@@ -326,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') || '')
@@ -482,18 +487,8 @@
// @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/srfvp/1/pot/${get(poTokenCacheStore)}`;
request.headers['X-Streaming-Context'] = btoa(
JSON.stringify({
@@ -504,23 +499,6 @@
}
request.method = 'POST';
if (!isSabr) {
if (request.method === 'POST') {
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<string, any>;
wrapped.context = data.video.ytjs?.innertube.session.context;
@@ -559,7 +537,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;
@@ -574,11 +552,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;
@@ -588,7 +563,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}`;
}
@@ -688,7 +663,7 @@
if (timestampIndex > 0) {
player.addChaptersTrack(
URL.createObjectURL(new Blob([chapterWebVTT])),
`data:text/vtt;base64,${btoa(chapterWebVTT)}`,
get(playerDefaultLanguage)
);
}
@@ -834,12 +809,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 +830,10 @@
if (!playerElement) return;
playerElement.currentTime = playerElement.currentTime - 10;
if (!document.fullscreenElement && userWantsFullscreen) {
shakaUi.getControls()?.toggleFullScreen();
}
return false;
});
@@ -866,6 +853,10 @@
player.setTextTrackVisibility(true);
}
}
if (!document.fullscreenElement && userWantsFullscreen) {
shakaUi.getControls()?.toggleFullScreen();
}
return false;
});
@@ -873,9 +864,7 @@
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
if (!playerElement) return;
playerElement.requestFullscreen();
shakaUi.getControls()?.toggleFullScreen();
}
return false;
});
@@ -884,12 +873,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();
}
});
});
@@ -952,12 +949,6 @@
}
onDestroy(async () => {
HttpFetchPlugin.cacheManager.clearCache();
if (playerElementResizeObserver) {
playerElementResizeObserver.disconnect();
}
if (Capacitor.getPlatform() === 'android') {
if (originalOrigination) {
await StatusBar.setOverlaysWebView({ overlay: false });
@@ -973,9 +964,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();
}
});
</script>
@@ -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;
+4 -4
View File
@@ -67,8 +67,7 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
});
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<VideoPlay> {
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<VideoPlay> {
? 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))}`;
}
}
+1 -1
View File
@@ -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 || ''}`;
}
+6 -3
View File
@@ -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';
@@ -121,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(error);
}
break;
case PART.MEDIA:
this.handleMedia(part);
+3 -3
View File
@@ -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());
}
+1 -1
View File
@@ -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(