Fix session restore and audio badges visibility
Session restore: - Add pendingSessionRestore state to playerStore - When restored track is played, load from Qobuz first - Seek to saved position after track loads Audio badges: - Always show DAC/EXC badges (very muted when off) - Adjust margins and sizing for better alignment
This commit is contained in:
1 parent
3fd3d25bdc
commit
dd758d3523
3 files changed
+93
-56
No files matched your search
@@ -67,79 +67,70 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if shouldShowBadges}
|
||||
<div
|
||||
class="audio-badges"
|
||||
onmouseenter={() => isHovering = true}
|
||||
onmouseleave={() => isHovering = false}
|
||||
>
|
||||
<!-- DAC Badge -->
|
||||
<div
|
||||
class="audio-badges"
|
||||
onmouseenter={() => isHovering = true}
|
||||
onmouseleave={() => isHovering = false}
|
||||
class="badge"
|
||||
class:active={dacPassthroughActive}
|
||||
title={showTooltips ? (dacPassthroughActive ? 'DAC Passthrough active' : 'DAC Passthrough inactive') : undefined}
|
||||
>
|
||||
<!-- DAC Badge -->
|
||||
<div
|
||||
class="badge"
|
||||
class:active={dacPassthroughActive}
|
||||
class:external={isExternal}
|
||||
title={showTooltips ? (dacPassthroughActive ? 'DAC Passthrough active' : 'DAC Passthrough inactive') : undefined}
|
||||
>
|
||||
<span class="badge-label">DAC</span>
|
||||
</div>
|
||||
|
||||
<!-- Exclusive Mode Badge -->
|
||||
<div
|
||||
class="badge"
|
||||
class:active={exclusiveModeActive}
|
||||
title={showTooltips ? (exclusiveModeActive ? 'Exclusive Mode active' : 'Exclusive Mode inactive') : undefined}
|
||||
>
|
||||
<span class="badge-label">EXC</span>
|
||||
</div>
|
||||
|
||||
<!-- Device Tooltip on hover -->
|
||||
{#if isHovering && showTooltips}
|
||||
<div class="device-tooltip">
|
||||
<div class="tooltip-label">Output Device</div>
|
||||
<div class="tooltip-device">{prettyDeviceName}</div>
|
||||
{#if currentDevice && currentDevice !== prettyDeviceName}
|
||||
<div class="tooltip-raw">{currentDevice}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<span class="badge-label">DAC</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Exclusive Mode Badge -->
|
||||
<div
|
||||
class="badge"
|
||||
class:active={exclusiveModeActive}
|
||||
title={showTooltips ? (exclusiveModeActive ? 'Exclusive Mode active' : 'Exclusive Mode inactive') : undefined}
|
||||
>
|
||||
<span class="badge-label">EXC</span>
|
||||
</div>
|
||||
|
||||
<!-- Device Tooltip on hover -->
|
||||
{#if isHovering && showTooltips}
|
||||
<div class="device-tooltip">
|
||||
<div class="tooltip-label">Output Device</div>
|
||||
<div class="tooltip-device">{prettyDeviceName}</div>
|
||||
{#if currentDevice && currentDevice !== prettyDeviceName}
|
||||
<div class="tooltip-raw">{currentDevice}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.audio-badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
gap: 3px;
|
||||
position: relative;
|
||||
margin-left: 8px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px 6px;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 8px;
|
||||
font-size: 7px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
letter-spacing: 0.3px;
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
transition: all 200ms ease;
|
||||
}
|
||||
|
||||
.badge.active {
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
background: rgba(34, 197, 94, 0.2);
|
||||
color: #22c55e;
|
||||
border-color: rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
|
||||
.badge.external:not(.active) {
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
color: rgba(99, 102, 241, 0.5);
|
||||
border-color: rgba(99, 102, 241, 0.2);
|
||||
border-color: rgba(34, 197, 94, 0.4);
|
||||
}
|
||||
|
||||
.badge-label {
|
||||
|
||||
@@ -61,6 +61,9 @@ let queueEnded = false;
|
||||
// Callbacks for track advancement (set by consumer)
|
||||
let onTrackEnded: (() => Promise<void>) | null = null;
|
||||
|
||||
// Session restore state - when set, next play will load the track first
|
||||
let pendingSessionRestore: { trackId: number; position: number } | null = null;
|
||||
|
||||
// Listeners
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
@@ -176,6 +179,28 @@ export function setQueueEnded(ended: boolean): void {
|
||||
|
||||
// ============ Playback Controls ============
|
||||
|
||||
/**
|
||||
* Set pending session restore - will load track on next play
|
||||
*/
|
||||
export function setPendingSessionRestore(trackId: number, position: number): void {
|
||||
pendingSessionRestore = { trackId, position };
|
||||
console.log('[Player] Set pending session restore:', { trackId, position });
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear pending session restore
|
||||
*/
|
||||
export function clearPendingSessionRestore(): void {
|
||||
pendingSessionRestore = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's a pending session restore
|
||||
*/
|
||||
export function hasPendingSessionRestore(): boolean {
|
||||
return pendingSessionRestore !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle play/pause
|
||||
*/
|
||||
@@ -188,7 +213,29 @@ export async function togglePlay(): Promise<void> {
|
||||
|
||||
try {
|
||||
if (newIsPlaying) {
|
||||
await invoke('resume_playback');
|
||||
// Check if we need to load the track first (session restore)
|
||||
if (pendingSessionRestore && pendingSessionRestore.trackId === currentTrack.id) {
|
||||
console.log('[Player] Loading restored track:', pendingSessionRestore.trackId);
|
||||
const savedPosition = pendingSessionRestore.position;
|
||||
pendingSessionRestore = null; // Clear before loading
|
||||
|
||||
// Load the track from Qobuz
|
||||
await invoke('play_track', { trackId: currentTrack.id });
|
||||
|
||||
// Seek to saved position after a short delay to let audio load
|
||||
if (savedPosition > 0) {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await invoke('seek', { position: savedPosition });
|
||||
console.log('[Player] Seeked to restored position:', savedPosition);
|
||||
} catch (seekErr) {
|
||||
console.error('[Player] Failed to seek to restored position:', seekErr);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
} else {
|
||||
await invoke('resume_playback');
|
||||
}
|
||||
} else {
|
||||
await invoke('pause_playback');
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
togglePlay,
|
||||
seek as playerSeek,
|
||||
setVolume as playerSetVolume,
|
||||
setPendingSessionRestore,
|
||||
startPolling,
|
||||
stopPolling,
|
||||
reset as resetPlayer,
|
||||
@@ -851,11 +852,9 @@
|
||||
quality: 'Restored',
|
||||
});
|
||||
|
||||
// Seek to saved position if > 0
|
||||
if (session.current_position_secs > 0) {
|
||||
// Note: actual seek will happen when user presses play
|
||||
console.log(`[Session] Will resume at ${session.current_position_secs}s`);
|
||||
}
|
||||
// Mark that this track needs to be loaded when user presses play
|
||||
setPendingSessionRestore(track.id, session.current_position_secs);
|
||||
console.log(`[Session] Track ${track.id} pending load, will resume at ${session.current_position_secs}s`);
|
||||
}
|
||||
|
||||
console.log('[Session] Session restored successfully');
|
||||
|
||||
Reference in new issue
Block a user