refactor(playback): move the audio engine into features/playback

Relocate the playback/queue/transport/audio-output engine out of the type-first
store/ + utils/playback/ + utils/audio/ dirs into a cohesive src/features/playback/,
structure-preserving:
  store/<x>                    -> features/playback/store/<x>
  store/audioListenerSetup/<x> -> features/playback/store/audioListenerSetup/<x>
  utils/playback/<x>           -> features/playback/utils/playback/<x>
  utils/audio/<x>              -> features/playback/utils/audio/<x>

184 files moved (107 source + 77 tests), 365 consumers rewritten. Pure move — no
behavior change, no state-split (the playerStore state-split stays a separate M5
question). Enabled by this session's decouple seams (artist/offline/orbit/auth →
core registries), so the engine carries no inbound core->feature inversion: store/
now holds only the 50 cross-cutting global stores (auth family, the seams, library
index, UI/settings stores).

KEPT OUT of the move (would re-create global->engine edges): the 3 pure config
helpers utils/audio/{loudnessPreAnalysisSlider,hiResCrossfadeResample} +
utils/playback/autodjOverlapCap (authStore + settings UI read them — they stay in
utils/). Ambiguous view-state stores (eqStore, queueToolbarStore,
playerBarLayoutStore) stay global (no engine imports).

Consumers use DEEP paths (@/features/playback/...), no barrel — matches the lib/
approach and avoids barrel-mock-collapse across the 140 usePlayerStore consumers.
Two tolerated type-only core->feature edges remain (localPlaybackStore->QueueItemRef,
localPlaybackMigration->HotCacheEntry, both erased).

tsc 0, lint 0, full suite 319/2353 green, iron-rule clean (no runtime store->feature
import). Behavior-touching only via the prerequisite bridge seam (already QA-flagged);
the move itself is pure.
This commit is contained in:
Psychotoxical
2026-06-30 15:00:20 +02:00
parent 6651abbc6f
commit cb1a110afb
393 changed files with 1127 additions and 1127 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { useAuthStore } from './store/authStore';
import { usePlayerStore } from './store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useLyricsStore } from './store/lyricsStore';
import { useThemeStore } from './store/themeStore';
import { useInstalledThemesStore } from './store/installedThemesStore';
+1 -1
View File
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it } from 'vitest';
import { useAuthStore } from '../store/authStore';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { makeTrack } from '../test/helpers/factories';
import { resetAllStores } from '../test/helpers/storeReset';
import { invokeMock, onInvoke } from '../test/mocks/tauri';
+1 -1
View File
@@ -4,7 +4,7 @@ import { useThemeStore } from '../store/themeStore';
import { coverIndexKeyFromRef, coverStorageKeyFromRef } from '../cover/storageKeys';
import { connectBaseUrlForServer } from '../utils/server/serverEndpoint';
import { serverIndexKeyForProfile } from '../utils/server/serverIndexKey';
import { getPlaybackServerId } from '../utils/playback/playbackServer';
import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer';
import { restBaseFromUrl } from '@/lib/api/subsonicClient';
import type { CoverArtRef, CoverArtTier } from '../cover/types';
+5 -5
View File
@@ -1,6 +1,6 @@
import React, { Suspense, useCallback, useEffect, useRef, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { ensurePlaybackServerActive } from '../utils/playback/playbackServer';
import { ensurePlaybackServerActive } from '@/features/playback/utils/playback/playbackServer';
import { navigatePathWithAlbumReturnTo, shouldSkipMainScrollResetOnRouteChange } from '../utils/navigation/albumDetailNavigation';
import { getCurrentWebview } from '@tauri-apps/api/webview';
import { PanelRight } from 'lucide-react';
@@ -58,13 +58,13 @@ import { IS_LINUX, IS_MACOS } from '@/lib/util/platform';
import { useConnectionStatus } from '../hooks/useConnectionStatus';
import { useIdlePlayQueuePull } from '../hooks/useIdlePlayQueuePull';
import { useAuthStore } from '../store/authStore';
import { usePlayerStore } from '../store/playerStore';
import '../store/previewPlayerVolumeSync';
import '../store/queueResolverBridge';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import '@/features/playback/store/previewPlayerVolumeSync';
import '@/features/playback/store/queueResolverBridge';
import { useThemeStore } from '../store/themeStore';
import { useFontStore } from '../store/fontStore';
import { useEqStore } from '../store/eqStore';
import { usePlaybackRateStore } from '../store/playbackRateStore';
import { usePlaybackRateStore } from '@/features/playback/store/playbackRateStore';
import { usePlaybackRateOrbitSync } from '@/features/orbit';
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
import {
+2 -2
View File
@@ -1,5 +1,5 @@
import { initAudioListeners } from '../store/initAudioListeners';
import '../store/playbackEngineBridgeRegister'; // installs the playback-engine bridge at boot
import { initAudioListeners } from '@/features/playback/store/initAudioListeners';
import '@/features/playback/store/playbackEngineBridgeRegister'; // installs the playback-engine bridge at boot
import { lazy, Suspense, useEffect, useState } from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { invoke } from '@tauri-apps/api/core';
+2 -2
View File
@@ -6,14 +6,14 @@
* payload) and the failure-tolerant path (no throw when invoke or storage
* misbehaves).
*/
import { installQueueUndoHotkey } from '../store/queueUndoHotkey';
import { installQueueUndoHotkey } from '@/features/playback/store/queueUndoHotkey';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
vi.mock('@tauri-apps/api/core', () => ({
invoke: vi.fn(),
}));
vi.mock('../store/queueUndoHotkey', () => ({
vi.mock('@/features/playback/store/queueUndoHotkey', () => ({
installQueueUndoHotkey: vi.fn(),
}));
+1 -1
View File
@@ -1,4 +1,4 @@
import { installQueueUndoHotkey } from '../store/queueUndoHotkey';
import { installQueueUndoHotkey } from '@/features/playback/store/queueUndoHotkey';
import { configureStartupSplash } from './startupSplash';
import { setupMusicNetworkRuntime } from './musicNetworkBridge';
import { invoke } from '@tauri-apps/api/core';
+4 -4
View File
@@ -2,8 +2,8 @@ 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 '../utils/playback/songToTrack';
import { shuffleArray } from '../utils/playback/shuffleArray';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray';
import React, { memo, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@@ -18,9 +18,9 @@ import {
writeBecauseYouLikeCache,
type BecauseYouLikeAnchor,
} from '../store/becauseYouLikeCache';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '../store/authStore';
import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum';
import { playAlbum, playAlbumShuffled } from '@/features/playback/utils/playback/playAlbum';
import { useLongPressAction } from '@/lib/hooks/useLongPressAction';
import { LongPressWaveOverlay } from './LongPressWaveOverlay';
import { formatHumanHoursMinutes } from '@/lib/format/formatHumanDuration';
+1 -1
View File
@@ -2,7 +2,7 @@ import { useState } from 'react';
import { NavLink } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Disc3, Search, Music4, AudioLines, MoreHorizontal } from 'lucide-react';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { MobileSearchOverlay } from '@/features/search';
import MobileMoreOverlay from './MobileMoreOverlay';
+1 -1
View File
@@ -53,7 +53,7 @@ vi.mock('@/features/offline/hooks/useOfflineBrowseContext', () => ({
import ContextMenu from './ContextMenu';
import { renderWithProviders } from '@/test/helpers/renderWithProviders';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '@/store/authStore';
import { resetAllStores } from '@/test/helpers/storeReset';
import { makeTrack, makeServer, seedQueue } from '@/test/helpers/factories';
+2 -2
View File
@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import type { Track } from '../store/playerStoreTypes';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { useOrbitStore } from '@/features/orbit';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useShallow } from 'zustand/react/shallow';
import { useAuthStore } from '../store/authStore';
import { useTranslation } from 'react-i18next';
+1 -1
View File
@@ -4,7 +4,7 @@ import { Save, Trash2, RotateCcw } from 'lucide-react';
import CustomSelect from '@/ui/CustomSelect';
import { useEqStore, EQ_BANDS, BUILTIN_PRESETS } from '../store/eqStore';
import { useThemeStore } from '../store/themeStore';
import { drawCurve } from '../utils/audio/eqCurve';
import { drawCurve } from '@/features/playback/utils/audio/eqCurve';
import VerticalFader from './equalizer/VerticalFader';
import AutoEqSection from './equalizer/AutoEqSection';
+3 -3
View File
@@ -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 '../utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
import { useNavigateToAlbum } from '@/features/album';
import { Play, ListPlus, ChevronLeft, ChevronRight } from 'lucide-react';
@@ -11,7 +11,7 @@ import { useArtistBanner, useArtistFanart } from '../cover/useArtistFanart';
import { useCoverArt } from '../cover/useCoverArt';
import { useHeroBackdrop } from '../cover/useHeroBackdrop';
import { useCachedUrl } from '@/ui/CachedImage';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useTranslation } from 'react-i18next';
import { useIsMobile } from '@/lib/hooks/useIsMobile';
import { useWindowVisibility } from '@/lib/hooks/useWindowVisibility';
@@ -19,7 +19,7 @@ import { useAuthStore } from '../store/authStore';
import { useThemeStore } from '../store/themeStore';
import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '../utils/mix/mixRatingFilter';
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum';
import { playAlbum, playAlbumShuffled } from '@/features/playback/utils/playback/playAlbum';
import { useLongPressAction } from '@/lib/hooks/useLongPressAction';
import { LongPressWaveOverlay } from './LongPressWaveOverlay';
import { albumArtistDisplayName, deriveAlbumArtistRefs } from '@/features/album';
+3 -3
View File
@@ -1,12 +1,12 @@
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress';
import { useEffect, useRef, useCallback } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { LrcLine } from '../api/lrclib';
import { useLyrics, type WordLyricsLine } from '../hooks/useLyrics';
import { useAuthStore } from '../store/authStore';
import { useTranslation } from 'react-i18next';
import type { Track } from '../store/playerStoreTypes';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { EaseScroller, targetForFraction } from '../utils/ui/easeScroll';
import OverlayScrollArea from '@/ui/OverlayScrollArea';
+4 -4
View File
@@ -1,8 +1,8 @@
import { queueSongStar } from '../store/pendingStarSync';
import { queueSongStar } from '@/features/playback/store/pendingStarSync';
import { usePlaybackCoverArt } from '../hooks/usePlaybackCoverArt';
import { usePlaybackTrackCoverRef } from '../cover/useLibraryCoverRef';
import type { Track } from '../store/playerStoreTypes';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress';
import React, { useState, useCallback, useRef, useEffect, useSyncExternalStore, CSSProperties } from 'react';
import { useNavigate } from 'react-router-dom';
import { usePlaybackLibraryNavigate } from '../hooks/usePlaybackLibraryNavigate';
@@ -13,7 +13,7 @@ import {
Shuffle, Repeat, Repeat1, Heart, Music, MicVocal, ListMusic, X,
Moon, Sunrise,
} from 'lucide-react';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useCachedUrl } from '@/ui/CachedImage';
import { OpenArtistRefInline } from '@/features/artist';
import { formatTrackTime } from '@/lib/format/formatDuration';
+2 -2
View File
@@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { X, Moon, Sunrise } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '../store/authStore';
import { useShallow } from 'zustand/react/shallow';
@@ -14,7 +14,7 @@ import {
parsePlaybackDelayCustomMinutes,
scheduleDelayMsFromSeconds,
scheduleSecondsFromCustomMinutes,
} from '../utils/playback/playbackScheduleDelay';
} from '@/features/playback/utils/playback/playbackScheduleDelay';
/** One tap = schedule; custom minutes still covers any duration. */
const PRESET_SECONDS = [30, 60, 120, 300, 600, 900, 1800, 3600] as const;
+1 -1
View File
@@ -1,7 +1,7 @@
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useShallow } from 'zustand/react/shallow';
import { formatPlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat';
import { useWindowVisibility } from '@/lib/hooks/useWindowVisibility';
+1 -1
View File
@@ -39,7 +39,7 @@ vi.mock('@/lib/api/subsonic', () => ({
import PlayerBar from './PlayerBar';
import { renderWithProviders } from '@/test/helpers/renderWithProviders';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '@/store/authStore';
import { resetAllStores } from '@/test/helpers/storeReset';
import { makeTrack, seedQueue } from '@/test/helpers/factories';
+4 -4
View File
@@ -1,4 +1,4 @@
import { queueSongStar } from '../store/pendingStarSync';
import { queueSongStar } from '@/features/playback/store/pendingStarSync';
import { coverArtIdFromRadio } from '../cover/ids';
import { resolvePlaybackTrackCoverArtId } from '../cover/resolveCoverArtId';
import React, { useCallback, useEffect, useRef, useState } from 'react';
@@ -8,7 +8,7 @@ import {
PictureInPicture2, Ellipsis,
} from 'lucide-react';
import { invoke } from '@tauri-apps/api/core';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useShallow } from 'zustand/react/shallow';
import { useAuthStore } from '../store/authStore';
import { useThemeStore } from '../store/themeStore';
@@ -20,10 +20,10 @@ import { useRadioMprisSync } from '@/features/radio';
import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress';
import PlaybackDelayModal from './PlaybackDelayModal';
import { usePlaybackScheduleRemaining } from '../utils/format/playbackScheduleFormat';
import { usePreviewStore } from '../store/previewStore';
import { usePreviewStore } from '@/features/playback/store/previewStore';
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
import { coerceOpenArtistRefs } from '@/lib/api/openArtistRefs';
import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs';
import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs';
import { PlayerTrackInfo } from './playerBar/PlayerTrackInfo';
import { PlayerTransportControls } from './playerBar/PlayerTransportControls';
import { PlayerSeekbarSection } from './playerBar/PlayerSeekbarSection';
+4 -4
View File
@@ -1,20 +1,20 @@
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { songToTrack } from '../utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import React, { memo, useMemo } from 'react';
import { Play, ListPlus, Star, Disc3 } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { CoverArtImage } from '../cover/CoverArtImage';
import { useCoverArt } from '../cover/useCoverArt';
import { useTrackCoverRef } from '../cover/useLibraryCoverRef';
import { COVER_DENSE_RAIL_CELL_CSS_PX } from '../cover/layoutSizes';
import { enqueueAndPlay } from '../utils/playback/playSong';
import { enqueueAndPlay } from '@/features/playback/utils/playback/playSong';
import { useDragDrop } from '@/lib/dnd/DragDropContext';
import { useOrbitSongRowBehavior } from '@/features/orbit';
import { useNavigateToAlbum } from '@/features/album';
import { useNavigateToArtist } from '@/features/artist';
import { OpenArtistRefInline } from '@/features/artist';
import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs';
import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs';
interface SongCardProps {
song: SubsonicSong;
+1 -1
View File
@@ -4,7 +4,7 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import React, { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { X } from 'lucide-react';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useShallow } from 'zustand/react/shallow';
import { ndGetSongPath } from '@/lib/api/navidromeAdmin';
import { useAuthStore } from '../store/authStore';
+4 -4
View File
@@ -1,16 +1,16 @@
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { songToTrack } from '../utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import React, { memo } from 'react';
import { useNavigateToAlbum } from '@/features/album';
import { useNavigateToArtist } from '@/features/artist';
import { Play, ListPlus } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { usePlayerStore } from '../store/playerStore';
import { enqueueAndPlay } from '../utils/playback/playSong';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { enqueueAndPlay } from '@/features/playback/utils/playback/playSong';
import { useDragDrop } from '@/lib/dnd/DragDropContext';
import { useOrbitSongRowBehavior } from '@/features/orbit';
import { formatTrackTime } from '@/lib/format/formatDuration';
import { resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs';
import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs';
import { tooltipAttrs } from '@/ui/tooltipAttrs';
interface Props {
+1 -1
View File
@@ -1,7 +1,7 @@
import React from 'react';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { Minus, Square, X } from 'lucide-react';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '../store/authStore';
import { IS_MACOS } from '@/lib/util/platform';
@@ -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 '../../utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import StarRating from '../StarRating';
import { AlbumToPlaylistSubmenu } from './AlbumArtistToPlaylistSubmenu';
import { MultiAlbumToPlaylistSubmenu } from './MultiAlbumToPlaylistSubmenu';
@@ -1,8 +1,8 @@
import { useTranslation } from 'react-i18next';
import { Play, Radio, Heart, ChevronRight, User, Disc3, ListMusic, Info, Sparkles, Star, Trash2, Share2 } from 'lucide-react';
import { queueSongStar } from '../../store/pendingStarSync';
import { queueSongStar } from '@/features/playback/store/pendingStarSync';
import { getMusicNetworkRuntime, useEnrichmentPrimary } from '../../music-network';
import type { Track } from '../../store/playerStoreTypes';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { useAuthStore } from '../../store/authStore';
import { renderPresetIcon } from '../settings/musicNetwork/presetIcon';
import StarRating from '../StarRating';
@@ -3,12 +3,12 @@ import { Play, ListPlus, Radio, Heart, ChevronRight, ChevronsRight, User, Disc3,
import { useNavigateToAlbum } from '@/features/album';
import { useNavigateToArtist } from '@/features/artist';
import { resolveAlbum, resolveMediaServerId, resolvePlaylist } from '@/features/offline';
import { queueSongStar } from '../../store/pendingStarSync';
import { queueSongStar } from '@/features/playback/store/pendingStarSync';
import { getMusicNetworkRuntime, useEnrichmentPrimary } from '../../music-network';
import type { Track } from '../../store/playerStoreTypes';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { useAuthStore } from '../../store/authStore';
import { usePlaylistStore } from '@/features/playlist';
import { songToTrack } from '../../utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { showToast } from '../../utils/ui/toast';
import { suggestOrbitTrack, hostEnqueueToOrbit, evaluateOrbitSuggestGate, OrbitSuggestBlockedError } from '@/features/orbit';
import { renderPresetIcon } from '../settings/musicNetwork/presetIcon';
@@ -1,6 +1,6 @@
import type React from 'react';
import type { SubsonicAlbum, SubsonicArtist } from '@/lib/api/subsonicTypes';
import type { QueueItemRef, Track } from '../../store/playerStoreTypes';
import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes';
import type { EntityShareKind } from '../../utils/share/shareLink';
import type { OfflineActionPolicy } from '@/features/offline';
@@ -2,7 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { ChevronRight, Folder, FolderOpen, Music } from 'lucide-react';
import type { SubsonicDirectoryEntry } from '@/lib/api/subsonicTypes';
import type { Track } from '../../store/playerStoreTypes';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import {
folderBrowserHasKeyModifiers, isFolderBrowserArrowKey,
type Column,
+1 -1
View File
@@ -1,5 +1,5 @@
import { memo, useEffect, useRef } from 'react';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../../store/playbackProgress';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress';
import {
formatPlaybarClock,
formatPlaybarToggleClock,
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
import { createPortal } from 'react-dom';
import type { TFunction } from 'i18next';
import { PlaybackRateControls } from '@/features/settings';
import { usePlaybackRateStore } from '../../store/playbackRateStore';
import { usePlaybackRateStore } from '@/features/playback/store/playbackRateStore';
import { useOrbitStore } from '@/features/orbit';
import {
clampPlaybackPitch,
@@ -13,7 +13,7 @@ import {
playbackPitchStep,
playbackSpeedStep,
varispeedSpeedFromSemitones,
} from '../../utils/audio/playbackRateHelpers';
} from '@/features/playback/utils/audio/playbackRateHelpers';
import { isOrbitPlaybackSyncActive } from '@/features/orbit';
import { usePlayerBarAnchoredPopover } from '../../hooks/usePlayerBarAnchoredPopover';
+4 -4
View File
@@ -1,10 +1,10 @@
import { Cast, Heart, Maximize2, Music } from 'lucide-react';
import type { TFunction } from 'i18next';
import { queueSongRating } from '../../store/pendingStarSync';
import { queueSongRating } from '@/features/playback/store/pendingStarSync';
import type { InternetRadioStation, SubsonicOpenArtistRef } from '@/lib/api/subsonicTypes';
import type { PlayerState, Track } from '../../store/playerStoreTypes';
import type { PlayerState, Track } from '@/features/playback/store/playerStoreTypes';
import type { RadioMetadata } from '@/features/radio';
import type { PreviewingTrack } from '../../store/previewStore';
import type { PreviewingTrack } from '@/features/playback/store/previewStore';
import { CoverArtImage } from '../../cover/CoverArtImage';
import { albumCoverRef } from '../../cover/ref';
import { useAlbumCoverRef } from '../../cover/useLibraryCoverRef';
@@ -13,7 +13,7 @@ import MarqueeText from '../MarqueeText';
import { OpenArtistRefInline } from '@/features/artist';
import StarRating from '../StarRating';
import { PlaybackBufferingOverlay } from '../playback/PlaybackBufferingOverlay';
import { usePlayerStore } from '../../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useEnrichmentPrimaryLabel } from '../../hooks/useEnrichmentPrimaryLabel';
import { useEnrichmentPrimaryIcon } from '../../hooks/useEnrichmentPrimaryIcon';
import { renderPresetIcon } from '../settings/musicNetwork/presetIcon';
@@ -2,9 +2,9 @@ import React from 'react';
import { Blend, Moon, Pause, Play, Repeat, Repeat1, SkipBack, SkipForward, Square, Sunrise } from 'lucide-react';
import { invoke } from '@tauri-apps/api/core';
import type { TFunction } from 'i18next';
import type { PlayerState } from '../../store/playerStoreTypes';
import { useAutodjTransitionUi } from '../../store/autodjTransitionUi';
import { usePreviewStore } from '../../store/previewStore';
import type { PlayerState } from '@/features/playback/store/playerStoreTypes';
import { useAutodjTransitionUi } from '@/features/playback/store/autodjTransitionUi';
import { usePreviewStore } from '@/features/playback/store/previewStore';
import PlaybackScheduleBadge from '../PlaybackScheduleBadge';
import { usePlaybackDelayPress } from '../../hooks/usePlaybackDelayPress';
import { usePlaybackScheduleRemaining } from '../../utils/format/playbackScheduleFormat';
@@ -2,8 +2,8 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { AudioLines, ChevronRight, Heart, Play, Square } from 'lucide-react';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import type { Track } from '../../store/playerStoreTypes';
import { previewInputFromSong, usePreviewStore } from '../../store/previewStore';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore';
import { useDragDrop } from '@/lib/dnd/DragDropContext';
import { formatRandomMixDuration } from '../../utils/componentHelpers/randomMixHelpers';
+4 -4
View File
@@ -1,20 +1,20 @@
import { AlbumCoverArtImage } from '../../cover/AlbumCoverArtImage';
import { getRandomSongs } from '@/lib/api/subsonicLibrary';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { songToTrack } from '../../utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Play, ListPlus, RefreshCw, Sparkles } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useAuthStore } from '../../store/authStore';
import { usePlayerStore } from '../../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import SongRail from '../SongRail';
import { playSongNow } from '../../utils/playback/playSong';
import { playSongNow } from '@/features/playback/utils/playback/playSong';
import { ndListSongs, ndInvalidateSongsCache } from '@/lib/api/navidromeBrowse';
import { usePerfProbeFlags } from '../../utils/perf/perfFlags';
import { useNavigateToAlbum } from '@/features/album';
import { useNavigateToArtist } from '@/features/artist';
import { OpenArtistRefInline } from '@/features/artist';
import { resolveTrackArtistRefs } from '../../utils/playback/trackArtistRefs';
import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs';
const RANDOM_RAIL_SIZE = 18;
const RATED_RAIL_FETCH = 60;
+4 -4
View File
@@ -1,11 +1,11 @@
import { queueSongStar } from '../store/pendingStarSync';
import { queueSongStar } from '@/features/playback/store/pendingStarSync';
import { getSong } from '@/lib/api/subsonicLibrary';
import { songToTrack } from '../utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { invoke } from '@tauri-apps/api/core';
import { getCurrentWindow } from '@tauri-apps/api/window';
import i18n from '@/lib/i18n';
import { usePlayerStore } from '../store/playerStore';
import { usePreviewStore } from '../store/previewStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { usePreviewStore } from '@/features/playback/store/previewStore';
import { useLyricsStore } from '../store/lyricsStore';
import { showToast } from '../utils/ui/toast';
import type { ActionContext, ShortcutSlot, ShortcutActionMeta } from '@/config/shortcutTypes';
+3 -3
View File
@@ -1,8 +1,8 @@
import { queueSongRating } from '../store/pendingStarSync';
import { queueSongRating } from '@/features/playback/store/pendingStarSync';
import i18n from '@/lib/i18n';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { showToast } from '../utils/ui/toast';
import { playByOpaqueId } from '../utils/playback/playByOpaqueId';
import { playByOpaqueId } from '@/features/playback/utils/playback/playByOpaqueId';
import type { ActionContext, CliContext } from '@/config/shortcutTypes';
import {
SHORTCUT_ACTION_REGISTRY,
+1 -1
View File
@@ -2,7 +2,7 @@ import {
buildCoverArtUrl,
buildCoverArtUrlForServer,
} from '@/lib/api/subsonicStreamUrl';
import { getPlaybackServerId } from '../utils/playback/playbackServer';
import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer';
import { useAuthStore } from '../store/authStore';
import { connectBaseUrlForServer } from '../utils/server/serverEndpoint';
import type { CoverArtRef, CoverArtTier } from './types';
+1 -1
View File
@@ -1,4 +1,4 @@
import { getPlaybackServerId } from '../utils/playback/playbackServer';
import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer';
import { useAuthStore } from '../store/authStore';
import { coverServerScopeForServerId } from './serverScope';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
+1 -1
View File
@@ -26,7 +26,7 @@ vi.mock('../store/authStore', () => ({
useAuthStore: { getState: () => mockState },
}));
vi.mock('../utils/playback/playbackServer', () => ({
vi.mock('@/features/playback/utils/playback/playbackServer', () => ({
getPlaybackServerId: () => null,
}));
+1 -1
View File
@@ -1,4 +1,4 @@
import { getPlaybackServerId } from '../utils/playback/playbackServer';
import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer';
import { useAuthStore } from '../store/authStore';
import { findServerByIdOrIndexKey } from '../utils/server/serverLookup';
import {
+1 -1
View File
@@ -1,7 +1,7 @@
import { useEffect } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { useAuthStore } from '../store/authStore';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
const TICK_MS = 60_000;
+2 -2
View File
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState, type Dispatch, type SetStateAction } from 'react';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { useAuthStore } from '../store/authStore';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import {
albumCoverRef,
albumCoverRefForPlayback,
@@ -12,7 +12,7 @@ import {
} from './ref';
import { coverServerScopeForServerId } from './serverScope';
import { resolveServerIdForIndexKey } from '../utils/server/serverLookup';
import { sameQueueTrackId } from '../utils/playback/queueIdentity';
import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity';
import {
resolveAlbumCoverRefFromLibrary,
resolveArtistCoverRefFromLibrary,
+1 -1
View File
@@ -4,7 +4,7 @@ import type { CoverArtHandle } from './types';
import { albumCoverRef } from './ref';
import { usePlaybackCoverArt } from './usePlaybackCoverArt';
import { useAuthStore } from '../store/authStore';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { makeTrack } from '../test/helpers/factories';
import { resetAllStores } from '../test/helpers/storeReset';
import { toQueueItemRefs } from '../utils/library/queueItemRef';
+1 -1
View File
@@ -3,7 +3,7 @@ import { resolvePlaybackCoverScope } from './ref';
import type { CoverArtHandle, CoverArtRef } from './types';
import { useCoverArt } from './useCoverArt';
import { useAuthStore } from '../store/authStore';
import { usePlayerStore } from '../store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
/** Cover art for playback queue — uses queue server when it differs from browsed server. */
export function usePlaybackCoverArt(
+2 -2
View File
@@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom';
import { useNavigateToAlbum } from '@/features/album/hooks/useNavigateToAlbum';
import { Play, ListPlus, HardDriveDownload, Check } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '@/store/authStore';
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
import { isOfflinePinComplete } from '@/features/offline';
@@ -16,7 +16,7 @@ import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '@/cover/layoutSizes';
import { resolveCoverDisplayTier } from '@/cover/tiers';
import { acquireUrl } from '@/utils/imageCache/urlPool';
import { OpenArtistRefInline } from '@/features/artist';
import { fetchAlbumTracks, playAlbum, playAlbumShuffled } from '@/utils/playback/playAlbum';
import { fetchAlbumTracks, playAlbum, playAlbumShuffled } from '@/features/playback/utils/playback/playAlbum';
import { useLongPressAction } from '@/lib/hooks/useLongPressAction';
import { LongPressWaveOverlay } from '@/components/LongPressWaveOverlay';
import { useDragDrop } from '@/lib/dnd/DragDropContext';
@@ -1,8 +1,8 @@
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import type { Track } from '@/store/playerStoreTypes';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import React, { useMemo, useState, useEffect } from 'react';
import { useTracklistColumns } from '@/utils/useTracklistColumns';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useTranslation } from 'react-i18next';
import { useIsMobile } from '@/lib/hooks/useIsMobile';
import { useSelectionStore } from '@/store/selectionStore';
@@ -1,8 +1,8 @@
import React from 'react';
import { AudioLines } from 'lucide-react';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import type { Track } from '@/store/playerStoreTypes';
import { songToTrack } from '@/utils/playback/songToTrack';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { formatLongDuration } from '@/lib/format/formatDuration';
interface Props {
+4 -4
View File
@@ -4,18 +4,18 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import type { ColDef } from '@/utils/useTracklistColumns';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import type { Track } from '@/store/playerStoreTypes';
import { songToTrack } from '@/utils/playback/songToTrack';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { useSelectionStore } from '@/store/selectionStore';
import { useThemeStore } from '@/store/themeStore';
import { previewInputFromSong, usePreviewStore } from '@/store/previewStore';
import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore';
import StarRating from '@/components/StarRating';
import { codecLabel, type ColKey } from '@/features/album/utils/albumTrackListHelpers';
import { formatLongDuration } from '@/lib/format/formatDuration';
import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers';
import i18n from '@/lib/i18n';
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
import { resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs';
import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs';
type ContextMenuFn = (
x: number,
@@ -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 '@/utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
interface UseAlbumTrackListSelectionArgs {
songs: SubsonicSong[];
+4 -4
View File
@@ -1,15 +1,15 @@
import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl';
import { setRating, star, unstar } from '@/lib/api/subsonicStarRating';
import { queueSongStar, queueSongRating } from '@/store/pendingStarSync';
import { queueSongStar, queueSongRating } from '@/features/playback/store/pendingStarSync';
import { getAlbumForServer } from '@/lib/api/subsonicLibrary';
import { getArtistInfo } from '@/lib/api/subsonicArtists';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { songToTrack } from '@/utils/playback/songToTrack';
import { shuffleArray } from '@/utils/playback/shuffleArray';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray';
import React, { useEffect, useState, useCallback, useMemo } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
import { invoke } from '@tauri-apps/api/core';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '@/store/authStore';
import { useOrbitSongRowBehavior } from '@/features/orbit';
import { useAlbumDetailData } from '@/features/album/hooks/useAlbumDetailData';
+2 -2
View File
@@ -1,6 +1,6 @@
import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl';
import { resolveAlbum } from '@/features/offline';
import { songToTrack } from '@/utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { useState, useEffect, useLayoutEffect, useRef, useMemo } from 'react';
import AlbumCard from '@/features/album/components/AlbumCard';
import { albumGridWarmCovers, coverDisplayCssPxForAlbumGrid } from '@/cover/layoutSizes';
@@ -17,7 +17,7 @@ import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate } from 'react-router-dom';
import { useOfflineStore } from '@/features/offline';
import { useDownloadModalStore } from '@/features/offline';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { invoke } from '@tauri-apps/api/core';
import { join } from '@tauri-apps/api/path';
import { showToast } from '@/utils/ui/toast';
+2 -2
View File
@@ -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 '@/utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import AlbumCard from '@/features/album/components/AlbumCard';
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
@@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
import { useAuthStore } from '@/store/authStore';
import { useOfflineStore } from '@/features/offline';
import { useDownloadModalStore } from '@/features/offline';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useZipDownloadStore } from '@/features/offline';
import { useRangeSelection } from '@/lib/hooks/useRangeSelection';
import { useMainstageInpageHeaderTight } from '@/hooks/useMainstageInpageHeaderTight';
+1 -1
View File
@@ -4,7 +4,7 @@ import { getAlbumList } from '@/lib/api/subsonicLibrary';
import { resolveAlbum } from '@/features/offline';
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
import { dedupeById } from '@/lib/util/dedupeById';
import { shuffleArray } from '@/utils/playback/shuffleArray';
import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray';
import React, { useEffect, useLayoutEffect, useState, useCallback, useRef } from 'react';
import { RefreshCw, Download, HardDriveDownload } from 'lucide-react';
import SelectionToggleButton from '@/components/SelectionToggleButton';
@@ -2,10 +2,10 @@ import React, { Fragment } from 'react';
import { useTranslation } from 'react-i18next';
import { AudioLines, ChevronRight, Play, Square } from 'lucide-react';
import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
import { usePlayerStore } from '@/store/playerStore';
import { previewInputFromSong, usePreviewStore } from '@/store/previewStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore';
import { useOrbitSongRowBehavior } from '@/features/orbit';
import { songToTrack } from '@/utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/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 React from 'react';
import { Check } from 'lucide-react';
import type { TFunction } from 'i18next';
import type { SubsonicArtist } from '@/lib/api/subsonicTypes';
import type { PlayerState } from '@/store/playerStoreTypes';
import type { PlayerState } from '@/features/playback/store/playerStoreTypes';
import { ARTISTS_INPAGE_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
import { ArtistCardAvatar } from '@/features/artist/components/ArtistAvatars';
@@ -2,7 +2,7 @@ import React from 'react';
import type { Virtualizer } from '@tanstack/react-virtual';
import type { TFunction } from 'i18next';
import type { SubsonicArtist } from '@/lib/api/subsonicTypes';
import type { PlayerState } from '@/store/playerStoreTypes';
import type { PlayerState } from '@/features/playback/store/playerStoreTypes';
import { OTHER_BUCKET, type ArtistListFlatRow } from '@/features/artist/utils/artistsHelpers';
import { ArtistRowAvatar } from '@/features/artist/components/ArtistAvatars';
@@ -1,6 +1,6 @@
import { useMemo } from 'react';
import type { SubsonicArtist } from '@/lib/api/subsonicTypes';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { ALL_SENTINEL, artistLetterBucket, compareBuckets, type ArtistListFlatRow } from '@/features/artist/utils/artistsHelpers';
interface UseArtistsFilteringArgs {
+1 -1
View File
@@ -6,7 +6,7 @@ import { useParams, useSearchParams } from 'react-router-dom';
import { AlbumCard } from '@/features/album';
import { ArrowDownUp } from 'lucide-react';
import { open } from '@tauri-apps/plugin-shell';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '@/store/authStore';
import { useTranslation } from 'react-i18next';
import { useArtistLayoutStore, type ArtistSectionId } from '@/features/artist/store/artistLayoutStore';
+1 -1
View File
@@ -4,7 +4,7 @@ import { LayoutGrid, List, Images } from 'lucide-react';
import SelectionToggleButton from '@/components/SelectionToggleButton';
import StarFilterButton from '@/components/StarFilterButton';
import OverlayScrollArea from '@/ui/OverlayScrollArea';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '@/store/authStore';
import { useTranslation } from 'react-i18next';
import { useVirtualizer } from '@tanstack/react-virtual';
@@ -1,9 +1,9 @@
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 '@/store/playerStoreTypes';
import { songToTrack } from '@/utils/playback/songToTrack';
import { runBulkPlayAll, runBulkShuffle } from '@/utils/playback/runBulkPlay';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { runBulkPlayAll, runBulkShuffle } from '@/features/playback/utils/playback/runBulkPlay';
import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
/** Ordered artist discography tracks for play-all / shuffle (network or local bytes). */
@@ -6,7 +6,7 @@ import { useDeviceSyncJobStore } from '@/features/deviceSync/store/deviceSyncJob
import { useDeviceSyncStore } from '@/features/deviceSync/store/deviceSyncStore';
import { showToast } from '@/utils/ui/toast';
import { trackToSyncInfo } from '@/features/deviceSync/utils/deviceSyncHelpers';
import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource';
import { fetchTracksForSource } from '@/features/playback/utils/playback/fetchTracksForSource';
export function useDeviceSyncJobEvents(
t: TFunction,
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource';
import { fetchTracksForSource } from '@/features/playback/utils/playback/fetchTracksForSource';
import { trackToSyncInfo, type SyncStatus } from '@/features/deviceSync/utils/deviceSyncHelpers';
import type { DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore';
@@ -6,7 +6,7 @@ import { useDeviceSyncStore, type DeviceSyncSource } from '@/features/deviceSync
import { useDeviceSyncJobStore } from '@/features/deviceSync/store/deviceSyncJobStore';
import { showToast } from '@/utils/ui/toast';
import { trackToSyncInfo, uuid } from '@/features/deviceSync/utils/deviceSyncHelpers';
import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource';
import { fetchTracksForSource } from '@/features/playback/utils/playback/fetchTracksForSource';
export interface SyncDelta {
addBytes: number;
@@ -4,7 +4,7 @@ import type { DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncSto
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { applyLegacyTemplate } from '@/features/deviceSync/utils/deviceSyncLegacyTemplate';
import { trackToSyncInfo } from '@/features/deviceSync/utils/deviceSyncHelpers';
import { fetchTracksForSource } from '@/utils/playback/fetchTracksForSource';
import { fetchTracksForSource } from '@/features/playback/utils/playback/fetchTracksForSource';
import { IS_WINDOWS } from '@/lib/util/platform';
export type MigrationPhase = 'closed' | 'loading' | 'preview' | 'executing' | 'done' | 'nothing';
@@ -9,7 +9,7 @@ import i18n from '@/lib/i18n';
import { formatTrackTime } from '@/lib/format/formatDuration';
import StarRating from '@/components/StarRating';
import { OpenArtistRefInline } from '@/features/artist';
import { resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs';
import { resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs';
export interface FavoriteSongRowCallbacks {
activate: (song: SubsonicSong, index: number, e: React.MouseEvent) => void;
@@ -2,9 +2,9 @@ import React, { useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { ListPlus, Play, SlidersHorizontal, X } from 'lucide-react';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useSelectionStore } from '@/store/selectionStore';
import { songToTrack } from '@/utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { AddToPlaylistSubmenu } from '@/components/ContextMenu';
import GenreFilterBar from '@/components/GenreFilterBar';
@@ -6,13 +6,13 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import type { ColDef } from '@/utils/useTracklistColumns';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { usePlayerStore } from '@/store/playerStore';
import { previewInputFromSong, usePreviewStore } from '@/store/previewStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { previewInputFromSong, usePreviewStore } from '@/features/playback/store/previewStore';
import { useSelectionStore } from '@/store/selectionStore';
import { useThemeStore } from '@/store/themeStore';
import { useDragDrop } from '@/lib/dnd/DragDropContext';
import { useOrbitSongRowBehavior } from '@/features/orbit';
import { songToTrack } from '@/utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { appendServerQuery } from '@/utils/navigation/detailServerScope';
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
import { useElementClientHeightById } from '@/lib/hooks/useResizeClientHeight';
@@ -5,7 +5,7 @@ import type {
InternetRadioStation, SubsonicAlbum, SubsonicArtist, SubsonicSong,
} from '@/lib/api/subsonicTypes';
import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { TopFavoriteArtist } from '@/features/favorites/components/TopFavoriteArtists';
import { useConnectionStatus } from '@/hooks/useConnectionStatus';
import { isActiveServerReachable } from '@/utils/network/activeServerReachability';
@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { ArrowDown, ArrowUp } from 'lucide-react';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
const CURRENT_YEAR = new Date().getFullYear();
const MIN_YEAR = 1950;
+2 -2
View File
@@ -1,4 +1,4 @@
import { queueSongStar, queueSongRating } from '@/store/pendingStarSync';
import { queueSongStar, queueSongRating } from '@/features/playback/store/pendingStarSync';
import React, { useEffect, useMemo, useState } from 'react';
import { useTracklistColumns, type ColDef } from '@/utils/useTracklistColumns';
import { TopFavoriteArtistsRow } from '@/features/favorites/components/TopFavoriteArtists';
@@ -11,7 +11,7 @@ import { useFavoritesSelection } from '@/features/favorites/hooks/useFavoritesSe
import { useBulkPlPickerOutsideClick } from '@/hooks/useBulkPlPickerOutsideClick';
import { AlbumRow } from '@/features/album';
import { ArtistRow } from '@/features/artist';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useTranslation } from 'react-i18next';
import { useSelectionStore } from '@/store/selectionStore';
import FavoritesOfflineHeader from '@/features/favorites/components/FavoritesOfflineHeader';
@@ -1,11 +1,11 @@
import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '@/store/authStore';
import { useLyrics, type WordLyricsLine } from '@/hooks/useLyrics';
import { useWordLyricsSync } from '@/hooks/useWordLyricsSync';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/store/playbackProgress';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress';
import type { LrcLine } from '@/api/lrclib';
import type { Track } from '@/store/playerStoreTypes';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { EaseScroller, targetForFraction } from '@/utils/ui/easeScroll';
// Fullscreen synced lyrics.
@@ -1,7 +1,7 @@
import React, { memo, useRef } from 'react';
import { Moon, Pause, Play, Sunrise } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { usePlaybackDelayPress } from '@/hooks/usePlaybackDelayPress';
import { usePlaybackScheduleRemaining } from '@/utils/format/playbackScheduleFormat';
import PlaybackDelayModal from '@/components/PlaybackDelayModal';
@@ -1,8 +1,8 @@
import { memo, useMemo, useSyncExternalStore } from 'react';
import { useTranslation } from 'react-i18next';
import { X } from 'lucide-react';
import { usePlayerStore } from '@/store/playerStore';
import type { Track } from '@/store/playerStoreTypes';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { resolveQueueTrack } from '@/utils/library/queueTrackView';
import {
getQueueResolverVersion,
@@ -1,5 +1,5 @@
import { memo, useEffect, useRef } from 'react';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/store/playbackProgress';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress';
import { formatTrackTime } from '@/lib/format/formatDuration';
/**
@@ -4,8 +4,8 @@ import {
Shuffle, ListMusic, ChevronDown, Star, MicVocal,
} from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { usePlayerStore } from '@/store/playerStore';
import { queueSongStar, queueSongRating } from '@/store/pendingStarSync';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { queueSongStar, queueSongRating } from '@/features/playback/store/pendingStarSync';
import { useAlbumCoverRef, useArtistCoverRef } from '@/cover/useLibraryCoverRef';
import { usePlaybackCoverArt } from '@/hooks/usePlaybackCoverArt';
import { useCachedUrl } from '@/ui/CachedImage';
@@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from 'react';
import { emit } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/core';
import { useTranslation } from 'react-i18next';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { registerQueueDragHitTest } from '@/lib/dnd/DragDropContext';
import MiniContextMenu from '@/features/miniPlayer/components/MiniContextMenu';
import type { MiniSyncPayload, MiniControlAction, MiniTrackInfo } from '@/features/miniPlayer/utils/miniPlayerBridge';
@@ -4,7 +4,7 @@ import { emit } from '@tauri-apps/api/event';
import { Blend, Infinity as InfinityIcon, ListMusic, MoveRight, Shuffle, Volume2, VolumeX, Waves } from 'lucide-react';
import type { TFunction } from 'i18next';
import type { MiniSyncPayload } from '@/features/miniPlayer/utils/miniPlayerBridge';
import { getTransitionMode } from '@/utils/playback/playbackTransition';
import { getTransitionMode } from '@/features/playback/utils/playback/playbackTransition';
interface Props {
state: MiniSyncPayload;
@@ -1,7 +1,7 @@
import React, { useEffect, useRef, useState } from 'react';
import { emit } from '@tauri-apps/api/event';
import { useDragDrop } from '@/lib/dnd/DragDropContext';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
interface Args {
queueOpen: boolean;
@@ -1,11 +1,11 @@
import { getCurrentWindow } from '@tauri-apps/api/window';
import { listen, emitTo } from '@tauri-apps/api/event';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '@/store/authStore';
import { setTransitionMode, type TransitionMode } from '@/utils/playback/playbackTransition';
import { setTransitionMode, type TransitionMode } from '@/features/playback/utils/playback/playbackTransition';
import { resolveQueueTrack } from '@/utils/library/queueTrackView';
import type { SubsonicOpenArtistRef } from '@/lib/api/subsonicTypes';
import type { Track } from '@/store/playerStoreTypes';
import type { Track } from '@/features/playback/store/playerStoreTypes';
export const MINI_WINDOW_LABEL = 'mini';
@@ -1,7 +1,7 @@
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { resolveQueueTrack } from '@/utils/library/queueTrackView';
import type { MiniSyncPayload, MiniTrackInfo } from '@/features/miniPlayer/utils/miniPlayerBridge';
import type { Track } from '@/store/playerStoreTypes';
import type { Track } from '@/features/playback/store/playerStoreTypes';
/** Half-width of the mini initial-snapshot queue window (matches the bridge). */
const MINI_SNAPSHOT_HALF = 100;
@@ -6,7 +6,7 @@ import { createPortal } from 'react-dom';
import { PlayCircle, Pause, User, Radio, RefreshCw } from 'lucide-react';
import { nowPlayingPresence } from '@/features/nowPlaying/api/nowPlayingPresence';
import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
@@ -5,13 +5,13 @@ import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 're
import { useTranslation } from 'react-i18next';
import { Info } from 'lucide-react';
import { open as shellOpen } from '@tauri-apps/plugin-shell';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '@/store/authStore';
import { usePlaybackServerId } from '@/hooks/usePlaybackServerId';
import { fetchBandsintownEvents, type BandsintownEvent } from '@/api/bandsintown';
import CachedImage from '@/ui/CachedImage';
import OverlayScrollArea from '@/ui/OverlayScrollArea';
import { primaryTrackArtistRef } from '@/utils/playback/trackArtistRefs';
import { primaryTrackArtistRef } from '@/features/playback/utils/playback/trackArtistRefs';
const TOUR_LIMIT = 5;
const BIO_CLAMP_LINES = 4;
@@ -2,7 +2,7 @@ import React, { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Cast, Clock, Radio, SkipForward, Users } from 'lucide-react';
import type { useRadioMetadata } from '@/features/radio';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { formatTrackTime } from '@/lib/format/formatDuration';
type NonNullStoreField<K extends keyof ReturnType<typeof usePlayerStore.getState>> =
@@ -5,7 +5,7 @@ import { coverIndexKeyFromRef } from '@/cover/storageKeys';
import { useNowPlayingPrewarm } from '@/features/nowPlaying/hooks/useNowPlayingPrewarm';
import { prewarmNowPlayingFetchers } from '@/features/nowPlaying/hooks/useNowPlayingFetchers';
import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { makeTrack } from '@/test/helpers/factories';
import { resetAllStores } from '@/test/helpers/storeReset';
import { toQueueItemRefs } from '@/utils/library/queueItemRef';
@@ -10,9 +10,9 @@ import { coverArtIdFromRadio } from '@/cover/ids';
import type { CoverArtRef } from '@/cover/types';
import { prewarmNowPlayingFetchers } from '@/features/nowPlaying/hooks/useNowPlayingFetchers';
import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { usePlaybackServerId } from '@/hooks/usePlaybackServerId';
import { primaryTrackArtistRef } from '@/utils/playback/trackArtistRefs';
import { primaryTrackArtistRef } from '@/features/playback/utils/playback/trackArtistRefs';
const NOW_PLAYING_COVER_CSS_PX = 800;
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { queueSongStar } from '@/store/pendingStarSync';
import { queueSongStar } from '@/features/playback/store/pendingStarSync';
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import type { Track } from '@/store/playerStoreTypes';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import type { TrackStats } from '@/music-network';
import { getMusicNetworkRuntime } from '@/music-network';
+3 -3
View File
@@ -8,10 +8,10 @@ import { usePlaybackLibraryNavigate } from '@/hooks/usePlaybackLibraryNavigate';
import { usePlaybackServerId } from '@/hooks/usePlaybackServerId';
import { useTranslation } from 'react-i18next';
import { Music, EyeOff, LayoutGrid, RotateCcw, Eye } from 'lucide-react';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '@/store/authStore';
import { useLyricsStore } from '@/store/lyricsStore';
import { songToTrack } from '@/utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { useRadioMetadata } from '@/features/radio';
import { useDragDrop } from '@/lib/dnd/DragDropContext';
import OverlayScrollArea from '@/ui/OverlayScrollArea';
@@ -38,7 +38,7 @@ import DiscographyCard from '@/features/nowPlaying/components/DiscographyCard';
import { useNowPlayingFetchers } from '@/features/nowPlaying/hooks/useNowPlayingFetchers';
import { useNowPlayingStarLove } from '@/features/nowPlaying/hooks/useNowPlayingStarLove';
import { useArtistInfoBatch } from '@/features/artist';
import { primaryTrackArtistRef, resolveTrackArtistRefs } from '@/utils/playback/trackArtistRefs';
import { primaryTrackArtistRef, resolveTrackArtistRefs } from '@/features/playback/utils/playback/trackArtistRefs';
import type { ArtistCardTab } from '@/features/nowPlaying/components/ArtistCard';
// ─── Main Page ────────────────────────────────────────────────────────────────
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
import { useOfflineStore } from '@/features/offline/store/offlineStore';
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { AlbumCoverArtImage } from '@/cover/AlbumCoverArtImage';
import { coverServerScopeForServerId } from '@/cover/serverScope';
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
@@ -33,7 +33,7 @@ import {
type OfflineLibraryCard,
} from '@/features/offline/utils/offlineLibraryHelpers';
import { showToast } from '@/utils/ui/toast';
import { shuffleArray } from '@/utils/playback/shuffleArray';
import { shuffleArray } from '@/features/playback/utils/playback/shuffleArray';
import { getMediaDir } from '@/utils/media/mediaDir';
import { canonicalQueueServerKey, resolveIndexKey } from '@/utils/server/serverIndexKey';
import { reconcileAllLibraryTiersFromDisk } from '@/features/offline/utils/libraryTierReconcile';
@@ -9,7 +9,7 @@ import { findLocalPlaybackEntry, hasLocalLibraryBytes } from '@/store/localPlayb
import { useOfflineStore, type OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
import { resolveTrackCoverArtId, trackToSong } from '@/utils/library/advancedSearchLocal';
import { canonicalQueueServerKey, resolveIndexKey } from '@/utils/server/serverIndexKey';
import type { Track } from '@/store/playerStoreTypes';
import type { Track } from '@/features/playback/store/playerStoreTypes';
import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
@@ -3,7 +3,7 @@ import { createPortal } from 'react-dom';
import { Copy, Trash2 } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { showToast } from '@/utils/ui/toast';
import { computeOrbitDriftMs } from '@/features/orbit/utils/orbit';
import {
@@ -1,11 +1,11 @@
import { getSong } from '@/lib/api/subsonicLibrary';
import { songToTrack } from '@/utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { useEffect, useRef, useState } from 'react';
import { X, RefreshCw, Shuffle, Settings2, Share2, HelpCircle, Activity } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
import { useHelpModalStore } from '@/store/helpModalStore';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import {
endOrbitSession,
leaveOrbitSession,
@@ -12,7 +12,7 @@ import {
} from '@/features/orbit/utils/orbit';
import { randomOrbitSessionName } from '@/features/orbit/utils/orbitNames';
import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { isLanUrl, serverShareBaseUrl } from '@/utils/server/serverEndpoint';
import { ORBIT_DEFAULT_MAX_USERS } from '@/features/orbit/api/orbit';
+2 -2
View File
@@ -1,9 +1,9 @@
import { getSong } from '@/lib/api/subsonicLibrary';
import { songToTrack } from '@/utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { useEffect, useRef } from 'react';
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import {
readOrbitState,
applyOrbitTransitionSettings,
+2 -2
View File
@@ -1,8 +1,8 @@
import { getSong } from '@/lib/api/subsonicLibrary';
import { songToTrack } from '@/utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { useEffect } from 'react';
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import {
writeOrbitState,
sweepGuestOutboxes,
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
import { usePlaybackRateStore } from '@/store/playbackRateStore';
import { usePlaybackRateStore } from '@/features/playback/store/playbackRateStore';
/** Re-sync playback rate when Orbit enters or leaves shared playback. */
export function usePlaybackRateOrbitSync() {
+2 -2
View File
@@ -1,9 +1,9 @@
import { createPlaylist, deletePlaylist, getPlaylist, getPlaylists, updatePlaylist } from '@/lib/api/subsonicPlaylists';
import { getSong } from '@/lib/api/subsonicLibrary';
import { songToTrack } from '@/utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { useAuthStore } from '@/store/authStore';
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import {
orbitOutboxPlaylistName,
type OrbitQueueItem,
+2 -2
View File
@@ -23,10 +23,10 @@ vi.mock('@/features/orbit/utils/remote', () => ({
}));
vi.mock('@/features/orbit/store/orbitStore', () => ({ useOrbitStore: { getState: () => orbitStore } }));
vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => ({}) } }));
vi.mock('@/store/playerStore', () => ({ usePlayerStore: { getState: () => ({ enqueue: vi.fn() }) } }));
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('@/utils/playback/songToTrack', () => ({ songToTrack: vi.fn() }));
vi.mock('@/features/playback/utils/playback/songToTrack', () => ({ songToTrack: vi.fn() }));
import { updateOrbitSettings } from '@/features/orbit/utils/host';
+2 -2
View File
@@ -1,9 +1,9 @@
import { createPlaylist, deletePlaylist } from '@/lib/api/subsonicPlaylists';
import { getSong } from '@/lib/api/subsonicLibrary';
import { songToTrack } from '@/utils/playback/songToTrack';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { useAuthStore } from '@/store/authStore';
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
import { usePlayerStore } from '@/store/playerStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import {
makeInitialOrbitState,
orbitOutboxPlaylistName,
@@ -1,29 +1,29 @@
import { playbackReportStart } from './playbackReportSession';
import { playbackReportStart } from '@/features/playback/store/playbackReportSession';
import { invoke } from '@tauri-apps/api/core';
import { getPlaybackServerId } from '../utils/playback/playbackServer';
import { getPlaybackSourceKind } from '../utils/playback/resolvePlaybackUrl';
import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer';
import { getPlaybackSourceKind } from '@/features/playback/utils/playback/resolvePlaybackUrl';
import {
bumpPlayGeneration,
getPlayGeneration,
setIsAudioPaused,
} from './engineState';
import { clearPreloadingIds } from './gaplessPreloadState';
import { deriveNormalizationSnapshot } from './normalizationSnapshot';
import type { PlayerState, QueueItemRef } from './playerStoreTypes';
import { resolveQueueTrack } from '../utils/library/queueTrackView';
import { seedQueueResolver } from '../utils/library/queueTrackResolver';
import { canonicalQueueServerKey } from '../utils/server/serverIndexKey';
import { sameQueueTrackId } from '../utils/playback/queueIdentity';
import { queueUndoRestoreAudioEngine } from './queueUndoAudioRestore';
} from '@/features/playback/store/engineState';
import { clearPreloadingIds } from '@/features/playback/store/gaplessPreloadState';
import { deriveNormalizationSnapshot } from '@/features/playback/store/normalizationSnapshot';
import type { PlayerState, QueueItemRef } from '@/features/playback/store/playerStoreTypes';
import { resolveQueueTrack } from '@/utils/library/queueTrackView';
import { seedQueueResolver } from '@/utils/library/queueTrackResolver';
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity';
import { queueUndoRestoreAudioEngine } from '@/features/playback/store/queueUndoAudioRestore';
import {
setPendingQueueListScrollTop,
type QueueUndoSnapshot,
} from './queueUndo';
import { refreshLoudnessForTrack } from './loudnessRefresh';
import { refreshWaveformForTrack } from '@/store/waveformRefresh';
import { stopRadio } from './radioPlayer';
import { clearAllPlaybackScheduleTimers } from './scheduleTimers';
import { syncUserQueueMutationToServer } from './queueSync';
} from '@/features/playback/store/queueUndo';
import { refreshLoudnessForTrack } from '@/features/playback/store/loudnessRefresh';
import { refreshWaveformForTrack } from '@/features/playback/store/waveformRefresh';
import { stopRadio } from '@/features/playback/store/radioPlayer';
import { clearAllPlaybackScheduleTimers } from '@/features/playback/store/scheduleTimers';
import { syncUserQueueMutationToServer } from '@/features/playback/store/queueSync';
type SetState = (
partial: Partial<PlayerState> | ((state: PlayerState) => Partial<PlayerState>),
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { QueueItemRef } from './playerStoreTypes';
import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes';
const getPlayQueueForServerMock = vi.fn();
@@ -7,11 +7,11 @@ vi.mock('@/lib/api/subsonicPlayQueue', () => ({
getPlayQueueForServer: (...args: unknown[]) => getPlayQueueForServerMock(...args),
}));
vi.mock('../utils/server/serverLookup', () => ({
vi.mock('@/utils/server/serverLookup', () => ({
resolveServerIdForIndexKey: (id: string) => id,
}));
vi.mock('../utils/playback/songToTrack', () => ({
vi.mock('@/features/playback/utils/playback/songToTrack', () => ({
songToTrack: (s: { id: string }) => ({
id: s.id,
title: s.id,
@@ -23,15 +23,15 @@ vi.mock('../utils/playback/songToTrack', () => ({
}),
}));
vi.mock('./pausedRestorePrepare', () => ({
vi.mock('@/features/playback/store/pausedRestorePrepare', () => ({
preparePausedRestoreOnStartup: vi.fn(),
}));
vi.mock('@/store/waveformRefresh', () => ({
vi.mock('@/features/playback/store/waveformRefresh', () => ({
refreshWaveformForTrack: vi.fn(),
}));
vi.mock('./queueSyncUiState', () => ({
vi.mock('@/features/playback/store/queueSyncUiState', () => ({
clearQueueHandoffPending: vi.fn(),
}));
@@ -43,7 +43,7 @@ const playerState = {
isPlaying: false,
};
vi.mock('./playerStore', () => ({
vi.mock('@/features/playback/store/playerStore', () => ({
usePlayerStore: {
getState: () => playerState,
setState: (partial: Partial<typeof playerState>) => {
@@ -52,13 +52,13 @@ vi.mock('./playerStore', () => ({
},
}));
import { applyServerPlayQueue } from './applyServerPlayQueue';
import { applyServerPlayQueue } from '@/features/playback/store/applyServerPlayQueue';
import {
_resetQueuePlaybackIdleForTest,
getIdlePullGeneration,
isIdleQueuePullSuspended,
touchQueueMutationClock,
} from './queuePlaybackIdle';
} from '@/features/playback/store/queuePlaybackIdle';
describe('applyServerPlayQueue idle guards', () => {
beforeEach(() => {

Some files were not shown because too many files have changed in this diff Show More