Updated youtubejs, improvements to cc

This commit is contained in:
WardPearce
2025-10-16 12:08:52 +13:00
parent 3741db4c23
commit 68cdffb629
11 changed files with 129 additions and 57 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.10.16",
"version": "1.10.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.10.16",
"version": "1.10.17",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
+2 -1
View File
@@ -24,6 +24,7 @@
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
"bgutils-js": "^3.2.0",
"capacitor-nodejs": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.9/capacitor-nodejs.tgz",
"chokidar": "~4.0.3",
"electron-is-dev": "~2.0.0",
"electron-serve": "~3.0.0",
@@ -43,4 +44,4 @@
"capacitor",
"electron"
]
}
}
@@ -1,4 +1,6 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const CapacitorNodejs = require('../../../node_modules/capacitor-nodejs/electron/dist/plugin.js');
module.exports = {
CapacitorNodejs,
}
+16 -29
View File
@@ -1,12 +1,12 @@
{
"name": "materialious",
"version": "1.10.15",
"version": "1.10.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "materialious",
"version": "1.10.15",
"version": "1.10.17",
"hasInstallScript": true,
"dependencies": {
"@capacitor-community/electron": "^5.0.1",
@@ -35,7 +35,7 @@
"sponsorblock-api": "^0.2.4",
"svelte-infinite-loading": "^1.4.0",
"svelte-persisted-store": "^0.12.0",
"youtubei.js": "^15.1.1"
"youtubei.js": "^16.0.0"
},
"devDependencies": {
"@capacitor/assets": "^3.0.5",
@@ -7988,18 +7988,6 @@
"node": ">=10"
}
},
"node_modules/jintr": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/jintr/-/jintr-3.3.1.tgz",
"integrity": "sha512-nnOzyhf0SLpbWuZ270Omwbj5LcXUkTcZkVnK8/veJXtSZOiATM5gMZMdmzN75FmTyj+NVgrGaPdH12zIJ24oIA==",
"funding": [
"https://github.com/sponsors/LuanRT"
],
"license": "MIT",
"dependencies": {
"acorn": "^8.8.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -8667,6 +8655,15 @@
"node": ">=0.6.0"
}
},
"node_modules/meriyah": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/meriyah/-/meriyah-6.1.4.tgz",
"integrity": "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==",
"license": "ISC",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -12330,15 +12327,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/undici": {
"version": "6.21.3",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
"license": "MIT",
"engines": {
"node": ">=18.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
@@ -14014,17 +14002,16 @@
}
},
"node_modules/youtubei.js": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/youtubei.js/-/youtubei.js-15.1.1.tgz",
"integrity": "sha512-fuEDj9Ky6cAQg93BrRVCbr+GTYNZQAIFZrx/a3oDRuGc3Mf5bS0dQfoYwwgjtSV7sgAKQEEdGtzRdBzOc8g72Q==",
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/youtubei.js/-/youtubei.js-16.0.0.tgz",
"integrity": "sha512-aMx+ulnrxzsgVsxTr7gbBVnIjti2NQUlMwCoo1/MzICCJS3iMLOPUFdo7bSpwskL6ljzQ/LxmmB4WQC3FtkBlA==",
"funding": [
"https://github.com/sponsors/LuanRT"
],
"license": "MIT",
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
"jintr": "^3.3.1",
"undici": "^6.21.3"
"meriyah": "^6.1.4"
}
},
"node_modules/zimmerframe": {
+2 -2
View File
@@ -68,6 +68,6 @@
"sponsorblock-api": "^0.2.4",
"svelte-infinite-loading": "^1.4.0",
"svelte-persisted-store": "^0.12.0",
"youtubei.js": "^15.1.1"
"youtubei.js": "^16.0.0"
}
}
}
+32 -5
View File
@@ -36,7 +36,6 @@
playerStatisticsByDefault,
playerYouTubeJsFallback,
playlistSettingsStore,
poTokenCacheStore,
sponsorBlockCategoriesStore,
sponsorBlockDisplayToastStore,
sponsorBlockStore,
@@ -281,6 +280,7 @@
const langCode = ISO6391.getCode(defaultLanguage);
const tracks = player.getTextTracks();
const subtitleTrack = tracks.find((track) => track.language === langCode);
if (subtitleTrack) {
@@ -295,7 +295,7 @@
async function loadVideo() {
showVideoRetry = false;
sabrAdapter = injectSabr(data.video, player);
sabrAdapter = await injectSabr(data.video, player);
try {
document.getElementsByClassName('shaka-ad-info')[0].remove();
@@ -340,8 +340,8 @@
}
if (data.video.captions) {
data.video.captions.forEach(async (caption) => {
player.addTextTrackAsync(
for (const caption of data.video.captions) {
await player.addTextTrackAsync(
caption.url.startsWith('http') ? caption.url : `${get(instanceStore)}${caption.url}`,
caption.language_code,
'captions',
@@ -349,7 +349,7 @@
undefined,
caption.label
);
});
}
}
if (data.content.timestamps) {
@@ -495,6 +495,33 @@
console.error('Player error:', error);
});
player.getNetworkingEngine()?.registerResponseFilter((type, response, _) => {
if (
type !== shaka.net.NetworkingEngine.RequestType.SEGMENT ||
!response.uri.includes('/api/timedtext')
) {
return;
}
const url = new URL(response.uri);
// Fix positioning for auto-generated subtitles
// Credit to Freetube!
if (
url.hostname.endsWith('.youtube.com') &&
url.pathname === '/api/timedtext' &&
url.searchParams.get('caps') === 'asr' &&
url.searchParams.get('kind') === 'asr' &&
url.searchParams.get('fmt') === 'vtt'
) {
const stringBody = new TextDecoder().decode(response.data);
// position:0% for LTR text and position:100% for RTL text
const cleaned = stringBody.replaceAll(/ align:start position:(?:10)?0%$/gm, '');
response.data = new TextEncoder().encode(cleaned).buffer;
}
});
// Required to stop buttons from being still selected when fullscreening
document?.addEventListener('fullscreenchange', async () => {
const buttons = document.querySelectorAll('.shaka-controls-button-panel button');
@@ -92,7 +92,7 @@
}
</script>
<article class="scroll no-padding" style="height: 75vh;" id="transcript">
<article class="scroll border" style="height: 75vh;" id="transcript">
<article class="no-elevate" style="position: sticky; top: 0; z-index: 3;">
<h6>{$_('transcript')}</h6>
<div class="field label suffix border">
@@ -1,3 +1,7 @@
.shaka-text-container > div {
align-items: center !important;
}
.player-theme {
font-family: var(--font) !important;
color: var(--on-surface) !important;
@@ -18,7 +22,7 @@
visibility: visible !important;
z-index: 99999999 !important;
pointer-events: auto !important;
max-height: 400px !important;
max-height: 400px;
}
.shaka-overflow-menu button,
@@ -97,3 +101,15 @@
visibility: visible !important;
}
}
@media only screen and (width <=400px) {
:deep(.shaka-text-container) {
font-size: 16px !important;
}
.player-theme .shaka-overflow-menu,
.player-theme .shaka-settings-menu {
max-height: 80%;
overflow-y: scroll;
}
}
+47 -13
View File
@@ -1,6 +1,7 @@
import { androidPoTokenMinter } from '$lib/android/youtube/minter';
import type {
AdaptiveFormats,
Captions,
Image,
StoryBoard,
Thumbnail,
@@ -11,7 +12,27 @@ import { interfaceRegionStore, poTokenCacheStore } from '$lib/store';
import { Capacitor } from '@capacitor/core';
import BG, { USER_AGENT } from 'bgutils-js';
import { get } from 'svelte/store';
import { Innertube, UniversalCache, Utils, YT, YTNodes } from 'youtubei.js';
import type { Types } from 'youtubei.js';
import { Innertube, UniversalCache, Utils, YT, YTNodes, Platform } from 'youtubei.js';
Platform.shim.eval = async (
data: Types.BuildScriptResult,
env: Record<string, Types.VMPrimative>
) => {
const properties = [];
if (env.n) {
properties.push(`n: exportedVars.nFunction("${env.n}")`);
}
if (env.sig) {
properties.push(`sig: exportedVars.sigFunction("${env.sig}")`);
}
const code = `${data.output}\nreturn { ${properties.join(', ')} }`;
return new Function(code)();
};
export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
if (!Capacitor.isNativePlatform()) {
@@ -22,8 +43,7 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
fetch: fetch,
cache: new UniversalCache(false),
location: get(interfaceRegionStore),
user_agent: USER_AGENT,
player_id: '0004de42'
user_agent: USER_AGENT
});
const requestKey = 'O43z0dpjhgX20SCx4KAo';
@@ -44,7 +64,7 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
pyv: true
},
contentPlaybackContext: {
signatureTimestamp: youtube.session.player?.sts
signatureTimestamp: youtube.session.player?.signature_timestamp
}
}
});
@@ -61,13 +81,7 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
throw new Error('Unable to pull video info from youtube.js');
}
if (video.basic_info.is_live) {
poTokenCacheStore.set(await platformMinter(requestKey, videoId));
} else {
poTokenCacheStore.set(BG.PoToken.generateColdStartToken(videoId));
platformMinter(requestKey, videoId).then((poToken) => poTokenCacheStore.set(poToken));
}
poTokenCacheStore.set(await platformMinter(requestKey, videoId));
let dashUri: string | undefined;
@@ -90,7 +104,6 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
await video.toDash({
manifest_options: {
is_sabr: true,
captions_format: 'vtt',
include_thumbnails: false
}
})
@@ -108,6 +121,27 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
authorThumbnails = [];
}
const captions: Captions[] = [];
video.captions?.caption_tracks?.forEach((caption) => {
const url = new URL(caption.base_url);
url.searchParams.set('potc', '1');
url.searchParams.set('pot', get(poTokenCacheStore) ?? '');
url.searchParams.set('c', youtube.session.context.client.clientName);
url.searchParams.set('fmt', 'vtt');
// Remove &xosf=1 as it adds `position:63% line:0%` to the subtitle lines
// placing them in the top right corner
url.searchParams.delete('xosf');
captions.push({
label: caption.name?.toString() || '',
language_code: caption.language_code,
// Add correct format to url.
url: url.toString()
});
});
const recommendedVideos: VideoBase[] = [];
video.watch_next_feed?.forEach((recommended: Record<string, any>) => {
if (
@@ -189,7 +223,7 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
formatStreams: [],
recommendedVideos: recommendedVideos,
authorThumbnails: authorThumbnails,
captions: [],
captions: captions,
authorId: video.basic_info.channel_id || '',
authorUrl: `/channel/${video.basic_info.channel_id}`,
authorVerified: false,
+6 -3
View File
@@ -8,7 +8,10 @@ import { get } from 'svelte/store';
import { poTokenCacheStore } from '$lib/store';
import { buildSabrFormat } from 'googlevideo/utils';
export function injectSabr(video: VideoPlay, player: shaka.Player): SabrStreamingAdapter | null {
export async function injectSabr(
video: VideoPlay,
player: shaka.Player
): Promise<SabrStreamingAdapter | null> {
if (!video.ytjs || !Capacitor.isNativePlatform()) return null;
const sabrAdapter = new SabrStreamingAdapter({
@@ -56,7 +59,7 @@ export function injectSabr(video: VideoPlay, player: shaka.Player): SabrStreamin
video.ytjs.video.cpn
);
sabrAdapter.setStreamingURL(
video.ytjs.innertube.session.player!.decipher(
await video.ytjs.innertube.session.player!.decipher(
parsedInfo.streaming_data?.server_abr_streaming_url
)
);
@@ -77,7 +80,7 @@ export function injectSabr(video: VideoPlay, player: shaka.Player): SabrStreamin
if (video.ytjs.video.streaming_data && !isPostLiveDVR && !isLive) {
sabrAdapter.setStreamingURL(
video.ytjs.innertube.session.player!.decipher(
await video.ytjs.innertube.session.player!.decipher(
video.ytjs.video.streaming_data?.server_abr_streaming_url
)
);
@@ -90,6 +90,8 @@
{/if}
</article>
<div class="space"></div>
{#if data.playlist.videos}
<ItemsList
items={data.playlist.videos}