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

如何在just_audio+audio_service中实现双队列独立重排互不影响?

解决方案:维护双独立队列实现模式间无干扰重排

核心思路是彻底分离线性和随机模式的数据源,不依赖just_audio自带的ShuffleOrder做动态索引映射,而是直接维护两个独立的播放队列,切换模式时加载对应队列,重排时仅修改当前模式(或指定模式)的队列。

具体实现步骤

1. 定义双队列存储

在自定义AudioHandler中维护两个独立队列,以及当前模式标记:

class MyAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
  final AudioPlayer _player = AudioPlayer();
  List<MediaItem> _linearQueue = []; // 线性模式队列(原顺序)
  List<MediaItem> _shuffledQueue = []; // 随机模式队列(自定义顺序)
  bool _isShuffled = false;

  // 初始化队列:传入初始列表,同时生成随机队列副本
  void initializeQueue(List<MediaItem> initialItems) {
    _linearQueue = List.from(initialItems);
    _shuffledQueue = List.from(initialItems)..shuffle();
    _loadActiveQueue();
  }
}

2. 实现队列加载逻辑

封装统一的队列加载方法,负责将指定队列转换为ConcatenatingAudioSource并更新播放器和AudioHandler队列:

Future<void> _loadActiveQueue() async {
  final targetQueue = _isShuffled ? _shuffledQueue : _linearQueue;
  final audioSources = targetQueue
      .map((item) => AudioSource.uri(Uri.parse(item.id)))
      .toList();
  
  await _player.setAudioSource(ConcatenatingAudioSource(children: audioSources));
  await updateQueue(targetQueue);
}

3. 模式切换逻辑

切换shuffle模式时,先保存当前播放的媒体项,加载对应队列后定位到该媒体项的位置,保证播放连贯性:

@override
Future<void> setShuffleMode(AudioServiceShuffleMode shuffleMode) async {
  final wasShuffled = _isShuffled;
  _isShuffled = shuffleMode == AudioServiceShuffleMode.all;
  
  if (wasShuffled == _isShuffled) return;
  
  // 保存当前播放的媒体项和进度
  final currentItem = await _player.currentMediaItem;
  final currentPosition = await _player.position;
  
  // 加载目标队列
  await _loadActiveQueue();
  
  // 定位到之前播放的媒体项
  if (currentItem != null) {
    final targetIndex = (_isShuffled ? _shuffledQueue : _linearQueue)
        .indexWhere((item) => item.id == currentItem.id);
    if (targetIndex != -1) {
      await _player.seek(currentPosition, index: targetIndex);
      // 恢复之前的播放状态
      if (await _player.playing) await _player.play();
    }
  }
  
  await super.setShuffleMode(shuffleMode);
}

4. 独立重排实现

分别为线性和随机队列实现重排方法,仅修改对应队列,根据当前模式决定是否立即生效:

// 重排线性队列(不影响随机队列)
Future<void> reorderLinear(int oldIndex, int newIndex) async {
  // 调整线性队列顺序
  _linearQueue.insert(newIndex, _linearQueue.removeAt(oldIndex));
  
  // 如果当前在线性模式,立即更新播放器
  if (!_isShuffled) {
    await _adjustPlayPositionAfterReorder(oldIndex, newIndex, _linearQueue);
    await _loadActiveQueue();
  }
}

// 重排随机队列(不影响线性队列)
Future<void> reorderShuffled(int oldIndex, int newIndex) async {
  // 调整随机队列顺序
  _shuffledQueue.insert(newIndex, _shuffledQueue.removeAt(oldIndex));
  
  // 如果当前在随机模式,立即更新播放器
  if (_isShuffled) {
    await _adjustPlayPositionAfterReorder(oldIndex, newIndex, _shuffledQueue);
    await _loadActiveQueue();
  }
}

// 辅助方法:重排后调整播放位置,避免跳错歌曲
Future<void> _adjustPlayPositionAfterReorder(int oldIndex, int newIndex, List<MediaItem> queue) async {
  final currentIndex = await _player.currentIndex;
  if (currentIndex == null) return;
  
  int adjustedIndex = currentIndex;
  if (oldIndex < currentIndex) {
    if (newIndex <= oldIndex) {
      adjustedIndex += 1;
    } else if (newIndex < currentIndex) {
      adjustedIndex -= 1;
    }
  } else if (oldIndex > currentIndex) {
    if (newIndex >= oldIndex) {
      adjustedIndex -= 1;
    } else if (newIndex > currentIndex) {
      adjustedIndex += 1;
    }
  }
  
  // 如果调整后的索引有效,跳转过去
  if (adjustedIndex >= 0 && adjustedIndex < queue.length) {
    await _player.seek(Duration.zero, index: adjustedIndex);
  }
}

为什么之前的方案无效?

  • ConcatenatingAudioSource.move()直接修改底层统一数据源,而ShuffleOrder只是基于该数据源做索引映射,必然导致线性和随机模式互相影响。
  • 自定义ShuffleOrder仅负责索引的动态计算,无法保存自定义的随机重排结果,且播放器不会主动监听ShuffleOrder的变化。

注意事项

  • 添加/删除歌曲时,需同步更新两个队列(比如添加到线性队列末尾时,可选择将新歌曲追加到随机队列末尾,或随机插入)。
  • 若需要支持“重新随机打乱”功能,可新增方法覆盖_shuffledQueue为_linearQueue的打乱副本,再根据当前模式决定是否加载。

内容的提问来源于stack exchange,提问作者Chaphasilor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 14:10:34