HarmonyOS 6.0 音频播放与控制

发布时间:2026/7/15 15:08:48
HarmonyOS 6.0 音频播放与控制 HarmonyOS NEXT的音频体系比很多人想的复杂——不是简单调个play/pause就完了。AVPlayer是个有状态机的东西状态流转不对直接crash后台播放要配session音频焦点中断要处理每一环都有坑。AVPlayer状态机这是理解音频播放的核心。AVPlayer有8个状态必须按顺序流转idle → initialized → prepared → playing ⇄ paused → completed ↓ stopped → prepared(重新prepare)状态流转规则很严格创建后是idle必须先设置fdSrc/urlSrc才能到initializedinitialized后调prepare()进入prepared只有prepared/playing/paused才能调play()playing状态调pause()到pausedpaused调play()回到playing播放完自动到completedcompleted调play()从头开始或调stop()再prepare()重新来任何状态调release()释放资源违反顺序的操作会抛异常。最常见的新手错误创建完直接play()——不行必须先prepare。import{media}fromkit.MediaKit;letavPlayer:media.AVPlayerawaitmedia.createAVPlayer();avPlayer.on(stateChange,(state:string){switch(state){caseinitialized:avPlayer.prepare();break;caseprepared:avPlayer.play();break;casecompleted:avPlayer.stop();break;}});avPlayer.urlSrchttps://example.com/audio.mp3;这种在stateChange里驱动下一步的模式是官方推荐写法避免手动判断当前状态。播放控制prepared之后就可以自由控制了avPlayer.play();// 播放avPlayer.pause();// 暂停avPlayer.stop();// 停止(回到prepared需重新prepare)avPlayer.seek(timeMs);// 跳转(毫秒)avPlayer.setVolume(0.5);// 音量(0.0-1.0)avPlayer.setSpeed(media.SpeedMode.SPEED_FORWARD_2_00_X);// 倍速seek的参数是毫秒不是秒这个细节容易忽略。setSpeed的倍速选项是枚举media.SpeedMode.SPEED_FORWARD_1_00_X// 1倍media.SpeedMode.SPEED_FORWARD_1_25_X// 1.25倍media.SpeedMode.SPEED_FORWARD_1_75_X// 1.75倍media.SpeedMode.SPEED_FORWARD_2_00_X// 2倍进度监听音频播放最核心的交互是进度条。AVPlayer提供几个回调avPlayer.on(timeUpdate,(timeMs:number){// 当前播放位置(毫秒)约每秒触发一次this.currentTimetimeMs;});avPlayer.on(durationUpdate,(durationMs:number){// 总时长(毫秒)prepared后触发this.totalDurationdurationMs;});avPlayer.on(seekDone,(timeMs:number){// seek完成后触发});Slider绑定进度Slider({value:this.currentTime,min:0,max:this.totalDuration0?this.totalDuration:1,step:1000}).onChange((value:number){avPlayer.seek(value);})注意Slider的onChange在拖动过程中持续触发可能导致频繁seek。更好的做法是用onActionEndSlider({...}).onChange((value:number){this.currentTimevalue;// 只更新UI})// onActionEnd在API 12可用本地文件播放用fdSrc播放本地文件import{fileIoasfs}fromkit.CoreFileKit;constfile:fs.Filefs.openSync(filePath,fs.OpenMode.READ_ONLY);avPlayer.fdSrc{fd:file.fd,offset:0,length:fs.statSync(filePath).size};注意fdSrc接受的是fdoffsetlength对象不是简单的路径。播放完要closeSync关闭fd。也可以用urlSrc播放网络音频avPlayer.urlSrchttps://example.com/audio.mp3;网络播放需要INTERNET权限。音频焦点与中断处理手机上音频焦点是个大问题——来电话了、闹钟响了、其他应用开始播放了你的音频怎么办import{audio}fromkit.AudioKit;letaudioManager:audio.AudioManageraudio.getAudioManager();letaudioSessionManager:audio.AudioSessionManageraudioManager.getSessionManager();// 请求音频焦点audioSessionManager.activate(audio.AudioSessionStrategy({audioContentType:audio.AudioContentType.CONTENT_TYPE_MUSIC,audioStreamUsage:audio.StreamUsage.STREAM_USAGE_MUSIC,audioInterruptMode:audio.InterruptMode.INDEPENDENT_MODE}));avPlayer.on(audioInterrupt,(interruptEvent:audio.InterruptEvent){if(interruptEvent.forceTypeaudio.InterruptForceType.INTERRUPT_FORCE){// 系统强制操作必须服从if(interruptEvent.hintTypeaudio.InterruptHint.INTERRUPT_HINT_PAUSE){// 被强制暂停}}else{// 可以自己决定if(interruptEvent.hintTypeaudio.InterruptHint.INTERRUPT_HINT_PAUSE){avPlayer.pause();}elseif(interruptEvent.hintTypeaudio.InterruptHint.INTERRUPT_HINT_RESUME){avPlayer.play();}}});forceType区分了强制和可选两种情况。来电、闹钟这种是强制的不处理直接被系统暂停。其他应用播放则可以协商。后台播放默认切到后台音频就停了。要后台播放得配后台任务module.json5声明{ requestPermissions: [ { name: ohos.permission.KEEP_BACKGROUND_RUNNING } ], abilities: [ { backgroundModes: [audioPlayback] } ] }代码里使用BackgroundTaskManagerimport{backgroundTaskManager}fromkit.BackgroundTasksKit;letbgMode:backgroundTaskManager.BackgroundModebackgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK;backgroundTaskManager.requestSuspendDelay(audio,bgMode,(){// 后台任务回调});不过后台播放涉及的配置和权限比较繁琐实际开发中建议封装成AudioService统一管理。播放列表模式单曲循环/列表循环/随机播放的实现enumPlayMode{SINGLE,// 单曲循环LIST,// 列表循环SHUFFLE// 随机播放}StateplayMode:PlayModePlayMode.LIST;StatecurrentIndex:number0;playList:string[][song1.mp3,song2.mp3,song3.mp3];avPlayer.on(completed,(){switch(this.playMode){casePlayMode.SINGLE:avPlayer.seek(0);avPlayer.play();break;casePlayMode.LIST:this.currentIndex(this.currentIndex1)%this.playList.length;this.playAtIndex(this.currentIndex);break;casePlayMode.SHUFFLE:this.currentIndexMath.floor(Math.random()*this.playList.length);this.playAtIndex(this.currentIndex);break;}});completed事件是切换下一首的时机。单曲循环直接seek(0)重新播放比重新prepare效率高。注意事项AVPlayer实例一次只能播放一个源切换源需要stop→release→重新createstateChange回调在UI线程不要在里面做耗时操作fdSrc播放完要释放fd否则文件句柄泄漏音量setVolume只影响当前流不改变系统音量模拟器可能不支持音频播放真机测试为准AVPlayer创建是异步的用await确保拿到实例再操作音频播放看似简单实际工程中状态管理焦点处理后台播放资源释放这些环节一个都不能少。状态机是核心理解了状态流转其他问题都好解决。