fix(cast): update play button status and add disconnect handoff

1. Play button fix: After resuming on cast device, update playerStore.isPlaying
   so the UI shows the correct playing state immediately.

2. Disconnect handoff: When disconnecting from Chromecast/DLNA while playing:
   - Captures current track and position from cast state
   - Shows confirm dialog asking if user wants to continue locally
   - If yes, starts local playback and seeks to saved position
   - If no, just resets player state

This provides seamless transitions in both directions:
- Local → Cast: auto-resumes with position
- Cast → Local: asks user, then resumes with position
This commit is contained in:
vicrodh committed 2026-01-28 12:09:38 -06:00
1 parent d52ebeac20
commit 685efcfd76
2 files changed
+87 -2

No files matched your search

+54 -1
View File
@@ -60,6 +60,10 @@ let onCastTrackEnded: (() => Promise<void>) | null = null;
// Callback for when cast disconnects (to reset player state)
let onCastDisconnected: (() => void) | null = null;
// Callback for asking user if they want to continue locally after disconnect
// Returns true if user wants to continue, false otherwise
let onAskContinueLocally: ((track: PlayingTrack, position: number) => Promise<boolean>) | null = null;
// Track end detection state
let lastTransportState: string = '';
let trackEndDetected = false;
@@ -175,6 +179,9 @@ export async function connectToDevice(device: CastDevice, protocol: CastProtocol
durationSecs: currentTrack.duration
});
// Update playerStore to reflect playing state (fixes play button UI)
setIsPlaying(true);
// Try to seek to the saved position (if significant)
if (currentPosition > 5) {
console.log('[CastStore] Seeking to saved position:', currentPosition);
@@ -200,16 +207,30 @@ export async function connectToDevice(device: CastDevice, protocol: CastProtocol
/**
* Disconnect from current device
*
* If a track was playing, asks user if they want to continue locally.
*/
export async function disconnect(): Promise<void> {
if (!state.isConnected || !state.protocol) return;
// Capture current state BEFORE disconnecting
const wasPlaying = state.isPlaying;
const currentTrack = getCurrentTrack();
const currentPosition = state.positionSecs;
const savedProtocol = state.protocol;
console.log('[CastStore] Disconnecting, current state:', {
wasPlaying,
trackId: currentTrack?.id,
position: currentPosition
});
// Stop position polling first
stopPositionPolling();
try {
await castStop();
switch (state.protocol) {
switch (savedProtocol) {
case 'chromecast':
await invoke('cast_disconnect');
break;
@@ -235,11 +256,35 @@ export async function disconnect(): Promise<void> {
};
notifyListeners();
// If there was a track playing, ask user if they want to continue locally
if (wasPlaying && currentTrack && !currentTrack.isLocal && onAskContinueLocally) {
console.log('[CastStore] Asking user if they want to continue locally...');
try {
const continueLocally = await onAskContinueLocally(currentTrack, currentPosition);
if (continueLocally) {
console.log('[CastStore] User wants to continue locally, starting playback...');
// Start local playback - the callback handler will do this
// since it has access to playbackService
} else {
console.log('[CastStore] User declined to continue locally');
// Just notify that cast disconnected
if (onCastDisconnected) {
onCastDisconnected();
}
}
} catch (err) {
console.error('[CastStore] Failed to handle continue locally:', err);
if (onCastDisconnected) {
onCastDisconnected();
}
}
} else {
// Notify player to reset its state
if (onCastDisconnected) {
onCastDisconnected();
}
}
}
/**
* Set callback for when cast disconnects
@@ -248,6 +293,14 @@ export function setOnCastDisconnected(callback: () => void): void {
onCastDisconnected = callback;
}
/**
* Set callback for asking user if they want to continue locally after disconnect
* The callback receives the track and position, and should return true if user wants to continue
*/
export function setOnAskContinueLocally(callback: (track: PlayingTrack, position: number) => Promise<boolean>): void {
onAskContinueLocally = callback;
}
/**
* Cast a track to the connected device
*/
+33 -1
View File
@@ -262,7 +262,8 @@
import {
subscribe as subscribeCast,
getCastState,
isCasting
isCasting,
setOnAskContinueLocally
} from '$lib/stores/castStore';
// Components
@@ -2255,6 +2256,37 @@
// Ignore storage errors
}
// Set up callback for cast disconnect handoff
setOnAskContinueLocally(async (track, position) => {
// Ask user if they want to continue locally
const continueLocally = window.confirm(
`Continue playing "${track.title}" on this device?`
);
if (continueLocally) {
try {
// Start local playback
await playTrack(track, { showLoadingToast: false });
// Seek to saved position after a short delay
if (position > 5) {
setTimeout(async () => {
try {
await playerSeek(position);
console.log('[CastHandoff] Seeked to position:', position);
} catch (seekErr) {
console.log('[CastHandoff] Could not restore position:', seekErr);
}
}, 1000);
}
} catch (err) {
console.error('[CastHandoff] Failed to resume local playback:', err);
}
}
return continueLocally;
});
// Note: loadFavorites() is called in handleLoginSuccess after login is confirmed
// This prevents API calls before authentication is complete