feat: next music from playlist
This commit is contained in:
@@ -64,6 +64,8 @@ const noop = () => {};
|
||||
const DEFAULT_CONTEXT = {
|
||||
currentTrack: null,
|
||||
queue: [],
|
||||
queueInfo: null,
|
||||
queueIndex: -1,
|
||||
status: "idle",
|
||||
isPlaying: false,
|
||||
isBuffering: false,
|
||||
@@ -209,7 +211,12 @@ const normalizeTrack = (trackInput = {}, options = {}) => {
|
||||
const PlayerProvider = ({ children }) => {
|
||||
const [activeRouteName] = useGlobal("activeRouteName");
|
||||
const [currentTrack, setCurrentTrack] = useState(null);
|
||||
const [queue, setQueue] = useState([]);
|
||||
const queueRef = useRef([]);
|
||||
const queueInfoRef = useRef({ id: null, type: null, name: null });
|
||||
const queueIndexRef = useRef(-1);
|
||||
const [queue, setQueueState] = useState([]);
|
||||
const [queueInfo, setQueueInfoState] = useState(queueInfoRef.current);
|
||||
const [queueIndex, setQueueIndexState] = useState(queueIndexRef.current);
|
||||
const [playback, setPlayback] = useState({
|
||||
status: "idle",
|
||||
isPlaying: false,
|
||||
@@ -222,6 +229,127 @@ const PlayerProvider = ({ children }) => {
|
||||
|
||||
const autoPlayRef = useRef(false);
|
||||
const pendingSeekValueRef = useRef(null);
|
||||
const didJustFinishRef = useRef(false);
|
||||
|
||||
const setQueueIndexValue = useCallback((index = -1) => {
|
||||
const list = queueRef.current;
|
||||
const hasItems = Array.isArray(list) && list.length > 0;
|
||||
let target = Number.isInteger(index) ? index : queueIndexRef.current;
|
||||
if (!hasItems) {
|
||||
target = -1;
|
||||
} else {
|
||||
target = Math.min(Math.max(target, -1), list.length - 1);
|
||||
}
|
||||
if (queueIndexRef.current === target) return;
|
||||
queueIndexRef.current = target;
|
||||
setQueueIndexState(target);
|
||||
}, []);
|
||||
|
||||
const setQueueInfoValue = useCallback((info) => {
|
||||
const next = {
|
||||
id: info?.id ?? null,
|
||||
type: info?.type ?? null,
|
||||
name: info?.name ?? null,
|
||||
};
|
||||
const prev = queueInfoRef.current;
|
||||
if (
|
||||
prev.id === next.id &&
|
||||
prev.type === next.type &&
|
||||
prev.name === next.name
|
||||
) {
|
||||
return;
|
||||
}
|
||||
queueInfoRef.current = next;
|
||||
setQueueInfoState(next);
|
||||
}, []);
|
||||
|
||||
const updateQueue = useCallback(
|
||||
(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 } : {}),
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
return normalizeTrack(item, {});
|
||||
})
|
||||
.filter((track) => !!track?.source)
|
||||
: [];
|
||||
|
||||
queueRef.current = normalized;
|
||||
setQueueState(normalized);
|
||||
|
||||
const info = {
|
||||
id: options.id ?? options.queueId ?? null,
|
||||
type: options.type ?? options.queueType ?? null,
|
||||
name: options.name ?? options.queueName ?? null,
|
||||
};
|
||||
setQueueInfoValue(info);
|
||||
|
||||
const currentTrackId =
|
||||
options.currentTrackId && typeof options.currentTrackId === "string"
|
||||
? options.currentTrackId
|
||||
: null;
|
||||
const indexCandidate =
|
||||
typeof options.index === "number"
|
||||
? options.index
|
||||
: typeof options.queueIndex === "number"
|
||||
? options.queueIndex
|
||||
: currentTrackId
|
||||
? normalized.findIndex((track) => track.id === currentTrackId)
|
||||
: queueIndexRef.current;
|
||||
setQueueIndexValue(indexCandidate);
|
||||
|
||||
return normalized;
|
||||
},
|
||||
[setQueueIndexValue, setQueueInfoValue]
|
||||
);
|
||||
|
||||
const setQueue = useCallback(
|
||||
(itemsOrConfig, maybeOptions = {}) => {
|
||||
if (typeof itemsOrConfig === "function") {
|
||||
const result = itemsOrConfig([...(queueRef.current || [])]);
|
||||
if (Array.isArray(result)) {
|
||||
updateQueue(result, maybeOptions);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(itemsOrConfig)) {
|
||||
updateQueue(itemsOrConfig, maybeOptions);
|
||||
return;
|
||||
}
|
||||
if (itemsOrConfig && typeof itemsOrConfig === "object") {
|
||||
const { items = [], ...rest } = itemsOrConfig;
|
||||
updateQueue(items, { ...rest, ...maybeOptions });
|
||||
return;
|
||||
}
|
||||
updateQueue([], {});
|
||||
},
|
||||
[updateQueue]
|
||||
);
|
||||
|
||||
const source = useMemo(() => {
|
||||
if (!currentTrack?.source) return null;
|
||||
@@ -328,6 +456,19 @@ const PlayerProvider = ({ children }) => {
|
||||
|
||||
setError(null);
|
||||
|
||||
if (Array.isArray(options.queue)) {
|
||||
updateQueue(options.queue, {
|
||||
id: options.queueId,
|
||||
type: options.queueType,
|
||||
name: options.queueName,
|
||||
queueIndex:
|
||||
typeof options.queueIndex === "number"
|
||||
? options.queueIndex
|
||||
: undefined,
|
||||
currentTrackId: normalized.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (sameTrack) {
|
||||
setCurrentTrack((prev) => ({ ...prev, ...normalized }));
|
||||
try {
|
||||
@@ -371,7 +512,7 @@ const PlayerProvider = ({ children }) => {
|
||||
}));
|
||||
setCurrentTrack(normalized);
|
||||
},
|
||||
[currentTrack?.id, player, toPlayerSeekValue]
|
||||
[currentTrack?.id, player, toPlayerSeekValue, updateQueue]
|
||||
);
|
||||
|
||||
const resume = useCallback(async () => {
|
||||
@@ -474,10 +615,65 @@ const PlayerProvider = ({ children }) => {
|
||||
setIsLooping((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentTrack?.id) {
|
||||
if (queueIndexRef.current !== -1) {
|
||||
setQueueIndexValue(-1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const idx = queueRef.current.findIndex((item) => item.id === currentTrack.id);
|
||||
if (idx !== queueIndexRef.current) {
|
||||
setQueueIndexValue(idx);
|
||||
}
|
||||
}, [currentTrack?.id, setQueueIndexValue]);
|
||||
|
||||
const handleTrackDidFinish = useCallback(() => {
|
||||
if (isLooping) return;
|
||||
const items = queueRef.current;
|
||||
if (!Array.isArray(items) || items.length === 0) return;
|
||||
|
||||
let idx = queueIndexRef.current;
|
||||
const currentId = currentTrack?.id ?? null;
|
||||
if ((idx == null || idx < 0) && currentId) {
|
||||
idx = items.findIndex((item) => item.id === currentId);
|
||||
}
|
||||
if (idx == null || idx < 0) return;
|
||||
const nextIdx = idx + 1;
|
||||
if (nextIdx >= items.length) return;
|
||||
|
||||
const nextTrack = items[nextIdx];
|
||||
if (!nextTrack) return;
|
||||
|
||||
play(nextTrack, {
|
||||
autoPlay: true,
|
||||
startPositionMs: 0,
|
||||
queueId: queueInfoRef.current.id,
|
||||
queueType: queueInfoRef.current.type,
|
||||
queueName: queueInfoRef.current.name,
|
||||
}).catch((err) => {
|
||||
setError(err);
|
||||
});
|
||||
}, [currentTrack?.id, isLooping, play]);
|
||||
|
||||
useEffect(() => {
|
||||
const finished = !!status?.didJustFinish;
|
||||
if (finished) {
|
||||
if (!didJustFinishRef.current) {
|
||||
didJustFinishRef.current = true;
|
||||
handleTrackDidFinish();
|
||||
}
|
||||
} else {
|
||||
didJustFinishRef.current = false;
|
||||
}
|
||||
}, [status?.didJustFinish, handleTrackDidFinish]);
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
currentTrack,
|
||||
queue,
|
||||
queueInfo,
|
||||
queueIndex,
|
||||
status: playback.status,
|
||||
isPlaying: playback.isPlaying,
|
||||
isBuffering: playback.isBuffering,
|
||||
@@ -508,6 +704,8 @@ const PlayerProvider = ({ children }) => {
|
||||
playback.positionMs,
|
||||
playback.status,
|
||||
queue,
|
||||
queueInfo,
|
||||
queueIndex,
|
||||
resume,
|
||||
seekBy,
|
||||
seekTo,
|
||||
@@ -516,6 +714,7 @@ const PlayerProvider = ({ children }) => {
|
||||
isLooping,
|
||||
setLooping,
|
||||
toggleLooping,
|
||||
setQueue,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user