Merge pull request #1277 from Materialious/update/1.12.10

Update/1.12.10
This commit is contained in:
Ward
2025-12-28 09:00:37 +00:00
committed by GitHub
13 changed files with 192 additions and 162 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 181
versionName "1.12.9"
versionCode 182
versionName "1.12.10"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -78,7 +78,11 @@
<release version="1.12.9" date="2025-12-28">
<release version="1.12.10" date="2025-12-28">
<url>https://github.com/Materialious/Materialious/releases/tag/1.12.10</url>
</release>
<release version="1.12.9" date="2025-12-28">
<url>https://github.com/Materialious/Materialious/releases/tag/1.12.9</url>
</release>
<release version="1.12.8" date="2025-12-23">
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.12.9",
"version": "1.12.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.12.9",
"version": "1.12.10",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.12.9",
"version": "1.12.10",
"description": "Modern material design for Invidious.",
"author": {
"name": "Ward Pearce",
@@ -44,4 +44,4 @@
"capacitor",
"electron"
]
}
}
+77 -77
View File
@@ -1,79 +1,79 @@
{
"name": "materialious",
"version": "1.12.9",
"private": true,
"scripts": {
"dev": "npm run patch:github && vite dev",
"build": "npm run patch:github && vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"eslint": "eslint .",
"format": "prettier --write .",
"patch:shaka": "node ./scripts/patchShaka.mjs",
"patch:github": "node ./scripts/githubContributors.mjs",
"postinstall": "npm run patch:shaka"
},
"devDependencies": {
"@capacitor/assets": "^3.0.5",
"@capacitor/cli": "^8.0.0",
"@sveltejs/adapter-auto": "^6.0.1",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.49.1",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@types/event-source-polyfill": "^1.0.5",
"@types/he": "^1.2.3",
"@types/human-number": "^1.0.2",
"@types/mousetrap": "^1.6.15",
"@vite-pwa/sveltekit": "^1.0.0",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.13.0",
"patch-package": "^8.0.1",
"prettier": "^3.6.2",
"prettier-plugin-svelte": "^3.4.0",
"svelte": "^5.45.6",
"svelte-check": "^4.3.3",
"tslib": "^2.7.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.47.0",
"vite": "^7.2.6"
},
"type": "module",
"dependencies": {
"@capacitor-community/electron": "^5.0.1",
"@capacitor-community/safe-area": "^7.0.0-alpha.1",
"@capacitor/android": "^8.0.0",
"@capacitor/app": "^8.0.0",
"@capacitor/browser": "^8.0.0",
"@capacitor/clipboard": "^8.0.0",
"@capacitor/core": "^8.0.0",
"@capacitor/preferences": "^8.0.0",
"@capacitor/screen-orientation": "^8.0.0",
"@capacitor/share": "^8.0.0",
"@macfja/serializer": "^1.1.4",
"@macfja/svelte-persistent-store": "^2.4.2",
"beercss": "^3.13.1",
"bgutils-js": "^3.2.0",
"capacitor-music-controls-plugin": "^6.1.0",
"capacitor-nodejs": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.9/capacitor-nodejs.tgz",
"dayjs": "^1.11.19",
"dexie": "^4.2.1",
"fuse.js": "^7.0.0",
"googlevideo": "^4.0.4",
"he": "^1.2.0",
"human-number": "^2.0.8",
"i18next": "^25.7.2",
"iso-3166": "^4.4.0",
"iso-639-1": "^3.1.5",
"material-dynamic-colors": "^1.1.1",
"media-captions": "^1.0.4",
"mousetrap": "^1.6.5",
"peerjs": "^1.5.5",
"shaka-player": "^4.16.11",
"sponsorblock-api": "^0.2.4",
"svelte-infinite-loading": "^1.4.0",
"youtubei.js": "^16.0.1"
}
"name": "materialious",
"version": "1.12.10",
"private": true,
"scripts": {
"dev": "npm run patch:github && vite dev",
"build": "npm run patch:github && vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"eslint": "eslint .",
"format": "prettier --write .",
"patch:shaka": "node ./scripts/patchShaka.mjs",
"patch:github": "node ./scripts/githubContributors.mjs",
"postinstall": "npm run patch:shaka"
},
"devDependencies": {
"@capacitor/assets": "^3.0.5",
"@capacitor/cli": "^8.0.0",
"@sveltejs/adapter-auto": "^6.0.1",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.49.1",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@types/event-source-polyfill": "^1.0.5",
"@types/he": "^1.2.3",
"@types/human-number": "^1.0.2",
"@types/mousetrap": "^1.6.15",
"@vite-pwa/sveltekit": "^1.0.0",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.13.0",
"patch-package": "^8.0.1",
"prettier": "^3.6.2",
"prettier-plugin-svelte": "^3.4.0",
"svelte": "^5.45.6",
"svelte-check": "^4.3.3",
"tslib": "^2.7.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.47.0",
"vite": "^7.2.6"
},
"type": "module",
"dependencies": {
"@capacitor-community/electron": "^5.0.1",
"@capacitor-community/safe-area": "^7.0.0-alpha.1",
"@capacitor/android": "^8.0.0",
"@capacitor/app": "^8.0.0",
"@capacitor/browser": "^8.0.0",
"@capacitor/clipboard": "^8.0.0",
"@capacitor/core": "^8.0.0",
"@capacitor/preferences": "^8.0.0",
"@capacitor/screen-orientation": "^8.0.0",
"@capacitor/share": "^8.0.0",
"@macfja/serializer": "^1.1.4",
"@macfja/svelte-persistent-store": "^2.4.2",
"beercss": "^3.13.1",
"bgutils-js": "^3.2.0",
"capacitor-music-controls-plugin": "^6.1.0",
"capacitor-nodejs": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.9/capacitor-nodejs.tgz",
"dayjs": "^1.11.19",
"dexie": "^4.2.1",
"fuse.js": "^7.0.0",
"googlevideo": "^4.0.4",
"he": "^1.2.0",
"human-number": "^2.0.8",
"i18next": "^25.7.2",
"iso-3166": "^4.4.0",
"iso-639-1": "^3.1.5",
"material-dynamic-colors": "^1.1.1",
"media-captions": "^1.0.4",
"mousetrap": "^1.6.5",
"peerjs": "^1.5.5",
"shaka-player": "^4.16.11",
"sponsorblock-api": "^0.2.4",
"svelte-infinite-loading": "^1.4.0",
"youtubei.js": "^16.0.1"
}
}
+100 -38
View File
@@ -2,7 +2,7 @@
import { page } from '$app/stores';
import { getBestThumbnail } from '$lib/images';
import { padTime, videoLength } from '$lib/numbers';
import { type PhasedDescription } from '$lib/timestamps';
import { type PhasedDescription, type Timestamp } from '$lib/timestamps';
import { SafeArea, SystemBarsStyle, SystemBarsType } from '@capacitor-community/safe-area';
import { Capacitor } from '@capacitor/core';
import { ScreenOrientation, type ScreenOrientationResult } from '@capacitor/screen-orientation';
@@ -78,12 +78,13 @@
let player: shaka.Player;
let sabrAdapter: SabrStreamingAdapter | null;
let playerContainer: HTMLElement;
let bufferBar: HTMLElement | undefined = $state();
let playerContainer: HTMLElement;
let playerBufferBar: HTMLElement | undefined = $state();
let playerCurrentPlaybackState = $state(false);
let playerCurrentTime = $state(0);
let playerMaxKnownTime = $state(data.video.lengthSeconds);
let playerPauseTimeUpdates = $state(false);
let playerIsBuffering = $state(false);
let playerVolume = $state(0);
let playerSettings: 'quality' | 'speed' | 'language' | 'root' = $state('root');
@@ -94,8 +95,9 @@
let playerTimelineTooltipVisible: boolean = $state(false);
let playerTimelineTimeHover: number = $state(0);
let playerTimelineMouseX: number = $state(0);
let playerTimelineLastUpdate: number = 0;
let playerVideoEndTimePretty: string = $state('');
let playerBufferedTo: number = $state(0);
let playerCloestTimestamp: Timestamp | undefined = $state();
let clickCount = $state(0);
// eslint-disable-next-line no-undef
@@ -110,6 +112,10 @@
updateVideoPlayerHeight();
});
function getMarkerWidth(startTime: number, endTime: number): string {
return `${((endTime - startTime) / playerMaxKnownTime) * 100}%`;
}
function restoreDefaultLanguage() {
if (!$playerDefaultLanguage || $playerDefaultLanguage === 'original') {
const languageAndRole = player
@@ -439,8 +445,7 @@
if (data.content.timestamps) {
let chapterWebVTT = 'WEBVTT\n\n';
let timestampIndex = 0;
data.content.timestamps.forEach((timestamp) => {
data.content.timestamps.forEach((timestamp, timestampIndex) => {
let endTime: string;
if (timestampIndex === data.content.timestamps.length - 1) {
endTime = videoLength(data.video.lengthSeconds);
@@ -449,15 +454,15 @@
}
chapterWebVTT += `${padTime(timestamp.timePretty)}.000 --> ${padTime(endTime)}.000\n${timestamp.title.replaceAll('-', '').trim()}\n\n`;
timestampIndex += 1;
});
if (timestampIndex > 0) {
try {
player.addChaptersTrack(
`data:text/vtt;base64,${btoa(chapterWebVTT)}`,
get(playerDefaultLanguage)
);
} catch {
// Continue regardless
}
}
} else {
@@ -802,7 +807,9 @@
playerElement?.addEventListener('timeupdate', () => {
if (!playerElement) return;
playerCurrentTime = playerElement.currentTime ?? 0;
if (!playerPauseTimeUpdates) {
playerCurrentTime = playerElement.currentTime ?? 0;
}
if (playerMaxKnownTime === 0 || playerCurrentTime > playerMaxKnownTime) {
playerMaxKnownTime = Number(playerElement.currentTime);
@@ -819,19 +826,18 @@
const buffered = playerElement.buffered;
if (buffered.length > 0 && bufferBar) {
const bufferedEnd = buffered.end(0); // Amount shaka has buffered
const startBuffer = Math.max(playerElement.currentTime, 0);
if (buffered.length > 0 && playerBufferBar) {
playerBufferedTo = buffered.end(0);
// Calculate the buffered width as a percentage starting from the current time
const bufferedWidth =
((bufferedEnd - startBuffer) / (playerMaxKnownTime - startBuffer)) * 100;
const bufferedPercent = (playerBufferedTo / playerMaxKnownTime) * 100;
const progressPercent = (playerCurrentTime / playerMaxKnownTime) * 100;
const currentSliderPercentage = (playerCurrentTime / playerMaxKnownTime) * 100;
const bufferAhead = Math.max(0, bufferedPercent - progressPercent);
const effectiveWidth = Math.min(Math.max(bufferedWidth, 0), currentSliderPercentage);
bufferBar.style.width = effectiveWidth + '%';
bufferBar.style.left = Math.min(currentSliderPercentage, 100) + '%';
const effectiveWidth = Math.min(bufferAhead, 100 - progressPercent);
playerBufferBar.style.left = progressPercent + '%';
playerBufferBar.style.width = effectiveWidth + '%';
}
});
@@ -915,24 +921,37 @@
}
}
let requestAnimationTooltip: number | undefined;
function handleMouseMove(event: MouseEvent) {
const currentTime = Date.now();
if (currentTime - playerTimelineLastUpdate < 60) return;
playerTimelineLastUpdate = currentTime;
if (requestAnimationTooltip) return;
const input = event.target as HTMLInputElement;
const boundingRect = input.getBoundingClientRect();
playerTimelineMouseX = event.clientX - boundingRect.left;
requestAnimationTooltip = requestAnimationFrame(() => {
const input = event.target as HTMLInputElement;
const rect = input.getBoundingClientRect();
playerTimelineMouseX = event.clientX - rect.left;
const percent = playerTimelineMouseX / input.clientWidth;
playerTimelineTimeHover = Math.round(percent * (data.video.lengthSeconds || 0));
playerTimelineTooltipVisible = true;
const percent = Math.min(Math.max(playerTimelineMouseX / input.clientWidth, 0), 1);
playerTimelineTimeHover = percent * (data.video.lengthSeconds ?? 0);
playerCloestTimestamp = data.content.timestamps.find((chapter, chapterIndex) => {
let endTime: number;
if (chapterIndex === data.content.timestamps.length - 1) {
endTime = data.video.lengthSeconds;
} else {
endTime = data.content.timestamps[chapterIndex + 1].time;
}
return playerTimelineTimeHover >= chapter.time && playerTimelineTimeHover < endTime;
});
playerTimelineTooltipVisible = true;
requestAnimationTooltip = undefined;
});
}
function handleMouseLeave() {
playerTimelineTooltipVisible = false;
}
let androidFirstTap = true;
function onVideoClick(
event: MouseEvent & {
currentTarget: EventTarget & HTMLDivElement;
@@ -942,11 +961,16 @@
event.target &&
event.target instanceof HTMLElement &&
event.target.id === 'player-tap-controls-area' &&
parseFloat(getComputedStyle(event.target).opacity) > 0 &&
playerElement
) {
clickCount++;
// Force android to tap an addtional time.
if (Capacitor.getPlatform() === 'android' && androidFirstTap) {
androidFirstTap = false;
return;
}
const container = event.currentTarget;
const rect = container.getBoundingClientRect();
@@ -960,8 +984,19 @@
toggleVideoPlaybackStatus();
}
if (
Capacitor.getPlatform() === 'android' &&
event.target &&
event.target instanceof HTMLElement &&
parseFloat(getComputedStyle(event.target).opacity) > 0
) {
androidFirstTap = false;
} else {
androidFirstTap = true;
}
clickCount = 0;
}, 300);
}, 200);
if (clickCount < 2) return;
@@ -1070,10 +1105,6 @@
<div class="player-status" id="player-tap-controls-area">
{#if playerIsBuffering}
<progress class="circle large indeterminate" value="50" max="100"></progress>
{:else if !playerCurrentPlaybackState}
<button class="extra inverse-primary" onclick={toggleVideoPlaybackStatus}>
<i>play_arrow</i>
</button>
{/if}
</div>
</div>
@@ -1107,11 +1138,26 @@
/>
{/key}
<span></span>
<div bind:this={bufferBar} class="buffered-bar"></div>
<div bind:this={playerBufferBar} class="buffered-bar"></div>
{#each data.content.timestamps as chapter, index (chapter)}
<div
class="chapter-marker"
style:left="{(chapter.time / playerMaxKnownTime) * 100}%"
style:width={getMarkerWidth(
chapter.time,
data.content.timestamps[index + 1]?.time || playerMaxKnownTime // Next chapter time or end of video
)}
></div>
{/each}
</label>
{#if playerTimelineTooltipVisible}
<div class="tooltip" style="position: absolute; left: {playerTimelineMouseX}px;">
{#if playerCloestTimestamp}
{playerCloestTimestamp.title}
<br />
{/if}
{videoLength(playerTimelineTimeHover)}
</div>
{/if}
@@ -1141,7 +1187,8 @@
step="0.1"
max="1"
/>
{/key} <span></span>
{/key}
<span></span>
</label>
{/if}
</nav>
@@ -1390,6 +1437,7 @@
opacity: 0;
transition: opacity 2s ease;
padding: 10px;
user-select: none;
}
#player-controls span {
@@ -1428,7 +1476,7 @@
}
#progress-slider {
block-size: 1.5em;
block-size: 1em;
margin: 0;
}
@@ -1437,6 +1485,7 @@
#player-container:hover #player-controls {
opacity: 1;
transition: opacity 0.3s ease;
user-select: all;
}
#player-container:focus-within #mobile-time,
@@ -1482,6 +1531,19 @@
border-bottom-right-radius: 2rem;
}
.chapter-marker {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
height: 1rem;
background-color: var(--secondary);
opacity: 0.5;
border-radius: 2rem;
z-index: 0;
pointer-events: none;
}
menu.mobile {
transform: scale(1) translateY(-40%) translateX(0);
width: 300px;
@@ -16,7 +16,6 @@
playerMiniplayerEnabled,
playerProxyVideosStore,
playerSavePlaybackPositionStore,
playerStatisticsByDefault,
playerTheatreModeByDefaultStore,
playerYouTubeJsAlways,
playerYouTubeJsFallback
@@ -294,20 +293,3 @@
</label>
</nav>
</div>
<div class="field no-margin">
<nav class="no-padding">
<div class="max">
<div>{$_('layout.player.playerStatistics')}</div>
</div>
<label class="switch" tabindex="0">
<input
type="checkbox"
bind:checked={$playerStatisticsByDefault}
onclick={() => playerStatisticsByDefault.set(!$playerStatisticsByDefault)}
role="switch"
/>
<span></span>
</label>
</nav>
</div>
-6
View File
@@ -34,7 +34,6 @@ import {
themeColorStore,
interfaceAutoExpandChapters,
playerDefaultPlaybackSpeed,
playerStatisticsByDefault,
playerCCByDefault,
playerMiniplayerEnabled
} from '$lib/store';
@@ -215,11 +214,6 @@ const persistedStores: {
store: playerDefaultPlaybackSpeed,
type: 'number'
},
{
name: 'playerStatistics',
store: playerStatisticsByDefault,
type: 'boolean'
},
{
name: 'CCByDefault',
store: playerCCByDefault,
-5
View File
@@ -165,11 +165,6 @@ export const playerDefaultPlaybackSpeed: Writable<number> = persist(
createStorage(),
'defaultPlaybackSpeed'
);
export const playerStatisticsByDefault = persist(
writable(false),
createStorage(),
'playerStatistics'
);
export const playerMiniplayerEnabled = persist(
writable(true),
createStorage(),
+2 -1
View File
@@ -1,7 +1,8 @@
import { decodeHtmlCharCodes } from './misc';
import { convertToSeconds } from './numbers';
export type Timestamps = { title: string; time: number; timePretty: string; endTime: number }[];
export type Timestamp = { title: string; time: number; timePretty: string; endTime: number };
export type Timestamps = Timestamp[];
export interface PhasedDescription {
description: string;
@@ -269,14 +269,6 @@
<svelte:head>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html webManifestLink}
{#if Capacitor.getPlatform() === 'android' && !$isAndroidTvStore}
<style>
nav.top {
height: 120px;
}
</style>
{/if}
</svelte:head>
<div>
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.12.9"
LATEST_VERSION = "1.12.10"
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")