From 6f6885404a40ae65d2b44234c9bc0e243daec7b5 Mon Sep 17 00:00:00 2001 From: WardPearce Date: Wed, 18 Feb 2026 07:13:43 +1300 Subject: [PATCH] Fix to Android color theme --- .../src/lib/components/player/Player.svelte | 90 ++++++++++++++++-- materialious/src/lib/player/android.ts | 95 ------------------- materialious/src/routes/+layout.svelte | 24 ++--- 3 files changed, 95 insertions(+), 114 deletions(-) delete mode 100644 materialious/src/lib/player/android.ts diff --git a/materialious/src/lib/components/player/Player.svelte b/materialious/src/lib/components/player/Player.svelte index 1d34f4b0..339ecfa0 100644 --- a/materialious/src/lib/components/player/Player.svelte +++ b/materialious/src/lib/components/player/Player.svelte @@ -3,7 +3,7 @@ import { getBestThumbnail } from '$lib/images'; import { videoLength } from '$lib/numbers'; import { generateChapterWebVTT, type ParsedDescription } from '$lib/description'; - import { Capacitor } from '@capacitor/core'; + import { Capacitor, SystemBars, SystemBarsStyle, SystemBarType } from '@capacitor/core'; import { error, type Page } from '@sveltejs/kit'; import Mousetrap from 'mousetrap'; import { CapacitorMusicControls } from 'capacitor-music-controls-plugin'; @@ -20,6 +20,8 @@ invidiousInstanceStore, isAndroidTvStore, playerAlwaysLoopStore, + playerAndroidLockOrientation, + playerAndroidPauseOnNetworkChange, playerAutoPlayStore, playerCCByDefault, playerDefaultLanguage, @@ -59,9 +61,10 @@ import Airplay from './settings/Airplay.svelte'; import Pip from './settings/Pip.svelte'; import FullscreenToggle from './settings/FullscreenToggle.svelte'; - import { AndroidPlayer } from '$lib/player/android'; import Timeline from './Timeline.svelte'; import TouchControls from './TouchControls.svelte'; + import { Network, type ConnectionStatus } from '@capacitor/network'; + import { ScreenOrientation, type ScreenOrientationResult } from '@capacitor/screen-orientation'; interface Props { data: { video: VideoPlay; content: ParsedDescription; playlistId: string | null }; @@ -86,11 +89,12 @@ let watchProgressInterval: ReturnType; let showVideoRetry = $state(false); - let androidPlayer: AndroidPlayer; - let player: shaka.Player; let sabrAdapter: SabrStreamingAdapter | null; + let androidInitialNetworkStatus: ConnectionStatus | undefined; + let androidOriginalOrigination: ScreenOrientationResult | undefined; + let playerContainer: HTMLElement; let playerCurrentPlaybackState = $state(false); let playerMaxKnownTime = $state(data.video.lengthSeconds); @@ -369,6 +373,58 @@ }, 5000); } + async function onAndroidFullscreenChange() { + const videoFormats = data.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 (!$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 { + await ScreenOrientation.lock({ + orientation: (androidOriginalOrigination as ScreenOrientationResult).type + }); + } + } + + async function androidHandleRotate() { + if ( + Capacitor.getPlatform() !== 'android' || + data.video.adaptiveFormats.length === 0 || + $isAndroidTvStore + ) + return; + + androidOriginalOrigination = await ScreenOrientation.orientation(); + + document.addEventListener('fullscreenchange', onAndroidFullscreenChange); + } + onMount(async () => { shaka.polyfill.installAll(); if (!shaka.Player.isBrowserSupported()) { @@ -417,8 +473,6 @@ window.addEventListener('resize', updateVideoPlayerHeight); updateVideoPlayerHeight(); - androidPlayer = new AndroidPlayer(data.video, playerElement); - await player.attach(playerElement); player?.addEventListener('error', (event) => { @@ -454,6 +508,19 @@ playerElement?.addEventListener('volumechange', saveVolumePreference); if (Capacitor.getPlatform() === 'android') { + await androidHandleRotate(); + + androidInitialNetworkStatus = await Network.getStatus(); + + Network.addListener('networkStatusChange', (networkStatus) => { + if ( + androidInitialNetworkStatus?.connectionType !== networkStatus.connectionType && + $playerAndroidPauseOnNetworkChange + ) { + playerElement?.pause(); + } + }); + await CapacitorMusicControls.create({ track: data.video.title, artist: data.video.author, @@ -778,7 +845,16 @@ if (Capacitor.getPlatform() === 'android') { await setStatusBarColor(); await CapacitorMusicControls.destroy(); - await androidPlayer.destroy(); + + if ($isAndroidTvStore) { + document.removeEventListener('fullscreenchange', onAndroidFullscreenChange); + } + + if (androidOriginalOrigination) { + await ScreenOrientation.lock({ + orientation: androidOriginalOrigination.type + }); + } } try { diff --git a/materialious/src/lib/player/android.ts b/materialious/src/lib/player/android.ts deleted file mode 100644 index 70587c9f..00000000 --- a/materialious/src/lib/player/android.ts +++ /dev/null @@ -1,95 +0,0 @@ -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/routes/+layout.svelte b/materialious/src/routes/+layout.svelte index e7f81a04..ffe6faec 100644 --- a/materialious/src/routes/+layout.svelte +++ b/materialious/src/routes/+layout.svelte @@ -81,20 +81,20 @@ }); onMount(async () => { - ui(); - - if (Capacitor.getPlatform() === 'android' && !$themeColorStore) { - try { - const colorPalette = await colorTheme.getColorPalette(); - const colorAsHex = convertToHexColorCode(colorPalette.primary); - themeColorStore.set(colorAsHex); - await ui('theme', colorAsHex); - } catch { - // Continue regardless of error - } - } + await ui(); if (Capacitor.getPlatform() === 'android') { + if (!$themeColorStore) { + try { + const colorPalette = await colorTheme.getColorPalette(); + const colorAsHex = convertToHexColorCode(colorPalette.primary); + themeColorStore.set(colorAsHex); + await ui('theme', colorAsHex); + } catch { + // Continue regardless of error + } + } + document.addEventListener('click', async (event: MouseEvent) => { // Handles opening links in browser for android. const link = (event.target as HTMLElement).closest('a');