Merge pull request #1437 from Materialious/update/1.15.5

Update/1.15.5
This commit is contained in:
Ward
2026-02-16 09:23:41 +13:00
committed by GitHub
17 changed files with 124 additions and 119 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">
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.15.4",
"version": "1.15.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.15.4",
"version": "1.15.5",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
+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
}
+49 -66
View File
@@ -30,6 +30,7 @@
playerDefaultLanguage,
playerDefaultPlaybackSpeed,
playerDefaultQualityStore,
playerPreferredVolumeStore,
playerProxyVideosStore,
playerSavePlaybackPositionStore,
playerState,
@@ -112,7 +113,7 @@
let playerCurrentPlaybackState = $state(false);
let playerMaxKnownTime = $state(data.video.lengthSeconds);
let playerIsBuffering = $state(true);
let playerVolume = $state(0);
let playerVolume = $state($playerPreferredVolumeStore);
let playerSettings: 'quality' | 'speed' | 'language' | 'root' = $state('root');
let playerTextTracks: shaka.extern.TextTrack[] | undefined = $state(undefined);
let playerCurrentVideoTrack: shaka.extern.VideoTrack | undefined = $state(undefined);
@@ -125,7 +126,6 @@
let playerCloestSponsor: Segment | undefined = $state();
let playerSliderElement: HTMLElement | undefined = $state();
let playerSliderDebounce: ReturnType<typeof setTimeout>;
let playerVolumeElement: HTMLElement | undefined = $state();
let playerIsFullscreen: boolean = $state(false);
let playerTimelineTooltip: HTMLDivElement | undefined = $state();
let playerTimelineThumbnails: TimelineThumbnail[] = $state([]);
@@ -184,7 +184,6 @@
let clickCounterTimeout: ReturnType<typeof setTimeout>;
let seekDirection: 'forwards' | 'backwards' | undefined = $state();
const STORAGE_KEY_VOLUME = 'shaka-preferred-volume';
playertheatreModeIsActive.subscribe(async () => {
await tick();
@@ -247,15 +246,13 @@
function saveVolumePreference() {
if (!playerElement) return;
playerVolume = playerElement.volume;
localStorage.setItem(STORAGE_KEY_VOLUME, playerElement.volume.toString());
playerPreferredVolumeStore.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 = $playerPreferredVolumeStore;
playerVolume = playerElement.volume;
}
function restoreQualityPreference() {
@@ -365,51 +362,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 {
@@ -967,17 +965,6 @@
showPlayerUI();
});
playerVolumeElement?.addEventListener('mousewheel', (event) => {
event.preventDefault();
const delta = Math.sign((event as any).deltaY);
const newVolume = Math.max(
0,
Math.min(1, (playerElement as HTMLMediaElement).volume - delta * 0.05)
);
(playerElement as HTMLMediaElement).volume = newVolume;
playerVolume = newVolume;
});
playerElement?.addEventListener('pause', async () => {
playerCurrentPlaybackState = false;
playerIsBuffering = false;
@@ -1465,11 +1452,7 @@
</i>
</button>
{#if !isMobile()}
<div
bind:this={playerVolumeElement}
class="player-slider volume m l"
{...playerVolumeSlider.root}
>
<div class="player-slider volume m l" {...playerVolumeSlider.root}>
<div class="track">
<div class="range"></div>
<div {...playerVolumeSlider.thumb}></div>
@@ -52,7 +52,8 @@ import {
playerAndroidLockOrientation,
playerYouTubeJsFallback,
playerYouTubeJsAlways,
interfaceSearchHistoryEnabled
interfaceSearchHistoryEnabled,
playerPreferredVolumeStore
} 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: playerPreferredVolumeStore,
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 playerPreferredVolumeStore: Writable<number> = persist(
writable(0.5),
createStorage(),
'preferredVolume'
);
export const playerPlaylistHistory: Writable<string[]> = writable([]);
export interface PlayerState {
-1
View File
@@ -9,7 +9,6 @@
} from '$lib/store';
import ui from 'beercss';
import { App } from '@capacitor/app';
import '$lib/fetchProxy';
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
import { setAmoledTheme, setStatusBarColor, setTheme } from '$lib/theme';
+1
View File
@@ -20,6 +20,7 @@ import { Preferences } from '@capacitor/preferences';
import { deserialize } from '@macfja/serializer';
import { isYTBackend } from '$lib/misc';
import { isOwnBackend } from '$lib/shared/index';
import '$lib/fetchProxy';
export const ssr = false;
export const prerender = false;
@@ -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,35 @@ 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 responseBody = await response.blob();
return new Response(responseBody, {
status: response.status
});
}
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")