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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 03:55:33