You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

音频播放器播放历史追踪逻辑优化求助:连续点击上下曲异常

音频播放器播放历史追踪的问题与优化方向

核心需求与难点

需要准确追踪音频播放器的所有歌曲播放历史,核心难点是处理用户连续点击上一曲、下一曲按钮的复杂场景。

测试数据(歌曲列表)

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 10:34:54