fix(qbzd): route ALL playback and queue calls through commandRouter

playbackService.ts: v2_play_track and v2_stop_playback now route
to daemon HTTP API when in remote mode.

9 views: bulk queue operations (add_tracks_to_queue, add_tracks_to_queue_next)
now use cmdAddTracksToQueue/cmdAddTracksToQueueNext instead of
direct invoke(). 14 call sites total.

This was the root cause of 'connects but nothing plays' — the
play/queue commands were always going to local Tauri, never to
the remote daemon.

cc
This commit is contained in:
vicrodh committed 2026-04-09 10:51:20 -06:00
1 parent e4b6024ade
commit fd1a4fade1
10 files changed
+57 -36

No files matched your search

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
import { invoke, convertFileSrc } from '@tauri-apps/api/core'; import { invoke, convertFileSrc } from '@tauri-apps/api/core';
import { cmdAddTracksToQueue, cmdAddTracksToQueueNext } from '$lib/services/commandRouter';
import { open, save } from '@tauri-apps/plugin-dialog'; import { open, save } from '@tauri-apps/plugin-dialog';
import { openUrl } from '@tauri-apps/plugin-opener'; import { openUrl } from '@tauri-apps/plugin-opener';
import { t, locale } from 'svelte-i18n'; import { t, locale } from 'svelte-i18n';
@@ -237,13 +238,13 @@
async function handleBulkPlayNext() { async function handleBulkPlayNext() {
const selected = album.tracks.filter(track => multiSelectedIds.has(track.id)); const selected = album.tracks.filter(track => multiSelectedIds.has(track.id));
await invoke('v2_add_tracks_to_queue_next', { tracks: buildAlbumQueueTracks(selected) }); await cmdAddTracksToQueueNext(buildAlbumQueueTracks(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
async function handleBulkPlayLater() { async function handleBulkPlayLater() {
const selected = album.tracks.filter(track => multiSelectedIds.has(track.id)); const selected = album.tracks.filter(track => multiSelectedIds.has(track.id));
await invoke('v2_add_tracks_to_queue', { tracks: buildAlbumQueueTracks(selected) }); await cmdAddTracksToQueue(buildAlbumQueueTracks(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { invoke, convertFileSrc } from '@tauri-apps/api/core'; import { invoke, convertFileSrc } from '@tauri-apps/api/core';
import { cmdAddTracksToQueue, cmdAddTracksToQueueNext } from '$lib/services/commandRouter';
import { open, save } from '@tauri-apps/plugin-dialog'; import { open, save } from '@tauri-apps/plugin-dialog';
import { openUrl } from '@tauri-apps/plugin-opener'; import { openUrl } from '@tauri-apps/plugin-opener';
import { setCustomImage, removeCustomImage as removeCustomImageFromStore } from '$lib/stores/customArtistImageStore'; import { setCustomImage, removeCustomImage as removeCustomImageFromStore } from '$lib/stores/customArtistImageStore';
@@ -444,13 +445,13 @@
async function handleBulkPlayNext() { async function handleBulkPlayNext() {
const selected = visibleTracks.filter(trk => multiSelectedIds.has(trk.id)); const selected = visibleTracks.filter(trk => multiSelectedIds.has(trk.id));
await invoke('v2_add_tracks_to_queue_next', { tracks: buildArtistQueueTracks(selected) }); await cmdAddTracksToQueueNext(buildArtistQueueTracks(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
async function handleBulkPlayLater() { async function handleBulkPlayLater() {
const selected = visibleTracks.filter(trk => multiSelectedIds.has(trk.id)); const selected = visibleTracks.filter(trk => multiSelectedIds.has(trk.id));
await invoke('v2_add_tracks_to_queue', { tracks: buildArtistQueueTracks(selected) }); await cmdAddTracksToQueue(buildArtistQueueTracks(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { cmdAddTracksToQueue, cmdAddTracksToQueueNext } from '$lib/services/commandRouter';
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import { ArrowLeft, CloudDownload, Heart, Info, ListPlus, Play, RefreshCw, Search, Shuffle, X, SquareCheckBig } from 'lucide-svelte'; import { ArrowLeft, CloudDownload, Heart, Info, ListPlus, Play, RefreshCw, Search, Shuffle, X, SquareCheckBig } from 'lucide-svelte';
import PlaylistModal from '$lib/components/PlaylistModal.svelte'; import PlaylistModal from '$lib/components/PlaylistModal.svelte';
@@ -121,13 +122,13 @@
async function handleBulkPlayNext() { async function handleBulkPlayNext() {
const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id)); const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id));
await invoke('v2_add_tracks_to_queue_next', { tracks: buildQueueTracks(selected) }); await cmdAddTracksToQueueNext(buildQueueTracks(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
async function handleBulkPlayLater() { async function handleBulkPlayLater() {
const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id)); const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id));
await invoke('v2_add_tracks_to_queue', { tracks: buildQueueTracks(selected) }); await cmdAddTracksToQueue(buildQueueTracks(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
+3 -2
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { cmdAddTracksToQueue, cmdAddTracksToQueueNext } from '$lib/services/commandRouter';
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import { ArrowLeft, CloudDownload, Heart, Info, ListPlus, Play, RefreshCw, Search, Shuffle, X, SquareCheckBig } from 'lucide-svelte'; import { ArrowLeft, CloudDownload, Heart, Info, ListPlus, Play, RefreshCw, Search, Shuffle, X, SquareCheckBig } from 'lucide-svelte';
import PlaylistModal from '$lib/components/PlaylistModal.svelte'; import PlaylistModal from '$lib/components/PlaylistModal.svelte';
@@ -122,13 +123,13 @@
async function handleBulkPlayNext() { async function handleBulkPlayNext() {
const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id)); const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id));
await invoke('v2_add_tracks_to_queue_next', { tracks: buildQueueTracks(selected) }); await cmdAddTracksToQueueNext(buildQueueTracks(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
async function handleBulkPlayLater() { async function handleBulkPlayLater() {
const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id)); const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id));
await invoke('v2_add_tracks_to_queue', { tracks: buildQueueTracks(selected) }); await cmdAddTracksToQueue(buildQueueTracks(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { cmdAddTracksToQueue, cmdAddTracksToQueueNext } from '$lib/services/commandRouter';
import { getUserInfo } from '$lib/stores/authStore'; import { getUserInfo } from '$lib/stores/authStore';
import { resolveArtistImage } from '$lib/stores/customArtistImageStore'; import { resolveArtistImage } from '$lib/stores/customArtistImageStore';
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
@@ -271,7 +272,7 @@
async function handleBulkPlayNext() { async function handleBulkPlayNext() {
const selected = filteredTracks.filter(trk => selectedTrackIds.has(trk.id)); const selected = filteredTracks.filter(trk => selectedTrackIds.has(trk.id));
const tracks = buildFavoritesQueueTracks(selected); const tracks = buildFavoritesQueueTracks(selected);
await invoke('v2_add_tracks_to_queue_next', { tracks }); await cmdAddTracksToQueueNext(tracks);
trackSelectMode = false; trackSelectMode = false;
selectedTrackIds = new Set(); selectedTrackIds = new Set();
} }
@@ -279,7 +280,7 @@
async function handleBulkPlayLater() { async function handleBulkPlayLater() {
const selected = filteredTracks.filter(trk => selectedTrackIds.has(trk.id)); const selected = filteredTracks.filter(trk => selectedTrackIds.has(trk.id));
const tracks = buildFavoritesQueueTracks(selected); const tracks = buildFavoritesQueueTracks(selected);
await invoke('v2_add_tracks_to_queue', { tracks }); await cmdAddTracksToQueue(tracks);
trackSelectMode = false; trackSelectMode = false;
selectedTrackIds = new Set(); selectedTrackIds = new Set();
} }
@@ -1298,7 +1299,7 @@
try { try {
const queueTracks = buildFavoritesQueueTracks(filteredTracks); const queueTracks = buildFavoritesQueueTracks(filteredTracks);
await invoke('v2_add_tracks_to_queue_next', { tracks: queueTracks }); await cmdAddTracksToQueueNext(queueTracks);
} catch (err) { } catch (err) {
console.error('Failed to add tracks next:', err); console.error('Failed to add tracks next:', err);
} }
@@ -1309,7 +1310,7 @@
try { try {
const queueTracks = buildFavoritesQueueTracks(filteredTracks); const queueTracks = buildFavoritesQueueTracks(filteredTracks);
await invoke('v2_add_tracks_to_queue', { tracks: queueTracks }); await cmdAddTracksToQueue(queueTracks);
} catch (err) { } catch (err) {
console.error('Failed to add tracks to queue:', err); console.error('Failed to add tracks to queue:', err);
} }
+3 -2
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { cmdAddTracksToQueue, cmdAddTracksToQueueNext } from '$lib/services/commandRouter';
import { resolveArtistImage } from '$lib/stores/customArtistImageStore'; import { resolveArtistImage } from '$lib/stores/customArtistImageStore';
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import { ArrowLeft, Disc3, Play, Music, Ellipsis, Heart, User, ChevronDown, ChevronUp, SquareCheckBig } from 'lucide-svelte'; import { ArrowLeft, Disc3, Play, Music, Ellipsis, Heart, User, ChevronDown, ChevronUp, SquareCheckBig } from 'lucide-svelte';
@@ -221,13 +222,13 @@
async function handleBulkPlayNext() { async function handleBulkPlayNext() {
const selected = visibleTracks.filter(trk => multiSelectedIds.has(trk.id)); const selected = visibleTracks.filter(trk => multiSelectedIds.has(trk.id));
await invoke('v2_add_tracks_to_queue_next', { tracks: buildTopTracksQueue(selected) }); await cmdAddTracksToQueueNext(buildTopTracksQueue(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
async function handleBulkPlayLater() { async function handleBulkPlayLater() {
const selected = visibleTracks.filter(trk => multiSelectedIds.has(trk.id)); const selected = visibleTracks.filter(trk => multiSelectedIds.has(trk.id));
await invoke('v2_add_tracks_to_queue', { tracks: buildTopTracksQueue(selected) }); await cmdAddTracksToQueue(buildTopTracksQueue(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
@@ -7,6 +7,7 @@
import ViewTransition from '../ViewTransition.svelte'; import ViewTransition from '../ViewTransition.svelte';
import { writeText } from '@tauri-apps/plugin-clipboard-manager'; import { writeText } from '@tauri-apps/plugin-clipboard-manager';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { cmdAddTracksToQueue, cmdAddTracksToQueueNext } from '$lib/services/commandRouter';
import { open, ask } from '@tauri-apps/plugin-dialog'; import { open, ask } from '@tauri-apps/plugin-dialog';
import TrackRow from '../TrackRow.svelte'; import TrackRow from '../TrackRow.svelte';
import PlaylistSuggestions from '../PlaylistSuggestions.svelte'; import PlaylistSuggestions from '../PlaylistSuggestions.svelte';
@@ -1183,7 +1184,7 @@
async function handleBulkPlayNext() { async function handleBulkPlayNext() {
const selected = displayTracks.filter(trk => multiSelectedKeys.has(getTrackKey(trk))); const selected = displayTracks.filter(trk => multiSelectedKeys.has(getTrackKey(trk)));
const { queueTracks } = buildQueueTracks(selected); const { queueTracks } = buildQueueTracks(selected);
await invoke('v2_add_tracks_to_queue_next', { tracks: queueTracks }); await cmdAddTracksToQueueNext(queueTracks);
multiSelectedKeys = new Set(); multiSelectedKeys = new Set();
multiSelectMode = false; multiSelectMode = false;
} }
@@ -1191,7 +1192,7 @@
async function handleBulkPlayLater() { async function handleBulkPlayLater() {
const selected = displayTracks.filter(trk => multiSelectedKeys.has(getTrackKey(trk))); const selected = displayTracks.filter(trk => multiSelectedKeys.has(getTrackKey(trk)));
const { queueTracks } = buildQueueTracks(selected); const { queueTracks } = buildQueueTracks(selected);
await invoke('v2_add_tracks_to_queue', { tracks: queueTracks }); await cmdAddTracksToQueue(queueTracks);
multiSelectedKeys = new Set(); multiSelectedKeys = new Set();
multiSelectMode = false; multiSelectMode = false;
} }
@@ -1968,7 +1969,7 @@
})); }));
try { try {
await invoke('v2_add_tracks_to_queue_next', { tracks: queueTracks }); await cmdAddTracksToQueueNext(queueTracks);
// Tell parent about local tracks added to queue // Tell parent about local tracks added to queue
if (localIds.length > 0) { if (localIds.length > 0) {
@@ -2013,7 +2014,7 @@
.map(trk => Math.abs(trk.id)); .map(trk => Math.abs(trk.id));
try { try {
await invoke('v2_add_tracks_to_queue', { tracks: queueTracks }); await cmdAddTracksToQueue(queueTracks);
// Tell parent about local tracks added to queue // Tell parent about local tracks added to queue
if (localIds.length > 0) { if (localIds.length > 0) {
+3 -2
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { cmdAddTracksToQueue, cmdAddTracksToQueueNext } from '$lib/services/commandRouter';
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import { ArrowLeft, Info, ListPlus, Play, RefreshCw, Search, Shuffle, X, SquareCheckBig } from 'lucide-svelte'; import { ArrowLeft, Info, ListPlus, Play, RefreshCw, Search, Shuffle, X, SquareCheckBig } from 'lucide-svelte';
import PlaylistModal from '$lib/components/PlaylistModal.svelte'; import PlaylistModal from '$lib/components/PlaylistModal.svelte';
@@ -137,13 +138,13 @@
async function handleBulkPlayNext() { async function handleBulkPlayNext() {
const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id)); const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id));
await invoke('v2_add_tracks_to_queue_next', { tracks: buildQueueTracks(selected) }); await cmdAddTracksToQueueNext(buildQueueTracks(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
async function handleBulkPlayLater() { async function handleBulkPlayLater() {
const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id)); const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id));
await invoke('v2_add_tracks_to_queue', { tracks: buildQueueTracks(selected) }); await cmdAddTracksToQueue(buildQueueTracks(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { cmdAddTracksToQueue, cmdAddTracksToQueueNext } from '$lib/services/commandRouter';
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import { ArrowLeft, CloudDownload, Heart, Info, ListPlus, Play, Search, Shuffle, X, SquareCheckBig } from 'lucide-svelte'; import { ArrowLeft, CloudDownload, Heart, Info, ListPlus, Play, Search, Shuffle, X, SquareCheckBig } from 'lucide-svelte';
import PlaylistModal from '$lib/components/PlaylistModal.svelte'; import PlaylistModal from '$lib/components/PlaylistModal.svelte';
@@ -117,13 +118,13 @@
async function handleBulkPlayNext() { async function handleBulkPlayNext() {
const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id)); const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id));
await invoke('v2_add_tracks_to_queue_next', { tracks: buildQueueTracks(selected) }); await cmdAddTracksToQueueNext(buildQueueTracks(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
async function handleBulkPlayLater() { async function handleBulkPlayLater() {
const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id)); const selected = filteredTracks.filter(trk => multiSelectedIds.has(trk.id));
await invoke('v2_add_tracks_to_queue', { tracks: buildQueueTracks(selected) }); await cmdAddTracksToQueue(buildQueueTracks(selected));
multiSelectMode = false; multiSelectedIds = new Set(); multiSelectMode = false; multiSelectedIds = new Set();
} }
+26 -14
View File
@@ -12,6 +12,9 @@
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { t } from '$lib/i18n'; import { t } from '$lib/i18n';
import { cmdStop, cmdPlayTrack } from '$lib/services/commandRouter';
import { getTarget } from '$lib/stores/playbackTargetStore';
import { remotePost } from '$lib/services/remoteApi';
import { getUserItem, setUserItem } from '$lib/utils/userStorage'; import { getUserItem, setUserItem } from '$lib/utils/userStorage';
import { import {
isPlaybackSourceLocal, isPlaybackSourceLocal,
@@ -192,7 +195,7 @@ export async function playTrack(
// Stop any leftover local playback so the active remote renderer owns the session. // Stop any leftover local playback so the active remote renderer owns the session.
if (!isCasting()) { if (!isCasting()) {
try { try {
await invoke('v2_stop_playback'); await cmdStop();
} catch { } catch {
// Ignore errors - player might not be playing locally // Ignore errors - player might not be playing locally
} }
@@ -204,7 +207,7 @@ export async function playTrack(
if (!isCasting()) { if (!isCasting()) {
// Stop current playback immediately // Stop current playback immediately
try { try {
await invoke('v2_stop_playback'); await cmdStop();
} catch { } catch {
// Ignore errors - player might not be playing // Ignore errors - player might not be playing
} }
@@ -250,19 +253,28 @@ export async function playTrack(
} else if (isLocal) { } else if (isLocal) {
await invoke('v2_library_play_track', { trackId: track.id }); await invoke('v2_library_play_track', { trackId: track.id });
} else { } else {
const result = await invoke<PlayTrackResult>('v2_play_track', { // Route to daemon or local based on playback target
trackId: track.id, const target = getTarget();
quality: effectiveQuality(track), if (target.type === 'qbzd') {
durationSecs: track.duration ? Math.round(track.duration) : null, await remotePost('/api/playback/play-track', {
forceLowestQuality: forceLowestQuality || null track_id: track.id,
}); quality: effectiveQuality(track),
});
} else {
const result = await invoke<PlayTrackResult>('v2_play_track', {
trackId: track.id,
quality: effectiveQuality(track),
durationSecs: track.duration ? Math.round(track.duration) : null,
forceLowestQuality: forceLowestQuality || null
});
// Update track format based on actual stream format_id from Qobuz // Update track format based on actual stream format_id from Qobuz
const actualFormat = formatIdToString(result.format_id); const actualFormat = formatIdToString(result.format_id);
if (actualFormat) { if (actualFormat) {
track.format = actualFormat; track.format = actualFormat;
// Re-set current track to update the UI with actual format // Re-set current track to update the UI with actual format
setCurrentTrack(track); setCurrentTrack(track);
}
} }
} }
} }