音频播放器播放历史追踪逻辑优化求助:连续点击上下曲异常
音频播放器播放历史追踪的问题与优化方向
核心需求与难点
需要准确追踪音频播放器的所有歌曲播放历史,核心难点是处理用户连续点击上一曲、下一曲按钮的复杂场景。
测试数据(歌曲列表)
export const tracksData = [ { name: 'song-1', path: 'path.mp3', id: 'song-1-id', }, { name: 'song-2', path: 'path.mp3', id: 'song-2-id', }, { name: 'song-3', path: 'path.mp3', id: 'song-3-id', }, { name: 'song-4', path: 'path.mp3', id: 'song-4-id', }, { name: 'song-5', path: 'path.mp3', id: 'song-5-id', }, { name: 'song-6', path: 'path.mp3', id: 'song-6-id', }, { name: 'song-7', path: 'path.mp3', id: 'song-7-id', }, { name: 'song-8', path: 'path.mp3', id: 'song-8-id', }, { name: 'song-9', path: 'path.mp3', id: 'song-9-id', }, { name: 'song-10', path: 'path.mp3', id: 'song-10-id', } ];
当前使用的变量说明
currentPlayingTrackIndex:歌曲数组的索引trackQueIndex:playedTrackedIndexes中的位置trackStartPoint === 0:表示处于队列起始,仅获取后续索引
当前实现逻辑
初始化歌曲逻辑(运行正常)
const initializeTracks = (tracks) => { const { path, name, id } = tracks[0]; const playingTrack = { path, name, id, }; return { playingTrack, currentPlayingTrackIndex: 0, trackQueIndex: 0, trackStartPoint: 0, playedTrackIndexes: [0] } };
获取下一曲逻辑(点击下一曲按钮或歌曲结束时触发)
const getNextTrack = (tracks, tracksQueData) => { const { currentPlayingTrackIndex, trackQueIndex, trackStartPoint, playedTrackIndexes } = tracksQueData; const isCurrentTrackTheLastOfPlayQue = trackStartPoint === 0; const nextTrackIndex = isCurrentTrackTheLastOfPlayQue ? currentPlayingTrackIndex + 1 : playedTrackIndexes[trackQueIndex + 1]; const { path, name, id } = tracks[nextTrackIndex]; const nextTrack = { path, name, id, }; const nextTrackQueIndex = trackQueIndex + 1; const startPoint = trackStartPoint === 0 ? trackStartPoint : trackStartPoint - 1; return { playingTrack: nextTrack, currentPlayingTrackIndex: nextTrackIndex, trackQueIndex: nextTrackQueIndex, trackStartPoint: startPoint, playedTrackIndexes: [...playedTrackIndexes, nextTrackIndex] } };
获取上一曲逻辑(点击上一曲按钮时触发)
const getPreviousTrack = (tracks, tracksQueData) => { const { currentPlayingTrackIndex, trackQueIndex, trackStartPoint, playedTrackIndexes } = tracksQueData; const previousTrackQueIndex = trackQueIndex - 1; const previousTrackIndex = playedTrackIndexes[previousTrackQueIndex]; const { path, name, id } = tracks[previousTrackIndex]; const previousTrack = { path, name, id, }; const startPoint = trackStartPoint + 1; return { playingTrack: previousTrack, currentPlayingTrackIndex: previousTrackIndex, trackQueIndex: previousTrackQueIndex, trackStartPoint: startPoint, playedTrackIndexes: [...playedTrackIndexes, previousTrackIndex] } };
问题场景(失败测试用例)
当前getPreviousTrack逻辑无法处理复杂的连续上下曲场景,比如以下测试用例:
it('Should return correct track object when getting previous track and the same tracks have been played multiple times, more complex', () => { const tracksQueData = { currentPlayingTrackIndex: 5, trackQueIndex: 5, trackStartPoint: 0, playedTrackIndexes: [0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 3, 4, 5] }; const track = getPreviousTrack(tracksTestData, tracksQueData); expect(track).toEqual( { playingTrack: { name: 'song-4', path: 'path.mp3', id: 'song-4-id', }, currentPlayingTrackIndex: 4, trackQueIndex: 4, trackStartPoint: 1, playedTrackIndexes: [0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 3, 4, 5, 4] }) });
预期点击上一曲后回到索引4的歌曲,且trackQueIndex应为20(原数组最后一位索引是21,减1后为20),但当前逻辑错误地将trackQueIndex设为4,还往历史数组里追加了重复项,完全偏离真实播放历史。
待实现功能
需要支持从曲库手动选择歌曲的功能:比如用户按顺序播放索引[0,1,2]后,选择tracksData[7],则playedTrackIndexes应更新为[0,1,2,7]。
正常运行的简单测试用例
it('Should return correct track object when getting previous track and the same tracks have been played multiple times', () => { const tracksQueData = { currentPlayingTrackIndex: 4, trackQueIndex: 4, trackStartPoint: 4, playedTrackIndexes: [0, 1, 2, 3, 2, 1, 2, 1, 2, 3, 4, 3, 2, 3, 4, 5, 4, 3, 2, 3, 2, 3, 4] }; const track = getPreviousTrack(tracksTestData, tracksQueData); expect(track).toEqual( { playingTrack: { name: 'song-4', path: 'path.mp3', id: 'song-4-id', }, currentPlayingTrackIndex: 3, trackQueIndex: 3, trackStartPoint: 5, playedTrackIndexes: [0, 1, 2, 3, 2, 1, 2, 1, 2, 3, 4, 3, 2, 3, 4, 5, 4, 3, 2, 3, 2, 3, 4, 3], }) });
优化方向建议
当前逻辑的核心问题是把「历史记录」和「操作轨迹」混在一起,每次上下曲都往历史数组追加项,导致数组冗余,trackStartPoint的逻辑过于复杂且容易出错。建议简化模型,用历史栈+当前位置指针的方式实现:
1. 简化状态变量
只保留三个核心变量:
playedHistory:完整的播放历史数组,记录用户播放过的歌曲索引顺序(比如[0,1,2,1,0])currentHistoryIndex:当前在playedHistory中的位置(比如正在播放playedHistory[3]时,值为3)currentTrackIndex:当前播放歌曲在歌曲列表中的索引(可选,可直接通过playedHistory[currentHistoryIndex]获取)
2. 重新定义各操作逻辑
初始化
const initializeTracks = (tracks) => { const initialIndex = 0; const { path, name, id } = tracks[initialIndex]; return { playingTrack: { path, name, id }, playedHistory: [initialIndex], currentHistoryIndex: 0 }; };
获取下一曲
const getNextTrack = (tracks, playerState) => { const { playedHistory, currentHistoryIndex } = playerState; let nextHistoryIndex, nextTrackIndex; // 如果当前处于历史栈末尾,说明是正常播放下一曲,需要新增历史记录 if (currentHistoryIndex === playedHistory.length - 1) { nextTrackIndex = playedHistory[currentHistoryIndex] + 1; // 处理歌曲列表边界,比如循环播放的话:nextTrackIndex %= tracks.length if (nextTrackIndex >= tracks.length) return playerState; // 或者循环到开头 playedHistory.push(nextTrackIndex); nextHistoryIndex = playedHistory.length - 1; } else { // 如果之前回退过历史,直接前进到下一个历史位置 nextHistoryIndex = currentHistoryIndex + 1; nextTrackIndex = playedHistory[nextHistoryIndex]; } const { path, name, id } = tracks[nextTrackIndex]; return { ...playerState, playingTrack: { path, name, id }, currentHistoryIndex: nextHistoryIndex }; };
获取上一曲
const getPreviousTrack = (tracks, playerState) => { const { playedHistory, currentHistoryIndex } = playerState; // 边界处理:已经是第一首,不做操作 if (currentHistoryIndex === 0) return playerState; const prevHistoryIndex = currentHistoryIndex - 1; const prevTrackIndex = playedHistory[prevHistoryIndex]; const { path, name, id } = tracks[prevTrackIndex]; return { ...playerState, playingTrack: { path, name, id }, currentHistoryIndex: prevHistoryIndex }; };
手动选择歌曲
const selectTrack = (tracks, playerState, trackIndex) => { const { path, name, id } = tracks[trackIndex]; // 追加到历史栈,更新当前位置 const newHistory = [...playerState.playedHistory, trackIndex]; return { ...playerState, playingTrack: { path, name, id }, playedHistory: newHistory, currentHistoryIndex: newHistory.length - 1 }; };
3. 为什么这个模型更可靠
- 历史数组
playedHistory严格记录用户的播放顺序,没有冗余数据 - 上下曲操作只是移动
currentHistoryIndex指针,只有播放新的未在当前历史后续的歌曲(正常下一曲、手动选歌)才会追加历史 - 逻辑清晰,边界处理简单,完全支持连续点击上下曲的场景,也能正确处理手动选歌的需求
内容的提问来源于stack exchange,提问作者Mystery Man
相关产品推荐
相关产品推荐

