mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 23:59:08 +00:00
fix: improve downloaded music queue
Some checks failed
🏗️ Build Apps / 🤖 Build Android APK (Phone) (push) Has been cancelled
🏗️ Build Apps / 🤖 Build Android APK (TV) (push) Has been cancelled
🏗️ Build Apps / 🍎 Build iOS IPA (Phone) (push) Has been cancelled
🔒 Lockfile Consistency Check / 🔍 Check bun.lock and package.json consistency (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (actions) (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (javascript-typescript) (push) Has been cancelled
🏷️🔀Merge Conflict Labeler / 🏷️ Labeling Merge Conflicts (push) Has been cancelled
🌐 Translation Sync / sync-translations (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (lint) (push) Has been cancelled
🚦 Security & Quality Gate / 📝 Validate PR Title (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Vulnerable Dependencies (push) Has been cancelled
🚦 Security & Quality Gate / 🚑 Expo Doctor Check (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (check) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (format) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (typecheck) (push) Has been cancelled
Some checks failed
🏗️ Build Apps / 🤖 Build Android APK (Phone) (push) Has been cancelled
🏗️ Build Apps / 🤖 Build Android APK (TV) (push) Has been cancelled
🏗️ Build Apps / 🍎 Build iOS IPA (Phone) (push) Has been cancelled
🔒 Lockfile Consistency Check / 🔍 Check bun.lock and package.json consistency (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (actions) (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (javascript-typescript) (push) Has been cancelled
🏷️🔀Merge Conflict Labeler / 🏷️ Labeling Merge Conflicts (push) Has been cancelled
🌐 Translation Sync / sync-translations (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (lint) (push) Has been cancelled
🚦 Security & Quality Gate / 📝 Validate PR Title (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Vulnerable Dependencies (push) Has been cancelled
🚦 Security & Quality Gate / 🚑 Expo Doctor Check (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (check) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (format) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (typecheck) (push) Has been cancelled
This commit is contained in:
@@ -29,6 +29,7 @@ import {
|
|||||||
setMaxCacheSizeMB,
|
setMaxCacheSizeMB,
|
||||||
} from "@/providers/AudioStorage";
|
} from "@/providers/AudioStorage";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
|
import { useNetworkStatus } from "@/providers/NetworkStatusProvider";
|
||||||
import { settingsAtom } from "@/utils/atoms/settings";
|
import { settingsAtom } from "@/utils/atoms/settings";
|
||||||
import { getAudioStreamUrl } from "@/utils/jellyfin/audio/getAudioStreamUrl";
|
import { getAudioStreamUrl } from "@/utils/jellyfin/audio/getAudioStreamUrl";
|
||||||
import { storage } from "@/utils/mmkv";
|
import { storage } from "@/utils/mmkv";
|
||||||
@@ -49,6 +50,14 @@ interface TrackMediaInfo {
|
|||||||
isTranscoding: boolean;
|
isTranscoding: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PreparedTrack {
|
||||||
|
track: Track;
|
||||||
|
sessionId: string | null;
|
||||||
|
mediaSource: MediaSourceInfo | null;
|
||||||
|
isTranscoding: boolean;
|
||||||
|
mediaInfo: TrackMediaInfo | null;
|
||||||
|
}
|
||||||
|
|
||||||
interface MusicPlayerState {
|
interface MusicPlayerState {
|
||||||
currentTrack: BaseItemDto | null;
|
currentTrack: BaseItemDto | null;
|
||||||
queue: BaseItemDto[];
|
queue: BaseItemDto[];
|
||||||
@@ -214,6 +223,20 @@ const shuffleArray = <T,>(array: T[], currentIndex: number): T[] => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Filter queue to only include downloaded items (for offline playback)
|
||||||
|
const filterQueueForOffline = (
|
||||||
|
queue: BaseItemDto[],
|
||||||
|
startIndex: number,
|
||||||
|
): { queue: BaseItemDto[]; startIndex: number } => {
|
||||||
|
const startItem = queue[startIndex];
|
||||||
|
const downloadedOnly = queue.filter((item) => getLocalPath(item.Id) !== null);
|
||||||
|
const newStartIndex = downloadedOnly.findIndex((t) => t.Id === startItem?.Id);
|
||||||
|
return {
|
||||||
|
queue: downloadedOnly,
|
||||||
|
startIndex: newStartIndex >= 0 ? newStartIndex : 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Convert BaseItemDto to TrackPlayer Track
|
// Convert BaseItemDto to TrackPlayer Track
|
||||||
const itemToTrack = (
|
const itemToTrack = (
|
||||||
item: BaseItemDto,
|
item: BaseItemDto,
|
||||||
@@ -255,6 +278,8 @@ export const MusicPlayerProvider: React.FC<MusicPlayerProviderProps> = ({
|
|||||||
const api = useAtomValue(apiAtom);
|
const api = useAtomValue(apiAtom);
|
||||||
const user = useAtomValue(userAtom);
|
const user = useAtomValue(userAtom);
|
||||||
const settings = useAtomValue(settingsAtom);
|
const settings = useAtomValue(settingsAtom);
|
||||||
|
const { isConnected, serverConnected } = useNetworkStatus();
|
||||||
|
const isOffline = !isConnected || serverConnected === false;
|
||||||
const initializedRef = useRef(false);
|
const initializedRef = useRef(false);
|
||||||
const playerSetupRef = useRef(false);
|
const playerSetupRef = useRef(false);
|
||||||
|
|
||||||
@@ -466,71 +491,139 @@ export const MusicPlayerProvider: React.FC<MusicPlayerProviderProps> = ({
|
|||||||
[api, user?.Id],
|
[api, user?.Id],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Helper to prepare a single track - checks cache first, then fetches from server
|
||||||
|
const prepareTrack = useCallback(
|
||||||
|
async (
|
||||||
|
item: BaseItemDto,
|
||||||
|
preferLocal: boolean,
|
||||||
|
): Promise<PreparedTrack | null> => {
|
||||||
|
if (!api || !user?.Id || !item.Id) return null;
|
||||||
|
|
||||||
|
// Check for local/cached version first
|
||||||
|
const cachedUrl = preferLocal ? getLocalPath(item.Id) : null;
|
||||||
|
|
||||||
|
if (cachedUrl) {
|
||||||
|
// Downloaded track - instant return, no API call needed
|
||||||
|
return {
|
||||||
|
track: itemToTrack(item, cachedUrl, api, false), // false to avoid redundant cache check
|
||||||
|
sessionId: null,
|
||||||
|
mediaSource: null,
|
||||||
|
isTranscoding: false,
|
||||||
|
mediaInfo: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not downloaded - need to fetch stream URL from server
|
||||||
|
try {
|
||||||
|
const result = await getAudioStreamUrl(api, user.Id, item.Id);
|
||||||
|
if (!result) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
track: itemToTrack(item, result.url, api, false),
|
||||||
|
sessionId: result.sessionId,
|
||||||
|
mediaSource: result.mediaSource,
|
||||||
|
isTranscoding: result.isTranscoding,
|
||||||
|
mediaInfo: {
|
||||||
|
mediaSource: result.mediaSource,
|
||||||
|
isTranscoding: result.isTranscoding,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
`[MusicPlayer] Failed to prepare track ${item.Id}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
// If server unreachable, check for cached version as fallback
|
||||||
|
const fallbackCached = getLocalPath(item.Id);
|
||||||
|
if (fallbackCached) {
|
||||||
|
return {
|
||||||
|
track: itemToTrack(item, fallbackCached, api, false),
|
||||||
|
sessionId: null,
|
||||||
|
mediaSource: null,
|
||||||
|
isTranscoding: false,
|
||||||
|
mediaInfo: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[api, user?.Id],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load remaining tracks in the background without blocking playback
|
||||||
|
const loadRemainingTracksInBackground = useCallback(
|
||||||
|
async (queue: BaseItemDto[], startIndex: number, preferLocal: boolean) => {
|
||||||
|
if (!api || !user?.Id) return;
|
||||||
|
|
||||||
|
const mediaInfoMap: Record<string, TrackMediaInfo> = {};
|
||||||
|
|
||||||
|
// Process tracks BEFORE the start index (insert at position 0, pushing current track forward)
|
||||||
|
const beforeTracks: Track[] = [];
|
||||||
|
for (let i = 0; i < startIndex; i++) {
|
||||||
|
const item = queue[i];
|
||||||
|
if (!item.Id) continue;
|
||||||
|
|
||||||
|
const prepared = await prepareTrack(item, preferLocal);
|
||||||
|
if (prepared) {
|
||||||
|
beforeTracks.push(prepared.track);
|
||||||
|
if (prepared.mediaInfo) {
|
||||||
|
mediaInfoMap[item.Id] = prepared.mediaInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert tracks before current track (they go at index 0)
|
||||||
|
if (beforeTracks.length > 0) {
|
||||||
|
await TrackPlayer.add(beforeTracks, 0);
|
||||||
|
// Update queue index since we inserted tracks before the current one
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
queueIndex: beforeTracks.length,
|
||||||
|
trackMediaInfoMap: { ...prev.trackMediaInfoMap, ...mediaInfoMap },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process tracks AFTER the start index (append to end)
|
||||||
|
for (let i = startIndex + 1; i < queue.length; i++) {
|
||||||
|
const item = queue[i];
|
||||||
|
if (!item.Id) continue;
|
||||||
|
|
||||||
|
const prepared = await prepareTrack(item, preferLocal);
|
||||||
|
if (prepared) {
|
||||||
|
await TrackPlayer.add(prepared.track); // Append to end
|
||||||
|
if (prepared.mediaInfo && item.Id) {
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
trackMediaInfoMap: {
|
||||||
|
...prev.trackMediaInfoMap,
|
||||||
|
[item.Id!]: prepared.mediaInfo!,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[api, user?.Id, prepareTrack],
|
||||||
|
);
|
||||||
|
|
||||||
const loadAndPlayQueue = useCallback(
|
const loadAndPlayQueue = useCallback(
|
||||||
async (queue: BaseItemDto[], startIndex: number) => {
|
async (queue: BaseItemDto[], startIndex: number) => {
|
||||||
if (!api || !user?.Id || queue.length === 0) return;
|
if (!api || !user?.Id || queue.length === 0) return;
|
||||||
|
|
||||||
const trackToLoad = queue[startIndex];
|
const targetItem = queue[startIndex];
|
||||||
setState((prev) => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
loadingTrackId: trackToLoad?.Id ?? null,
|
loadingTrackId: targetItem?.Id ?? null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get stream URLs for all tracks
|
|
||||||
const tracks: Track[] = [];
|
|
||||||
const mediaInfoMap: Record<string, TrackMediaInfo> = {};
|
|
||||||
let startTrackMediaSource: MediaSourceInfo | null = null;
|
|
||||||
let startTrackIsTranscoding = false;
|
|
||||||
|
|
||||||
const preferLocal = settings?.preferLocalAudio ?? true;
|
const preferLocal = settings?.preferLocalAudio ?? true;
|
||||||
|
|
||||||
for (let i = 0; i < queue.length; i++) {
|
// PHASE 1: Prepare and play the target track immediately
|
||||||
const item = queue[i];
|
const targetTrackResult = await prepareTrack(targetItem, preferLocal);
|
||||||
if (!item.Id) continue;
|
|
||||||
|
|
||||||
// Check for cached/downloaded version
|
if (!targetTrackResult) {
|
||||||
const cachedUrl = getLocalPath(item.Id);
|
|
||||||
|
|
||||||
// If preferLocal and we have a local file, use it directly without server request
|
|
||||||
if (preferLocal && cachedUrl) {
|
|
||||||
tracks.push(itemToTrack(item, cachedUrl, api, true));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to get stream URL from server
|
|
||||||
const result = await getAudioStreamUrl(api, user.Id, item.Id);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
tracks.push(itemToTrack(item, result.url, api, preferLocal));
|
|
||||||
// Store media info for all tracks
|
|
||||||
mediaInfoMap[item.Id] = {
|
|
||||||
mediaSource: result.mediaSource,
|
|
||||||
isTranscoding: result.isTranscoding,
|
|
||||||
};
|
|
||||||
// Store first track's session ID
|
|
||||||
if (tracks.length === 1) {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
playSessionId: result.sessionId,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
// Store media source info for the starting track
|
|
||||||
if (i === startIndex) {
|
|
||||||
startTrackMediaSource = result.mediaSource;
|
|
||||||
startTrackIsTranscoding = result.isTranscoding;
|
|
||||||
}
|
|
||||||
} else if (cachedUrl) {
|
|
||||||
// Fallback to cached version if server is unreachable
|
|
||||||
console.log(
|
|
||||||
`[MusicPlayer] Using cached file (offline) for ${item.Name}: ${cachedUrl}`,
|
|
||||||
);
|
|
||||||
tracks.push(itemToTrack(item, cachedUrl, api, true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tracks.length === 0) {
|
|
||||||
setState((prev) => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
@@ -539,32 +632,40 @@ export const MusicPlayerProvider: React.FC<MusicPlayerProviderProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset TrackPlayer and add new queue
|
// Reset and start playback immediately with just the target track
|
||||||
await TrackPlayer.reset();
|
await TrackPlayer.reset();
|
||||||
await TrackPlayer.add(tracks);
|
await TrackPlayer.add(targetTrackResult.track);
|
||||||
await TrackPlayer.skip(startIndex);
|
|
||||||
await TrackPlayer.play();
|
await TrackPlayer.play();
|
||||||
|
|
||||||
const currentTrack = queue[startIndex];
|
// Update state for immediate playback
|
||||||
setState((prev) => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
queue,
|
queue,
|
||||||
originalQueue: queue,
|
originalQueue: queue,
|
||||||
queueIndex: startIndex,
|
queueIndex: 0, // Target track is at index 0 in TrackPlayer initially
|
||||||
currentTrack,
|
currentTrack: targetItem,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
loadingTrackId: null,
|
loadingTrackId: null,
|
||||||
isPlaying: true,
|
isPlaying: true,
|
||||||
streamUrl: tracks[startIndex]?.url || null,
|
streamUrl: targetTrackResult.track.url || null,
|
||||||
duration: currentTrack?.RunTimeTicks
|
playSessionId: targetTrackResult.sessionId,
|
||||||
? Math.floor(currentTrack.RunTimeTicks / 10000000)
|
duration: targetItem?.RunTimeTicks
|
||||||
|
? Math.floor(targetItem.RunTimeTicks / 10000000)
|
||||||
: 0,
|
: 0,
|
||||||
mediaSource: startTrackMediaSource,
|
mediaSource: targetTrackResult.mediaSource,
|
||||||
isTranscoding: startTrackIsTranscoding,
|
isTranscoding: targetTrackResult.isTranscoding,
|
||||||
trackMediaInfoMap: mediaInfoMap,
|
trackMediaInfoMap:
|
||||||
|
targetTrackResult.mediaInfo && targetItem.Id
|
||||||
|
? { [targetItem.Id]: targetTrackResult.mediaInfo }
|
||||||
|
: {},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
reportPlaybackStart(currentTrack, state.playSessionId);
|
reportPlaybackStart(targetItem, targetTrackResult.sessionId);
|
||||||
|
|
||||||
|
// PHASE 2: Load remaining tracks in background (non-blocking)
|
||||||
|
if (queue.length > 1) {
|
||||||
|
loadRemainingTracksInBackground(queue, startIndex, preferLocal);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[MusicPlayer] Error loading queue:", error);
|
console.error("[MusicPlayer] Error loading queue:", error);
|
||||||
setState((prev) => ({
|
setState((prev) => ({
|
||||||
@@ -574,7 +675,14 @@ export const MusicPlayerProvider: React.FC<MusicPlayerProviderProps> = ({
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, user?.Id, reportPlaybackStart, state.playSessionId],
|
[
|
||||||
|
api,
|
||||||
|
user?.Id,
|
||||||
|
reportPlaybackStart,
|
||||||
|
settings?.preferLocalAudio,
|
||||||
|
prepareTrack,
|
||||||
|
loadRemainingTracksInBackground,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const playTrack = useCallback(
|
const playTrack = useCallback(
|
||||||
@@ -615,9 +723,23 @@ export const MusicPlayerProvider: React.FC<MusicPlayerProviderProps> = ({
|
|||||||
let finalQueue = queue;
|
let finalQueue = queue;
|
||||||
let finalIndex = startIndex;
|
let finalIndex = startIndex;
|
||||||
|
|
||||||
|
// When offline, filter to downloaded items only
|
||||||
|
if (isOffline) {
|
||||||
|
const filtered = filterQueueForOffline(queue, startIndex);
|
||||||
|
finalQueue = filtered.queue;
|
||||||
|
finalIndex = filtered.startIndex;
|
||||||
|
|
||||||
|
if (finalQueue.length === 0) {
|
||||||
|
console.warn(
|
||||||
|
"[MusicPlayer] No downloaded tracks available for offline playback",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Apply shuffle if enabled
|
// Apply shuffle if enabled
|
||||||
if (state.shuffleEnabled) {
|
if (state.shuffleEnabled) {
|
||||||
finalQueue = shuffleArray(queue, startIndex);
|
finalQueue = shuffleArray(finalQueue, finalIndex);
|
||||||
finalIndex = 0;
|
finalIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,6 +752,7 @@ export const MusicPlayerProvider: React.FC<MusicPlayerProviderProps> = ({
|
|||||||
state.shuffleEnabled,
|
state.shuffleEnabled,
|
||||||
reportPlaybackStopped,
|
reportPlaybackStopped,
|
||||||
loadAndPlayQueue,
|
loadAndPlayQueue,
|
||||||
|
isOffline,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1158,6 +1281,50 @@ export const MusicPlayerProvider: React.FC<MusicPlayerProviderProps> = ({
|
|||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Check if the track exists in TrackPlayer queue (might not be loaded yet due to background loading)
|
||||||
|
const tpQueue = await TrackPlayer.getQueue();
|
||||||
|
const targetItem = state.queue[index];
|
||||||
|
|
||||||
|
if (index >= tpQueue.length) {
|
||||||
|
// Track not loaded yet - need to load it first
|
||||||
|
if (!targetItem) return;
|
||||||
|
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
isLoading: true,
|
||||||
|
loadingTrackId: targetItem?.Id ?? null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const preferLocal = settings?.preferLocalAudio ?? true;
|
||||||
|
const prepared = await prepareTrack(targetItem, preferLocal);
|
||||||
|
|
||||||
|
if (!prepared) {
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
isLoading: false,
|
||||||
|
loadingTrackId: null,
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the track at the correct position
|
||||||
|
await TrackPlayer.add(prepared.track, index);
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
isLoading: false,
|
||||||
|
loadingTrackId: null,
|
||||||
|
...(prepared.mediaInfo && targetItem.Id
|
||||||
|
? {
|
||||||
|
trackMediaInfoMap: {
|
||||||
|
...prev.trackMediaInfoMap,
|
||||||
|
[targetItem.Id]: prepared.mediaInfo,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report stop for current track
|
||||||
if (state.currentTrack && state.playSessionId) {
|
if (state.currentTrack && state.playSessionId) {
|
||||||
reportPlaybackStopped(
|
reportPlaybackStopped(
|
||||||
state.currentTrack,
|
state.currentTrack,
|
||||||
@@ -1169,14 +1336,13 @@ export const MusicPlayerProvider: React.FC<MusicPlayerProviderProps> = ({
|
|||||||
await TrackPlayer.skip(index);
|
await TrackPlayer.skip(index);
|
||||||
|
|
||||||
setState((prev) => {
|
setState((prev) => {
|
||||||
const targetTrack = prev.queue[index];
|
const mediaInfo = targetItem?.Id
|
||||||
const mediaInfo = targetTrack?.Id
|
? prev.trackMediaInfoMap[targetItem.Id]
|
||||||
? prev.trackMediaInfoMap[targetTrack.Id]
|
|
||||||
: null;
|
: null;
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
queueIndex: index,
|
queueIndex: index,
|
||||||
currentTrack: targetTrack,
|
currentTrack: targetItem,
|
||||||
mediaSource: mediaInfo?.mediaSource ?? null,
|
mediaSource: mediaInfo?.mediaSource ?? null,
|
||||||
isTranscoding: mediaInfo?.isTranscoding ?? false,
|
isTranscoding: mediaInfo?.isTranscoding ?? false,
|
||||||
};
|
};
|
||||||
@@ -1189,6 +1355,8 @@ export const MusicPlayerProvider: React.FC<MusicPlayerProviderProps> = ({
|
|||||||
state.playSessionId,
|
state.playSessionId,
|
||||||
state.progress,
|
state.progress,
|
||||||
reportPlaybackStopped,
|
reportPlaybackStopped,
|
||||||
|
settings?.preferLocalAudio,
|
||||||
|
prepareTrack,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user