mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-21 21:16:09 +02:00
fix repeating end of queue bug
This commit is contained in:
+9
@@ -171,6 +171,15 @@ class RoomLibraryRepositoryTest {
|
||||
assertEquals(2L, dao.countQueueEntries(session.id))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun playbackWindowNeverClampsPastTheEndBackToTheLastTrack() {
|
||||
assertEquals(0L, boundedPlaybackWindowStart(-10, 3))
|
||||
assertEquals(2L, boundedPlaybackWindowStart(2, 3))
|
||||
assertNull(boundedPlaybackWindowStart(3, 3))
|
||||
assertNull(boundedPlaybackWindowStart(99, 3))
|
||||
assertNull(boundedPlaybackWindowStart(0, 0))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun userSnapshotsRotateRejectDamageAndRestoreTheNewestValidCopy() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
|
||||
+13
-3
@@ -37,6 +37,11 @@ private const val ACTIVE_PLAYBACK_CONTEXT_ID = "active-context"
|
||||
|
||||
class StaleRevisionException : IllegalStateException("STALE_REVISION")
|
||||
|
||||
internal fun boundedPlaybackWindowStart(start: Long, total: Long): Long? {
|
||||
val normalized = start.coerceAtLeast(0)
|
||||
return normalized.takeIf { it < total }
|
||||
}
|
||||
|
||||
private data class RemoteSyncHandle(
|
||||
val syncId: String,
|
||||
val sourceKey: String,
|
||||
@@ -2208,8 +2213,13 @@ class AstraLibraryRepository private constructor(
|
||||
val session = userDao.getPlaybackSession(sessionId)
|
||||
?: error("Playback context $sessionId does not exist")
|
||||
val total = userDao.countQueueEntries(sessionId)
|
||||
val boundedStart = if (total == 0L) 0L else start.coerceIn(0, total - 1)
|
||||
val entries = userDao.getQueueWindow(sessionId, boundedStart, limit)
|
||||
val requestedStart = start.coerceAtLeast(0)
|
||||
val boundedStart = boundedPlaybackWindowStart(requestedStart, total)
|
||||
val entries = if (boundedStart == null) {
|
||||
emptyList()
|
||||
} else {
|
||||
userDao.getQueueWindow(sessionId, boundedStart, limit)
|
||||
}
|
||||
val tracks = LinkedHashMap<String, ActiveTrackView>()
|
||||
for (chunk in entries.map(PlaybackQueueEntryEntity::trackPath).distinct().chunked(MAX_PAGE_SIZE)) {
|
||||
requireCatalog().catalogDao().getActiveTracks(chunk).forEach { tracks[it.path] = it }
|
||||
@@ -2222,7 +2232,7 @@ class AstraLibraryRepository private constructor(
|
||||
return mapOf(
|
||||
"sessionId" to session.id,
|
||||
"items" to items,
|
||||
"windowStart" to boundedStart.toDouble(),
|
||||
"windowStart" to (boundedStart ?: requestedStart).toDouble(),
|
||||
"activePosition" to session.activePosition.toDouble(),
|
||||
"totalCount" to total.toDouble(),
|
||||
"contextJson" to session.contextJson,
|
||||
|
||||
@@ -76,6 +76,15 @@ export interface PlaybackStartOptions {
|
||||
source: PlaybackSource;
|
||||
}
|
||||
|
||||
function toVirtualRntpTrack(
|
||||
item: DbTrack & { queuePosition: number },
|
||||
): RntpTrack {
|
||||
return {
|
||||
...toRntpTrack(dbTrackToTrack(item)),
|
||||
astraQueuePosition: item.queuePosition,
|
||||
};
|
||||
}
|
||||
|
||||
const NEXT_REPEAT: Record<RepeatModeStr, RepeatModeStr> = {
|
||||
none: 'all',
|
||||
all: 'one',
|
||||
@@ -337,7 +346,7 @@ export function restoreVirtualPlaybackContext(
|
||||
restorePlaybackSession(null);
|
||||
return;
|
||||
}
|
||||
const queueTracks = tracks.map(toRntpTrack);
|
||||
const queueTracks = window.items.map(toVirtualRntpTrack);
|
||||
const activeIndex = Math.max(
|
||||
0,
|
||||
Math.min(queueTracks.length - 1, window.activePosition - window.windowStart),
|
||||
@@ -426,7 +435,7 @@ async function startVirtualWindow(
|
||||
shuffle: boolean,
|
||||
): Promise<void> {
|
||||
const tracks = window.items.map(dbTrackToTrack);
|
||||
const queueTracks = tracks.map(toRntpTrack);
|
||||
const queueTracks = window.items.map(toVirtualRntpTrack);
|
||||
const startIndex = Math.max(
|
||||
0,
|
||||
Math.min(queueTracks.length - 1, window.activePosition - window.windowStart),
|
||||
@@ -496,14 +505,22 @@ async function replenishVirtualContext(): Promise<void> {
|
||||
100,
|
||||
);
|
||||
if (virtualContext !== context || next.items.length === 0) return;
|
||||
const additions = next.items.map(dbTrackToTrack).map(toRntpTrack);
|
||||
const additions = next.items
|
||||
.filter((item) => (
|
||||
item.queuePosition >= context.loadedEnd &&
|
||||
item.queuePosition < context.totalCount
|
||||
))
|
||||
.map(toVirtualRntpTrack);
|
||||
if (additions.length === 0) return;
|
||||
const nextLoadedEnd = Number(additions[additions.length - 1].astraQueuePosition) + 1;
|
||||
if (!Number.isFinite(nextLoadedEnd) || nextLoadedEnd <= context.loadedEnd) return;
|
||||
const before = useQueueStore.getState();
|
||||
await appendUpcomingChunked(additions, before.tracks.length);
|
||||
useQueueStore.getState().setSnapshot(
|
||||
[...before.tracks, ...additions],
|
||||
localIndex,
|
||||
);
|
||||
context.loadedEnd = next.items[next.items.length - 1].queuePosition + 1;
|
||||
context.loadedEnd = Math.min(context.totalCount, nextLoadedEnd);
|
||||
}
|
||||
|
||||
/** Returns a bounded page from the native virtual queue, or null for ordinary queues. */
|
||||
@@ -519,12 +536,15 @@ export async function getVirtualQueuePage(
|
||||
Math.max(1, Math.min(100, limit)),
|
||||
);
|
||||
if (virtualContext !== context) return null;
|
||||
const boundedStart = Math.max(0, start);
|
||||
return {
|
||||
items: window.items.map((item) => ({
|
||||
track: {
|
||||
...toRntpTrack(dbTrackToTrack(item)),
|
||||
astraQueuePosition: item.queuePosition,
|
||||
},
|
||||
items: window.items
|
||||
.filter((item) => (
|
||||
item.queuePosition >= boundedStart &&
|
||||
item.queuePosition < window.totalCount
|
||||
))
|
||||
.map((item) => ({
|
||||
track: toVirtualRntpTrack(item),
|
||||
queuePosition: item.queuePosition,
|
||||
})),
|
||||
activePosition: window.activePosition,
|
||||
@@ -599,8 +619,7 @@ async function mutateVirtualQueue(
|
||||
const boundedActive = Math.max(0, activeLocal);
|
||||
const upcoming = window.items
|
||||
.filter((item) => item.queuePosition > window.activePosition)
|
||||
.map(dbTrackToTrack)
|
||||
.map(toRntpTrack);
|
||||
.map(toVirtualRntpTrack);
|
||||
const before = useQueueStore.getState();
|
||||
const prefix = before.tracks.slice(0, boundedActive + 1);
|
||||
|
||||
|
||||
@@ -212,7 +212,6 @@ export const QueueTray = memo(function QueueTray({ onClose, embedded = false }:
|
||||
const loadVirtualPage = useCallback(async (reset: boolean) => {
|
||||
const state = getVirtualQueueState();
|
||||
if (!state || (!reset && virtualLoading.current)) return;
|
||||
virtualLoading.current = true;
|
||||
const generation = reset ? ++virtualLoadGeneration.current : virtualLoadGeneration.current;
|
||||
const existing = reset ? [] : virtualTracksRef.current;
|
||||
const lastPosition = existing.length > 0
|
||||
@@ -221,6 +220,15 @@ export const QueueTray = memo(function QueueTray({ onClose, embedded = false }:
|
||||
const start = typeof lastPosition === 'number'
|
||||
? lastPosition + 1
|
||||
: state.activePosition + 1;
|
||||
if (start >= state.totalCount) {
|
||||
if (reset) {
|
||||
virtualLoading.current = false;
|
||||
virtualTracksRef.current = [];
|
||||
setVirtualTracks([]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
virtualLoading.current = true;
|
||||
try {
|
||||
const page = await getVirtualQueuePage(start, 100);
|
||||
if (
|
||||
@@ -228,10 +236,15 @@ export const QueueTray = memo(function QueueTray({ onClose, embedded = false }:
|
||||
generation !== virtualLoadGeneration.current ||
|
||||
getVirtualQueueState()?.sessionId !== state.sessionId
|
||||
) return;
|
||||
const next = reset ? page.items.map((item) => item.track) : [
|
||||
...existing,
|
||||
...page.items.map((item) => item.track),
|
||||
];
|
||||
const existingPositions = new Set(
|
||||
existing.map((track) => track.astraQueuePosition).filter(
|
||||
(position): position is number => typeof position === 'number'
|
||||
)
|
||||
);
|
||||
const incoming = page.items
|
||||
.filter((item) => item.queuePosition >= start && !existingPositions.has(item.queuePosition))
|
||||
.map((item) => item.track);
|
||||
const next = reset ? incoming : [...existing, ...incoming];
|
||||
// Keep no more than five tray pages in JS.
|
||||
const bounded = next.slice(-500);
|
||||
virtualTracksRef.current = bounded;
|
||||
|
||||
Reference in New Issue
Block a user