资讯详情 Flutter/OpenHarmony 三方库适配实战:audioplayers 多播放器
📅 2026/10/22 17:54:12
Flutter/OpenHarmony 三方库适配实战audioplayers 多播放器一、项目信息项目内容仓库名称audioplayers仓库地址https://atomgit.com/oh-flutter/audioplayers基于版本audioplayers 6.5.1适配 TAG6.5.1-ohos-1.0.1OHOS 插件包audioplayers_ohos 5.2.1OHOS 原生入口AudioplayersPlugin原生播放器WrappedPlayer MediaPlayerPlayer实例方法通道xyz.luan/audioplayers全局方法通道xyz.luan/audioplayers.globalaudioplayers支持多个播放器实例同时存在公开 API 包括音源设置、播放、暂停、跳转、音量、倍速、事件流和音频上下文。OHOS 适配需要在保留这些语义的同时处理多个AVPlayer、音频焦点、后台任务和资源释放。二、插件架构本次适配采用实例通道与全局通道分离的结构audioplayers Dart | -- xyz.luan/audioplayers | create / setSource / resume / pause / seek / dispose | -- xyz.luan/audioplayers.global | init / setAudioContext / global log / global error | -- xyz.luan/audioplayers.global/events | v AudioplayersPlugin | -- HashMapplayerId, WrappedPlayer | -- 每个播放器独立 EventChannel | -- MediaPlayerPlayer / SoundPoolPlayer | -- ohos.multimedia.media.AVPlayer实例命令通过playerId找到对应播放器全局命令管理默认音频上下文、日志和全局错误。每个播放器拥有独立事件通道多个播放器同时播放时不会互相串流。三、插件声明与初始化audioplayers_ohos/pubspec.yamlname:audioplayers_ohosversion:5.2.1flutter:plugin:implements:audioplayersplatforms:ohos:package:xyz.luan.audioplayerspluginClass:AudioplayersPlugin原生插件在引擎附加时创建通道和 SoundPool 管理器onAttachedToEngine(binding:FlutterPluginBinding):void{this.contextbinding.getApplicationContext();this.binaryMessengerbinding.getBinaryMessenger();this.soundPoolManagernewSoundPoolManager(this);this.methodsnewMethodChannel(this.binaryMessenger,xyz.luan/audioplayers,);this.globalMethodsnewMethodChannel(this.binaryMessenger,xyz.luan/audioplayers.global,);this.globalEventsnewEventHandler(newEventChannel(this.binaryMessenger,xyz.luan/audioplayers.global/events,),);}Ability 分离时暂停所有正在播放的播放器并停止后台长时任务引擎销毁时释放所有实例、SoundPool 管理器和事件通道。四、创建播放器与事件通道实例通道收到create后创建独立播放器和事件通道if(call.methodcreate){constplayerIdcall.argument(playerId)asstring;consteventHandlernewEventHandler(newEventChannel(this.binaryMessenger!,xyz.luan/audioplayers/events/${playerId},),);this.players.set(playerId,newWrappedPlayer(this,eventHandler,this.defaultAudioContext.copy(),this.soundPoolManager!,),);response.success(1);return;}事件通道会发送 prepared、duration、complete、seek complete、current position、log 和 error。位置更新使用定时器只在存在实际播放中的播放器时启动startUpdates():void{this.stopUpdates();this.updateIntervalsetInterval((){this.updateCallback();},200);}stopUpdates():void{if(this.updateInterval!null){clearInterval(this.updateInterval);this.updateIntervalnull;}}没有播放任务后停止定时器可以避免空闲页面持续消耗计时器资源。五、方法映射实例命令按 Dart API 映射到底层播放器switch(call.method){casesetSourceUrl:awaitplayer.setSource(newUrlSource(call.argument(url)asstring,call.argument(isLocal)asboolean,),);break;casesetSourceBytes:player.setSource(newBytesSource(call.argument(bytes)asArrayBuffer),);break;caseresume:awaitthis.startContinuousTask();player.play();break;casepause:player.pause();awaitthis.stopContinuousTask();break;casestop:awaitplayer.stop();break;caseseek:player.seek(call.argument(position)asnumber);break;casesetVolume:player.setVolume(call.argument(volume)asnumber);break;casesetPlaybackRate:player.setRate(call.argument(playbackRate)asnumber);break;casedispose:player.dispose();this.players.remove(playerId);break;}未知或文本 MIME 不能作为音频播放。URL 音源会先按扩展名判断 MIME文本文件会通过错误事件返回而不是让AVPlayer抛出难以理解的底层错误。六、URL 和字节流音源URL 音源区分远程和本地setForMediaPlayer(mediaPlayer:media.AVPlayer):void{if(this.isLocal){constfilefs.openSync(this.url);mediaPlayer.urlfd://${file.fd};}else{mediaPlayer.urlthis.url;}}本地文件通过 fd 加载远程地址交给AVPlayer。字节流音源使用AVDataSrcDescriptor提供读取回调constsource:media.AVDataSrcDescriptor{fileSize:this.bytes.byteLength,callback:(buffer,length,position){if(positionnull||positionsource.fileSize){return-1;}constcopyLengthMath.min(length,this.bytes.byteLength-position,);newUint8Array(buffer).set(newUint8Array(this.bytes.slice(position,positioncopyLength)),);returncopyLength;},};mediaPlayer.dataSrcsource;低延迟 SoundPool 当前不支持任意字节流这种组合会明确抛出不支持错误。七、音频焦点与后台任务播放器通过FocusManager管理音频会话。获得焦点后播放临时失去焦点时暂停并标记恢复状态永久失去焦点时停止等待自动恢复maybeRequestAudioFocus(player:WrappedPlayer):void{conststrategy:audio.AudioSessionStrategy{concurrencyMode:this.getFocusStrategy(),};this.audioSessionManager.activateAudioSession(strategy).then(()player.actuallyPlay()).catch((err:BusinessError){Log.e(TAG,activate failed:${err.code},${err.message});});}后台播放通过 AVSession 和长时任务实现this.sessionawaitAVSessionManager.createAVSession(context,audioplayers,audio,);awaitthis.session.activate();awaitbackgroundTaskManager.startBackgroundRunning(context,backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK,wantAgentObj,);宿主 HAP 必须配置{ module: { abilities: [ { backgroundModes: [audioPlayback], }, ], requestPermissions: [ { name: ohos.permission.KEEP_BACKGROUND_RUNNING, reason: $string:background_running_reason, usedScene: { abilities: [EntryAbility], when: inuse, }, }, ], }, }同时需要在资源文件中提供权限用途文案。未配置长时任务时切后台后播放可能被系统回收。八、Dart 使用示例多个实例播放finalbackgroundAudioPlayer(playerId:background);finaleffectAudioPlayer(playerId:effect);awaitbackground.setSourceUrl(https://example.com/background.mp3,isLocal:false,);awaitbackground.setReleaseMode(ReleaseMode.loop);awaitbackground.play();awaiteffect.setSourceAsset(sounds/click.wav);awaiteffect.setVolume(0.6);awaiteffect.play();effect.onPlayerComplete.listen((_){effect.dispose();});内存字节流finalplayerAudioPlayer();finalbytesawaitrootBundle.load(assets/audio.wav);awaitplayer.setSourceBytes(bytes.buffer.asUint8List());页面销毁时依次停止并释放播放器awaitplayer.stop();awaitplayer.dispose();九、构建、测试结果验证环境Flutter:3.44.9ohos-0.0.1-canary1OHOS SDK:26DevEco Studio:26.0.0模拟器:emulator 7.0.0.107(SP8DEVC00E999R4P11)验证内容适配包flutter analyze --no-pub通过。主包flutter analyze --no-pub通过。22 个 Dart 测试通过。示例 HAP 构建、安装和启动成功。十、遗留问题setBalance、音效、空间音频和不同音频焦点策略仍需按系统版本验证。HLS、低延迟直播和复杂网络切换需要真机测试。后台播放依赖宿主 HAP 的长时任务和权限配置。audioplayers_android_exo中部分.gitignore是循环符号链接构建工具链扫描全仓库时应排除该目录。示例中的icudtl.dat较大本次远端快照不提交该构建资源。十一、代码提交与协作欢迎加入 CPF-Flutter 鸿蒙社区参与 FlutterOHOS 经验交流。