clear last tickets
This commit is contained in:
+134
-59
@@ -61,8 +61,8 @@ const HIDDEN_ROUTE_NAMES = new Set([
|
||||
Routes.Register,
|
||||
]);
|
||||
|
||||
const noopAsync = async () => {};
|
||||
const noop = () => {};
|
||||
const noopAsync = async () => { };
|
||||
const noop = () => { };
|
||||
|
||||
const DEFAULT_CONTEXT = {
|
||||
currentTrack: null,
|
||||
@@ -272,36 +272,36 @@ const PlayerProvider = ({ children }) => {
|
||||
(items = [], options = {}) => {
|
||||
const normalized = Array.isArray(items)
|
||||
? items
|
||||
.map((item) => {
|
||||
if (!item) return null;
|
||||
if (typeof item === "object") {
|
||||
const { metadata, context, ...rest } = item;
|
||||
const safeMetadata =
|
||||
metadata && typeof metadata === "object"
|
||||
? { ...metadata }
|
||||
: undefined;
|
||||
if (safeMetadata) {
|
||||
delete safeMetadata.queue;
|
||||
}
|
||||
const safeContext =
|
||||
context && typeof context === "object"
|
||||
? { ...context }
|
||||
: undefined;
|
||||
if (safeContext) {
|
||||
delete safeContext.queue;
|
||||
}
|
||||
return normalizeTrack(
|
||||
{
|
||||
...rest,
|
||||
...(safeMetadata ? { metadata: safeMetadata } : {}),
|
||||
...(safeContext ? { context: safeContext } : {}),
|
||||
},
|
||||
{}
|
||||
);
|
||||
.map((item) => {
|
||||
if (!item) return null;
|
||||
if (typeof item === "object") {
|
||||
const { metadata, context, ...rest } = item;
|
||||
const safeMetadata =
|
||||
metadata && typeof metadata === "object"
|
||||
? { ...metadata }
|
||||
: undefined;
|
||||
if (safeMetadata) {
|
||||
delete safeMetadata.queue;
|
||||
}
|
||||
return normalizeTrack(item, {});
|
||||
})
|
||||
.filter((track) => !!track?.source)
|
||||
const safeContext =
|
||||
context && typeof context === "object"
|
||||
? { ...context }
|
||||
: undefined;
|
||||
if (safeContext) {
|
||||
delete safeContext.queue;
|
||||
}
|
||||
return normalizeTrack(
|
||||
{
|
||||
...rest,
|
||||
...(safeMetadata ? { metadata: safeMetadata } : {}),
|
||||
...(safeContext ? { context: safeContext } : {}),
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
return normalizeTrack(item, {});
|
||||
})
|
||||
.filter((track) => !!track?.source)
|
||||
: [];
|
||||
|
||||
queueRef.current = normalized;
|
||||
@@ -448,9 +448,105 @@ const PlayerProvider = ({ children }) => {
|
||||
if (!player) return;
|
||||
try {
|
||||
player.loop = !!isLooping;
|
||||
} catch (_err) {}
|
||||
} catch (_err) { }
|
||||
}, [player, isLooping]);
|
||||
|
||||
|
||||
const seekDebounceRef = useRef(null);
|
||||
const pendingSeekPosRef = useRef(null);
|
||||
const isSeekingRef = useRef(false);
|
||||
|
||||
const waitForActiveSeek = useCallback(async () => {
|
||||
if (!isSeekingRef.current) return;
|
||||
// Poll every 50ms until seek is done
|
||||
while (isSeekingRef.current) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const seekTo = useCallback(
|
||||
async (positionMs) => {
|
||||
if (!player) return;
|
||||
const bounded = Math.max(0, Number(positionMs) || 0);
|
||||
const seekValue = toPlayerSeekValue(bounded);
|
||||
|
||||
// Cancel any pending debounce
|
||||
if (seekDebounceRef.current) {
|
||||
clearTimeout(seekDebounceRef.current);
|
||||
seekDebounceRef.current = null;
|
||||
}
|
||||
|
||||
// Store pending seek position
|
||||
pendingSeekPosRef.current = seekValue;
|
||||
|
||||
// Update local state immediately for UI responsiveness
|
||||
setPlayback((prev) => ({
|
||||
...prev,
|
||||
positionMs: bounded,
|
||||
}));
|
||||
|
||||
return new Promise((resolve) => {
|
||||
seekDebounceRef.current = setTimeout(async () => {
|
||||
try {
|
||||
isSeekingRef.current = true;
|
||||
if (status?.isLoaded) {
|
||||
await player.seekTo?.(seekValue);
|
||||
} else {
|
||||
pendingSeekValueRef.current = seekValue;
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
isSeekingRef.current = false;
|
||||
pendingSeekPosRef.current = null;
|
||||
resolve();
|
||||
}
|
||||
}, 100); // 100ms debounce
|
||||
});
|
||||
},
|
||||
[player, status?.isLoaded, toPlayerSeekValue]
|
||||
);
|
||||
|
||||
const seekBy = useCallback(
|
||||
async (deltaMs) => {
|
||||
const currentPos = pendingSeekPosRef.current !== null
|
||||
? (Platform.OS === "web" ? pendingSeekPosRef.current : pendingSeekPosRef.current * 1000)
|
||||
: playback.positionMs;
|
||||
const next = Math.max(0, currentPos + Number(deltaMs || 0));
|
||||
await seekTo(next);
|
||||
},
|
||||
[playback.positionMs, seekTo]
|
||||
);
|
||||
|
||||
// Helper to flush pending seek before playing
|
||||
const flushPendingSeek = useCallback(async () => {
|
||||
// 1. Cancel pending debounce and execute immediately
|
||||
if (seekDebounceRef.current) {
|
||||
clearTimeout(seekDebounceRef.current);
|
||||
seekDebounceRef.current = null;
|
||||
}
|
||||
if (pendingSeekPosRef.current !== null) {
|
||||
const seekValue = pendingSeekPosRef.current;
|
||||
pendingSeekPosRef.current = null;
|
||||
try {
|
||||
isSeekingRef.current = true;
|
||||
if (status?.isLoaded) {
|
||||
await player.seekTo?.(seekValue);
|
||||
} else {
|
||||
pendingSeekValueRef.current = seekValue;
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
isSeekingRef.current = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Wait for any active seek to complete
|
||||
await waitForActiveSeek();
|
||||
}, [player, status?.isLoaded, waitForActiveSeek]);
|
||||
|
||||
|
||||
const play = useCallback(
|
||||
async (trackInput, options = {}) => {
|
||||
const normalized = normalizeTrack(trackInput, options);
|
||||
@@ -489,6 +585,9 @@ const PlayerProvider = ({ children }) => {
|
||||
if (sameTrack) {
|
||||
setCurrentTrack((prev) => ({ ...prev, ...normalized }));
|
||||
try {
|
||||
// Flush any pending seek first
|
||||
await flushPendingSeek();
|
||||
|
||||
if (targetPositionMs > 0) {
|
||||
await player?.seekTo?.(targetSeekValue);
|
||||
}
|
||||
@@ -529,17 +628,18 @@ const PlayerProvider = ({ children }) => {
|
||||
}));
|
||||
setCurrentTrack(normalized);
|
||||
},
|
||||
[currentTrack?.id, player, toPlayerSeekValue, updateQueue]
|
||||
[currentTrack?.id, player, toPlayerSeekValue, updateQueue, flushPendingSeek]
|
||||
);
|
||||
|
||||
const resume = useCallback(async () => {
|
||||
if (!currentTrack) return;
|
||||
try {
|
||||
await flushPendingSeek();
|
||||
await player?.play?.();
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
}
|
||||
}, [player, currentTrack]);
|
||||
}, [player, currentTrack, flushPendingSeek]);
|
||||
|
||||
const pause = useCallback(async () => {
|
||||
try {
|
||||
@@ -578,32 +678,7 @@ const PlayerProvider = ({ children }) => {
|
||||
[currentTrack, playback.isPlaying, pause, play, resume]
|
||||
);
|
||||
|
||||
const seekTo = useCallback(
|
||||
async (positionMs) => {
|
||||
if (!player) return;
|
||||
const bounded = Math.max(0, Number(positionMs) || 0);
|
||||
const seekValue = toPlayerSeekValue(bounded);
|
||||
|
||||
try {
|
||||
if (status?.isLoaded) {
|
||||
await player.seekTo?.(seekValue);
|
||||
} else {
|
||||
pendingSeekValueRef.current = seekValue;
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
}
|
||||
},
|
||||
[player, status?.isLoaded, toPlayerSeekValue]
|
||||
);
|
||||
|
||||
const seekBy = useCallback(
|
||||
async (deltaMs) => {
|
||||
const next = Math.max(0, playback.positionMs + Number(deltaMs || 0));
|
||||
await seekTo(next);
|
||||
},
|
||||
[playback.positionMs, seekTo]
|
||||
);
|
||||
|
||||
const stop = useCallback(async () => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user