mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-20 12:40:15 +02:00
rewrite entire queue system to native
This commit is contained in:
+97
-21
@@ -6,6 +6,20 @@ import type { PlaybackSource } from '@/types/audio';
|
||||
interface QueueSnapshotOptions {
|
||||
/** Omit to retain the current queue source; pass null when clearing playback. */
|
||||
source?: PlaybackSource | null;
|
||||
/** Omit to retain transport metadata; pass null for an ordinary RNTP queue. */
|
||||
transport?: {
|
||||
sessionId: string;
|
||||
queueRevision: number;
|
||||
windowStart: number;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface QueueTransportState {
|
||||
sessionId: string;
|
||||
queueRevision: number;
|
||||
windowStart: number;
|
||||
activeLocalIndex: number;
|
||||
tracks: RntpTrack[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -18,6 +32,7 @@ interface QueueStore {
|
||||
activeIndex: number;
|
||||
hasSnapshot: boolean;
|
||||
source: PlaybackSource | null;
|
||||
transport: QueueTransportState | null;
|
||||
refreshFromNative: () => Promise<void>;
|
||||
refreshActiveIndex: () => Promise<void>;
|
||||
setSnapshot: (
|
||||
@@ -47,53 +62,100 @@ export const useQueueStore = create<QueueStore>((set) => ({
|
||||
activeIndex: -1,
|
||||
hasSnapshot: false,
|
||||
source: null,
|
||||
transport: null,
|
||||
refreshFromNative: async () => {
|
||||
// Mid chunked-load the native queue is partial and index-shifted — wait it out.
|
||||
// Mid chunked-load the native queue is partial — wait until all appends land.
|
||||
await queueLoadSettled();
|
||||
const [tracks, activeIndex] = await Promise.all([
|
||||
TrackPlayer.getQueue(),
|
||||
TrackPlayer.getActiveTrackIndex(),
|
||||
]);
|
||||
set({
|
||||
tracks,
|
||||
activeIndex: normalizeActiveIndex(activeIndex, tracks.length),
|
||||
hasSnapshot: true,
|
||||
set((state) => {
|
||||
const normalized = normalizeActiveIndex(activeIndex, tracks.length);
|
||||
return {
|
||||
tracks,
|
||||
activeIndex: normalized,
|
||||
hasSnapshot: true,
|
||||
transport: state.transport
|
||||
? { ...state.transport, activeLocalIndex: normalized, tracks }
|
||||
: null,
|
||||
};
|
||||
});
|
||||
},
|
||||
refreshActiveIndex: async () => {
|
||||
const activeIndex = await TrackPlayer.getActiveTrackIndex();
|
||||
set((s) => ({
|
||||
activeIndex: normalizeActiveIndex(
|
||||
set((s) => {
|
||||
const normalized = normalizeActiveIndex(
|
||||
activeIndex == null ? activeIndex : nativeIndexToAbsolute(activeIndex),
|
||||
s.tracks.length,
|
||||
),
|
||||
}));
|
||||
);
|
||||
return {
|
||||
activeIndex: normalized,
|
||||
transport: s.transport
|
||||
? { ...s.transport, activeLocalIndex: normalized, tracks: s.tracks }
|
||||
: null,
|
||||
};
|
||||
});
|
||||
},
|
||||
setSnapshot: (tracks, activeIndex = 0, options) =>
|
||||
set((state) => ({
|
||||
tracks,
|
||||
activeIndex: normalizeActiveIndex(activeIndex, tracks.length),
|
||||
hasSnapshot: true,
|
||||
source: options && Object.hasOwn(options, 'source')
|
||||
? options.source ?? null
|
||||
: state.source,
|
||||
})),
|
||||
set((state) => {
|
||||
const normalized = normalizeActiveIndex(activeIndex, tracks.length);
|
||||
const transport = options && Object.hasOwn(options, 'transport')
|
||||
? options.transport
|
||||
? {
|
||||
...options.transport,
|
||||
activeLocalIndex: normalized,
|
||||
tracks,
|
||||
}
|
||||
: null
|
||||
: state.transport
|
||||
? { ...state.transport, activeLocalIndex: normalized, tracks }
|
||||
: null;
|
||||
return {
|
||||
tracks,
|
||||
activeIndex: normalized,
|
||||
hasSnapshot: true,
|
||||
source: options && Object.hasOwn(options, 'source')
|
||||
? options.source ?? null
|
||||
: state.source,
|
||||
transport,
|
||||
};
|
||||
}),
|
||||
setActiveIndex: (activeIndex) =>
|
||||
set((s) => ({ activeIndex: normalizeActiveIndex(activeIndex, s.tracks.length) })),
|
||||
set((s) => {
|
||||
const normalized = normalizeActiveIndex(activeIndex, s.tracks.length);
|
||||
return {
|
||||
activeIndex: normalized,
|
||||
transport: s.transport
|
||||
? { ...s.transport, activeLocalIndex: normalized, tracks: s.tracks }
|
||||
: null,
|
||||
};
|
||||
}),
|
||||
insertTrack: (track, index) =>
|
||||
set((s) => {
|
||||
const insertAt = boundedInsertIndex(index, s.tracks.length);
|
||||
const tracks = [...s.tracks];
|
||||
tracks.splice(insertAt, 0, track);
|
||||
const activeIndex = s.activeIndex >= insertAt ? s.activeIndex + 1 : s.activeIndex;
|
||||
return { tracks, activeIndex, hasSnapshot: true };
|
||||
return {
|
||||
tracks,
|
||||
activeIndex,
|
||||
hasSnapshot: true,
|
||||
transport: s.transport
|
||||
? { ...s.transport, activeLocalIndex: activeIndex, tracks }
|
||||
: null,
|
||||
};
|
||||
}),
|
||||
replaceUpcoming: (upcoming) =>
|
||||
set((s) => {
|
||||
const prefixEnd = s.activeIndex >= 0 ? s.activeIndex + 1 : 0;
|
||||
const tracks = [...s.tracks.slice(0, prefixEnd), ...upcoming];
|
||||
return {
|
||||
tracks: [...s.tracks.slice(0, prefixEnd), ...upcoming],
|
||||
tracks,
|
||||
hasSnapshot: true,
|
||||
transport: s.transport
|
||||
? { ...s.transport, tracks }
|
||||
: null,
|
||||
};
|
||||
}),
|
||||
moveItem: (fromIndex, toIndex) =>
|
||||
@@ -114,7 +176,14 @@ export const useQueueStore = create<QueueStore>((set) => ({
|
||||
activeIndex += 1;
|
||||
}
|
||||
|
||||
return { tracks, activeIndex, hasSnapshot: true };
|
||||
return {
|
||||
tracks,
|
||||
activeIndex,
|
||||
hasSnapshot: true,
|
||||
transport: s.transport
|
||||
? { ...s.transport, activeLocalIndex: activeIndex, tracks }
|
||||
: null,
|
||||
};
|
||||
}),
|
||||
removeIndices: (indices) =>
|
||||
set((s) => {
|
||||
@@ -143,6 +212,13 @@ export const useQueueStore = create<QueueStore>((set) => ({
|
||||
tracks,
|
||||
activeIndex: normalizeActiveIndex(activeIndex, tracks.length),
|
||||
hasSnapshot: true,
|
||||
transport: s.transport
|
||||
? {
|
||||
...s.transport,
|
||||
activeLocalIndex: normalizeActiveIndex(activeIndex, tracks.length),
|
||||
tracks,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -29,6 +29,7 @@ const LYRICS_VISIBLE_KEY = 'lyrics_visible';
|
||||
const NOW_PLAYING_COMPANION_KEY = 'now_playing_companion';
|
||||
const HOME_GREETING_TEXT_MODE_KEY = 'home_greeting_text_mode';
|
||||
const LISTENING_HISTORY_ENABLED_KEY = 'listening_history_enabled';
|
||||
const NATIVE_QUEUE_ENABLED_KEY = 'native_queue_enabled';
|
||||
|
||||
/** Which visualizer the now-playing scope stage shows. */
|
||||
export type ScopeMode = 'spectrum' | 'scope';
|
||||
@@ -68,6 +69,7 @@ interface SettingsStore {
|
||||
nowPlayingCompanion: NowPlayingCompanion;
|
||||
homeGreetingTextMode: HomeGreetingTextMode;
|
||||
listeningHistoryEnabled: boolean;
|
||||
nativeQueueEnabled: boolean;
|
||||
loaded: boolean;
|
||||
load: () => Promise<void>;
|
||||
setArtistGroupingMode: (mode: ArtistGroupingMode) => Promise<void>;
|
||||
@@ -79,6 +81,7 @@ interface SettingsStore {
|
||||
setNowPlayingCompanion: (companion: NowPlayingCompanion) => Promise<void>;
|
||||
setHomeGreetingTextMode: (mode: HomeGreetingTextMode) => Promise<void>;
|
||||
setListeningHistoryEnabled: (enabled: boolean) => Promise<void>;
|
||||
setNativeQueueEnabled: (enabled: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
@@ -91,6 +94,7 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
nowPlayingCompanion: 'queue',
|
||||
homeGreetingTextMode: 'messages',
|
||||
listeningHistoryEnabled: true,
|
||||
nativeQueueEnabled: false,
|
||||
loaded: false,
|
||||
|
||||
load: async () => {
|
||||
@@ -106,6 +110,7 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
NOW_PLAYING_COMPANION_KEY,
|
||||
HOME_GREETING_TEXT_MODE_KEY,
|
||||
LISTENING_HISTORY_ENABLED_KEY,
|
||||
NATIVE_QUEUE_ENABLED_KEY,
|
||||
]);
|
||||
const grouping = values[ARTIST_GROUPING_KEY] ?? null;
|
||||
const includeSingles = values[INCLUDE_SINGLES_KEY] ?? null;
|
||||
@@ -116,6 +121,7 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
const nowPlayingCompanion = values[NOW_PLAYING_COMPANION_KEY] ?? null;
|
||||
const homeGreetingTextMode = values[HOME_GREETING_TEXT_MODE_KEY] ?? null;
|
||||
const listeningHistoryEnabled = values[LISTENING_HISTORY_ENABLED_KEY] !== '0';
|
||||
const nativeQueueEnabled = parseBoolean(values[NATIVE_QUEUE_ENABLED_KEY] ?? null);
|
||||
set({
|
||||
artistGroupingMode: parseGroupingMode(grouping),
|
||||
includeSingles: parseBoolean(includeSingles),
|
||||
@@ -126,6 +132,7 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
nowPlayingCompanion: parseNowPlayingCompanion(nowPlayingCompanion),
|
||||
homeGreetingTextMode: parseHomeGreetingTextMode(homeGreetingTextMode),
|
||||
listeningHistoryEnabled,
|
||||
nativeQueueEnabled,
|
||||
loaded: true,
|
||||
});
|
||||
},
|
||||
@@ -194,4 +201,12 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
setNativeQueueEnabled: async (enabled) => {
|
||||
if (get().nativeQueueEnabled === enabled) return;
|
||||
set({ nativeQueueEnabled: enabled });
|
||||
await AstraLibraryData.setSettings({
|
||||
[NATIVE_QUEUE_ENABLED_KEY]: enabled ? 'true' : 'false',
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user