继承 audio_service 中的 BaseAudioHandler 实现 AudioCustomHandler

在 AudioCustomHandler 类中对接系统控制台与 Flutter 软件的音频交互逻辑。 为了释放在 AudioCustomHandler 初始化时占用的资源,该类使用单例模式。

  • _instance: AudioCustomHandler 的单例对象
  • static AudioCustomHandler of():单例获取函数

class AudioCustomHandler extends BaseAudioHandler { static AudioCustomHandler? _instance; static AudioCustomHandler of() { _instance ??= AudioCustomHandler(); return _instance!; } // ...... }

AudioCustomHandler 的初始化函数

初始化控制台的状态、功能、信息

控制台要提前进行初始化,后续控制台状态变更只需要复制当前状态,然后修改对应状态即可。

  • controls:设置控制台功能
    • 上一首:MediaControl.skipToPrevious
    • 播放:MediaControl.play
    • 暂停:MediaControl.pause
    • 下一首:MediaControl.skipToNext
  • systemActions:设置软件支持的系统级操作
    • 进度切换:MediaAction.seek
  • processingState:音频处理状态
    • idle:还没有加载任何资源。
    • loading:资源加载中。
    • buffering:正在缓存资源。
    • ready:资源有足够的缓冲,可用于回放。
    • completed:到达资源的终点。
    • error:资源加载异常。
    • playing:播放状态。
    • updatePosition:播放位置。
    • bufferedPosition:缓冲位置

监听音频播放状态、信息

这里要用到全局音频播放单例,控制台的状态变化务必使用 playbackState.value.copyWith 拷贝当前状态,然后对需要更新的状态进行更新。

  • _streamSubscriptions:该参数用于存储订阅信息,以便在软件退出时进行释放。
  • 订阅播放状态:AudioPlayerUtil.of().playerStateStream
  • 订阅播放信息变化:AudioPlayerUtil.of().currentIndexStream
  • 订阅播放进度变化:AudioPlayerUtil.of().positionStream

class AudioCustomHandler extends BaseAudioHandler { // ...... final List<StreamSubscription> _streamSubscriptions = []; /// 初始化 AudioCustomHandler init() { // 初始化控制台的状态、功能、信息 playbackState.add( PlaybackState( controls: [ MediaControl.skipToPrevious, MediaControl.play, MediaControl.pause, MediaControl.skipToNext, ], systemActions: {MediaAction.seek}, processingState: AudioProcessingState.ready, playing: false, updatePosition: Duration.zero, bufferedPosition: Duration.zero, ), ); // 监听播放器状态变化,同步到通知栏 _streamSubscriptions.add( AudioPlayerUtil.of().playerStateStream.listen((state) { AudioProcessingState processingState = state.processingState == ProcessingState.completed ? AudioProcessingState.completed : state.processingState == ProcessingState.ready ? AudioProcessingState.ready : state.processingState == ProcessingState.loading ? AudioProcessingState.loading : state.processingState == ProcessingState.buffering ? AudioProcessingState.buffering : AudioProcessingState.idle; playbackState.add( playbackState.value.copyWith( playing: state.playing, processingState: processingState, ), ); }), ); // 监听当前播放歌曲 _streamSubscriptions.add( AudioPlayerUtil.of().currentIndexStream.listen((index) { mediaItem.add( MediaItem( id: 'media_id', title: AudioPlayerUtil.of().currentAudio?.name ?? '未知歌曲', artist: AudioPlayerUtil.of().currentAudio?.artist, duration: AudioPlayerUtil.of().duration, artUri: Uri.parse( AudioPlayerUtil.of().currentAudio?.image ?? 'assets/images/logo.png', ), ), ); }), ); // 监听播放进度 _streamSubscriptions.add( AudioPlayerUtil.of().positionStream.listen((position) { playbackState.add( playbackState.value.copyWith( updatePosition: position, bufferedPosition: position, ), ); }), ); return this; } // ...... }

方法介绍

这里的方法都是对 **BaseAudioHandler 方法的重写,然后使用全局音频控制器 AudioPlayerUtil **进行控制。

  • 音频播放:Future<void> play()
  • 音频暂停:Future<void> pause()
  • 音频停止:Future<void> stop()
  • 指定音频播放位置:Future<void> seek(Duration position)
  • 跳转到指定音频:Future<void> skipToQueueItem(int index)
  • 下一首:Future<void> skipToNext()
  • 上一首:Future<void> skipToPrevious()

class AudioCustomHandler extends BaseAudioHandler { // ...... @override Future<void> play() => AudioPlayerUtil.of().play(); @override Future<void> pause() => AudioPlayerUtil.of().pause(); @override Future<void> stop() => AudioPlayerUtil.of().stop(); @override Future<void> seek(Duration position) => AudioPlayerUtil.of().seek(position); @override Future<void> skipToQueueItem(int index) => AudioPlayerUtil.of().seek(Duration.zero, index: index); @override Future<void> skipToNext() => AudioPlayerUtil.of().next(); @override Future<void> skipToPrevious() => AudioPlayerUtil.of().previous(); }

audio_service 库的初始化

初始化位置一般在启动页,在用户同意协议之后。

  • builder:这个参数需要的是 AudioHandler 对象,AudioCustomHandler.of().init() 函数会返回 AudioCustomHandler 对象,AudioCustomHandler 继承自 BaseAudioHandler, BaseAudioHandler 又继承自 AudioHandler
  • config:这个参数我并未深究,其用途读者可以自行查阅 audio_service 的文档

// .... AudioService.init( builder: () => AudioCustomHandler.of().init(), config: AudioServiceConfig( androidNotificationChannelId: 'com.xxx.xxxxxx.audio', androidNotificationChannelName: 'xxxxxx', ), ); // ...

资源销毁

单例模式中的资源不释放/销毁问题也不大,因为单例的释放/销毁一般都伴随着整个进程的结束。但为了养成一个良好的编程习惯,还是要销毁。 销毁时机可以放在主页面的销毁函数中,一般主页面销毁意味着整个 App 的退出,进程的结束。


class AudioCustomHandler extends BaseAudioHandler { // ...... Future<void> dispose() async { while (_streamSubscriptions.isNotEmpty) { await _streamSubscriptions.removeLast().cancel(); } return Future.value(); } }

附上源码


import 'dart:async'; import 'package:audio_service/audio_service.dart'; import 'package:ephemeris_mobile/utils/AudioPlayerUtil.dart'; import 'package:just_audio/just_audio.dart'; class AudioCustomHandler extends BaseAudioHandler { final List<StreamSubscription> _streamSubscriptions = []; static AudioCustomHandler? _instance; static AudioCustomHandler of() { _instance ??= AudioCustomHandler(); return _instance!; } /// 初始化 AudioCustomHandler init() { // 初始化控制台的状态、功能、信息 playbackState.add( PlaybackState( controls: [ MediaControl.skipToPrevious, MediaControl.play, MediaControl.pause, MediaControl.skipToNext, ], systemActions: {MediaAction.seek}, processingState: AudioProcessingState.ready, playing: false, updatePosition: Duration.zero, bufferedPosition: Duration.zero, ), ); // 监听播放器状态变化,同步到通知栏 _streamSubscriptions.add( AudioPlayerUtil.of().playerStateStream.listen((state) { AudioProcessingState processingState = state.processingState == ProcessingState.completed ? AudioProcessingState.completed : state.processingState == ProcessingState.ready ? AudioProcessingState.ready : state.processingState == ProcessingState.loading ? AudioProcessingState.loading : state.processingState == ProcessingState.buffering ? AudioProcessingState.buffering : AudioProcessingState.idle; playbackState.add( playbackState.value.copyWith( playing: state.playing, processingState: processingState, ), ); }), ); // 监听当前播放歌曲 _streamSubscriptions.add( AudioPlayerUtil.of().currentIndexStream.listen((index) { mediaItem.add( MediaItem( id: 'media_id', title: AudioPlayerUtil.of().currentAudio?.name ?? '未知歌曲', artist: AudioPlayerUtil.of().currentAudio?.artist, duration: AudioPlayerUtil.of().duration, artUri: Uri.parse( AudioPlayerUtil.of().currentAudio?.image ?? 'assets/images/logo.png', ), ), ); }), ); // 监听播放进度 _streamSubscriptions.add( AudioPlayerUtil.of().positionStream.listen((position) { playbackState.add( playbackState.value.copyWith( updatePosition: position, bufferedPosition: position, ), ); }), ); return this; } @override Future<void> play() => AudioPlayerUtil.of().play(); @override Future<void> pause() => AudioPlayerUtil.of().pause(); @override Future<void> stop() => AudioPlayerUtil.of().stop(); @override Future<void> seek(Duration position) => AudioPlayerUtil.of().seek(position); @override Future<void> skipToQueueItem(int index) => AudioPlayerUtil.of().seek(Duration.zero, index: index); @override Future<void> skipToNext() => AudioPlayerUtil.of().next(); @override Future<void> skipToPrevious() => AudioPlayerUtil.of().previous(); Future<void> dispose() async { while (_streamSubscriptions.isNotEmpty) { await _streamSubscriptions.removeLast().cancel(); } return Future.value(); } }

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐