From 836c9855af4f95814013b2b02aab8315de71a44e Mon Sep 17 00:00:00 2001 From: root Date: Mon, 6 Apr 2026 14:57:27 +0500 Subject: [PATCH] fix(player): use untrack() in toggleRequest effect to prevent play/pause loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reading audioStore.isPlaying inside the toggleRequest $effect caused Svelte 5 to subscribe to it, so the effect re-ran on every isPlaying change. When resuming from ListeningMode, play() would fire onplay → isPlaying=true → effect re-ran → called pause() → onpause → isPlaying=false → effect re-ran → called play() → infinite loop. Wrapping the isPlaying read in untrack() limits the effect's subscription to toggleRequest only. --- ui/src/routes/+layout.svelte | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/ui/src/routes/+layout.svelte b/ui/src/routes/+layout.svelte index 304c00c..a27e721 100644 --- a/ui/src/routes/+layout.svelte +++ b/ui/src/routes/+layout.svelte @@ -2,7 +2,7 @@ import '../app.css'; import { page, navigating } from '$app/state'; import { goto } from '$app/navigation'; - import { setContext } from 'svelte'; + import { setContext, untrack } from 'svelte'; import type { Snippet } from 'svelte'; import type { LayoutData } from './$types'; import { audioStore } from '$lib/audio.svelte'; @@ -156,15 +156,23 @@ }); // Handle toggle requests from AudioPlayer controller. + // IMPORTANT: isPlaying must be read inside untrack() so the effect only + // re-runs when toggleRequest increments, not every time isPlaying changes. + // Without untrack the effect subscribes to both toggleRequest AND isPlaying, + // causing an infinite play/pause loop: play() fires onplay → isPlaying=true + // → effect re-runs → sees isPlaying=true → calls pause() → onpause fires + // → isPlaying=false → effect re-runs → calls play() → … $effect(() => { // Read toggleRequest to subscribe; ignore value 0 (initial). const _req = audioStore.toggleRequest; if (!audioEl || _req === 0) return; - if (audioStore.isPlaying) { - audioEl.pause(); - } else { - audioEl.play().catch(() => {}); - } + untrack(() => { + if (audioStore.isPlaying) { + audioEl!.pause(); + } else { + audioEl!.play().catch(() => {}); + } + }); }); // Handle seek requests from AudioPlayer controller.