From eaf77f7eca844427ceaf4ebd76c8c881c20a1628 Mon Sep 17 00:00:00 2001
From: WardPearce
Date: Wed, 18 Feb 2026 01:49:56 +1300
Subject: [PATCH 01/14] Bump version
---
materialious/android/app/build.gradle | 4 ++--
materialious/electron/materialious.metainfo.xml | 6 +++++-
materialious/electron/package.json | 2 +-
materialious/package.json | 2 +-
update_versions.py | 2 +-
5 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/materialious/android/app/build.gradle b/materialious/android/app/build.gradle
index 9c76dc20..6f8c42d3 100644
--- a/materialious/android/app/build.gradle
+++ b/materialious/android/app/build.gradle
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 217
- versionName "1.15.9"
+ versionCode 218
+ versionName "1.16.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
diff --git a/materialious/electron/materialious.metainfo.xml b/materialious/electron/materialious.metainfo.xml
index de438669..d34ad67f 100644
--- a/materialious/electron/materialious.metainfo.xml
+++ b/materialious/electron/materialious.metainfo.xml
@@ -82,7 +82,11 @@
-
+
+
+ https://github.com/Materialious/Materialious/releases/tag/1.16.0
+
+
https://github.com/Materialious/Materialious/releases/tag/1.15.9
diff --git a/materialious/electron/package.json b/materialious/electron/package.json
index 2f30b3bd..e4806509 100644
--- a/materialious/electron/package.json
+++ b/materialious/electron/package.json
@@ -1,6 +1,6 @@
{
"name": "Materialious",
- "version": "1.15.9",
+ "version": "1.16.0",
"description": "Modern material design for YouTube and Invidious.",
"author": {
"name": "Ward Pearce",
diff --git a/materialious/package.json b/materialious/package.json
index 223b4c79..ca4f02c7 100644
--- a/materialious/package.json
+++ b/materialious/package.json
@@ -1,6 +1,6 @@
{
"name": "materialious",
- "version": "1.15.9",
+ "version": "1.16.0",
"private": true,
"scripts": {
"dev": "npm run patch:github && vite dev",
diff --git a/update_versions.py b/update_versions.py
index 92b15e29..84972e4b 100644
--- a/update_versions.py
+++ b/update_versions.py
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime
-LATEST_VERSION = "1.15.9"
+LATEST_VERSION = "1.16.0"
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")
From 7782e636e5653e9fb8e9f223637ed94d6e10489f Mon Sep 17 00:00:00 2001
From: WardPearce
Date: Wed, 18 Feb 2026 03:11:43 +1300
Subject: [PATCH 02/14] Restructure
---
materialious/src/lib/api/youtubejs/video.ts | 2 -
.../{ => layout}/ContentColumn.svelte | 0
.../components/{ => layout}/ItemsList.svelte | 16 ++++----
.../lib/components/{ => player}/Player.svelte | 14 +++----
.../{ => thumbnail}/ChannelThumbnail.svelte | 6 +--
.../{ => thumbnail}/HashtagThumbnail.svelte | 4 +-
.../{ => thumbnail}/PlaylistThumbnail.svelte | 6 +--
.../VideoThumbnail.svelte} | 10 ++---
.../components/{ => watch}/Transcript.svelte | 9 ++--
.../{ => player}/sabr/ShakaPlayerAdapter.ts | 3 +-
.../src/lib/{ => player}/sabr/helpers.ts | 0
.../src/lib/{ => player}/sabr/index.ts | 2 -
.../thumbnails.ts} | 6 +--
materialious/src/routes/(app)/+layout.svelte | 4 +-
materialious/src/routes/(app)/+page.svelte | 2 +-
.../routes/(app)/channel/[slug]/+page.svelte | 2 +-
.../routes/(app)/hashtag/[slug]/+page.svelte | 2 +-
.../routes/(app)/playlist/[slug]/+page.svelte | 2 +-
.../routes/(app)/search/[slug]/+page.svelte | 2 +-
.../routes/(app)/subscriptions/+page.svelte | 2 +-
.../routes/(app)/watch/[slug]/+page.svelte | 4 +-
.../(no-layout)/embed/[slug]/+page@.svelte | 2 +-
.../routes/(no-layout)/tv/[slug]/+page.svelte | 4 +-
.../routes/api/proxy/[urlToProxy]/+server.ts | 41 +++++++++++++++++--
.../static/localApi/ghContributors.json | 2 +-
25 files changed, 90 insertions(+), 57 deletions(-)
rename materialious/src/lib/components/{ => layout}/ContentColumn.svelte (100%)
rename materialious/src/lib/components/{ => layout}/ItemsList.svelte (94%)
rename materialious/src/lib/components/{ => player}/Player.svelte (99%)
rename materialious/src/lib/components/{ => thumbnail}/ChannelThumbnail.svelte (88%)
rename materialious/src/lib/components/{ => thumbnail}/HashtagThumbnail.svelte (88%)
rename materialious/src/lib/components/{ => thumbnail}/PlaylistThumbnail.svelte (91%)
rename materialious/src/lib/components/{Thumbnail.svelte => thumbnail/VideoThumbnail.svelte} (97%)
rename materialious/src/lib/components/{ => watch}/Transcript.svelte (95%)
rename materialious/src/lib/{ => player}/sabr/ShakaPlayerAdapter.ts (99%)
rename materialious/src/lib/{ => player}/sabr/helpers.ts (100%)
rename materialious/src/lib/{ => player}/sabr/index.ts (98%)
rename materialious/src/lib/{timelineThumbnails.ts => player/thumbnails.ts} (96%)
diff --git a/materialious/src/lib/api/youtubejs/video.ts b/materialious/src/lib/api/youtubejs/video.ts
index 7e638ff9..f9d89d3e 100644
--- a/materialious/src/lib/api/youtubejs/video.ts
+++ b/materialious/src/lib/api/youtubejs/video.ts
@@ -100,8 +100,6 @@ export async function getVideoYTjs(videoId: string): Promise {
video.streaming_data.adaptive_formats = video.streaming_data.adaptive_formats.filter(
(format) => format.xtags !== 'CgcKAnZiEgEx'
);
- } else {
- throw new Error('Video did not provide streaming data.');
}
const adaptiveFormats: AdaptiveFormats[] = [];
diff --git a/materialious/src/lib/components/ContentColumn.svelte b/materialious/src/lib/components/layout/ContentColumn.svelte
similarity index 100%
rename from materialious/src/lib/components/ContentColumn.svelte
rename to materialious/src/lib/components/layout/ContentColumn.svelte
diff --git a/materialious/src/lib/components/ItemsList.svelte b/materialious/src/lib/components/layout/ItemsList.svelte
similarity index 94%
rename from materialious/src/lib/components/ItemsList.svelte
rename to materialious/src/lib/components/layout/ItemsList.svelte
index 4d8c99f2..e161549f 100644
--- a/materialious/src/lib/components/ItemsList.svelte
+++ b/materialious/src/lib/components/layout/ItemsList.svelte
@@ -1,16 +1,16 @@
diff --git a/materialious/src/routes/(app)/search/[slug]/+page.svelte b/materialious/src/routes/(app)/search/[slug]/+page.svelte
index 86086d34..1907e015 100644
--- a/materialious/src/routes/(app)/search/[slug]/+page.svelte
+++ b/materialious/src/routes/(app)/search/[slug]/+page.svelte
@@ -4,7 +4,7 @@
import { searchCacheStore } from '$lib/store';
import { _ } from '$lib/i18n';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
- import ItemsList from '$lib/components/ItemsList.svelte';
+ import ItemsList from '$lib/components/layout/ItemsList.svelte';
import type { SearchOptions, SearchResults } from '$lib/api/model';
let { data } = $props();
diff --git a/materialious/src/routes/(app)/subscriptions/+page.svelte b/materialious/src/routes/(app)/subscriptions/+page.svelte
index 48de51ca..0576b495 100644
--- a/materialious/src/routes/(app)/subscriptions/+page.svelte
+++ b/materialious/src/routes/(app)/subscriptions/+page.svelte
@@ -3,7 +3,7 @@
import type { PlaylistPageVideo, Video, VideoBase } from '$lib/api/model';
import { feedCacheStore, feedLoadingStore } from '$lib/store';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
- import ItemsList from '$lib/components/ItemsList.svelte';
+ import ItemsList from '$lib/components/layout/ItemsList.svelte';
import { resolve } from '$app/paths';
import { _ } from '$lib/i18n';
import PageLoading from '$lib/components/PageLoading.svelte';
diff --git a/materialious/src/routes/(app)/watch/[slug]/+page.svelte b/materialious/src/routes/(app)/watch/[slug]/+page.svelte
index 24bddf7a..a3cd4841 100644
--- a/materialious/src/routes/(app)/watch/[slug]/+page.svelte
+++ b/materialious/src/routes/(app)/watch/[slug]/+page.svelte
@@ -8,8 +8,8 @@
} from '$lib/api/index';
import type { Comments, PlaylistPage } from '$lib/api/model';
import ShareVideo from '$lib/components/ShareVideo.svelte';
- import Thumbnail from '$lib/components/Thumbnail.svelte';
- import Transcript from '$lib/components/Transcript.svelte';
+ import Thumbnail from '$lib/components/thumbnail/VideoThumbnail.svelte';
+ import Transcript from '$lib/components/watch/Transcript.svelte';
import { getBestThumbnail } from '$lib/images';
import { letterCase } from '$lib/letterCasing';
import { cleanNumber, numberWithCommas } from '$lib/numbers';
diff --git a/materialious/src/routes/(no-layout)/embed/[slug]/+page@.svelte b/materialious/src/routes/(no-layout)/embed/[slug]/+page@.svelte
index 9c6d2b15..d441a8c0 100644
--- a/materialious/src/routes/(no-layout)/embed/[slug]/+page@.svelte
+++ b/materialious/src/routes/(no-layout)/embed/[slug]/+page@.svelte
@@ -1,5 +1,5 @@
diff --git a/materialious/src/routes/(no-layout)/tv/[slug]/+page.svelte b/materialious/src/routes/(no-layout)/tv/[slug]/+page.svelte
index 5fac02d1..379c0e1c 100644
--- a/materialious/src/routes/(no-layout)/tv/[slug]/+page.svelte
+++ b/materialious/src/routes/(no-layout)/tv/[slug]/+page.svelte
@@ -1,6 +1,6 @@
+
+{#if playerElement && hasWebkitShowPlaybackTargetPicker(playerElement)}
+
+{/if}
diff --git a/materialious/src/lib/components/player/settings/CaptionSettings.svelte b/materialious/src/lib/components/player/settings/CaptionSettings.svelte
new file mode 100644
index 00000000..512cc532
--- /dev/null
+++ b/materialious/src/lib/components/player/settings/CaptionSettings.svelte
@@ -0,0 +1,41 @@
+
+
+{#if playerTextTracks && playerTextTracks.length > 0 && !video.liveNow}
+
+{/if}
diff --git a/materialious/src/lib/components/player/settings/FullscreenToggle.svelte b/materialious/src/lib/components/player/settings/FullscreenToggle.svelte
new file mode 100644
index 00000000..d4c65578
--- /dev/null
+++ b/materialious/src/lib/components/player/settings/FullscreenToggle.svelte
@@ -0,0 +1,16 @@
+
+
+
diff --git a/materialious/src/lib/components/player/settings/Pip.svelte b/materialious/src/lib/components/player/settings/Pip.svelte
new file mode 100644
index 00000000..805c628a
--- /dev/null
+++ b/materialious/src/lib/components/player/settings/Pip.svelte
@@ -0,0 +1,14 @@
+
+
+{#if document.pictureInPictureEnabled}
+
+{/if}
diff --git a/materialious/src/lib/components/player/settings/Settings.svelte b/materialious/src/lib/components/player/settings/Settings.svelte
new file mode 100644
index 00000000..98425d7b
--- /dev/null
+++ b/materialious/src/lib/components/player/settings/Settings.svelte
@@ -0,0 +1,186 @@
+
+
+
diff --git a/materialious/src/lib/css/global.css b/materialious/src/lib/css/global.css
index 16f69699..b6ffae70 100644
--- a/materialious/src/lib/css/global.css
+++ b/materialious/src/lib/css/global.css
@@ -133,6 +133,12 @@ menu {
border: none !important;
}
+menu.player-settings {
+ transform: scale(1) translateY(-40%) translateX(0) !important;
+ width: 300px !important;
+ height: 200px !important;
+}
+
@media screen and (max-width: 1000px) {
menu.mobile {
position: fixed !important;
diff --git a/materialious/src/lib/player/android.ts b/materialious/src/lib/player/android.ts
new file mode 100644
index 00000000..70587c9f
--- /dev/null
+++ b/materialious/src/lib/player/android.ts
@@ -0,0 +1,95 @@
+import type { VideoPlay } from '$lib/api/model';
+import { playerAndroidLockOrientation, playerAndroidPauseOnNetworkChange } from '$lib/store';
+import { setStatusBarColor } from '$lib/theme';
+import { get } from 'svelte/store';
+import { Capacitor, SystemBars, SystemBarsStyle, SystemBarType } from '@capacitor/core';
+import { ScreenOrientation, type ScreenOrientationResult } from '@capacitor/screen-orientation';
+import { Network, type ConnectionStatus } from '@capacitor/network';
+
+export class AndroidPlayer {
+ video: VideoPlay;
+ originalOrigination: ScreenOrientationResult | undefined;
+ initialNetworkStatus: ConnectionStatus | undefined;
+ playerElement: HTMLMediaElement | undefined;
+
+ constructor(video: VideoPlay, playerElement: HTMLMediaElement | undefined) {
+ this.video = video;
+ this.playerElement = playerElement;
+
+ if (Capacitor.getPlatform() !== 'android') return;
+
+ ScreenOrientation.orientation().then((originalOrigination) => {
+ this.originalOrigination = originalOrigination;
+ });
+ Network.getStatus().then((initialNetworkStatus) => {
+ this.initialNetworkStatus = initialNetworkStatus;
+ });
+
+ document.addEventListener('fullscreenchange', this.onFullscreenChange);
+
+ Network.addListener('networkStatusChange', (networkStatus) => {
+ if (
+ this.initialNetworkStatus?.connectionType !== networkStatus.connectionType &&
+ get(playerAndroidPauseOnNetworkChange)
+ ) {
+ this.playerElement?.pause();
+ }
+ });
+ }
+
+ async destroy() {
+ if (Capacitor.getPlatform() !== 'android') return;
+
+ document.removeEventListener('fullscreenchange', this.onFullscreenChange);
+
+ if (this.originalOrigination) {
+ await ScreenOrientation.lock({
+ orientation: this.originalOrigination.type
+ });
+ }
+
+ await Network.removeAllListeners();
+ }
+
+ async onFullscreenChange() {
+ if (Capacitor.getPlatform() !== 'android') return;
+
+ const videoFormats = this.video.adaptiveFormats.filter((format) =>
+ format.type.startsWith('video/')
+ );
+
+ const isFullScreen = !!document.fullscreenElement;
+
+ if (isFullScreen) {
+ // Ensure bar color is black while in fullscreen
+ await SystemBars.setStyle({ style: SystemBarsStyle.Light });
+ await SystemBars.hide({
+ bar: SystemBarType.NavigationBar
+ });
+ await SystemBars.hide({
+ bar: SystemBarType.StatusBar
+ });
+ } else {
+ await setStatusBarColor();
+ }
+
+ if (!get(playerAndroidLockOrientation)) return;
+
+ if (isFullScreen && videoFormats[0].resolution) {
+ const widthHeight = videoFormats[0].resolution.split('x');
+
+ if (widthHeight.length !== 2) return;
+
+ if (Number(widthHeight[0]) > Number(widthHeight[1])) {
+ await ScreenOrientation.lock({ orientation: 'landscape' });
+ } else {
+ await ScreenOrientation.lock({ orientation: 'portrait' });
+ }
+ } else {
+ if (this.originalOrigination)
+ await ScreenOrientation.lock({
+ orientation: this.originalOrigination.type
+ });
+ }
+ }
+}
diff --git a/materialious/src/lib/player/index.ts b/materialious/src/lib/player/index.ts
index de96dc57..4dc41653 100644
--- a/materialious/src/lib/player/index.ts
+++ b/materialious/src/lib/player/index.ts
@@ -3,6 +3,8 @@ import type { PlaylistPageVideo, VideoPlay } from '$lib/api/model';
import {
isAndroidTvStore,
playerAutoplayNextByDefaultStore,
+ playerDefaultLanguage,
+ playerDefaultQualityStore,
playerPlaylistHistory,
playlistSettingsStore,
syncPartyConnectionsStore
@@ -11,6 +13,8 @@ import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
import { loadEntirePlaylist } from '$lib/playlist';
import { unsafeRandomItem } from '$lib/misc';
+import type shaka from 'shaka-player/dist/shaka-player.ui';
+import ISO6391 from 'iso-639-1';
export interface PlayerEvent {
type: 'pause' | 'seek' | 'change-video' | 'play' | 'playlist' | 'goto';
@@ -104,3 +108,90 @@ export async function goToNextVideo(video: VideoPlay, playlistId: string | null)
);
}
}
+
+export function restoreQualityPreference(player: shaka.Player) {
+ const numericValue = parseInt(get(playerDefaultQualityStore), 10);
+
+ if (isNaN(numericValue)) {
+ player.configure({ abr: { enabled: true } });
+ return;
+ }
+
+ // Get video-only variant tracks
+ const tracks = player.getVariantTracks().filter((t) => t.height !== null);
+
+ // Sort by resolution descending
+ const sortedTracks = tracks.sort((a, b) => (b.height as number) - (a.height as number));
+
+ // Try exact match
+ let selectedTrack = sortedTracks.find((t) => t.height === numericValue);
+
+ // Try next best (lower than target)
+ if (!selectedTrack) {
+ selectedTrack = sortedTracks.find((t) => (t.height as number) < numericValue);
+ }
+
+ // Try next higher
+ if (!selectedTrack) {
+ selectedTrack = sortedTracks.find((t) => (t.height as number) > numericValue);
+ }
+
+ if (selectedTrack) {
+ player.configure({ abr: { enabled: false } });
+ player.selectVariantTrack(selectedTrack, true);
+ } else {
+ player.configure({ abr: { enabled: true } });
+ }
+}
+
+export function restoreDefaultLanguage(player: shaka.Player) {
+ if (!get(playerDefaultLanguage) || get(playerDefaultLanguage) === 'original') {
+ const languageAndRole = player.getAudioLanguagesAndRoles().find(({ role }) => role === 'main');
+ if (languageAndRole !== undefined) {
+ player.selectAudioLanguage(languageAndRole.language);
+ return;
+ }
+ } else if (get(playerDefaultLanguage)) {
+ const audioLanguages = player.getAudioLanguages();
+ const langCode = ISO6391.getCode(get(playerDefaultLanguage));
+
+ for (const audioLanguage of audioLanguages) {
+ if (audioLanguage.startsWith(langCode)) {
+ player.selectAudioLanguage(audioLanguage);
+ break;
+ }
+ }
+ }
+}
+
+export function toggleSubtitles(player: shaka.Player) {
+ const isVisible = player.isTextTrackVisible();
+ if (isVisible) {
+ player.setTextTrackVisibility(false);
+ } else {
+ let langCode: string;
+ if (get(playerDefaultLanguage) === 'original') {
+ const languageAndRole = player
+ .getAudioLanguagesAndRoles()
+ .find(({ role }) => role === 'main');
+
+ if (!languageAndRole) {
+ return;
+ }
+
+ langCode = languageAndRole.language;
+ } else {
+ const defaultLanguage = get(playerDefaultLanguage);
+ langCode = ISO6391.getCode(defaultLanguage);
+ }
+
+ const tracks = player.getTextTracks();
+
+ const subtitleTrack = tracks.find((track) => track.language === langCode);
+
+ if (subtitleTrack) {
+ player.selectTextTrack(subtitleTrack);
+ player.setTextTrackVisibility(true);
+ }
+ }
+}
From 2f5fe89ec65c876c2617eff2040e155bf60b690b Mon Sep 17 00:00:00 2001
From: WardPearce
Date: Wed, 18 Feb 2026 04:50:10 +1300
Subject: [PATCH 04/14] Move timeline to its own component
---
.../src/lib/components/player/Player.svelte | 458 ++----------------
.../src/lib/components/player/Timeline.svelte | 378 +++++++++++++++
materialious/src/lib/css/global.css | 43 ++
3 files changed, 448 insertions(+), 431 deletions(-)
create mode 100644 materialious/src/lib/components/player/Timeline.svelte
diff --git a/materialious/src/lib/components/player/Player.svelte b/materialious/src/lib/components/player/Player.svelte
index afe7d5b3..68afe180 100644
--- a/materialious/src/lib/components/player/Player.svelte
+++ b/materialious/src/lib/components/player/Player.svelte
@@ -1,8 +1,8 @@
+
+
+ {#snippet timelineTooltip(key: 'thumb' | 'timeline', timeInSeconds: number)}
+ {#if playerTimelineThumbnails.length > 0}
+
+ {/if}
+ {#if playerCloestSponsor}
+
+ {sponsorSegments[playerCloestSponsor.category]}
+
+ {:else if playerCloestTimestamp}
+
+ {truncate(playerCloestTimestamp.title, 20)}
+
+ {/if}
+ {videoLength(timeInSeconds)}
+ {/snippet}
+
+ {#if !userManualSeeking && playerShowTimelineThumbnail}
+
+ {@render timelineTooltip('timeline', playerTimelineTimeHover)}
+
+ {/if}
+
+
+ {#if playerSliderInteracted}
+
+ {@render timelineTooltip('thumb', currentTime)}
+
+ {/if}
+
+
+
+ {#each content.timestamps as chapter, index (chapter)}
+
+ {/each}
+ {#if !$sponsorBlockTimelineStore}
+ {#each segments as segment (segment)}
+
+ {/each}
+ {/if}
+
+
+
diff --git a/materialious/src/lib/css/global.css b/materialious/src/lib/css/global.css
index b6ffae70..045e2bab 100644
--- a/materialious/src/lib/css/global.css
+++ b/materialious/src/lib/css/global.css
@@ -139,6 +139,49 @@ menu.player-settings {
height: 200px !important;
}
+.player-slider {
+ height: var(--player-timeline-height);
+ margin: 0 auto;
+ border-radius: 0.25rem;
+}
+
+.player-slider.full-width {
+ width: 100%;
+}
+
+.player-slider.volume {
+ width: 200px;
+}
+
+.player-slider .track {
+ background: var(--secondary-container);
+ height: 100%;
+ position: relative;
+ border-radius: 0.25rem;
+}
+
+.player-slider .range {
+ position: absolute;
+ background: var(--inverse-primary);
+ inset: 0;
+ right: var(--percentage-inv);
+ border-radius: 0.25rem;
+ opacity: 0.5;
+}
+
+.player-slider [data-melt-slider-thumb] {
+ position: absolute;
+ border-radius: 1rem;
+ background: var(--primary);
+ left: var(--percentage);
+ top: 50%;
+ width: 5px;
+ height: 35px;
+ z-index: 3;
+ cursor: grab;
+ transform: translate(-50%, -50%);
+}
+
@media screen and (max-width: 1000px) {
menu.mobile {
position: fixed !important;
From e0981bcdd7e77825c8233e99b4268390d2bcc033 Mon Sep 17 00:00:00 2001
From: WardPearce
Date: Wed, 18 Feb 2026 05:01:05 +1300
Subject: [PATCH 05/14] Created TouchControls component
---
.../src/lib/components/player/Player.svelte | 118 ++-------------
.../components/player/TouchControls.svelte | 135 ++++++++++++++++++
2 files changed, 146 insertions(+), 107 deletions(-)
create mode 100644 materialious/src/lib/components/player/TouchControls.svelte
diff --git a/materialious/src/lib/components/player/Player.svelte b/materialious/src/lib/components/player/Player.svelte
index 68afe180..1d34f4b0 100644
--- a/materialious/src/lib/components/player/Player.svelte
+++ b/materialious/src/lib/components/player/Player.svelte
@@ -61,6 +61,7 @@
import FullscreenToggle from './settings/FullscreenToggle.svelte';
import { AndroidPlayer } from '$lib/player/android';
import Timeline from './Timeline.svelte';
+ import TouchControls from './TouchControls.svelte';
interface Props {
data: { video: VideoPlay; content: ParsedDescription; playlistId: string | null };
@@ -97,7 +98,7 @@
let playerVolume = $state($playerPreferredVolumeStore);
let playerVideoEndTimePretty: string = $state('');
let playerIsFullscreen = $state(false);
- let playerInitalInteract = true;
+ let playerInitalInteract = $state(true);
const playerVolumeSlider = new Slider({
onValueChange: (volumeToSet) => {
@@ -111,11 +112,6 @@
step: 0.01
});
- let clickCount = $state(0);
- let clickCounterTimeout: ReturnType;
-
- let seekDirection: 'forwards' | 'backwards' | undefined = $state();
-
playertheatreModeIsActive.subscribe(async () => {
await tick();
updateVideoPlayerHeight();
@@ -778,45 +774,6 @@
}
}
- function onVideoClick(type: 'pause' | 'seekLeft' | 'seekRight') {
- seekDirection = undefined;
-
- if (!playerElement) return;
-
- if (isMobile() && playerInitalInteract) {
- showPlayerUI();
- playerInitalInteract = false;
- clickCount = 0;
- return;
- }
-
- clickCount++;
-
- if (clickCounterTimeout) clearTimeout(clickCounterTimeout);
-
- clickCounterTimeout = setTimeout(() => {
- if (clickCount == 1) {
- toggleVideoPlaybackStatus();
- }
- clickCount = 0;
- }, 200);
-
- if (clickCount < 2) return;
-
- if (type === 'seekLeft') {
- seekDirection = 'backwards';
- playerElement.currentTime = Math.max(0, playerElement.currentTime - playerDoubleTapSeek);
- } else if (type === 'pause') {
- toggleFullscreen();
- } else {
- seekDirection = 'forwards';
- playerElement.currentTime = Math.min(
- playerMaxKnownTime,
- playerElement.currentTime + playerDoubleTapSeek
- );
- }
- }
-
onDestroy(async () => {
if (Capacitor.getPlatform() === 'android') {
await setStatusBarColor();
@@ -836,7 +793,6 @@
if (watchProgressInterval) clearInterval(watchProgressInterval);
if (sabrAdapter) sabrAdapter.dispose();
- if (clickCounterTimeout) clearTimeout(clickCounterTimeout);
if (showPlayerUiTimeout) clearTimeout(showPlayerUiTimeout);
if (player) {
@@ -887,31 +843,15 @@
{/if}
-
-
-
onVideoClick('seekLeft')} role="presentation">
- {#if clickCount > 1 && seekDirection === 'backwards'}
-
-
-{(clickCount - 1) * playerDoubleTapSeek}
-
- {/if}
-
-
onVideoClick('pause')} role="presentation">
-
- {#if playerIsBuffering}
-
- {/if}
-
-
-
onVideoClick('seekRight')} role="presentation">
- {#if clickCount > 1 && seekDirection === 'forwards'}
-
-
+{(clickCount - 1) * playerDoubleTapSeek}
-
- {/if}
-
-
-
+
{#if showControls}
+ import { isMobile } from '$lib/misc';
+ import { playerDoubleTapSeek } from '$lib/player';
+ import { onDestroy } from 'svelte';
+
+ let {
+ playerElement,
+ showPlayerUI,
+ toggleVideoPlaybackStatus,
+ toggleFullscreen,
+ playerMaxKnownTime = $bindable(),
+ playerIsBuffering = $bindable(),
+ playerInitalInteract = $bindable()
+ }: {
+ showPlayerUI: () => void;
+ toggleVideoPlaybackStatus: () => void;
+ toggleFullscreen: () => void;
+ playerElement: HTMLMediaElement | undefined;
+ playerMaxKnownTime: number;
+ playerIsBuffering: boolean;
+ playerInitalInteract: boolean;
+ } = $props();
+
+ let clickCount = $state(0);
+ let clickCounterTimeout: ReturnType;
+
+ let seekDirection: 'forwards' | 'backwards' | undefined = $state();
+
+ function onTouchControl(type: 'pause' | 'seekLeft' | 'seekRight') {
+ seekDirection = undefined;
+
+ if (!playerElement) return;
+
+ if (isMobile() && playerInitalInteract) {
+ showPlayerUI();
+ playerInitalInteract = false;
+ clickCount = 0;
+ return;
+ }
+
+ clickCount++;
+
+ if (clickCounterTimeout) clearTimeout(clickCounterTimeout);
+
+ clickCounterTimeout = setTimeout(() => {
+ if (clickCount == 1) {
+ toggleVideoPlaybackStatus();
+ }
+ clickCount = 0;
+ }, 200);
+
+ if (clickCount < 2) return;
+
+ if (type === 'seekLeft') {
+ seekDirection = 'backwards';
+ playerElement.currentTime = Math.max(0, playerElement.currentTime - playerDoubleTapSeek);
+ } else if (type === 'pause') {
+ toggleFullscreen();
+ } else {
+ seekDirection = 'forwards';
+ playerElement.currentTime = Math.min(
+ playerMaxKnownTime,
+ playerElement.currentTime + playerDoubleTapSeek
+ );
+ }
+ }
+
+ onDestroy(() => {
+ if (clickCounterTimeout) clearTimeout(clickCounterTimeout);
+ });
+
+
+
+
+
onTouchControl('seekLeft')} role="presentation">
+ {#if clickCount > 1 && seekDirection === 'backwards'}
+
+
-{(clickCount - 1) * playerDoubleTapSeek}
+
+ {/if}
+
+
onTouchControl('pause')} role="presentation">
+
+ {#if playerIsBuffering}
+
+ {/if}
+
+
+
onTouchControl('seekRight')} role="presentation">
+ {#if clickCount > 1 && seekDirection === 'forwards'}
+
+
+{(clickCount - 1) * playerDoubleTapSeek}
+
+ {/if}
+
+
+
+
+
From ccd800dbb30005eb7db7c900d33068b9cd95bf50 Mon Sep 17 00:00:00 2001
From: WardPearce
Date: Wed, 18 Feb 2026 06:07:02 +1300
Subject: [PATCH 06/14] Improved share component
---
materialious/src/lib/components/Share.svelte | 85 ++++++++++
.../src/lib/components/ShareVideo.svelte | 79 ---------
.../player/settings/CaptionSettings.svelte | 2 +-
.../src/lib/components/watch/Playlist.svelte | 122 ++++++++++++++
.../lib/components/watch/Transcript.svelte | 8 +-
materialious/src/lib/i18n/locales/en.json | 4 +-
materialious/src/lib/misc.ts | 8 +
materialious/src/lib/player/index.ts | 5 +
.../routes/(app)/channel/[slug]/+page.svelte | 56 +++----
.../routes/(app)/playlist/[slug]/+page.svelte | 59 +++----
.../routes/(app)/watch/[slug]/+page.svelte | 156 +++++-------------
11 files changed, 319 insertions(+), 265 deletions(-)
create mode 100644 materialious/src/lib/components/Share.svelte
delete mode 100644 materialious/src/lib/components/ShareVideo.svelte
create mode 100644 materialious/src/lib/components/watch/Playlist.svelte
diff --git a/materialious/src/lib/components/Share.svelte b/materialious/src/lib/components/Share.svelte
new file mode 100644
index 00000000..21358f6d
--- /dev/null
+++ b/materialious/src/lib/components/Share.svelte
@@ -0,0 +1,85 @@
+
+
+
diff --git a/materialious/src/lib/components/ShareVideo.svelte b/materialious/src/lib/components/ShareVideo.svelte
deleted file mode 100644
index d82cefda..00000000
--- a/materialious/src/lib/components/ShareVideo.svelte
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
-
diff --git a/materialious/src/lib/components/player/settings/CaptionSettings.svelte b/materialious/src/lib/components/player/settings/CaptionSettings.svelte
index 512cc532..fe2408e2 100644
--- a/materialious/src/lib/components/player/settings/CaptionSettings.svelte
+++ b/materialious/src/lib/components/player/settings/CaptionSettings.svelte
@@ -16,7 +16,7 @@
{#if playerTextTracks && playerTextTracks.length > 0 && !video.liveNow}