多媒体播放:AVPlayer播放音频与视频的完整流程(35)
·
在鸿蒙(HarmonyOS)开发中,AVPlayer 是功能最完善的音视频播放引擎,集成了流媒体和本地资源解析、媒体解封装、解码和渲染功能。无论是播放音频还是视频,都遵循一套严格的状态机流转机制。
以下是使用 AVPlayer 播放音视频的完整流程与核心指南:
一、 核心状态机流转
AVPlayer 的执行逻辑强依赖于其内部状态(state)。如果在错误的状态下调用方法,系统会抛出异常。核心状态流转如下:
- idle(空闲):创建实例后的初始状态。
- initialized(已初始化):设置资源(URL/FD)后的状态。
- prepared(准备就绪):调用
prepare()后的状态,此时可获取时长、设置音量等。 - playing(播放中):调用
play()后的状态。 - paused(暂停):调用
pause()后的状态。 - completed(完成):播放自然结束后的状态。
- released(已释放):调用
release()销毁实例后的状态。
import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct AVPlayerStateDemo {
// 声明 AVPlayer 实例
private avPlayer: media.AVPlayer | null = null;
// 用于 UI 显示当前状态
@State currentState: string = 'Idle';
aboutToAppear() {
this.initAVPlayer();
}
// 1. 创建实例并绑定状态机监听
private async initAVPlayer() {
try {
this.avPlayer = await media.createAVPlayer();
// 核心:监听状态变化(必须在设置资源前完成)
this.avPlayer.on('stateChange', (state: string) => {
console.info(`AVPlayer state changed to: ${state}`);
this.currentState = state;
switch (state) {
case 'idle':
// 初始状态,准备设置资源
this.setSource();
break;
case 'initialized':
// 资源设置成功,进入准备阶段
this.avPlayer?.prepare();
break;
case 'prepared':
// 准备就绪,可以获取时长、音量等,并执行播放
console.info(`Duration: ${this.avPlayer?.duration}ms`);
// 此处不自动播放,留给按钮控制
break;
case 'playing':
// 正在播放中
break;
case 'paused':
// 暂停状态
break;
case 'completed':
// 播放自然结束
console.info('Playback completed');
break;
case 'stopped':
// 停止状态,可重新 prepare 或 release
break;
case 'released':
// 资源已释放
break;
}
});
// 监听错误事件
this.avPlayer.on('error', (err: BusinessError) => {
console.error(`AVPlayer Error: Code=${err.code}, Message=${err.message}`);
// 发生错误时通常需要重置或释放资源
this.avPlayer?.reset();
});
} catch (error) {
console.error(`Create AVPlayer failed: ${(error as BusinessError).message}`);
}
}
// 2. 设置播放资源(以网络 URL 为例,需 INTERNET 权限)
private setSource() {
if (this.avPlayer) {
// 设置 URL 后,状态机会自动流转到 initialized
this.avPlayer.url = 'https://example.com/sample_audio.mp3';
}
}
// 3. 基础播控方法(严格遵循状态机规则)
private togglePlayPause() {
if (!this.avPlayer) return;
// 只有在 prepared, playing, paused, completed 状态下才能调用 play
if (['prepared', 'paused', 'completed'].includes(this.currentState)) {
this.avPlayer.play();
}
// 只有在 playing 状态下才能调用 pause
else if (this.currentState === 'playing') {
this.avPlayer.pause();
}
}
// 4. 切换资源或重置
private resetPlayer() {
// 在 prepared, playing, paused, completed 状态下可调用 reset
// 调用后状态机会回到 idle
this.avPlayer?.reset();
}
// 5. 销毁资源(页面退出时必须调用)
aboutToDisappear() {
if (this.avPlayer) {
this.avPlayer.release();
this.avPlayer = null;
}
}
build() {
Column({ space: 20 }) {
Text(`当前状态: ${this.currentState}`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
Button('播放 / 暂停')
.onClick(() => this.togglePlayPause())
.enabled(['prepared', 'playing', 'paused', 'completed'].includes(this.currentState))
Button('重置 (Reset)')
.onClick(() => this.resetPlayer())
.enabled(['prepared', 'playing', 'paused', 'completed', 'stopped'].includes(this.currentState))
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
二、 完整开发步骤
1. 创建实例与注册监听
首先创建 AVPlayer 实例,并务必在设置资源前注册状态和错误监听,以免错过状态变化事件。
import { media } from '@kit.MediaKit';
// 创建 AVPlayer 实例
let avPlayer: media.AVPlayer = await media.createAVPlayer();
// 监听状态变化(核心)
avPlayer.on('stateChange', (state: string) => {
switch (state) {
case 'initialized':
avPlayer.prepare(); // 资源设置成功后,进入准备阶段
break;
case 'prepared':
avPlayer.play(); // 准备就绪后,开始播放
break;
}
});
// 监听错误信息
avPlayer.on('error', (err) => {
console.error(`播放器错误: ${err.message}`);
});
2. 设置播放资源
根据资源类型选择不同的设置方式:
- 网络资源:直接设置
url属性(需申请ohos.permission.INTERNET权限)。 - 本地 Rawfile 资源:通过
resourceManager.getRawFd()获取文件描述符,赋值给fdSrc属性。 - 沙箱文件:通过
fs.open()获取沙箱路径的文件描述符,赋值给fdSrc或使用fd://协议赋值给url。
3. 视频专属:设置显示窗口
如果播放的是视频,必须获取显示画面的 surfaceId 并绑定给 AVPlayer。
- 在 UI 层使用
XComponent组件。 - 在
XComponent的onLoad回调中,通过getXComponentSurfaceId()获取surfaceId。 - 将
surfaceId赋值给avPlayer.surfaceId。
4. 播放控制与参数设置
在 prepared、playing 或 paused 状态下,可以执行播控操作和参数调节:
- 基础播控:
play()(播放)、pause()(暂停)、stop()(停止)。 - 进度跳转:
seek(timeMs),配合on('seekDone')监听跳转完成。 - 参数调节:
setVolume()(音量)、setSpeed()(倍速)、setMediaMuted()(静音)。
5. 资源释放与重置
- 切换视频/音频:当需要播放下一个资源时,调用
reset()方法,播放器会回到idle状态,允许重新设置资源。 - 退出页面/销毁:当不再需要播放器时,必须调用
release()销毁实例,进入released状态,以回收系统内存资源。
import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct AVPlayerVideoDemo {
private avPlayer: media.AVPlayer | null = null;
private xComponentController: XComponentController = new XComponentController();
// 用于暂存 SurfaceId
private surfaceId: string = '';
// 视频资源 URL(需 INTERNET 权限)
private videoUrl: string = 'https://www.w3schools.com/html/mov_bbb.mp4';
@State isPlaying: boolean = false;
@State currentTime: number = 0;
@State duration: number = 0;
aboutToAppear() {
this.initAVPlayer();
}
private async initAVPlayer() {
try {
this.avPlayer = await media.createAVPlayer();
// 1. 注册状态监听
this.avPlayer.on('stateChange', (state: string) => {
switch (state) {
case 'initialized':
// 【3. 视频专属:设置显示窗口】
// ⚠️ 注意:surfaceId 必须在 initialized 状态下设置,不能在 idle 状态设置!
if (this.avPlayer && this.surfaceId) {
this.avPlayer.surfaceId = this.surfaceId;
this.avPlayer.prepare();
}
break;
case 'prepared':
// 【4. 播放控制】准备就绪后,可获取时长并播放
this.duration = this.avPlayer!.duration / 1000;
this.avPlayer!.play();
this.isPlaying = true;
break;
case 'playing':
this.isPlaying = true;
break;
case 'paused':
this.isPlaying = false;
break;
case 'completed':
this.isPlaying = false;
break;
}
});
// 监听播放进度
this.avPlayer.on('timeUpdate', (time: number) => {
this.currentTime = time / 1000;
});
// 监听跳转完成
this.avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`Seek completed at: ${seekDoneTime}ms`);
});
// 监听错误
this.avPlayer.on('error', (err: BusinessError) => {
console.error(`AVPlayer Error: ${err.message}`);
this.avPlayer?.reset();
});
} catch (error) {
console.error(`Create AVPlayer failed: ${(error as BusinessError).message}`);
}
}
// 2. 设置播放资源
private setSource() {
if (this.avPlayer) {
// 网络资源:直接设置 url
this.avPlayer.url = this.videoUrl;
}
}
// 【4. 播放控制与参数设置】
private togglePlayPause() {
if (!this.avPlayer) return;
if (this.isPlaying) {
this.avPlayer.pause();
} else {
this.avPlayer.play();
}
}
private seekTo(timeMs: number) {
this.avPlayer?.seek(timeMs, media.SeekMode.SEEK_PREV_SYNC);
}
// 【5. 资源释放与重置】
private resetPlayer() {
this.avPlayer?.reset(); // 回到 idle 状态,允许重新设置资源
}
aboutToDisappear() {
// 退出页面时,必须调用 release 销毁实例
this.avPlayer?.release();
this.avPlayer = null;
}
build() {
Column({ space: 20 }) {
// 【3. 视频专属:XComponent 渲染画布】
XComponent({
id: 'videoSurface',
type: XComponentType.SURFACE,
controller: this.xComponentController
})
.width('100%')
.height(250)
.onLoad(() => {
// 在 onLoad 回调中获取 surfaceId
this.surfaceId = this.xComponentController.getXComponentSurfaceId();
// 获取到 SurfaceId 后,开始设置资源
this.setSource();
})
Text(`${this.currentTime.toFixed(1)}s / ${this.duration.toFixed(1)}s`)
.fontSize(16)
Row({ space: 20 }) {
Button(this.isPlaying ? '暂停' : '播放')
.onClick(() => this.togglePlayPause())
Button('快进 5s')
.onClick(() => this.seekTo((this.currentTime + 5) * 1000))
Button('重置')
.onClick(() => this.resetPlayer())
}
}
.width('100%')
.height('100%')
.padding(20)
}
}
三、 进阶开发建议
- 后台播放与播控中心:如需实现熄屏播放或接入系统播控中心,需要接入
AVSession(媒体会话)并申请长时任务(LongTimeTask)。 - 音频焦点管理:播放音频时可能会被系统通话或其他应用打断。建议监听
audioInterrupt事件,在焦点丢失时暂停播放,恢复时继续。 - 流媒体缓冲:播放 HLS/DASH 等网络流媒体时,可通过
on('bufferingUpdate')监听缓冲进度,在 UI 上展示 Loading 状态。 - 首帧渲染:视频播放时,可通过
on('startRenderFrame')监听首帧渲染完成事件,此时可移除视频封面图,实现平滑过渡。
1、 后台播放与播控中心(AVSession + 长时任务)
import { mediaSession } from '@kit.MediaKit';
import { backgroundTaskManager } from '@kit.BasicServicesKit';
// 1. 创建并激活媒体会话
private avSession: mediaSession.AVSession | null = null;
private initAVSession() {
this.avSession = mediaSession.createAVSession(getContext(), 'MusicSession', 'audio');
// 设置元数据(歌曲信息、封面等)
this.avSession.setAVMetadata({
title: '夜曲',
artist: '周杰伦',
album: '十一月的萧邦',
// 封面图需转换为 PixelMap 传入
});
// 监听系统播控中心的指令(如锁屏界面的播放/暂停按钮)
this.avSession.on('play', () => this.avPlayer?.play());
this.avSession.on('pause', () => this.avPlayer?.pause());
}
// 2. 开启长时任务(防止后台被杀)
private async startBackgroundTask() {
try {
await backgroundTaskManager.startBackgroundRunning(getContext(), 'AudioPlayback', {
backgroundModes: [backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK]
});
} catch (error) {
console.error('Start background task failed:', error);
}
}
// ⚠️ 在 AVPlayer 的 timeUpdate 回调中,实时同步进度给播控中心:
// this.avSession.setAVPlaybackState({ state: mediaSession.AVPlaybackState.PLAYBACK_STATE_PLAYING, position: timeMs });
2、 音频焦点管理(Audio Interrupt)
// 在 AVPlayer 初始化后,注册音频焦点打断监听
this.avPlayer.on('audioInterrupt', (event: media.AudioInterruptEvent) => {
console.info(`Audio Interrupt Event: ${event.eventType}, Hint: ${event.hintType}`);
switch (event.hintType) {
case media.InterruptHint.INTERRUPT_HINT_PAUSE:
// 临时失去焦点(如来电),系统建议暂停
this.avPlayer?.pause();
break;
case media.InterruptHint.INTERRUPT_HINT_RESUME:
// 焦点恢复,可继续播放
this.avPlayer?.play();
break;
case media.InterruptHint.INTERRUPT_HINT_STOP:
// 永久失去焦点(如打开了其他音乐App),系统建议停止
this.avPlayer?.stop();
break;
case media.InterruptHint.INTERRUPT_HINT_DUCK:
// 音量压低(如导航播报),可在此处手动降低音量
this.avPlayer?.setVolume(0.2);
break;
case media.InterruptHint.INTERRUPT_HINT_UNDUCK:
// 音量恢复
this.avPlayer?.setVolume(1.0);
break;
}
});
3、 流媒体缓冲状态监听
// 声明一个状态变量用于控制 UI Loading 动画
@State isBuffering: boolean = false;
// 在初始化时注册缓冲进度监听
this.avPlayer.on('bufferingUpdate', (infoType: media.BufferingInfoType, value: number) => {
// value 为缓冲进度百分比(0-100)
if (infoType === media.BufferingInfoType.BUFFERING_PERCENT) {
if (value < 100) {
this.isBuffering = true; // 显示 Loading 动画
} else {
this.isBuffering = false; // 隐藏 Loading 动画
}
}
});
// 在 UI 层根据状态展示 Loading
if (this.isBuffering) {
LoadingProgress().width(50).height(50)
}
4、 首帧渲染与平滑过渡(视频专属)
// 声明状态变量控制封面图的显隐
@State showCover: boolean = true;
// 在初始化时注册首帧渲染监听
this.avPlayer.on('startRenderFrame', () => {
console.info('Video first frame rendered!');
// 触发 UI 更新,隐藏封面图
this.showCover = false;
});
// UI 层:使用 Stack 将封面图与视频画布叠放
Stack() {
// 视频渲染画布
XComponent({ id: 'videoSurface', type: XComponentType.SURFACE, controller: this.xComponentController })
.width('100%')
.height('100%')
// 视频封面图
if (this.showCover) {
Image($r('app.media.video_cover'))
.width('100%')
.height('100%')
.objectFit(ImageFit.Cover)
.transition(TransitionEffect.OPACITY.animation({ duration: 300 })) // 添加淡出动画
}
}
5、 进阶播控与多端协同体验
- 精准进度跳转与倍速控制:在拖动进度条或切换倍速时,由于底层解码需要时间,操作并非瞬间完成。务必通过
on('seekDone')和on('speedDone')事件监听操作结果,再更新 UI 上的时间标签或倍速按钮状态,避免界面与实际播放进度不同步。 - 音视频输出设备路由切换:当用户插入有线耳机、连接蓝牙耳机或断开设备时,音频输出通道会发生变化。应用可通过
on('audioOutputDeviceChangeWithInfo')监听输出设备变更事件,从而动态调整 UI 提示(如“已切换至蓝牙耳机”),提升用户体验。 - 视频画面自适应(视频专属):在视频播放过程中,当切换不同分辨率的视频源时,视频宽高比可能会发生改变。通过监听
on('videoSizeChange')事件,可以实时获取最新的视频宽高信息,进而动态调整XComponent窗口的大小或缩放模式(如等比例缩放、全屏拉伸),避免画面变形或留黑边。
6、 网络流媒体与性能优化
- HLS 自适应码率切换:针对 HLS 协议的网络流媒体,播放器支持动态调整清晰度。可通过
on('availableBitrates')监听当前可用的码率列表,并在需要时调用setBitrate()切换清晰度,配合on('bitrateDone')确认切换成功,实现流畅的画质无缝切换。 - 缓冲状态可视化:在弱网环境下播放在线资源时,通过
on('bufferingUpdate')实时获取缓冲百分比和缓存进度。结合该事件,可以在 UI 层动态展示 Loading 动画或缓冲进度条,缓解用户的等待焦虑。 - 首帧渲染无缝衔接(视频专属):为了提升视频起播的视觉体验,可以利用
on('startRenderFrame')事件。当首帧视频画面真正渲染到屏幕上时触发该事件,此时再移除视频封面图,能够实现封面与视频画面的完美、平滑过渡,消除起播时的黑屏或闪烁感。
7、 异常兜底与生命周期管理
- 全局错误恢复机制:网络波动或服务异常是常态。除了基础的
on('error')监听外,建议封装统一的错误处理逻辑。当捕获到错误时,根据错误码进行分类处理(如提示用户检查网络、重试播放等),并调用reset()将播放器重置为idle状态,防止在error状态下执行其他操作导致应用崩溃。 - 严格的内存回收策略:
AVPlayer在prepared、playing、paused等工作状态下会占用大量系统运行内存。当用户离开播放页面、应用切入后台或暂时不需要播放时,务必及时调用reset()(用于切换资源)或release()(用于彻底销毁),避免内存泄漏导致应用被系统强杀。 - 后台防杀与播控中心集成:若需实现音乐或播客的后台/熄屏播放,单纯依赖
AVPlayer是不够的。必须接入AVSession(媒体会话)将播放信息同步给系统,并申请长时任务(LongTimeTask),防止应用在后台被系统强制挂起或中断。
更多推荐



所有评论(0)