From 8b6c3e7bced654c6e8cdeb3bb43ece87ea771dc4 Mon Sep 17 00:00:00 2001 From: sphet98 <260131232+Sphet98@users.noreply.github.com> Date: Sun, 8 Feb 2026 00:28:24 +0200 Subject: [PATCH 1/8] Add AirPlay support Enable AirPlay when available. Currently only audio will be AirPlayed since the video format is incompatible. --- materialious/src/lib/components/Player.svelte | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/materialious/src/lib/components/Player.svelte b/materialious/src/lib/components/Player.svelte index 1de79a20..948e7e30 100644 --- a/materialious/src/lib/components/Player.svelte +++ b/materialious/src/lib/components/Player.svelte @@ -649,6 +649,11 @@ }); playerElement = document.getElementById('player') as HTMLMediaElement; + // Enable AirPlay if supported + if (hasWebkitShowPlaybackTargetPicker(playerElement)) { + playerElement.setAttribute('x-webkit-airplay', 'allow'); + } + playerElement.loop = playerLoop; if ($playerState) { @@ -988,6 +993,18 @@ playerTextTracks = player.getTextTracks(); }); + function hasWebkitShowPlaybackTargetPicker( + el: HTMLMediaElement + ): el is HTMLMediaElement & { webkitShowPlaybackTargetPicker: () => void } { + return typeof (el as any).webkitShowPlaybackTargetPicker === "function"; + } + + function handleAirPlayClick() { + if(playerElement && hasWebkitShowPlaybackTargetPicker(playerElement)) { + playerElement.webkitShowPlaybackTargetPicker(); + } + } + async function getLastPlayPos(): Promise { if (loadTimeFromUrl($page) || !$playerSavePlaybackPositionStore) return 0; @@ -1513,6 +1530,14 @@ {/if} + {#if playerElement && hasWebkitShowPlaybackTargetPicker(playerElement)} + + {/if} {#if document.pictureInPictureEnabled} { + await Clipboard.write({ string: bookmarkletSaveToUrl() }); + addToast({ + data: { + text: $_('player.share.copiedSuccess') + } + }); + }}>{$_('copyUrl')} {/if} From d8d92061cafe705680c5adf6bc23d809b89e216e Mon Sep 17 00:00:00 2001 From: WardPearce Date: Mon, 9 Feb 2026 16:39:51 +1300 Subject: [PATCH 6/8] Show player UI on shortcut --- materialious/electron/package-lock.json | 4 +-- .../electron/src/rt/electron-plugins.js | 2 ++ materialious/src/lib/components/Player.svelte | 28 +++++++++++++------ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/materialious/electron/package-lock.json b/materialious/electron/package-lock.json index bd263b59..8ccdf839 100644 --- a/materialious/electron/package-lock.json +++ b/materialious/electron/package-lock.json @@ -1,12 +1,12 @@ { "name": "Materialious", - "version": "1.13.13", + "version": "1.13.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Materialious", - "version": "1.13.13", + "version": "1.13.14", "license": "MIT", "dependencies": { "@capacitor-community/electron": "^5.0.0", diff --git a/materialious/electron/src/rt/electron-plugins.js b/materialious/electron/src/rt/electron-plugins.js index b33b2826..5f312366 100644 --- a/materialious/electron/src/rt/electron-plugins.js +++ b/materialious/electron/src/rt/electron-plugins.js @@ -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, } \ No newline at end of file diff --git a/materialious/src/lib/components/Player.svelte b/materialious/src/lib/components/Player.svelte index 948e7e30..f952b3c7 100644 --- a/materialious/src/lib/components/Player.svelte +++ b/materialious/src/lib/components/Player.svelte @@ -837,6 +837,9 @@ } else { playerElement.pause(); } + + showPlayerUI(); + return false; }); @@ -844,44 +847,48 @@ Mousetrap.bind('right', () => { if (!playerElement) return; playerElement.currentTime = playerElement.currentTime + playerDoubleTapSeek; + showPlayerUI(); return false; }); Mousetrap.bind('left', () => { if (!playerElement) return; - playerElement.currentTime = playerElement.currentTime - playerDoubleTapSeek; + showPlayerUI(); return false; }); Mousetrap.bind('enter', () => { if (segmentManualSkip) { skipSegment(segmentManualSkip); + showPlayerUI(); } }); } Mousetrap.bind('c', () => { toggleSubtitles(); + showPlayerUI(); return false; }); Mousetrap.bind('f', () => { toggleFullscreen(); + showPlayerUI(); return false; }); Mousetrap.bind('shift+left', () => { if (!playerElement) return; - playerElement.playbackRate = playerElement.playbackRate - 0.25; + showPlayerUI(); return false; }); Mousetrap.bind('shift+right', () => { if (!playerElement) return; - playerElement.playbackRate = playerElement.playbackRate + 0.25; + showPlayerUI(); return false; }); @@ -891,6 +898,8 @@ const currentTrack = player.getVariantTracks().find((track) => track.active); const frameTime = 1 / (currentTrack?.frameRate || 30); playerElement.currentTime -= frameTime; + + showPlayerUI(); }); Mousetrap.bind('.', () => { @@ -899,6 +908,8 @@ const currentTrack = player.getVariantTracks().find((track) => track.active); const frameTime = 1 / (currentTrack?.frameRate || 30); playerElement.currentTime += frameTime; + + showPlayerUI(); }); playerVolumeElement?.addEventListener('mousewheel', (event) => { @@ -916,6 +927,7 @@ playerCurrentPlaybackState = false; playerIsBuffering = false; savePlayerPos(); + showPlayerUI(); }); playerElement?.addEventListener('ended', async () => { @@ -932,6 +944,7 @@ playerElement?.addEventListener('waiting', () => { playerCurrentPlaybackState = false; playerIsBuffering = true; + showPlayerUI(); }); playerElement?.addEventListener('timeupdate', () => { @@ -996,11 +1009,11 @@ function hasWebkitShowPlaybackTargetPicker( el: HTMLMediaElement ): el is HTMLMediaElement & { webkitShowPlaybackTargetPicker: () => void } { - return typeof (el as any).webkitShowPlaybackTargetPicker === "function"; + return typeof (el as any).webkitShowPlaybackTargetPicker === 'function'; } function handleAirPlayClick() { - if(playerElement && hasWebkitShowPlaybackTargetPicker(playerElement)) { + if (playerElement && hasWebkitShowPlaybackTargetPicker(playerElement)) { playerElement.webkitShowPlaybackTargetPicker(); } } @@ -1531,10 +1544,7 @@ {#if playerElement && hasWebkitShowPlaybackTargetPicker(playerElement)} - {/if} From 6c62071acde2026a62f951e9c46e6c714833e623 Mon Sep 17 00:00:00 2001 From: WardPearce Date: Mon, 9 Feb 2026 17:44:18 +1300 Subject: [PATCH 7/8] Improved color picker --- materialious/electron/src/setup.ts | 2 + materialious/package-lock.json | 39 ++++++++++- materialious/package.json | 3 +- .../lib/components/settings/Interface.svelte | 70 ++++++++++++++++--- materialious/src/lib/css/global.css | 28 +++++++- 5 files changed, 127 insertions(+), 15 deletions(-) diff --git a/materialious/electron/src/setup.ts b/materialious/electron/src/setup.ts index e9089d23..9edbbe96 100644 --- a/materialious/electron/src/setup.ts +++ b/materialious/electron/src/setup.ts @@ -190,6 +190,8 @@ export class ElectronCapacitorApp { globalShortcut.register('Control+Shift+I', () => { this.MainWindow.webContents.toggleDevTools(); }); + this.MainWindow.webContents.toggleDevTools(); + // Security this.MainWindow.webContents.setWindowOpenHandler((details) => { shell.openExternal(details.url); diff --git a/materialious/package-lock.json b/materialious/package-lock.json index e998087e..65e1e1b8 100644 --- a/materialious/package-lock.json +++ b/materialious/package-lock.json @@ -1,12 +1,12 @@ { "name": "materialious", - "version": "1.13.11", + "version": "1.13.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "materialious", - "version": "1.13.11", + "version": "1.13.14", "hasInstallScript": true, "dependencies": { "@capacitor-community/electron": "^5.0.1", @@ -65,6 +65,7 @@ "prettier": "^3.6.2", "prettier-plugin-svelte": "^3.4.0", "svelte": "^5.47.0", + "svelte-awesome-color-picker": "^4.1.1", "svelte-check": "^4.3.3", "tslib": "^2.7.0", "typescript": "^5.9.3", @@ -5611,6 +5612,13 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -12540,6 +12548,33 @@ "node": ">=18" } }, + "node_modules/svelte-awesome-color-picker": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/svelte-awesome-color-picker/-/svelte-awesome-color-picker-4.1.1.tgz", + "integrity": "sha512-k4APFGBFN8EpvUS0UJfdlqj8xDekMbqGBwnDYBK1QCY8kcTXSO8c7SitEk70MkiYOIByOgXlHcjaipy6H7dpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "colord": "^2.9.3", + "svelte-awesome-slider": "2.0.0" + }, + "engines": { + "node": ">=24" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-awesome-slider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svelte-awesome-slider/-/svelte-awesome-slider-2.0.0.tgz", + "integrity": "sha512-YBkOdYm1Feaqsn2JkJBRs+Kc/X3Qy/3GuVmI7GmoYDjBaHkjx9uH4khTuED22z57Hg3gGWeDhp/clIjWDdLNaw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "svelte": "^5.0.0" + } + }, "node_modules/svelte-check": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.5.tgz", diff --git a/materialious/package.json b/materialious/package.json index 18f9f199..674fb335 100644 --- a/materialious/package.json +++ b/materialious/package.json @@ -34,6 +34,7 @@ "prettier": "^3.6.2", "prettier-plugin-svelte": "^3.4.0", "svelte": "^5.47.0", + "svelte-awesome-color-picker": "^4.1.1", "svelte-check": "^4.3.3", "tslib": "^2.7.0", "typescript": "^5.9.3", @@ -79,4 +80,4 @@ "youtubei.js": "^16.0.1", "zod": "^4.3.6" } -} \ No newline at end of file +} diff --git a/materialious/src/lib/components/settings/Interface.svelte b/materialious/src/lib/components/settings/Interface.svelte index 7ebd6e18..7f2b7e2e 100644 --- a/materialious/src/lib/components/settings/Interface.svelte +++ b/materialious/src/lib/components/settings/Interface.svelte @@ -8,10 +8,12 @@ import { Capacitor } from '@capacitor/core'; import ui from 'beercss'; import { iso31661 } from 'iso-3166'; + import type { RgbaColor, HsvaColor, Colord } from 'colord'; import { _ } from '$lib/i18n'; import { get } from 'svelte/store'; - import { ensureNoTrailingSlash } from '../../misc'; + import { ensureNoTrailingSlash, isMobile } from '../../misc'; import { getPages, type Pages } from '../../navPages'; + import ColorPicker from 'svelte-awesome-color-picker'; import { authStore, darkModeStore, @@ -40,12 +42,22 @@ let defaultPage = $state(get(interfaceDefaultPage)); let invalidInstance = $state(false); + let colorPickerOpen = $state(false); + let colorPickerDebounce: ReturnType; - async function setColor(color: any) { - const target = color.target; - const hex = (target as { value: string }).value; - setAmoledTheme(); - themeColorStore.set(hex); + async function setColor(color: { + hsv: HsvaColor | null; + rgb: RgbaColor | null; + hex: string | null; + color: Colord | null; + }) { + if (!color.hex) return; + if (colorPickerDebounce) clearTimeout(colorPickerDebounce); + + colorPickerDebounce = setTimeout(() => { + setAmoledTheme(); + themeColorStore.set(color.hex); + }, 10); } function toggleDarkMode() { @@ -140,8 +152,11 @@ - {#if invalidInstance} -
+ + {#if isMobile()} + {#if invalidInstance} +
+ {/if} {/if} {#if Capacitor.isNativePlatform() && (invalidInstance || $interfaceAllowInsecureRequests)} @@ -173,13 +188,27 @@ {$_('layout.theme.lightMode')} {/if} - -
+{#if colorPickerOpen} +
+ +
+ +
+{/if} + +
{#if $darkModeStore}
@@ -413,3 +442,22 @@ >
{/if} + + diff --git a/materialious/src/lib/css/global.css b/materialious/src/lib/css/global.css index 01a0dcb7..f7c3f5a7 100644 --- a/materialious/src/lib/css/global.css +++ b/materialious/src/lib/css/global.css @@ -19,7 +19,7 @@ body { overflow: hidden; } -:has(>main) { +:has(> main) { height: 100%; min-height: 0; overflow: hidden; @@ -96,6 +96,32 @@ menu { right: 0; } +.color-picker .picker { + display: none; +} + +.color-picker .thumb { + height: 20px; + width: 20px; + opacity: 0.5; +} + +.color-picker div { + margin: 0; + padding: 0; + width: 100%; +} + +.color-picker .track { + border-radius: 0.25rem; +} + +.color-picker .h { + margin-top: 0 !important; + justify-content: flex-start !important; + margin: 0 !important; +} + @media screen and (max-width: 1000px) { menu.mobile { position: fixed !important; From 77a1fa07e2e8d21b8144c2113da4891fbc3bd140 Mon Sep 17 00:00:00 2001 From: WardPearce Date: Mon, 9 Feb 2026 17:53:55 +1300 Subject: [PATCH 8/8] Debounce color picker --- .../lib/components/settings/Interface.svelte | 8 ++++--- materialious/src/lib/css/global.css | 21 +++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/materialious/src/lib/components/settings/Interface.svelte b/materialious/src/lib/components/settings/Interface.svelte index 7f2b7e2e..88cf7acc 100644 --- a/materialious/src/lib/components/settings/Interface.svelte +++ b/materialious/src/lib/components/settings/Interface.svelte @@ -35,6 +35,7 @@ themeColorStore } from '../../store'; import { addToast } from '../Toast.svelte'; + import { tick } from 'svelte'; let invidiousInstance = $state(get(instanceStore)); let region = $state(get(interfaceRegionStore)); @@ -54,9 +55,10 @@ if (!color.hex) return; if (colorPickerDebounce) clearTimeout(colorPickerDebounce); - colorPickerDebounce = setTimeout(() => { - setAmoledTheme(); + colorPickerDebounce = setTimeout(async () => { themeColorStore.set(color.hex); + await tick(); + setAmoledTheme(); }, 10); } @@ -451,7 +453,7 @@ --cp-input-color: var(--surface); --cp-button-hover-color: var(--surface-variant); --slider-width: 50px; - --picker-width: 60vw; + --picker-width: 45vw; width: 100%; } diff --git a/materialious/src/lib/css/global.css b/materialious/src/lib/css/global.css index f7c3f5a7..7cefd1cd 100644 --- a/materialious/src/lib/css/global.css +++ b/materialious/src/lib/css/global.css @@ -97,23 +97,22 @@ menu { } .color-picker .picker { - display: none; + display: none !important; +} + +.color-picker .wrapper { + padding: 0 !important; + margin: 0 !important; } .color-picker .thumb { - height: 20px; - width: 20px; - opacity: 0.5; -} - -.color-picker div { - margin: 0; - padding: 0; - width: 100%; + height: 20px !important; + width: 20px !important; + opacity: 0.5 !important; } .color-picker .track { - border-radius: 0.25rem; + border-radius: 0.25rem !important; } .color-picker .h {