fix(theme-v2): ensure theme audio autoplays on load and handles unlock gesture

This commit is contained in:
HorizonCode committed 2026-11-01 14:54:04 +01:00
1 parent ad3fe7c586
commit d38250a5e4
4 files changed
+36 -8

No files matched your search

@@ -76,6 +76,7 @@
themeAssetName, themeAssetName,
themeHasLaunchButton, themeHasLaunchButton,
type ThemeV2Asset, type ThemeV2Asset,
type ThemeV2Audio,
type ThemeV2Background, type ThemeV2Background,
type ThemeV2BehaviorTarget, type ThemeV2BehaviorTarget,
type ThemeV2ButtonAction, type ThemeV2ButtonAction,
@@ -252,6 +253,7 @@
let lastGradientBackground = $state<Extract<ThemeV2Background, { type: 'gradient' }> | undefined>( let lastGradientBackground = $state<Extract<ThemeV2Background, { type: 'gradient' }> | undefined>(
undefined undefined
); );
let lastAudio = $state<ThemeV2Audio | undefined>(undefined);
let assetBase = $state(''); let assetBase = $state('');
let savedFolder = $state<string | undefined>(undefined); let savedFolder = $state<string | undefined>(undefined);
let editorFolderPath = $state<string | undefined>(undefined); let editorFolderPath = $state<string | undefined>(undefined);
@@ -1803,6 +1805,7 @@
? normalizeThemeV2(JSON.parse(context.themeJson) as Partial<ThemeV2Definition>) ? normalizeThemeV2(JSON.parse(context.themeJson) as Partial<ThemeV2Definition>)
: createDefaultThemeV2(); : createDefaultThemeV2();
definition = ensureDefaults(parsed); definition = ensureDefaults(parsed);
lastAudio = parsed.audio ? { ...parsed.audio } : undefined;
savedFolder = context.folderName ?? undefined; savedFolder = context.folderName ?? undefined;
editorFolderPath = context.folderPath ?? undefined; editorFolderPath = context.folderPath ?? undefined;
const httpBase = await themeAssetBase(); const httpBase = await themeAssetBase();
@@ -1992,6 +1995,9 @@
color: current.type === 'color' ? current.color : (lastColorBackground?.color ?? '#181825'), color: current.type === 'color' ? current.color : (lastColorBackground?.color ?? '#181825'),
opacity: lastColorBackground?.opacity ?? opacity, opacity: lastColorBackground?.opacity ?? opacity,
}; };
if (!definition.audio && lastAudio) {
definition.audio = { ...lastAudio };
}
} else if (type === 'gradient') { } else if (type === 'gradient') {
definition.background = { definition.background = {
type: 'gradient', type: 'gradient',
@@ -2004,6 +2010,9 @@
: (lastGradientBackground?.angle ?? 135), : (lastGradientBackground?.angle ?? 135),
opacity: lastGradientBackground?.opacity ?? opacity, opacity: lastGradientBackground?.opacity ?? opacity,
}; };
if (!definition.audio && lastAudio) {
definition.audio = { ...lastAudio };
}
} else if (type === 'video') { } else if (type === 'video') {
const candidateVideo = const candidateVideo =
current.type === 'video' && current.src current.type === 'video' && current.src
@@ -2024,6 +2033,9 @@
if (candidateVideo) { if (candidateVideo) {
lastVideoBackground = { ...definition.background }; lastVideoBackground = { ...definition.background };
} }
if (definition.audio) {
lastAudio = { ...definition.audio };
}
definition.audio = undefined; definition.audio = undefined;
} else { } else {
const candidateImage = const candidateImage =
@@ -2043,6 +2055,9 @@
if (candidateImage) { if (candidateImage) {
lastImageBackground = { ...definition.background }; lastImageBackground = { ...definition.background };
} }
if (!definition.audio && lastAudio) {
definition.audio = { ...lastAudio };
}
} }
} }
@@ -2086,6 +2101,7 @@
const asset = await chooseAsset('audio'); const asset = await chooseAsset('audio');
if (!asset) return; if (!asset) return;
definition.audio = { src: asset.name, loop: definition.audio?.loop !== false }; definition.audio = { src: asset.name, loop: definition.audio?.loop !== false };
lastAudio = { ...definition.audio };
} }
function uniqueFamily(label: string) { function uniqueFamily(label: string) {
@@ -5622,7 +5638,10 @@
size="icon" size="icon"
class="size-8 text-muted-foreground" class="size-8 text-muted-foreground"
title="Remove music" title="Remove music"
onclick={() => (definition.audio = undefined)} onclick={() => {
definition.audio = undefined;
lastAudio = undefined;
}}
> >
<X class="size-3.5" /> <X class="size-3.5" />
</Button> </Button>
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { custom_theme_volume } from '$lib/global'; import { custom_theme_volume } from '$lib/global';
import { themeMediaSource, themeVideoLifecycle } from '$lib/themeMedia'; import { playThemeMedia, themeMediaSource, themeVideoLifecycle } from '$lib/themeMedia';
import type { ThemeV2Definition } from '$lib/themeV2'; import type { ThemeV2Definition } from '$lib/themeV2';
import { themeAssetUrl } from '$lib/themeV2'; import { themeAssetUrl } from '$lib/themeV2';
import { reduceAnimations } from '$lib/userSettings'; import { reduceAnimations } from '$lib/userSettings';
@@ -64,6 +64,14 @@
if (animFrame !== undefined) { if (animFrame !== undefined) {
cancelAnimationFrame(animFrame); cancelAnimationFrame(animFrame);
} }
if (audioElement) {
try {
audioElement.pause();
audioElement.src = '';
} catch {
// ignore
}
}
}); });
const assetUrl = (path: string | undefined) => { const assetUrl = (path: string | undefined) => {
@@ -126,10 +134,8 @@
$effect(() => { $effect(() => {
if (!audioElement || !definition.audio) return; if (!audioElement || !definition.audio) return;
audioElement.volume = $custom_theme_volume; audioElement.volume = $custom_theme_volume;
if (!preview) { if (!preview && audioElement.paused) {
audioElement.play().catch(() => { playThemeMedia(audioElement);
// Autoplay can be rejected until the window has been interacted with.
});
} }
}); });
</script> </script>
@@ -225,6 +231,8 @@
use:themeMediaSource={assetUrl(definition.audio.src)} use:themeMediaSource={assetUrl(definition.audio.src)}
loop={definition.audio.loop !== false} loop={definition.audio.loop !== false}
autoplay autoplay
preload="auto"
playsinline
></audio> ></audio>
{/if} {/if}
</div> </div>
+2 -2
View File
@@ -208,7 +208,7 @@ const armAutoplayRetry = (node: HTMLMediaElement) => {
if (node.isConnected) { if (node.isConnected) {
node.muted = false; node.muted = false;
const targetVolume = get(custom_theme_volume); const targetVolume = get(custom_theme_volume);
if (targetVolume > 0) node.volume = targetVolume; if (targetVolume >= 0) node.volume = targetVolume;
void node.play().catch(() => {}); void node.play().catch(() => {});
} }
}; };
@@ -230,7 +230,7 @@ export const playThemeMedia = (node: HTMLMediaElement) => {
node.muted = false; node.muted = false;
const currentVolume = get(custom_theme_volume); const currentVolume = get(custom_theme_volume);
if (node.volume === 0 && currentVolume > 0) { if (currentVolume >= 0) {
node.volume = currentVolume; node.volume = currentVolume;
} }
+1
View File
@@ -434,6 +434,7 @@ export const loadTheme = async (theme: Theme, themeContainer: HTMLElement, volum
if (theme.definition) { if (theme.definition) {
setGlobalVolume(volume); setGlobalVolume(volume);
disableMediaControls(); disableMediaControls();
void invoke('unlock_media_autoplay').catch(() => {});
return; return;
} }
if (theme.scriptUrl.length > 0) { if (theme.scriptUrl.length > 0) {