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