如何在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
相关产品推荐
相关产品推荐

