Flutter中如何用just_audio实现队列音频自定义次数循环
解决方案
一、实现指定次数循环并自动切换队列项
just_audio的LoopMode.one是无限循环,要实现指定次数循环,需要手动监听播放完成事件,计数循环次数,达到目标后切换下一曲。
1. 新增状态变量
在MyAudioHandler中添加变量跟踪循环状态:
class MyAudioHandler extends BaseAudioHandler { // 当前已循环次数 int _currentLoopCount = 0; // 目标循环次数 int _targetLoopCount = 1; // 当前音频的实际时长 Duration? _currentAudioActualDuration; // 订阅当前索引变化的流 StreamSubscription<int>? _currentIndexSubscription; MyAudioHandler() { // 监听播放完成事件 _audioPlayer.playerStateStream.listen((state) { if (state.processingState == ProcessingState.completed) { _handleLoopCompletion(); } }); // 监听队列索引变化,重置循环状态 _currentIndexSubscription = _audioPlayer.currentIndexStream.listen((index) { if (index != null && index < queue.value.length) { _resetLoopState(index); } }); } }
2. 重置循环状态与计算目标次数
添加方法处理索引切换时的循环状态重置,以及根据自定义总时长计算目标循环次数:
void _resetLoopState(int index) async { _currentLoopCount = 0; final currentItem = queue.value[index]; // 从MediaItem的extras中获取自定义总播放时长 final totalDurationMs = currentItem.extras?['totalPlayDuration'] as int?; if (totalDurationMs == null) { _targetLoopCount = 1; _currentAudioActualDuration = null; return; } // 获取音频实际时长 _currentAudioActualDuration = await _audioPlayer.durationFuture; if (_currentAudioActualDuration == null) { _targetLoopCount = 1; return; } // 计算目标循环次数(总时长 ÷ 音频实际时长) _targetLoopCount = totalDurationMs ~/ _currentAudioActualDuration!.inMilliseconds; // 若需处理总时长非整数倍的情况,可取消下方注释调整逻辑 // if (totalDurationMs % _currentAudioActualDuration!.inMilliseconds != 0) { // _targetLoopCount += 1; // } }
3. 处理循环完成逻辑
添加方法判断是否达到循环次数,决定继续循环或切换下一曲:
Future<void> _handleLoopCompletion() async { _currentLoopCount++; if (_currentLoopCount >= _targetLoopCount) { // 达到目标次数,切换至下一曲 await skipToNext(); } else { // 未达次数,重新播放当前曲 await _audioPlayer.seek(Duration.zero); await _audioPlayer.play(); } }
4. 修改播放与队列添加逻辑
取消无限循环设置,改用手动控制:
@override Future<void> play() async { setRepeatMode(AudioServiceRepeatMode.none); _audioPlayer.setLoopMode(LoopMode.none); _audioPlayer.play(); } @override Future<void> addQueueItems(List<MediaItem> mediaItems) async { final audioSource = mediaItems.map(_createAudioSource); _playlist.addAll(audioSource.toList()); final newQueue = queue.value..addAll(mediaItems); queue.add(newQueue); await _audioPlayer.setAudioSource(_playlist); setRepeatMode(AudioServiceRepeatMode.none); _audioPlayer.setLoopMode(LoopMode.none); }
二、让自定义时长生效
just_audio会自动读取音频文件的实际时长,因此不能直接用MediaItem.duration存储总播放时长,改用extras字段存储,再在AudioHandler中更新显示的时长:
1. 修改MediaItem创建方式
将自定义总时长存入extras:
ElevatedButton( onPressed: () async { List<MediaItem> mediaItems = [ MediaItem( id: 'assets/audio/jingle.mp3', title: 'jingle', // 存储总播放时长(15秒) extras: {'totalPlayDuration': Duration(seconds: 15).inMilliseconds}, ), MediaItem( id: 'assets/audio/jazz.mp3', title: 'jazz', // 存储总播放时长(60秒) extras: {'totalPlayDuration': Duration(seconds: 60).inMilliseconds}, ), ]; getIt<AudioHandler>().addQueueItems(mediaItems); }, child: const Text('Add to queue'), ),
2. 更新MediaItem显示时长
在索引变化时,将自定义总时长设置为MediaItem的显示时长:
// 在MyAudioHandler的初始化监听中添加 _currentIndexSubscription = _audioPlayer.currentIndexStream.listen((index) { if (index != null && index < queue.value.length) { final item = queue.value[index]; final totalDurationMs = item.extras?['totalPlayDuration'] as int?; // 用自定义总时长覆盖显示的duration final displayDuration = totalDurationMs != null ? Duration(milliseconds: totalDurationMs) : item.duration; setMediaItem(item.copyWith(duration: displayDuration)); } });
三、清理资源
记得在AudioHandler销毁时取消订阅:
@override Future<void> dispose() async { _currentIndexSubscription?.cancel(); await _audioPlayer.dispose(); await super.dispose(); }
内容的提问来源于stack exchange,提问作者Kdon
相关产品推荐
相关产品推荐

