☰
Flutter/OpenHarmony 三方库适配实战:just_audio 音频播放
2026/10/22 7:36:44 网站建设 项目流程

Flutter/OpenHarmony 三方库适配实战:just_audio 音频播放

一、项目信息

项目内容
仓库名称just_audio
仓库地址https://atomgit.com/oh-flutter/just_audio
基于版本just_audio 0.10.5
适配 TAG0.10.5-ohos-1.0.4
OHOS 插件包just_audio内置ohos实现
OHOS 原生入口JustAudioOhosPlugin
原生播放器MediaAvPlayer
主方法通道com.ryanheise.just_audio.methods

just_audio提供播放器、播放列表、进度流、缓冲状态和音频源抽象。OHOS 适配的
重点是让这些概念对应到AVPlayer生命周期,同时保留原库的 Dart API。

二、通道设计

原库使用“主通道创建播放器,每个播放器独立方法和事件通道”的结构:

JustAudioOhosPlugin | +-- com.ryanheise.just_audio.methods | init / disposePlayer / disposeAllPlayers | +-- AudioPlayer(id) | +-- com.ryanheise.just_audio.methods.<id> +-- com.ryanheise.just_audio.events.<id> +-- com.ryanheise.just_audio.data.<id> | +-- MediaAvPlayer | +-- @ohos.multimedia.media.AVPlayer

主通道只管理播放器实例的创建和销毁,具体load、play、pause、seek、
setVolume等命令由每个播放器自己的通道处理。这样多个播放器之间不会串号。

三、插件注册与生命周期

插件声明在主just_audio/pubspec.yaml中:

flutter:plugin:platforms:ohos:pluginClass:JustAudioOhosPlugin

原生插件创建主方法通道,并监听引擎重启:

exportdefaultclassJustAudioOhosPluginimplementsFlutterPlugin{onAttachedToEngine(binding:FlutterPluginBinding):void{constcontext=binding.getApplicationContext();constmessenger=binding.getBinaryMessenger();this.methodCallHandler=newMainMethodCallHandler(context,messenger);this.channel=newMethodChannel(messenger,'com.ryanheise.just_audio.methods',);this.channel.setMethodCallHandler(this.methodCallHandler);this.lifecycleListener={onPreEngineRestart:()=>{this.methodCallHandler?.dispose();},}asEngineLifecycleListener;binding.getFlutterEngine().addEngineLifecycleListener(this.lifecycleListener,);}}

热重启时必须先释放所有底层播放器,否则旧AVPlayer会继续占用音频焦点和媒体
资源。引擎销毁时还需要移除监听器、解绑 MethodChannel 并清理全局 Context。

四、创建播放器时等待原生初始化

主处理器使用播放器 id 管理实例:

case'init':{constid=call.argument('id')asstring;if(this.players.has(id)){result.error('Platform player '+id+' already exists',null,null);break;}constplayer=newAudioPlayer(this.messenger!,id);awaitplayer.waitForInitialization();this.players.set(id,player);result.success(null);break;}

MediaAvPlayer的构造函数不能等待异步初始化,因此它保存初始化 Promise:

constructor(){this.initPromise=this.initAudioPlayer();}asyncwaitForInitialization():Promise<void>{awaitthis.initPromise;if(this.initError!=null){thrownewError(this.initError.message);}}

主通道在init响应前等待这个 Promise,避免 Dart 端刚收到创建结果就调用load
时底层avPlayer仍为null。

五、每个播放器的通道与事件

播放器构造时创建三条通道:

this.methodChannel=newMethodChannel(messenger,'com.ryanheise.just_audio.methods.'+id,);this.eventChannel=newBetterEventChannel(messenger,'com.ryanheise.just_audio.events.'+id,);this.dataEventChannel=newBetterEventChannel(messenger,'com.ryanheise.just_audio.data.'+id,);

BetterEventChannel保存当前EventSink。没有 Dart 监听器时不发送事件,取消
订阅后清空 sink:

onListen(args:Object,eventSink:EventSink):void{this.eventSink=eventSink;}onCancel(args:Object):void{this.eventSink=null;}

播放器方法通道支持加载、播放、暂停、音量、速度、跳转和播放列表调整:

switch(call.method){case'load':awaitthis.load(call,result);break;case'play':awaitthis.play(result);break;case'pause':this.pause();result.success(newMap<String,Object>());break;case'setVolume':this.setVolume(call.argument('volume')asnumber);break;case'setSpeed':this.setSpeed(call.argument('speed')asnumber);break;case'seek':awaitthis.seek(call.argument('positionMs')asnumber,call.argument('index')asnumber|null,result,);break;}

跳转操作需要等待异步完成并回执,否则 Dart 的seek()Future 可能永久等待。

六、AVPlayer 状态与错误

MediaAvPlayer将AVPlayer状态机转换为播放器状态:

  • 初始化阶段等待avPlayer创建完成。
  • load设置播放源并等待initialized/prepared。
  • 缓冲事件更新 buffered position。
  • 完成事件将处理状态改为 completed。
  • 错误事件通过 EventChannel 抛回 Dart。

初始化失败必须明确上报:

onErrorCallback(err:BusinessError<void>):void{this.sendError(err.code?.toString()??err.name,err.message,);}

如果加载失败但不发送错误,Dart 端的load()可能一直处于 loading。适配中特别
处理了未 prepare 完成时的播放器操作错误,使其转换为可读错误;已 prepare 后的
时序错误则记录日志,避免正常切歌流程触发误报。

七、音频源处理

适配层支持网络 URL、本地文件、Asset 和播放列表来源。本地文件使用文件描述符,
避免将路径字符串交给不支持该路径的播放器接口。

MediaSource负责将 Dart 参数转换为具体播放源。加载时根据progressive、
hls、dash、concatenating、clipping、looping等类型创建不同数据结构。

播放列表操作通过concatenatingInsertAll、concatenatingRemoveRange和
concatenatingMove更新底层列表。索引变化后必须同步当前播放项、初始位置和
shuffle 顺序。

八、Dart 使用示例

网络音频播放:

finalplayer=AudioPlayer();awaitplayer.setUrl('https://example.com/audio.mp3');awaitplayer.setVolume(0.8);awaitplayer.setSpeed(1.25);player.playbackEventStream.listen((event){debugPrint('position=${event.position}, state=${event.processingState}');});awaitplayer.seek(constDuration(seconds:30));awaitplayer.pause();awaitplayer.dispose();

加载本地文件:

awaitplayer.setFilePath('/data/storage/el2/base/files/audio.wav');

网络播放需要宿主 HAP 声明:

{ module: { requestPermissions: [ { name: 'ohos.permission.INTERNET' }, ], }, }

页面销毁时必须调用dispose(),不能只暂停播放器。暂停不会释放AVPlayer、音频
焦点和所有事件监听。

九、构建、测试与结果

执行:

flutter analyze --no-pub lib fluttertest

结果:

  • 静态分析无问题。
  • 70 个 Dart 测试通过。
  • 示例 HAP 构建、安装和启动成功。

十、遗留问题

  • 截图不能替代不同采样率、编码格式和网络抖动的真机测试。
  • 音频焦点、蓝牙耳机切换和来电中断需要系统级场景验证。
  • 后台播放需要宿主应用配置长时任务和相关权限,不能只依赖插件。
  • 播放列表、Clipping、DASH/HLS 组合场景应继续补充集成测试。

十一、代码提交与协作

欢迎加入 CPF-Flutter 鸿蒙社区,参与 Flutter
OHOS 经验交流。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询