HarmonyOS 7 实战 14:音视频开发,AVPlayer 与 AVRecorder 避坑指南
播放器卡一下,用户就卸载了
做应用,尤其是内容类应用,音视频功能是标配。但音视频开发有个特点:入门容易,精通难。
很多同学第一次写播放器,直接 AVPlayer.play() 一把梭,本地跑得飞起。结果一上生产环境,网络稍微波动一下,播放器就卡死了;用户切到后台,声音突然没了;播完一集,内存泄漏了……
音视频的坑,大部分不在 API 本身,而在状态机和生命周期。这篇就把 AVPlayer 和 AVRecorder 的状态流转、生命周期管理和常见坑讲清楚,代码都是能直接跑的。
一、AVPlayer 状态机:别在错误的时候做错误的事
AVPlayer 是一个典型的状态机。它的状态包括:idle、initialized、prepared、playing、paused、completed、stopped、released。
你只能在正确的状态调用正确的方法。 比如,在 idle 状态下调 play(),直接抛异常崩掉;在 prepared 之前调 play(),同样崩溃。
初始化和播放的代码如下:
typescript
import media from '@ohos.multimedia.media';
let avPlayer: media.AVPlayer;
async function initPlayer(url: string) {
avPlayer = await media.createAVPlayer();
// 1. 注册状态回调
avPlayer.on('stateChange', (state: string) => {
console.info('当前状态: ' + state);
switch (state) {
case 'initialized':
// 2. 准备资源
avPlayer.prepare();
break;
case 'prepared':
// 3. 准备好了,开始播放
avPlayer.play();
break;
case 'playing':
// 更新 UI
break;
case 'error':
console.error('播放出错');
break;
}
});
// 4. 设置资源
avPlayer.url = url;
}
看明白了吗?prepare() 必须在 stateChange 回调里 initialized 之后调用,play() 必须在 prepared 之后调用。 如果你直接 avPlayer.url = url; avPlayer.prepare(); avPlayer.play();,100% 会出错,因为此时状态还没流转到 initialized。
坑来了:网络 URL 要设 url,本地资源要用 fdSrc。
如果你加载的是 rawfile 里的视频,不能直接设 url,要先用 resourceManager 打开文件,获取文件描述符(fd),再赋值给 fdSrc:
typescript
const context = getContext(this);
const fileDescriptor = await context.resourceManager.getRawFd('video.mp4');
avPlayer.fdSrc = { fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };
二、后台播放与焦点抢占
用户切到后台,你希望音乐继续播放。但这需要申请长时任务,否则系统直接把你的播放器杀了。
在 module.json5 里声明:
json
{
"module": {
"abilities": [
{
"name": "EntryAbility",
"backgroundModes": ["audioPlayback"]
}
]
}
}
然后在代码里申请长时任务:
typescript
import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager';
let backgroundTaskId: number;
function startBackgroundPlay() {
const context = getContext(this);
backgroundTaskManager.startBackgroundTask(context, backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK)
.then((id) => {
backgroundTaskId = id;
});
}
function stopBackgroundPlay() {
if (backgroundTaskId) {
backgroundTaskManager.stopBackgroundTask(backgroundTaskId);
}
}
另一个坑:音频焦点被抢占。
用户正在听你的应用播放音乐,突然来了个电话,或者打开了抖音。如果你不做处理,两个应用的声音会同时播放,用户直接疯掉。
HarmonyOS 提供了音频焦点管理:
typescript
import audio from '@ohos.multimedia.audio';
const audioManager = audio.getAudioManager();
const interruptMode = audio.InterruptMode.SHARE_MODE;
audioManager.on('audioInterrupt', (event) => {
if (event.forceType === audio.InterruptForceType.INTERRUPT_FORCE) {
// 系统强制打断,比如来电
avPlayer.pause();
} else {
// 建议打断,比如其他应用申请焦点
avPlayer.pause();
}
});
收到打断回调后,必须立即 pause(),否则系统可能会直接杀掉你的进程。
三、AVRecorder 录制:权限与格式的坑
录制比播放更麻烦,因为涉及到麦克风权限和编码格式。
首先,权限:
json
{
"requestPermissions": [
{ "name": "ohos.permission.MICROPHONE" },
{ "name": "ohos.permission.WRITE_MEDIA" }
]
}
这两个都是 user_grant 级别,必须动态申请。千万别在应用启动时就弹麦克风权限,用户会觉得很恐怖。 必须等用户点击“开始录音”按钮时再申请。
录制参数配置:
typescript
const avRecorderConfig: media.AVRecorderConfig = {
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
profile: {
audioBitrate: 96000,
audioChannels: 2,
audioCodec: media.CodecMimeType.AUDIO_AAC,
audioSampleRate: 48000,
fileFormat: media.ContainerFormatType.CFT_MPEG_4A
},
url: 'fd://' + file.fd
};
avRecorder = await media.createAVRecorder();
await avRecorder.prepare(avRecorderConfig);
await avRecorder.start();
坑来了:录制格式选错,文件打不开。
如果你选了 CFT_MPEG_4A,文件后缀必须是 .m4a。选了 CFT_MPEG_4,后缀是 .mp4。如果你随便写个 .mp3,生成的音频文件在某些播放器上无法播放。格式要和容器严格对应。
另外,录制完成后必须调用 release():
typescript
await avRecorder.stop(); await avRecorder.release(); avRecorder = null;
不调 release,麦克风会被一直占用,其他应用没法录音,用户只能重启手机。
四、音视频常见坑合集
坑一:release() 没调用,内存泄漏。
AVPlayer 和 AVRecorder 都是占用底层硬解码资源的。每次使用完,必须在页面 aboutToDisappear 里调用 release(),并置空对象。否则播放几个视频后,应用直接闪退。
坑二:网络波动导致播放卡死。
AVPlayer 有缓冲机制,但网络极差时会一直 buffering。你需要监听 on('bufferingUpdate'),如果卡顿超过 5 秒,提示用户网络不佳,或者自动降级清晰度。
坑三:视频渲染尺寸不对。
如果你用 XComponent 渲染视频,需要监听 on('videoSizeChange'),获取视频的真实宽高,然后调整 XComponent 的布局,否则视频会被拉伸变形。
五、总结
音视频开发的核心是尊重状态机,管好生命周期。记住三点:
-
严格遵循 AVPlayer 的状态流转,别跳状态调用方法。
-
后台播放申请长时任务,处理音频焦点抢占。
-
用完必须
release(),否则内存泄漏。
更多推荐

所有评论(0)