diff --git a/src/lib/components/CastPicker.svelte b/src/lib/components/CastPicker.svelte index 13089f118..2c1a75ed5 100644 --- a/src/lib/components/CastPicker.svelte +++ b/src/lib/components/CastPicker.svelte @@ -11,6 +11,21 @@ type CastProtocol, type CastDevice } from '$lib/stores/castStore'; + import { + connectToRemote, + disconnectFromRemote, + isRemoteMode, + playbackTarget, + } from '$lib/stores/playbackTargetStore'; + import { connectRemoteEvents, disconnectRemoteEvents } from '$lib/services/remoteEvents'; + import { pingRemote } from '$lib/services/remoteApi'; + + interface QbzdDevice { + name: string; + host: string; + port: number; + baseUrl: string; + } interface Props { isOpen: boolean; @@ -19,10 +34,11 @@ let { isOpen, onClose }: Props = $props(); - let activeProtocol = $state('chromecast'); + let activeProtocol = $state('qbzd'); let chromecastDevices = $state([]); let dlnaDevices = $state([]); let airplayDevices = $state([]); + let qbzdDevices = $state([]); let loading = $state(false); let error = $state(null); let discoveryStarted = $state(false); @@ -36,6 +52,7 @@ case 'chromecast': return chromecastDevices; case 'dlna': return dlnaDevices; case 'airplay': return airplayDevices; + case 'qbzd': return []; // qbzd uses its own list } }); @@ -76,8 +93,8 @@ // Note: AirPlay discovery disabled until RAOP streaming is implemented await Promise.allSettled([ invoke('v2_cast_start_discovery'), - invoke('v2_dlna_start_discovery') - // invoke('v2_airplay_start_discovery') // Disabled - see docs/AIRPLAY_IMPLEMENTATION_STATUS.md + invoke('v2_dlna_start_discovery'), + invoke('v2_qbzd_start_discovery'), ]); // Poll for devices pollDevices(); @@ -92,8 +109,8 @@ try { await Promise.allSettled([ invoke('v2_cast_stop_discovery'), - invoke('v2_dlna_stop_discovery') - // invoke('v2_airplay_stop_discovery') // Disabled + invoke('v2_dlna_stop_discovery'), + invoke('v2_qbzd_stop_discovery'), ]); } catch (err) { console.error('Failed to stop discovery:', err); @@ -106,10 +123,10 @@ try { // Poll active protocols in parallel // Note: AirPlay polling disabled until RAOP streaming is implemented - const [chromecast, dlna] = await Promise.allSettled([ + const [chromecast, dlna, qbzd] = await Promise.allSettled([ invoke('v2_cast_get_devices'), - invoke('v2_dlna_get_devices') - // invoke('v2_airplay_get_devices') // Disabled + invoke('v2_dlna_get_devices'), + invoke('v2_qbzd_get_devices'), ]); if (chromecast.status === 'fulfilled') { @@ -118,10 +135,9 @@ if (dlna.status === 'fulfilled') { dlnaDevices = dlna.value; } - // AirPlay disabled - // if (airplay.status === 'fulfilled') { - // airplayDevices = airplay.value; - // } + if (qbzd.status === 'fulfilled') { + qbzdDevices = qbzd.value; + } } catch (err) { console.error('Failed to get devices:', err); } @@ -138,7 +154,7 @@ connecting = true; error = null; try { - await connectToDevice(device, activeProtocol); + await connectToDevice(device, activeProtocol as CastProtocol); onClose(); } catch (err) { error = String(err); @@ -149,22 +165,48 @@ async function handleDisconnect() { try { - await disconnect(); + if ($isRemoteMode) { + disconnectRemoteEvents(); + disconnectFromRemote(); + } else { + await disconnect(); + } } catch (err) { error = String(err); } } - function getProtocolIcon(protocol: CastProtocol) { + async function handleQbzdConnect(device: QbzdDevice) { + connecting = true; + error = null; + try { + const reachable = await pingRemote(device.baseUrl, ''); + if (!reachable) { + error = `Cannot reach ${device.name} at ${device.baseUrl}`; + return; + } + connectToRemote(device.baseUrl, '', device.name); + connectRemoteEvents(); + onClose(); + } catch (err) { + error = String(err); + } finally { + connecting = false; + } + } + + function getProtocolIcon(protocol: CastProtocol | 'qbzd') { switch (protocol) { + case 'qbzd': return Monitor; case 'chromecast': return Cast; case 'dlna': return Tv; case 'airplay': return Speaker; } } - function getProtocolLabel(protocol: CastProtocol): string { + function getProtocolLabel(protocol: CastProtocol | 'qbzd'): string { switch (protocol) { + case 'qbzd': return 'QBZ Daemon'; case 'chromecast': return 'Chromecast'; case 'dlna': return 'DLNA'; case 'airplay': return 'AirPlay'; @@ -183,7 +225,21 @@ - {#if castState.isConnected && castState.device} + {#if $isRemoteMode} +
+
+ +
+ {$t('player.connectedTo')} + {$playbackTarget.name} +
+
+ +
+ {:else if castState.isConnected && castState.device} {@const ProtocolIcon = getProtocolIcon(castState.protocol!)}
@@ -201,6 +257,17 @@ {:else}
+
+ {:else if error} +
+

{error}

+
+ {:else if activeProtocol === 'qbzd'} + + {#if loading && qbzdDevices.length === 0} +
+ +

{$t('toast.loadingDevices')}

+
+ {:else if qbzdDevices.length === 0} +
+ +

{$t('player.noQbzdFound')}

+

{$t('player.noQbzdFoundHint')}

+
+ {:else} +
+ {#each qbzdDevices as device} + + {/each} +
+ {/if} {:else if loading && devices().length === 0}

{$t('toast.loadingDevices')}

- {:else if error} -
-

{error}

-
{:else if devices().length === 0}
diff --git a/src/lib/i18n/locales/de.json b/src/lib/i18n/locales/de.json index fc6a70247..8bdc0adfe 100644 --- a/src/lib/i18n/locales/de.json +++ b/src/lib/i18n/locales/de.json @@ -563,6 +563,8 @@ "player": { "nowPlaying": "Aktuell abgespielt", "controllingRemote": "Steuert: {name}", + "noQbzdFound": "Keine QBZ-Daemons in Ihrem Netzwerk gefunden", + "noQbzdFoundHint": "Stellen Sie sicher, dass qbzd auf dem Zielgerat lauft", "queue": "Warteschlange", "upNext": "Als Nächstes", "ofTotal": "von", diff --git a/src/lib/i18n/locales/en.json b/src/lib/i18n/locales/en.json index 9c43f63be..88ab62381 100644 --- a/src/lib/i18n/locales/en.json +++ b/src/lib/i18n/locales/en.json @@ -563,6 +563,8 @@ "player": { "nowPlaying": "Now Playing", "controllingRemote": "Controlling: {name}", + "noQbzdFound": "No QBZ Daemons found on your network", + "noQbzdFoundHint": "Make sure qbzd is running on the target device", "queue": "Queue", "upNext": "Up Next", "ofTotal": "of", diff --git a/src/lib/i18n/locales/es.json b/src/lib/i18n/locales/es.json index 400d857b6..9d7ace666 100644 --- a/src/lib/i18n/locales/es.json +++ b/src/lib/i18n/locales/es.json @@ -563,6 +563,8 @@ "player": { "nowPlaying": "Reproduciendo", "controllingRemote": "Controlando: {name}", + "noQbzdFound": "No se encontraron daemons QBZ en tu red", + "noQbzdFoundHint": "Asegurate de que qbzd este corriendo en el dispositivo", "queue": "Cola", "upNext": "Siguiente", "ofTotal": "de", diff --git a/src/lib/i18n/locales/fr.json b/src/lib/i18n/locales/fr.json index 92e719620..3782afd5e 100644 --- a/src/lib/i18n/locales/fr.json +++ b/src/lib/i18n/locales/fr.json @@ -563,6 +563,8 @@ "player": { "nowPlaying": "Lecture en cours", "controllingRemote": "Controle : {name}", + "noQbzdFound": "Aucun daemon QBZ trouve sur votre reseau", + "noQbzdFoundHint": "Assurez-vous que qbzd est en cours d'execution sur l'appareil cible", "queue": "File d'attente", "upNext": "À suivre", "ofTotal": "de", diff --git a/src/lib/i18n/locales/pt.json b/src/lib/i18n/locales/pt.json index b3e78f9fc..1c25e8d7e 100644 --- a/src/lib/i18n/locales/pt.json +++ b/src/lib/i18n/locales/pt.json @@ -563,6 +563,8 @@ "player": { "nowPlaying": "Tocando Agora", "controllingRemote": "Controlando: {name}", + "noQbzdFound": "Nenhum daemon QBZ encontrado na sua rede", + "noQbzdFoundHint": "Certifique-se de que o qbzd esta em execucao no dispositivo alvo", "queue": "Fila", "upNext": "A Seguir", "ofTotal": "de",