mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
refactor(lib): move songToTrack + pure server-scope helpers to lib/media; split trackServerScope
songToTrack (the canonical Subsonic-song -> Track mapper) and the pure server-scope helpers (activeServerProfileId, stampTrackServerId/s, isMultiServerQueue, profileIdFromQueueRef) operate only on the lib/media model + authStore + server-key utils -- no playback-store read. Move them to lib/media so the ~58 app-wide consumers (cover, context menus, sharing, lucky-mix, library browse, pages, hooks) depend on lib, not on the playback feature. trackServerScope is split: the store-reading queue helpers (queueItemRefAt, filterQueueRefs*, activeServerQueueTrackIds) stay in features/playback/utils/playback/trackServerScope and build on the pure lib half. With Track/QueueItemRef (prior commit), songToTrack and shuffleArray now in lib, the media-domain model is fully out of features/playback. The remaining playerStoreTypes imports are PlayerState only (the store shape). tsc 0, lint 0, full suite 2353/2353, build OK.
This commit is contained in:
@@ -2,7 +2,7 @@ import { getArtist, getArtistInfo } from '@/lib/api/subsonicArtists';
|
||||
import { filterAlbumsToActiveLibrary } from '@/lib/api/subsonicLibrary';
|
||||
import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { shuffleArray } from '@/lib/util/shuffleArray';
|
||||
import React, { memo, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getRandomAlbums } from '@/lib/api/subsonicLibrary';
|
||||
import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
||||
import { useNavigateToAlbum } from '@/features/album';
|
||||
import { Play, ListPlus, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { Play, ListPlus, Star, Disc3 } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import React, { memo } from 'react';
|
||||
import { useNavigateToAlbum } from '@/features/album';
|
||||
import { useNavigateToArtist } from '@/features/artist';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
|
||||
import { star, unstar } from '@/lib/api/subsonicStarRating';
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import StarRating from '../StarRating';
|
||||
import { AlbumToPlaylistSubmenu } from './AlbumArtistToPlaylistSubmenu';
|
||||
import { MultiAlbumToPlaylistSubmenu } from './MultiAlbumToPlaylistSubmenu';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { getMusicNetworkRuntime, useEnrichmentPrimary } from '../../music-networ
|
||||
import type { Track } from '@/lib/media/trackTypes';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import { usePlaylistStore } from '@/features/playlist';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { showToast } from '../../utils/ui/toast';
|
||||
import { suggestOrbitTrack, hostEnqueueToOrbit, evaluateOrbitSuggestGate, OrbitSuggestBlockedError } from '@/features/orbit';
|
||||
import { renderPresetIcon } from '../settings/musicNetwork/presetIcon';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AlbumCoverArtImage } from '../../cover/AlbumCoverArtImage';
|
||||
import { getRandomSongs } from '@/lib/api/subsonicLibrary';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Play, ListPlus, RefreshCw, Sparkles } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { queueSongStar } from '@/features/playback/store/pendingStarSync';
|
||||
import { getSong } from '@/lib/api/subsonicLibrary';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
import i18n from '@/lib/i18n';
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { AudioLines } from 'lucide-react';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import type { Track } from '@/lib/media/trackTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { formatLongDuration } from '@/lib/format/formatDuration';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import type { ColDef } from '@/utils/useTracklistColumns';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import type { Track } from '@/lib/media/trackTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore';
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
|
||||
interface UseAlbumTrackListSelectionArgs {
|
||||
songs: SubsonicSong[];
|
||||
|
||||
@@ -4,7 +4,7 @@ import { queueSongStar, queueSongRating } from '@/features/playback/store/pendin
|
||||
import { getAlbumForServer } from '@/lib/api/subsonicLibrary';
|
||||
import { getArtistInfo } from '@/lib/api/subsonicArtists';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { shuffleArray } from '@/lib/util/shuffleArray';
|
||||
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl';
|
||||
import { resolveAlbum } from '@/features/offline';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { useState, useEffect, useLayoutEffect, useRef, useMemo } from 'react';
|
||||
import AlbumCard from '@/features/album/components/AlbumCard';
|
||||
import { albumGridWarmCovers, coverDisplayCssPxForAlbumGrid } from '@/cover/layoutSizes';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl';
|
||||
import { resolveAlbum } from '@/features/offline';
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import AlbumCard from '@/features/album/components/AlbumCard';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore';
|
||||
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import ArtistTopTrackCover from '@/features/artist/components/ArtistTopTrackCover';
|
||||
import { topSongAlbumForCover } from '@/features/artist/components/topSongAlbumForCover';
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TFunction } from 'i18next';
|
||||
import { getSimilarSongs2, getTopSongs } from '@/lib/api/subsonicArtists';
|
||||
import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import type { Track } from '@/lib/media/trackTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { runBulkPlayAll, runBulkShuffle } from '@/features/playback/utils/playback/runBulkPlay';
|
||||
import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ListPlus, Play, SlidersHorizontal, X } from 'lucide-react';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import { useSelectionStore } from '@/store/selectionStore';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { AddToPlaylistSubmenu } from '@/components/ContextMenu';
|
||||
import GenreFilterBar from '@/components/GenreFilterBar';
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useSelectionStore } from '@/store/selectionStore';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { appendServerQuery } from '@/utils/navigation/detailServerScope';
|
||||
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
|
||||
import { useElementClientHeightById } from '@/lib/hooks/useResizeClientHeight';
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Music, EyeOff, LayoutGrid, RotateCcw, Eye } from 'lucide-react';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useLyricsStore } from '@/store/lyricsStore';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { useRadioMetadata } from '@/features/radio';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import OverlayScrollArea from '@/ui/OverlayScrollArea';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getSong } from '@/lib/api/subsonicLibrary';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { X, RefreshCw, Shuffle, Settings2, Share2, HelpCircle, Activity } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getSong } from '@/lib/api/subsonicLibrary';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getSong } from '@/lib/api/subsonicLibrary';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { useEffect } from 'react';
|
||||
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createPlaylist, deletePlaylist, getPlaylist, getPlaylists, updatePlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import { getSong } from '@/lib/api/subsonicLibrary';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
|
||||
@@ -26,7 +26,7 @@ vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => ({}) } }))
|
||||
vi.mock('@/features/playback/store/playerStore', () => ({ usePlayerStore: { getState: () => ({ enqueue: vi.fn() }) } }));
|
||||
vi.mock('@/lib/api/subsonicPlaylists', () => ({ createPlaylist: vi.fn(), deletePlaylist: vi.fn() }));
|
||||
vi.mock('@/lib/api/subsonicLibrary', () => ({ getSong: vi.fn() }));
|
||||
vi.mock('@/features/playback/utils/playback/songToTrack', () => ({ songToTrack: vi.fn() }));
|
||||
vi.mock('@/lib/media/songToTrack', () => ({ songToTrack: vi.fn() }));
|
||||
|
||||
import { updateOrbitSettings } from '@/features/orbit/utils/host';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createPlaylist, deletePlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import { getSong } from '@/lib/api/subsonicLibrary';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
|
||||
@@ -11,7 +11,7 @@ vi.mock('@/utils/server/serverLookup', () => ({
|
||||
resolveServerIdForIndexKey: (id: string) => id,
|
||||
}));
|
||||
|
||||
vi.mock('@/features/playback/utils/playback/songToTrack', () => ({
|
||||
vi.mock('@/lib/media/songToTrack', () => ({
|
||||
songToTrack: (s: { id: string }) => ({
|
||||
id: s.id,
|
||||
title: s.id,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getPlayQueueForServer, type PlayQueueResult } from '@/lib/api/subsonicPlayQueue';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { bindQueueServerId } from '@/features/playback/utils/playback/playbackServer';
|
||||
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
|
||||
import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getSimilarSongs2, getTopSongs } from '@/lib/api/subsonicArtists';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { buildInfiniteQueueCandidates } from '@/features/playback/utils/playback/buildInfiniteQueueCandidates';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { ensureQueueServerPinned } from '@/features/playback/utils/playback/playbackServer';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { setIsAudioPaused } from '@/features/playback/store/engineState';
|
||||
|
||||
@@ -39,7 +39,7 @@ vi.mock('@/features/playback/store/queueTrackView', () => ({
|
||||
vi.mock('@/features/playback/utils/playback/buildInfiniteQueueCandidates', () => ({
|
||||
buildInfiniteQueueCandidates: vi.fn(() => Promise.resolve([])),
|
||||
}));
|
||||
vi.mock('@/features/playback/utils/playback/songToTrack', () => ({ songToTrack: (s: unknown) => s }));
|
||||
vi.mock('@/lib/media/songToTrack', () => ({ songToTrack: (s: unknown) => s }));
|
||||
vi.mock('@/features/playback/utils/playback/playbackServer', () => ({ ensureQueueServerPinned: () => null }));
|
||||
vi.mock('@/features/playback/store/queueTrackResolver', () => ({ seedQueueResolver: vi.fn() }));
|
||||
vi.mock('@/features/playback/store/queueItemRef', () => ({ toQueueItemRefs: () => [] }));
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
playbackProfileIdForTrack,
|
||||
shouldBindQueueServerForPlay,
|
||||
} from '@/features/playback/utils/playback/playbackServer';
|
||||
import { stampTrackServerId, stampTrackServerIds } from '@/features/playback/utils/playback/trackServerScope';
|
||||
import { stampTrackServerId, stampTrackServerIds } from '@/lib/media/trackServerScope';
|
||||
import {
|
||||
findLocalPlaybackUrl,
|
||||
hasLocalPersistentPlaybackBytes,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { QueueItemRef, Track } from '@/lib/media/trackTypes';
|
||||
import { stampTrackServerId } from '@/features/playback/utils/playback/trackServerScope';
|
||||
import { stampTrackServerId } from '@/lib/media/trackServerScope';
|
||||
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@ import { libraryGetTracksBatch, type TrackRefDto } from '@/lib/api/library';
|
||||
import { getSongForServer } from '@/lib/api/subsonicLibrary';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import type { QueueItemRef, Track } from '@/lib/media/trackTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
|
||||
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
|
||||
import { trackToSong } from '@/lib/library/advancedSearchLocal';
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { resolvePlaybackUrl } from '@/features/playback/utils/playback/resolvePlaybackUrl';
|
||||
import { resolveReplayGainDb } from '@/features/playback/utils/audio/resolveReplayGainDb';
|
||||
import { audioPlayHiResBlendArgs } from '@/utils/audio/hiResCrossfadeResample';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import {
|
||||
bumpPlayGeneration,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
passesMixMinRatings,
|
||||
} from '@/utils/mix/mixRatingFilter';
|
||||
import { shuffleArray } from '@/lib/util/shuffleArray';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
/**
|
||||
* Infinite queue source strategy (Instant Mix-like):
|
||||
* 1) Prefer artist-driven candidates (Top + Similar) around the current track.
|
||||
|
||||
@@ -6,7 +6,7 @@ import { fetchAllSongsByGenre, getGenres } from '@/lib/api/subsonicGenres';
|
||||
import type { SubsonicGenre } from '@/lib/api/subsonicTypes';
|
||||
import { libraryScopeForServer } from '@/lib/api/subsonicClient';
|
||||
import type { Track } from '@/lib/media/trackTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { shuffleArray } from '@/lib/util/shuffleArray';
|
||||
import { trackToSong } from '@/lib/library/advancedSearchLocal';
|
||||
import { type AlbumBrowseSort } from '@/lib/library/albumBrowseSort';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import { resolveAlbumForActiveServer } from '@/store/mediaResolver';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { orbitSnapshot } from '@/store/orbitRuntime';
|
||||
import { fadeOut } from '@/features/playback/utils/playback/fadeOut';
|
||||
import { shouldAutodjInterruptBlend } from '@/features/playback/utils/playback/autodjManualBlend';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { resolveAlbum, resolveArtist, resolveMediaServerId } from '@/store/mediaResolver';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { shuffleArray } from '@/lib/util/shuffleArray';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getSong } from '@/lib/api/subsonicLibrary';
|
||||
import { resolveAlbumForActiveServer } from '@/store/mediaResolver';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { playAlbum } from '@/features/playback/utils/playback/playAlbum';
|
||||
import { playArtistShuffled } from '@/features/playback/utils/playback/playArtistShuffled';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import { fadeOut } from '@/features/playback/utils/playback/fadeOut';
|
||||
import { shouldAutodjInterruptBlend } from '@/features/playback/utils/playback/autodjManualBlend';
|
||||
|
||||
@@ -18,11 +18,13 @@ import {
|
||||
} from '@/utils/server/serverIndexKey';
|
||||
import {
|
||||
activeServerProfileId,
|
||||
filterQueueRefsForServerProfile,
|
||||
isMultiServerQueue,
|
||||
profileIdFromQueueRef,
|
||||
queueItemRefAt,
|
||||
stampTrackServerIds,
|
||||
} from '@/lib/media/trackServerScope';
|
||||
import {
|
||||
filterQueueRefsForServerProfile,
|
||||
queueItemRefAt,
|
||||
} from '@/features/playback/utils/playback/trackServerScope';
|
||||
|
||||
function playbackServerFromRef(ref: QueueItemRef): ServerProfile | undefined {
|
||||
|
||||
@@ -2,12 +2,14 @@ import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import type { Track } from '@/lib/media/trackTypes';
|
||||
import {
|
||||
isMultiServerQueue,
|
||||
stampTrackServerId,
|
||||
} from '@/lib/media/trackServerScope';
|
||||
import {
|
||||
activeServerQueueTrackIds,
|
||||
filterQueueRefsForActiveServer,
|
||||
isMultiServerQueue,
|
||||
queueItemRefAt,
|
||||
stampTrackServerId,
|
||||
} from '@/features/playback/utils/playback/trackServerScope';
|
||||
|
||||
const baseTrack = (): Track => ({
|
||||
|
||||
@@ -1,29 +1,13 @@
|
||||
import type { QueueItemRef, Track } from '@/lib/media/trackTypes';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import type { QueueItemRef } from '@/lib/media/trackTypes';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
|
||||
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
|
||||
|
||||
/** Active saved-server profile id (auth UUID), when logged in. */
|
||||
export function activeServerProfileId(): string | undefined {
|
||||
return useAuthStore.getState().activeServerId ?? undefined;
|
||||
}
|
||||
import { activeServerProfileId, profileIdFromQueueRef } from '@/lib/media/trackServerScope';
|
||||
|
||||
/**
|
||||
* Ensure every track carries an owning server before it enters the queue.
|
||||
* Explicit `track.serverId` wins; otherwise `fallbackServerId`, then active server.
|
||||
* Store-reading queue-scope helpers. These read the live player store (current
|
||||
* queue + pinned server), so they stay in the playback feature; the pure stamp
|
||||
* / classify helpers they build on live in `@/lib/media/trackServerScope`.
|
||||
*/
|
||||
export function stampTrackServerId(track: Track, fallbackServerId?: string): Track {
|
||||
const serverId = track.serverId ?? fallbackServerId ?? activeServerProfileId();
|
||||
if (!serverId || track.serverId === serverId) {
|
||||
return serverId && !track.serverId ? { ...track, serverId } : track;
|
||||
}
|
||||
return { ...track, serverId };
|
||||
}
|
||||
|
||||
export function stampTrackServerIds(tracks: Track[], fallbackServerId?: string): Track[] {
|
||||
return tracks.map(t => stampTrackServerId(t, fallbackServerId));
|
||||
}
|
||||
|
||||
/** Canonical queue ref at `index`, or the currently playing slot. */
|
||||
export function queueItemRefAt(index?: number): QueueItemRef | null {
|
||||
@@ -34,22 +18,6 @@ export function queueItemRefAt(index?: number): QueueItemRef | null {
|
||||
return queueItems[idx] ?? null;
|
||||
}
|
||||
|
||||
/** True when queue refs resolve to more than one server bucket. */
|
||||
export function isMultiServerQueue(refs: QueueItemRef[]): boolean {
|
||||
const keys = new Set<string>();
|
||||
for (const ref of refs) {
|
||||
if (!ref.serverId) continue;
|
||||
keys.add(canonicalQueueServerKey(ref.serverId) || ref.serverId);
|
||||
if (keys.size > 1) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function profileIdFromQueueRef(ref: QueueItemRef | null | undefined): string {
|
||||
if (!ref?.serverId) return '';
|
||||
return resolveServerIdForIndexKey(ref.serverId) || ref.serverId;
|
||||
}
|
||||
|
||||
function refsForServerProfile(refs: QueueItemRef[], profileId: string): QueueItemRef[] {
|
||||
if (!profileId) return [];
|
||||
return refs.filter(ref => queueRefProfileIdForTarget(ref, profileId));
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Check, ListPlus, X } from 'lucide-react';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { formatTrackTime } from '@/lib/format/formatDuration';
|
||||
import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage';
|
||||
import { COVER_DENSE_SEARCH_CSS_PX } from '@/cover/layoutSizes';
|
||||
|
||||
@@ -10,7 +10,7 @@ import StarRating from '@/components/StarRating';
|
||||
import { PlaylistArtistCell } from '@/features/playlist/components/PlaylistArtistCell';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { usePlaylistLayoutStore } from '@/features/playlist/store/playlistLayoutStore';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { getQueueTracksView } from '@/features/playback/store/queueTrackView';
|
||||
import { codecLabel } from '@/utils/componentHelpers/playlistDetailHelpers';
|
||||
import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers';
|
||||
|
||||
@@ -17,7 +17,7 @@ import { previewInputFromSong, usePreviewStore } from '@/features/playback/store
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useDragDrop } from '@/lib/dnd/DragDropContext';
|
||||
import { useOrbitSongRowBehavior } from '@/features/orbit';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import type { PlaylistSortKey, PlaylistSortDir } from '@/features/playlist/utils/playlistDisplayedSongs';
|
||||
import { AddToPlaylistSubmenu } from '@/components/ContextMenu';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import type { Track } from '@/lib/media/trackTypes';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { getDisplayedSongs, type PlaylistSortDir, type PlaylistSortKey } from '@/features/playlist/utils/playlistDisplayedSongs';
|
||||
|
||||
export interface PlaylistDerivedOptions {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { resolveMediaServerId, resolvePlaylist } from '@/features/offline';
|
||||
import { getGenres } from '@/lib/api/subsonicGenres';
|
||||
import { filterSongsToActiveLibrary } from '@/lib/api/subsonicLibrary';
|
||||
import type { SubsonicPlaylist, SubsonicGenre } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import { usePlaylistStore } from '@/features/playlist/store/playlistStore';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getPlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import { playPlaylistAll } from '@/features/playlist/utils/playlistBulkPlayActions';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type React from 'react';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
|
||||
export interface StartPlaylistRowDragDeps {
|
||||
e: React.MouseEvent;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Play } from 'lucide-react';
|
||||
import { updatePlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import { resolvePlaylist, resolveMediaServerId } from '@/features/offline';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import type { Track } from '@/lib/media/trackTypes';
|
||||
import { useState, useRef, useMemo } from 'react';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { useDragDrop, registerQueueDragHitTest } from '@/lib/dnd/DragDropContext';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import type { Track } from '@/lib/media/trackTypes';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { subscribeLibrarySyncIdle, subscribeLibrarySyncProgress } from '@/lib/api/library';
|
||||
import type { SearchResults, SubsonicArtist } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import {
|
||||
LIVE_SEARCH_DEBOUNCE_NETWORK_MS,
|
||||
LIVE_SEARCH_DEBOUNCE_RACE_MS,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { search } from '@/lib/api/subsonicSearch';
|
||||
import type { SearchResults, SubsonicArtist } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore';
|
||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { SubsonicArtist } from '@/lib/api/subsonicTypes';
|
||||
import type { ServerProfile } from '@/store/authStoreTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { activateShareSearchServer } from '@/utils/share/enqueueShareSearchPayload';
|
||||
import { sharePayloadTotal, type ShareSearchMatch } from '@/utils/share/shareSearch';
|
||||
import type { ShareSearchPreviewState } from '@/features/search/hooks/useShareSearchPreview';
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getMusicFolders } from '@/lib/api/subsonicLibrary';
|
||||
import { search as subsonicSearch } from '@/lib/api/subsonicSearch';
|
||||
import { filterSongsForLuckyMixRatings, getMixMinRatingsConfigFromAuth } from '../../utils/mix/mixRatingFilter';
|
||||
import { shuffleArray } from '@/lib/util/shuffleArray';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { showToast } from '../../utils/ui/toast';
|
||||
import { switchActiveServer } from '../../utils/server/switchActiveServer';
|
||||
import i18n from '@/lib/i18n';
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
import type { Track } from '@/lib/media/trackTypes';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { makeSubsonicSong } from '@/test/helpers/factories';
|
||||
|
||||
describe('songToTrack', () => {
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import type { Track } from '@/lib/media/trackTypes';
|
||||
import { coerceOpenArtistRefs } from '@/lib/api/openArtistRefs';
|
||||
import { activeServerProfileId } from '@/features/playback/utils/playback/trackServerScope';
|
||||
import { activeServerProfileId } from '@/lib/media/trackServerScope';
|
||||
|
||||
export function songToTrack(song: SubsonicSong): Track {
|
||||
return {
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { Track, QueueItemRef } from '@/lib/media/trackTypes';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
|
||||
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
|
||||
|
||||
/**
|
||||
* Pure server-scope helpers for the shared media model: stamp the owning server
|
||||
* onto a Track, and classify queue refs by server profile. No playback-store
|
||||
* read, so they live in lib/media next to the Track model. The store-reading
|
||||
* queue helpers (queueItemRefAt, filterQueueRefs*) stay in
|
||||
* features/playback/utils/playback/trackServerScope and build on these.
|
||||
*/
|
||||
|
||||
/** Active saved-server profile id (auth UUID), when logged in. */
|
||||
export function activeServerProfileId(): string | undefined {
|
||||
return useAuthStore.getState().activeServerId ?? undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure every track carries an owning server before it enters the queue.
|
||||
* Explicit `track.serverId` wins; otherwise `fallbackServerId`, then active server.
|
||||
*/
|
||||
export function stampTrackServerId(track: Track, fallbackServerId?: string): Track {
|
||||
const serverId = track.serverId ?? fallbackServerId ?? activeServerProfileId();
|
||||
if (!serverId || track.serverId === serverId) {
|
||||
return serverId && !track.serverId ? { ...track, serverId } : track;
|
||||
}
|
||||
return { ...track, serverId };
|
||||
}
|
||||
|
||||
export function stampTrackServerIds(tracks: Track[], fallbackServerId?: string): Track[] {
|
||||
return tracks.map(t => stampTrackServerId(t, fallbackServerId));
|
||||
}
|
||||
|
||||
/** True when queue refs resolve to more than one server bucket. */
|
||||
export function isMultiServerQueue(refs: QueueItemRef[]): boolean {
|
||||
const keys = new Set<string>();
|
||||
for (const ref of refs) {
|
||||
if (!ref.serverId) continue;
|
||||
keys.add(canonicalQueueServerKey(ref.serverId) || ref.serverId);
|
||||
if (keys.size > 1) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function profileIdFromQueueRef(ref: QueueItemRef | null | undefined): string {
|
||||
if (!ref?.serverId) return '';
|
||||
return resolveServerIdForIndexKey(ref.serverId) || ref.serverId;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getAlbumList } from '@/lib/api/subsonicLibrary';
|
||||
import { resolveAlbum } from '@/features/offline';
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ArrowUpDown, ArrowDown, ArrowUp, TrendingUp, UsersRound, Play, ListPlus } from 'lucide-react';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { queueSongStar } from '@/features/playback/store/pendingStarSync';
|
||||
import type { SubsonicSong, SubsonicGenre } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import { usePreviewStore } from '@/features/playback/store/previewStore';
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useDownloadModalStore } from '@/features/offline';
|
||||
import type { EntityShareKind } from '../share/shareLink';
|
||||
import { copyEntityShareLink } from '../share/copyEntityShareLink';
|
||||
import { sanitizeFilename, shuffleArray } from './contextMenuHelpers';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { showToast } from '../ui/toast';
|
||||
|
||||
export async function copyShareLink(
|
||||
|
||||
@@ -2,7 +2,7 @@ import { fetchSimilarTracksRouted } from '@/lib/api/subsonicArtists';
|
||||
import { filterSongsToActiveLibrary, getRandomSongs } from '@/lib/api/subsonicLibrary';
|
||||
import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import type { QueueItemRef } from '@/lib/media/trackTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getSong } from '@/lib/api/subsonicLibrary';
|
||||
import { resolveAlbum, resolveArtist } from '@/features/offline';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import type { Location, NavigateFunction } from 'react-router-dom';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
|
||||
@@ -49,7 +49,7 @@ vi.mock('@/features/playback/store/playerStore', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@/features/playback/utils/playback/songToTrack', () => ({
|
||||
vi.mock('@/lib/media/songToTrack', () => ({
|
||||
songToTrack: mocks.songToTrack,
|
||||
}));
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/lib/api/subs
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import type { ServerProfile } from '../../store/authStoreTypes';
|
||||
import { usePlayerStore } from '@/features/playback/store/playerStore';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import type { Track } from '@/lib/media/trackTypes';
|
||||
import { orbitBulkGuard } from '@/features/orbit';
|
||||
import { findServerIdForShareUrl } from './shareLink';
|
||||
|
||||
Reference in New Issue
Block a user