Flutter集成just_audio与audio_service:处理切歌按钮事件及加密音频播放
用just_audio + audio_service处理上/下一首及加密音频播放
一、处理上一首/下一首按钮点击
要实现上/下一首切换,核心是在自定义AudioHandler中实现对应的回调方法,同时结合just_audio的队列管理逻辑:
维护音频队列
在自定义AudioHandler中,先定义一个队列列表存储所有待播放的MediaItem,同时记录当前播放索引:class MyAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler { final AudioPlayer _player = AudioPlayer(); List<MediaItem> _queue = []; int _currentIndex = 0; // 初始化时加载队列(示例) void initQueue(List<MediaItem> items) { _queue = items; queue.add(_queue); // 通知UI更新队列 } }实现上/下一首回调
重写skipToPrevious和skipToNext方法,修改当前索引后切换播放源:@override Future<void> skipToPrevious() async { if (_currentIndex > 0) { _currentIndex--; await _playCurrentItem(); } } @override Future<void> skipToNext() async { if (_currentIndex < _queue.length - 1) { _currentIndex++; await _playCurrentItem(); } } // 播放当前索引对应的音频 Future<void> _playCurrentItem() async { final currentItem = _queue[_currentIndex]; // 后续替换成解密后的路径加载逻辑 await _player.setFilePath(currentItem.id); // id存储加密文件路径,后续解密用 await _player.play(); // 更新当前播放状态 mediaItem.add(currentItem); playbackState.add(playbackState.value.copyWith( controls: [MediaControl.skipToPrevious, MediaControl.pause, MediaControl.skipToNext], processingState: ProcessingState.ready, playing: true, )); }UI层触发事件
在UI按钮的点击事件中,直接调用AudioHandler的对应方法:ElevatedButton( onPressed: () => audioHandler.skipToPrevious(), child: const Icon(Icons.skip_previous), ), ElevatedButton( onPressed: () => audioHandler.skipToNext(), child: const Icon(Icons.skip_next), ),
二、加密音频的解密与播放
加密音频无法直接用setFilePath加载,需先解密到临时文件,再让播放器加载临时文件:
封装解密逻辑
写一个解密方法,将加密文件解密到临时目录(需依赖path_provider获取临时路径):import 'dart:io'; import 'package:path_provider/path_provider.dart'; Future<String> decryptAudio(String encryptedFilePath) async { // 1. 读取加密文件内容 final encryptedFile = File(encryptedFilePath); final encryptedBytes = await encryptedFile.readAsBytes(); // 2. 执行实际解密逻辑(替换成你的算法,比如AES) final decryptedBytes = _yourDecryptionLogic(encryptedBytes); // 3. 创建临时文件存储解密后的音频 final tempDir = await getTemporaryDirectory(); final tempFilePath = '${tempDir.path}/temp_decrypted_${DateTime.now().millisecondsSinceEpoch}.mp3'; final tempFile = File(tempFilePath); await tempFile.writeAsBytes(decryptedBytes); return tempFilePath; } // 示例解密逻辑(替换成你的真实实现) List<int> _yourDecryptionLogic(List<int> encryptedBytes) { // 这里写解密代码,比如AES解密、自定义算法还原等 return encryptedBytes; // 仅示例,实际需替换 }修改播放逻辑
把_playCurrentItem方法改成先解密再加载:Future<void> _playCurrentItem() async { final currentItem = _queue[_currentIndex]; // 从currentItem.id取出加密文件路径,解密得到临时文件路径 final decryptedPath = await decryptAudio(currentItem.id); // 加载解密后的临时文件 await _player.setFilePath(decryptedPath); await _player.play(); // 更新播放状态 mediaItem.add(currentItem.copyWith( artUri: currentItem.artUri, // 保留原封面 )); playbackState.add(playbackState.value.copyWith( controls: [MediaControl.skipToPrevious, MediaControl.pause, MediaControl.skipToNext], processingState: ProcessingState.ready, playing: true, )); // 播放结束后删除临时文件,避免占用空间 _player.playerStateStream.listen((state) { if (state.processingState == ProcessingState.completed) { File(decryptedPath).delete(); } }); }初始化队列的调整
原来的队列初始化代码中,MediaItem的id存储加密文件的路径,而非解密后的路径:// 示例:添加加密音频到队列 final encryptedFilePath = PathHelper().pathAudio! + "name_encrypted.mp3"; mediaItem.add(MediaItem( id: encryptedFilePath, // 存储加密文件的实际路径 title: item.title!, artUri: Uri.file(PathHelper().pathAudio! + "name.jpg"), // 封面路径不变 ));
注意事项
- 临时文件管理:播放完成后及时删除临时文件,也可在APP退出时清理整个临时目录,避免占用设备存储。
- 解密性能优化:大文件解密耗时较长,建议在Isolate中执行解密逻辑,同时UI层显示加载状态,避免卡顿。
- 权限配置:确保APP拥有读取原始加密文件目录和写入临时目录的权限,Android需配置
READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE,iOS需配置对应的文件访问权限。
内容的提问来源于stack exchange,提问作者Muhiddinxon Abduhamidov
相关产品推荐
相关产品推荐

