Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

### Fixed

- Fixed locate track functionality to check current playßlist first
- Fixed continuous playback selecting next track from wrong context when navigating between views
- Fixed discovery row buttons (import and open URL) not working in playlist view

## [0.2.8] - 2026-03-15
Expand Down
6 changes: 6 additions & 0 deletions src-tauri/.cargo/audit.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[advisories]
ignore = [
# glib unsoundness in VariantStrIter - transitive dep from Tauri's GTK/webkit2gtk on Linux.
# Cannot upgrade independently; must wait for Tauri to update gtk-rs dependencies.
"RUSTSEC-2024-0429",
]
30 changes: 15 additions & 15 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

108 changes: 98 additions & 10 deletions src/lib/hooks/useAppSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export interface AppSetupResult {
deviceController: ReturnType<typeof createDeviceController>
exportController: ReturnType<typeof createExportController>
playlistController: ReturnType<typeof createPlaylistController>
playPreview: (release: DiscoveryRelease, trackIndex: number) => void
playNextTrack: () => void
playPreviousTrack: () => void
onMountSetup: () => Promise<() => void>
Expand Down Expand Up @@ -147,7 +148,7 @@ export function createAppSetup(config: AppSetupConfig): AppSetupResult {
getActiveView: () => get(activeView),
})

const trackController = createTrackController(
const rawTrackController = createTrackController(
{
playerStore,
libraryStore,
Expand All @@ -169,6 +170,19 @@ export function createAppSetup(config: AppSetupConfig): AppSetupResult {
}
)

// Wrap trackController.play to capture the playback queue context when
// the user initiates library playback (double-click, Enter, etc.)
const trackController = {
...rawTrackController,
play(track: Track) {
hasLibraryQueueContext = true
libraryQueueContextActiveView = get(activeView)
libraryQueueContextPlaylistId = get(libraryStore).selectedPlaylistId
libraryQueueTracks = get(displayedTracks)
rawTrackController.play(track)
},
}

const deviceController = createDeviceController(
{ devicesStore, settingsStore, toastStore },
{
Expand Down Expand Up @@ -228,6 +242,77 @@ export function createAppSetup(config: AppSetupConfig): AppSetupResult {
}
)

// =========================================================================
// Playback Queue
// =========================================================================
// Tracks the playback context so continuous playback, next/previous use the
// correct track list even when the user navigates to a different view.
// When the current view matches the playback context, live data is used
// (so sort changes, track adds/removes are reflected immediately).
// When navigated away, a frozen snapshot is used instead.

// Library track queue
let hasLibraryQueueContext = false
let libraryQueueContextActiveView: ActiveView = 'library'
let libraryQueueContextPlaylistId: string | null = null
let libraryQueueTracks: Track[] = []

// Discovery release queue
let hasDiscoveryQueueContext = false
let discoveryQueueContextPlaylistId: string | null = null
let discoveryQueueReleases: DiscoveryRelease[] = []

// Keep the frozen queue snapshot up-to-date while the context view is active.
// When the user navigates away, the snapshot freezes at the last known state.
displayedTracks.subscribe((tracks) => {
if (!hasLibraryQueueContext) return
if (get(activeView) !== libraryQueueContextActiveView) return
if (get(libraryStore).selectedPlaylistId === libraryQueueContextPlaylistId) {
libraryQueueTracks = tracks
}
})

displayedReleases.subscribe((releases) => {
if (!hasDiscoveryQueueContext) return
const ui = get(uiStore)
if (ui.activeView !== 'discovery') return
if ((ui.selectedPlaylistId ?? null) === discoveryQueueContextPlaylistId) {
discoveryQueueReleases = releases
}
})

/** Get the current library track queue, using live data when the view matches. */
function getLibraryQueue(): Track[] {
if (!hasLibraryQueueContext) return get(displayedTracks)
const currentPlaylistId = get(libraryStore).selectedPlaylistId
if (get(activeView) === libraryQueueContextActiveView && currentPlaylistId === libraryQueueContextPlaylistId) {
return get(displayedTracks)
}
return libraryQueueTracks
}

/** Get the current discovery release queue, using live data when the view matches. */
function getDiscoveryQueue(): DiscoveryRelease[] {
if (!hasDiscoveryQueueContext) return get(displayedReleases)
const ui = get(uiStore)
if (ui.activeView === 'discovery' && (ui.selectedPlaylistId ?? null) === discoveryQueueContextPlaylistId) {
return get(displayedReleases)
}
return discoveryQueueReleases
}

/**
* Play a discovery preview, capturing the release queue context.
* Use this instead of playerStore.playPreview() for user-initiated preview playback.
*/
function playPreview(release: DiscoveryRelease, trackIndex: number) {
hasDiscoveryQueueContext = true
const ui = get(uiStore)
discoveryQueueContextPlaylistId = ui.activeView === 'discovery' ? (ui.selectedPlaylistId ?? null) : null
discoveryQueueReleases = get(displayedReleases)
playerStore.playPreview(release, trackIndex)
}

// =========================================================================
// Track Navigation
// =========================================================================
Expand Down Expand Up @@ -264,8 +349,8 @@ export function createAppSetup(config: AppSetupConfig): AppSetupResult {
return
}

// Move to next release in the filtered view, wrapping around
const releases = get(displayedReleases)
// Move to next release in the queue (or current view as fallback), wrapping around
const releases = getDiscoveryQueue()
const releaseIdx = releases.findIndex((r) => r.id === preview.releaseId)
if (releaseIdx === -1 || releases.length === 0) return

Expand All @@ -281,9 +366,10 @@ export function createAppSetup(config: AppSetupConfig): AppSetupResult {
}
const id = get(currentTrack)?.id
if (!id) return
const tracks = get(displayedTracks)
const tracks = getLibraryQueue()
if (tracks.length === 0) return
const idx = tracks.findIndex((t) => t.id === id)
if (idx >= 0 && idx < tracks.length - 1) trackController.play(tracks[idx + 1])
if (idx >= 0) playerStore.play(tracks[(idx + 1) % tracks.length])
}

function playPreviousTrack() {
Expand All @@ -299,8 +385,8 @@ export function createAppSetup(config: AppSetupConfig): AppSetupResult {
return
}

// Move to previous release in the filtered view, wrapping around
const releases = get(displayedReleases)
// Move to previous release in the queue (or current view as fallback), wrapping around
const releases = getDiscoveryQueue()
const releaseIdx = releases.findIndex((r) => r.id === preview.releaseId)
if (releaseIdx === -1 || releases.length === 0) return

Expand All @@ -316,9 +402,10 @@ export function createAppSetup(config: AppSetupConfig): AppSetupResult {
}
const id = get(currentTrack)?.id
if (!id) return
const tracks = get(displayedTracks)
const tracks = getLibraryQueue()
if (tracks.length === 0) return
const idx = tracks.findIndex((t) => t.id === id)
if (idx > 0) trackController.play(tracks[idx - 1])
if (idx >= 0) playerStore.play(tracks[(idx - 1 + tracks.length) % tracks.length])
}

// =========================================================================
Expand All @@ -342,7 +429,7 @@ export function createAppSetup(config: AppSetupConfig): AppSetupResult {
for (const release of releases) {
const trackIdx = findPreviewableTrackIndex(release, 'first')
if (trackIdx !== -1) {
playerStore.playPreview(release, trackIdx)
playPreview(release, trackIdx)
return
}
}
Expand Down Expand Up @@ -732,6 +819,7 @@ export function createAppSetup(config: AppSetupConfig): AppSetupResult {
deviceController,
exportController,
playlistController,
playPreview,
playNextTrack,
playPreviousTrack,
onMountSetup,
Expand Down
Loading
Loading