fix: previews follow the app volume; volume bar fill always accent

The preview backend never received a volume — clips played at the crate
default (0.7) regardless of the slider, even muted. The session now sets
the preview volume from the app store (mute → 0) before the first load and
mirrors it live while the modal is open; the debug overlay's PREVIEW row
exposes the backend's resting volume as proof.

The player-bar volume (and fallback seek) fill is now always the accent
color — matching the waveform scrubber's played region — stepping up to
the hover shade on hover instead of white-until-hover.

Also syncs package-lock to the soulstream name + alpha version.
This commit is contained in:
KeelrLeek committed 2026-07-23 16:25:42 -05:00
1 parent 793f78370e
commit 691fdcdc9a
10 files changed
+63 -10

No files matched your search

+1
View File
@@ -124,6 +124,7 @@
<b>PREVIEW</b>
{previewUi.snapshot?.phase ?? "—"} · gen {previewUi.snapshot?.requestGeneration ?? "—"} ·
idx {previewUi.snapshot?.activeManifestIndex ?? "—"}
{#if stats.previewVolume != null}· vol {stats.previewVolume.toFixed(2)}{/if}
</div>
{/if}
</div>
+4 -2
View File
@@ -706,15 +706,17 @@
border-radius: 2px;
}
/* Always the accent — matching the waveform scrubber's played region
(user decision 2026-07-23; the old white-until-hover read as off). */
.fill {
position: relative;
height: 100%;
background: var(--text-primary);
background: var(--accent);
border-radius: 2px;
}
.track:hover .fill {
background: var(--accent);
background: var(--accent-hover);
}
/* waveform scrubber */
+9
View File
@@ -18,6 +18,7 @@
previewCommands,
closePreview,
} from "../lib/preview/previewSession.svelte.js";
import { player } from "../lib/state.svelte.js";
import {
playableManifestIndices,
playableCursor,
@@ -25,6 +26,14 @@
} from "../lib/preview/session.js";
import { accentFromImage } from "../lib/color.js";
// Preview audio follows the APP volume — mute means silent previews
// too. Mirrored live while the modal is open (the session also sets it
// once at open, before the first load).
$effect(() => {
if (!previewUi.open) return;
previewCommands.setVolume(player.muted ? 0 : player.volume);
});
const st = $derived(previewUi.snapshot);
const manifest = $derived(st?.manifest ?? null);
const items = $derived(manifest?.items ?? []);
+8
View File
@@ -30,6 +30,7 @@ let service = null;
let unsubscribe = null;
let focusParts = null; // { coordinator, gateway }
let demoTicker = null;
let readPlayerRef = null; // injected with the bridge; app volume source
// ------------------------------------------------------------- gateway ----
@@ -50,6 +51,7 @@ export function notePlaybackGeneration() {
/** One-time wiring from state.svelte.js. */
export function initPreviewBridge({ readPlayer, resumeMain }) {
readPlayerRef = readPlayer;
focusParts = createProductionFocus({
readPlayer,
readPlaybackGeneration: () => playbackGeneration,
@@ -81,6 +83,11 @@ export async function openPreview(manifest, { playFull } = {}) {
focus: focusParts,
})
);
// Preview volume follows the APP volume (mute = 0) — set it BEFORE the
// first load so the opening clip can't blast at the backend default.
// The overlay keeps it mirrored live while the modal is open.
const p = readPlayerRef?.();
if (p) service.setVolume(p.muted ? 0 : (p.volume ?? 0.7));
previewUi.open = true;
await service.open({ sessionId: crypto.randomUUID(), manifest });
}
@@ -176,4 +183,5 @@ export const previewCommands = {
seek: (ms) => service?.seek(ms),
setMuted: (v) => service?.setMuted(v),
playFullTrack: (starter) => service?.playFullTrack(starter),
setVolume: (v) => service?.setVolume(v),
};
+7
View File
@@ -348,6 +348,13 @@ export function createPreviewService({
if (value && state.phase === PHASE.PLAYING) void transport.pause();
},
/** Mirror the APP's volume (and mute, as 0) onto the preview
* transport — previews must never play louder than the player bar
* says. No-op for transports without a volume control (fixture). */
setVolume(v) {
void transport.setVolume?.(v);
},
async togglePlay() {
if (!state) return;
const sid = state.sessionId;