Improved proxy

This commit is contained in:
WardPearce
2026-02-16 08:17:59 +13:00
parent 77201761fa
commit 1958caf261
14 changed files with 121 additions and 99 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 212
versionName "1.15.4"
versionCode 213
versionName "1.15.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -77,7 +77,11 @@
<release version="1.15.4" date="2026-2-16">
<release version="1.15.5" date="2026-2-16">
<url>https://github.com/Materialious/Materialious/releases/tag/1.15.5</url>
</release>
<release version="1.15.4" date="2026-2-16">
<url>https://github.com/Materialious/Materialious/releases/tag/1.15.4</url>
</release>
<release version="1.15.3" date="2026-2-15">
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.15.4",
"version": "1.15.5",
"description": "Modern material design for YouTube and Invidious.",
"author": {
"name": "Ward Pearce",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.15.4",
"version": "1.15.5",
"private": true,
"scripts": {
"dev": "npm run patch:github && vite dev",
@@ -96,4 +96,4 @@
"youtubei.js": "^16.0.1",
"zod": "^4.3.6"
}
}
}
@@ -71,9 +71,6 @@ export async function getVideoYTjs(videoId: string): Promise<VideoPlay> {
contentCheckOk: true,
racyCheckOk: true,
playbackContext: {
adPlaybackContext: {
pyv: true
},
contentPlaybackContext: {
signatureTimestamp: innertube.session.player?.signature_timestamp
}
+46 -49
View File
@@ -30,6 +30,7 @@
playerDefaultLanguage,
playerDefaultPlaybackSpeed,
playerDefaultQualityStore,
playerPreferredVolume,
playerProxyVideosStore,
playerSavePlaybackPositionStore,
playerState,
@@ -184,7 +185,6 @@
let clickCounterTimeout: ReturnType<typeof setTimeout>;
let seekDirection: 'forwards' | 'backwards' | undefined = $state();
const STORAGE_KEY_VOLUME = 'shaka-preferred-volume';
playertheatreModeIsActive.subscribe(async () => {
await tick();
@@ -246,16 +246,12 @@
function saveVolumePreference() {
if (!playerElement) return;
playerVolume = playerElement.volume;
localStorage.setItem(STORAGE_KEY_VOLUME, playerElement.volume.toString());
playerPreferredVolume.set(playerElement.volume);
}
function restoreVolumePreference() {
const savedVolume = localStorage.getItem(STORAGE_KEY_VOLUME);
if (savedVolume && playerElement) {
playerElement.volume = parseFloat(savedVolume);
playerVolume = playerElement.volume;
}
if (!playerElement) return;
playerElement.volume = $playerPreferredVolume;
}
function restoreQualityPreference() {
@@ -365,51 +361,52 @@
async function setupSponsorSkip() {
if (!$sponsorBlockUrlStore || !$sponsorBlockCategoriesStore || !$sponsorBlockStore) return;
const sponsorBlock = new SponsorBlock('', { baseURL: $sponsorBlockUrlStore });
if ($sponsorBlockCategoriesStore && $sponsorBlockUrlStore && $sponsorBlockUrlStore !== '') {
const sponsorBlock = new SponsorBlock('', { baseURL: $sponsorBlockUrlStore });
const sponsorBlockCategoryKeys = Object.keys($sponsorBlockCategoriesStore);
try {
segments = await sponsorBlock.getSegments(
data.video.videoId,
Object.keys($sponsorBlockCategoriesStore) as Category[]
);
} catch (error) {
console.error('Sponsorskip errored with:', error);
}
if (sponsorBlockCategoryKeys.length === 0) return;
if (segments.length === 0) return;
playerElement?.addEventListener('timeupdate', () => {
segments.forEach((segment) => {
if (!playerElement) return;
if (
playerElement.currentTime >= segment.startTime &&
playerElement.currentTime <= segment.endTime
) {
const segmentTrigger = $sponsorBlockCategoriesStore[segment.category];
if (segmentTrigger === 'automatic') {
skipSegment(segment);
} else if (segmentTrigger === 'manual') {
if (!segmentManualSkip) {
addToast({
data: {
text: `${$_('upcomingSegment')} ${segment.category}`,
action: {
action: () => skipSegment(segment),
text: $_('skip')
}
}
});
}
segmentManualSkip = segment;
}
}
});
});
try {
segments = await sponsorBlock.getSegments(
data.video.videoId,
sponsorBlockCategoryKeys as Category[]
);
} catch (error) {
console.error('Sponsorskip errored with:', error);
}
if (segments.length === 0) return;
playerElement?.addEventListener('timeupdate', () => {
segments.forEach((segment) => {
if (!playerElement) return;
if (
playerElement.currentTime >= segment.startTime &&
playerElement.currentTime <= segment.endTime
) {
const segmentTrigger = $sponsorBlockCategoriesStore[segment.category];
if (segmentTrigger === 'automatic') {
skipSegment(segment);
} else if (segmentTrigger === 'manual') {
if (!segmentManualSkip) {
addToast({
data: {
text: `${$_('upcomingSegment')} ${segment.category}`,
action: {
action: () => skipSegment(segment),
text: $_('skip')
}
}
});
}
segmentManualSkip = segment;
}
}
});
});
}
function loadTimeFromUrl(page: Page): boolean {
@@ -52,7 +52,8 @@ import {
playerAndroidLockOrientation,
playerYouTubeJsFallback,
playerYouTubeJsAlways,
interfaceSearchHistoryEnabled
interfaceSearchHistoryEnabled,
playerPreferredVolume
} from '$lib/store';
import { isOwnBackend } from '$lib/shared';
@@ -66,6 +67,7 @@ type PersistedStore<T> = {
const zBoolean = z.coerce.boolean();
const zNumber = z.coerce.number();
const zFloat = z.coerce.number().min(0).max(1);
const zString = z.string();
const zArray = z.array(z.string());
const zChapterMode = z.enum(['automatic', 'manual', 'timeline']);
@@ -256,6 +258,11 @@ export const persistedStores: PersistedStore<any>[] = [
store: playerMiniplayerEnabled,
schema: zBoolean
},
{
name: 'preferredVolume',
store: playerPreferredVolume,
schema: zFloat
},
{
name: 'sponsorBlockCategoriesv2',
store: sponsorBlockCategoriesStore,
+18
View File
@@ -1,5 +1,7 @@
import { isUnrestrictedPlatform, timeout } from '$lib/misc';
import { Capacitor } from '@capacitor/core';
import sodium from 'libsodium-wrappers-sumo';
import { isOwnBackend } from './shared';
export const originalFetch = window.fetch;
const corsProxyUrl =
@@ -62,6 +64,22 @@ export const fetchProxied = async (
}
}
if (isOwnBackend() && Capacitor.getPlatform() === 'web' && requestOptions?.body) {
if (requestOptions.body instanceof ArrayBuffer) {
requestOptions.body = new Uint8Array(requestOptions?.body);
}
if (requestOptions.body instanceof Uint8Array) {
await sodium.ready;
requestOptions.body = sodium.to_base64(requestOptions.body);
requestOptions.headers = {
...requestOptions.headers,
__is_base64_encoded: 'true'
};
}
}
// Use the original fetch with the proxied URL and options
return originalFetch(requestInput, requestOptions);
};
-1
View File
@@ -13,7 +13,6 @@ export class ImageCache {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => {
this.cache.set(src, img);
resolve(img);
-3
View File
@@ -43,9 +43,6 @@ export async function injectSabr(
contentCheckOk: true,
racyCheckOk: true,
playbackContext: {
adPlaybackContext: {
pyv: true
},
contentPlaybackContext: {
signatureTimestamp: video.ytjs.innertube.session.player?.signature_timestamp
},
+5
View File
@@ -179,6 +179,11 @@ export const playerAndroidPauseOnNetworkChange = persist(
createStorage(),
'pauseOnNetworkChange'
);
export const playerPreferredVolume: Writable<number> = persist(
writable(0.5),
createStorage(),
'preferredVolume'
);
export const playerPlaylistHistory: Writable<string[]> = writable([]);
export interface PlayerState {
@@ -3,6 +3,8 @@ import { env } from '$env/dynamic/public';
import { error } from '@sveltejs/kit';
import { parse as tldParse } from 'tldts';
import { USER_AGENT } from 'bgutils-js';
import sodium from 'libsodium-wrappers-sumo';
const allowedBaseDomains: string[] = [
'youtube.com',
@@ -30,6 +32,13 @@ for (const dynamicDomain of dynamicAllowDomainsEnvVars) {
}
}
function copyHeader(headerName: string, to: Headers, from: Headers) {
const hdrVal = from.get(headerName);
if (hdrVal) {
to.set(headerName, hdrVal);
}
}
async function proxyRequest(
request: Request,
urlToProxy: string,
@@ -71,49 +80,38 @@ async function proxyRequest(
}
}
if (urlToProxyObj.pathname.includes('v1/player')) {
urlToProxyObj.searchParams.set(
'$fields',
'playerConfig,captions,playabilityStatus,streamingData,responseContext.mainAppWebResponseContext.datasyncId,videoDetails.isLive,videoDetails.isLiveContent,videoDetails.title,videoDetails.author,playbackTracking'
);
}
const requestHeaders = request.headers;
const requestHeaders = new Headers();
requestHeaders.set('host', urlToProxyObj.host);
requestHeaders.set('origin', urlToProxyObj.origin);
requestHeaders.set('user-agent', USER_AGENT);
for (const key of [
'referer',
'x-forwarded-for',
'x-requested-with',
'sec-ch-ua-mobile',
'sec-ch-ua',
'sec-ch-ua-platform'
]) {
requestHeaders.delete(key);
}
copyHeader('range', requestHeaders, request.headers);
const cookieHeader = requestHeaders.get('cookie');
if (cookieHeader) {
const modifiedCookies = cookieHeader
.split(';')
.filter((cookie) => !cookie.trim().startsWith('userid='))
.join('; ');
requestHeaders.set('cookie', modifiedCookies);
}
const fetchRes = await fetch(urlToProxy.toString(), {
const requestOptions: RequestInit = {
method: request.method,
headers: requestHeaders,
body: request.body,
credentials: 'same-origin',
...(request.body ? { duplex: 'half' } : {})
});
};
return new Response(fetchRes.body, {
status: fetchRes.status,
statusText: fetchRes.statusText,
...(request.body ? { duplex: 'half' } : {})
let body;
if (request.body && request.headers.has('__is_base64_encoded')) {
request.headers.delete('__is_base64_encoded');
await sodium.ready;
body = Uint8Array.from(sodium.from_base64(await request.text()));
} else {
body = request.body;
}
const response = await fetch(urlToProxy, { ...requestOptions, body });
const responseHeaders = new Headers();
const responseBody = await response.blob();
return new Response(responseBody, {
status: response.status,
headers: responseHeaders
});
}
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime
LATEST_VERSION = "1.15.4"
LATEST_VERSION = "1.15.5"
RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D
WORKING_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "materialious")