@@ -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">
|
||||
|
||||
Generated
+2
-2
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
Generated
+8
-8
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-10
@@ -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
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 || ''}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user