使用AVPlayer在HarmonyOS中实现视频播放(ArkTS)
在HarmonyOS应用开发中,视频播放功能通过强大的AVPlayer组件实现,它提供了完整的音视频播放解决方案。
1. AVPlayer概述
AVPlayer是HarmonyOS多媒体框架中的重要组件,它是一个功能完善的音视频播放API,集成了流媒体和本地资源解析、媒体资源解封装、视频解码和渲染功能。
核心特性:
- 格式支持广泛:支持MP4、MKV等主流视频格式,以及M4A、AAC、MP3等音频格式
- 播放源灵活:支持本地文件、网络流媒体等多种数据源
- 功能全面:提供播放控制、音量调节、倍速播放、画面缩放等丰富功能
- 高性能:底层硬件加速,保证播放流畅性
2. 开发环境准备
在开始编码前,需要确保开发环境正确配置:
- DevEco Studio:建议使用4.0 Release或更高版本
- SDK:API 9或更高版本
- 权限配置 :如使用网络视频,需在
module.json5
中声明权限:
json
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
3. AVPlayer完整开发流程
3.1 创建AVPlayer实例
首先需要导入媒体模块并创建AVPlayer实例:
typescript
import { media } from '@kit.MediaKit';
import { BusinessError } from '@ohos.base';
class VideoPlayer {
private avPlayer: media.AVPlayer | null = null;
// 创建AVPlayer实例
async createAVPlayer(): Promise<void> {
try {
this.avPlayer = await media.createAVPlayer();
console.info('AVPlayer创建成功');
// 设置监听器
this.setAVPlayerCallback();
} catch (error) {
console.error(`AVPlayer创建失败: ${(error as BusinessError).message}`);
}
}
}
3.2 设置状态和事件监听
AVPlayer通过状态机管理播放生命周期,必须设置状态和错误监听:
typescript
private setAVPlayerCallback(): void {
if (!this.avPlayer) {
return;
}
// 状态变化监听
this.avPlayer.on('stateChange', (state: string, reason: media.StateChangeReason) => {
console.info(`状态变化: ${state}`);
switch (state) {
case 'initialized':
console.info('资源初始化完成');
this.avPlayer?.prepare();
break;
case 'prepared':
console.info('资源准备完成,开始播放');
this.avPlayer?.play();
break;
case 'playing':
console.info('播放中');
break;
case 'paused':
console.info('已暂停');
break;
case 'completed':
console.info('播放完成');
break;
case 'stopped':
console.info('已停止');
break;
case 'idle':
console.info('重置状态,可重新设置资源');
break;
case 'released':
console.info('资源已释放');
break;
}
});
// 错误监听
this.avPlayer.on('error', (error: BusinessError) => {
console.error(`播放错误: code=${error.code}, message=${error.message}`);
// 出错时重置播放器
this.avPlayer?.reset();
});
// 其他有用的监听器
this.avPlayer.on('durationUpdate', (duration: number) => {
console.info(`视频总时长: ${duration}ms`);
});
this.avPlayer.on('timeUpdate', (time: number) => {
console.info(`当前播放位置: ${time}ms`);
});
this.avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`跳转完成,时间: ${seekDoneTime}ms`);
});
this.avPlayer.on('bufferingUpdate', (infoType: media.BufferingInfoType, value: number) => {
console.info(`缓冲更新: type=${infoType}, value=${value}`);
});
}
3.3 设置播放资源
AVPlayer支持多种资源设置方式:
3.3.1 设置网络视频
typescript
// 设置网络视频URL
setNetworkVideo(url: string): void {
if (!this.avPlayer) {
console.error('AVPlayer未初始化');
return;
}
// 确保在idle状态下设置URL
if (this.avPlayer.state === 'idle') {
this.avPlayer.url = url;
} else {
console.error('当前状态不能设置URL');
}
}
3.3.2 设置本地视频
typescript
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
// 设置本地文件
async setLocalVideo(filePath: string): Promise<void> {
if (!this.avPlayer) {
return;
}
try {
// 检查文件是否存在
const isAccess = fs.accessSync(filePath);
if (!isAccess) {
console.error('文件不存在');
return;
}
// 打开文件获取文件描述符
const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
// 使用fdSrc设置文件描述符
const avFileDescriptor: media.AVFileDescriptor = {
fd: file.fd,
offset: 0,
length: fs.statSync(filePath).size
};
this.avPlayer.fdSrc = avFileDescriptor;
console.info('本地视频设置成功');
} catch (error) {
console.error(`设置本地视频失败: ${(error as BusinessError).message}`);
}
}
3.3.3 使用资源管理器
typescript
// 使用ResourceManager获取RawFile资源
async setRawFileResource(resourceName: string): Promise<void> {
try {
const context = getContext(this) as common.UIAbilityContext;
const resourceMgr = context.resourceManager;
const rawFileDescriptor = await resourceMgr.getRawFd(resourceName);
const avFileDescriptor: media.AVFileDescriptor = {
fd: rawFileDescriptor.fd,
offset: rawFileDescriptor.offset,
length: rawFileDescriptor.length
};
if (this.avPlayer) {
this.avPlayer.fdSrc = avFileDescriptor;
}
} catch (error) {
console.error(`设置资源文件失败: ${(error as BusinessError).message}`);
}
}
3.4 设置视频显示窗口
视频播放需要与XComponent组件配合,提供渲染表面:
typescript
import { XComponentController, XComponent } from '@ohos.arkui.xcomponent';
@Entry
@Component
struct VideoPlayerComponent {
private avPlayer: VideoPlayer = new VideoPlayer();
private xComponentController: XComponentController = new XComponentController();
@State isPlaying: boolean = false;
build() {
Column() {
// 视频显示区域
XComponent({
id: 'video_surface',
type: 'surface',
controller: this.xComponentController
})
.width('100%')
.height(300)
.backgroundColor('#000000')
.onLoad(() => {
// XComponent加载完成后获取surfaceId
let surfaceId = this.xComponentController.getXComponentSurfaceId();
console.info('SurfaceId: ' + surfaceId);
// 创建AVPlayer并设置surface
this.avPlayer.createAVPlayer().then(() => {
if (this.avPlayer.avPlayer) {
this.avPlayer.avPlayer.surfaceId = surfaceId;
}
});
})
// 播放控制区域
Row({ space: 20 }) {
Button('播放网络视频')
.onClick(() => {
let url = 'https://example.com/sample.mp4';
this.avPlayer.setNetworkVideo(url);
})
Button(this.isPlaying ? '暂停' : '播放')
.onClick(() => {
if (this.isPlaying) {
this.avPlayer.avPlayer?.pause();
} else {
this.avPlayer.avPlayer?.play();
}
this.isPlaying = !this.isPlaying;
})
Button('停止')
.onClick(() => {
this.avPlayer.avPlayer?.stop();
this.isPlaying = false;
})
}
.margin(10)
}
.width('100%')
.height('100%')
}
}
4. 播放控制与状态管理
4.1 完整的播放控制方法
typescript
class AdvancedVideoPlayer extends VideoPlayer {
private currentPosition: number = 0;
private duration: number = 0;
private playbackSpeed: number = 1.0;
// 播放控制
async play(): Promise<void> {
if (!this.avPlayer) return;
try {
if (this.avPlayer.state === 'prepared' ||
this.avPlayer.state === 'paused' ||
this.avPlayer.state === 'completed') {
await this.avPlayer.play();
}
} catch (error) {
console.error(`播放失败: ${(error as BusinessError).message}`);
}
}
async pause(): Promise<void> {
if (!this.avPlayer) return;
try {
if (this.avPlayer.state === 'playing') {
await this.avPlayer.pause();
}
} catch (error) {
console.error(`暂停失败: ${(error as BusinessError).message}`);
}
}
async stop(): Promise<void> {
if (!this.avPlayer) return;
try {
if (['prepared', 'playing', 'paused', 'completed'].includes(this.avPlayer.state)) {
await this.avPlayer.stop();
}
} catch (error) {
console.error(`停止失败: ${(error as BusinessError).message}`);
}
}
// 跳转到指定位置
async seekTo(position: number): Promise<void> {
if (!this.avPlayer) return;
try {
// 确保位置在有效范围内
const validPosition = Math.max(0, Math.min(position, this.duration));
await this.avPlayer.seek(validPosition);
} catch (error) {
console.error(`跳转失败: ${(error as BusinessError).message}`);
}
}
// 设置播放速度
setSpeed(speed: number): void {
if (!this.avPlayer) return;
const validSpeeds = [0.75, 1.0, 1.25, 1.75, 2.0];
if (validSpeeds.includes(speed)) {
try {
this.avPlayer.setSpeed(speed);
this.playbackSpeed = speed;
} catch (error) {
console.error(`设置播放速度失败: ${(error as BusinessError).message}`);
}
}
}
// 设置音量
setVolume(volume: number): void {
if (!this.avPlayer) return;
// 音量范围 0.0 - 1.0
const safeVolume = Math.max(0.0, Math.min(1.0, volume));
try {
this.avPlayer.setVolume(safeVolume);
} catch (error) {
console.error(`设置音量失败: ${(error as BusinessError).message}`);
}
}
// 获取当前播放信息
getPlaybackInfo(): PlaybackInfo {
return {
currentPosition: this.currentPosition,
duration: this.duration,
playbackSpeed: this.playbackSpeed,
state: this.avPlayer?.state || 'unknown'
};
}
}
interface PlaybackInfo {
currentPosition: number;
duration: number;
playbackSpeed: number;
state: string;
}
4.2 增强的事件监听
typescript
private setEnhancedAVPlayerCallback(): void {
if (!this.avPlayer) return;
// 基础状态监听
this.avPlayer.on('stateChange', (state: string, reason: media.StateChangeReason) => {
console.info(`状态变化: ${state}, 原因: ${reason}`);
this.handleStateChange(state, reason);
});
// 错误监听
this.avPlayer.on('error', (error: BusinessError) => {
console.error(`播放错误: code=${error.code}, message=${error.message}`);
this.handlePlaybackError(error);
});
// 时间更新 - 用于进度显示
this.avPlayer.on('timeUpdate', (time: number) => {
this.currentPosition = time;
this.updateProgressDisplay(time, this.duration);
});
// 时长更新
this.avPlayer.on('durationUpdate', (duration: number) => {
this.duration = duration;
console.info(`视频总时长: ${this.formatTime(duration)}`);
});
// 缓冲更新
this.avPlayer.on('bufferingUpdate', (infoType: media.BufferingInfoType, value: number) => {
this.handleBufferingUpdate(infoType, value);
});
// 跳转完成
this.avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`跳转完成: ${this.formatTime(seekDoneTime)}`);
this.currentPosition = seekDoneTime;
});
// 视频尺寸变化
this.avPlayer.on('videoSizeChange', (width: number, height: number) => {
console.info(`视频尺寸: ${width}x${height}`);
this.adjustVideoLayout(width, height);
});
// 音频中断处理
this.avPlayer.on('audioInterrupt', (info: audio.InterruptEvent) => {
this.handleAudioInterruption(info);
});
}
private handleStateChange(state: string, reason: media.StateChangeReason): void {
switch (state) {
case 'idle':
this.onIdleState();
break;
case 'initialized':
this.onInitializedState();
break;
case 'prepared':
this.onPreparedState();
break;
case 'playing':
this.onPlayingState();
break;
case 'paused':
this.onPausedState();
break;
case 'completed':
this.onCompletedState();
break;
case 'stopped':
this.onStoppedState();
break;
case 'released':
this.onReleasedState();
break;
case 'error':
this.onErrorState(reason);
break;
}
}
private formatTime(milliseconds: number): string {
const seconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const displayMinutes = minutes % 60;
const displaySeconds = seconds % 60;
if (hours > 0) {
return `${hours}:${displayMinutes.toString().padStart(2, '0')}:${displaySeconds.toString().padStart(2, '0')}`;
} else {
return `${minutes}:${displaySeconds.toString().padStart(2, '0')}`;
}
}
5. 高级功能实现
5.1 视频缩放模式设置
typescript
// 设置视频缩放模式
setVideoScaleType(scaleType: media.VideoScaleType): void {
if (!this.avPlayer) return;
try {
if (['prepared', 'playing', 'paused', 'completed'].includes(this.avPlayer.state)) {
this.avPlayer.videoScaleType = scaleType;
}
} catch (error) {
console.error(`设置缩放模式失败: ${(error as BusinessError).message}`);
}
}
// 获取支持的缩放模式
getSupportedScaleTypes(): media.VideoScaleType[] {
return [
media.VideoScaleType.VIDEO_SCALE_TYPE_FIT,
media.VideoScaleType.VIDEO_SCALE_TYPE_FIT_CROP,
media.VideoScaleType.VIDEO_SCALE_TYPE_FILL
];
}
5.2 循环播放设置
typescript
// 设置循环播放
setLooping(loop: boolean): void {
if (!this.avPlayer) return;
try {
this.avPlayer.loop = loop;
} catch (error) {
console.error(`设置循环播放失败: ${(error as BusinessError).message}`);
}
}
5.3 音频焦点管理
typescript
import { audio } from '@kit.AudioKit';
private handleAudioInterruption(info: audio.InterruptEvent): void {
console.info(`音频中断事件: forceType=${info.forceType}, hintType=${info.hintType}`);
switch (info.forceType) {
case audio.InterruptForceType.INTERRUPT_FORCE:
// 强制中断(如来电)
if (this.isPlaying) {
this.pause();
// 保存状态以便恢复
this.shouldResumeOnFocusGain = true;
}
break;
case audio.InterruptForceType.INTERRUPT_SHARE:
// 共享模式,可降低音量但不暂停
if (info.hintType === audio.InterruptHint.INTERRUPT_HINT_PAUSE) {
this.pause();
} else if (info.hintType === audio.InterruptHint.INTERRUPT_HINT_RESUME) {
this.play();
}
break;
}
}
// 设置音频中断模式
setAudioInterruptMode(mode: audio.InterruptMode): void {
if (!this.avPlayer) return;
try {
if (['prepared', 'playing', 'paused', 'completed'].includes(this.avPlayer.state)) {
this.avPlayer.audioInterruptMode = mode;
}
} catch (error) {
console.error(`设置音频中断模式失败: ${(error as BusinessError).message}`);
}
}
6. 完整的UI组件实现
下面是一个完整的视频播放器UI组件实现:
typescript
@Entry
@Component
struct FullFeaturedVideoPlayer {
private avPlayer: AdvancedVideoPlayer = new AdvancedVideoPlayer();
private xComponentController: XComponentController = new XComponentController();
@State isPlaying: boolean = false;
@State currentTime: string = '00:00';
@State totalTime: string = '00:00';
@State progress: number = 0;
@State showControls: boolean = true;
@State isBuffering: boolean = false;
@State bufferProgress: number = 0;
build() {
Column() {
// 视频播放区域
Stack() {
XComponent({
id: 'video_surface',
type: 'surface',
controller: this.xComponentController
})
.width('100%')
.height(240)
.backgroundColor('#000000')
.onLoad(() => {
this.initializePlayer();
})
// 缓冲指示器
if (this.isBuffering) {
Progress({ value: 0, total: 100 })
.width(100)
.height(100)
.color('#FFFFFF')
}
// 控制层
if (this.showControls) {
Column() {
// 顶部控制栏
Row() {
Button('返回')
.fontSize(16)
.fontColor('#FFFFFF')
.backgroundColor('#66000000')
.borderRadius(20)
Blank()
Text('视频播放器')
.fontSize(18)
.fontColor('#FFFFFF')
Blank()
Button('设置')
.fontSize(16)
.fontColor('#FFFFFF')
.backgroundColor('#66000000')
.borderRadius(20)
}
.width('100%')
.padding(10)
Blank()
// 中间播放控制
Row() {
Button('前一个')
.onClick(() => {
// 前一个视频逻辑
})
Button(this.isPlaying ? '❚❚' : '▶')
.fontSize(24)
.fontColor('#FFFFFF')
.backgroundColor('#CC000000')
.width(60)
.height(60)
.borderRadius(30)
.onClick(() => {
this.togglePlayPause();
})
Button('后一个')
.onClick(() => {
// 后一个视频逻辑
})
}
Blank()
// 底部进度控制
Column() {
// 进度条
Row() {
Text(this.currentTime)
.fontSize(12)
.fontColor('#FFFFFF')
Slider({
value: this.progress,
min: 0,
max: 100,
step: 1,
style: SliderStyle.OutSet
})
.width('70%')
.onChange((value: number) => {
this.onProgressChange(value);
})
Text(this.totalTime)
.fontSize(12)
.fontColor('#FFFFFF')
}
// 底部控制按钮
Row({ space: 20 }) {
Button('倍速')
.onClick(() => {
this.showSpeedOptions();
})
Button('缩放')
.onClick(() => {
this.showScaleOptions();
})
Button('静音')
.onClick(() => {
this.toggleMute();
})
Button('全屏')
.onClick(() => {
this.toggleFullScreen();
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.margin({ top: 10 })
}
.width('100%')
.padding(10)
.backgroundColor('#66000000')
}
}
}
.width('100%')
.height(240)
.onClick(() => {
this.showControls = !this.showControls;
})
// 视频列表或其他内容
List({ space: 10 }) {
ForEach(this.getVideoList(), (item: VideoItem) => {
ListItem() {
VideoListItem({ item: item })
.onClick(() => {
this.playVideo(item);
})
}
})
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#F0F0F0')
}
private initializePlayer(): void {
let surfaceId = this.xComponentController.getXComponentSurfaceId();
this.avPlayer.createAVPlayer().then(() => {
if (this.avPlayer.avPlayer) {
this.avPlayer.avPlayer.surfaceId = surfaceId;
this.setupPlayerCallbacks();
}
});
}
private setupPlayerCallbacks(): void {
// 设置时间更新回调
this.avPlayer.setTimeUpdateCallback((time: number, duration: number) => {
this.currentTime = this.formatTime(time);
this.totalTime = this.formatTime(duration);
this.progress = duration > 0 ? (time / duration) * 100 : 0;
});
// 设置缓冲状态回调
this.avPlayer.setBufferingCallback((isBuffering: boolean, progress: number) => {
this.isBuffering = isBuffering;
this.bufferProgress = progress;
});
}
private togglePlayPause(): void {
if (this.isPlaying) {
this.avPlayer.pause();
} else {
this.avPlayer.play();
}
this.isPlaying = !this.isPlaying;
}
private onProgressChange(value: number): void {
if (this.avPlayer.getPlaybackInfo().duration > 0) {
const targetTime = (value / 100) * this.avPlayer.getPlaybackInfo().duration;
this.avPlayer.seekTo(targetTime);
}
}
private formatTime(milliseconds: number): string {
const seconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const displayMinutes = minutes % 60;
const displaySeconds = seconds % 60;
if (hours > 0) {
return `${hours}:${displayMinutes.toString().padStart(2, '0')}:${displaySeconds.toString().padStart(2, '0')}`;
} else {
return `${minutes}:${displaySeconds.toString().padStart(2, '0')}`;
}
}
private getVideoList(): VideoItem[] {
// 返回视频列表
return [
{ id: '1', title: '示例视频1', url: 'https://example.com/video1.mp4', thumbnail: '' },
{ id: '2', title: '示例视频2', url: 'https://example.com/video2.mp4', thumbnail: '' }
];
}
private playVideo(item: VideoItem): void {
this.avPlayer.setNetworkVideo(item.url);
this.isPlaying = true;
}
}
@Component
struct VideoListItem {
private item: VideoItem;
build() {
Row() {
Image(this.item.thumbnail)
.width(60)
.height(45)
.backgroundColor('#DDDDDD')
Column() {
Text(this.item.title)
.fontSize(16)
.fontColor('#333333')
Text('点击播放')
.fontSize(12)
.fontColor('#666666')
}
.layoutWeight(1)
.margin({ left: 10 })
}
.width('100%')
.padding(10)
.backgroundColor('#FFFFFF')
.borderRadius(8)
}
}
interface VideoItem {
id: string;
title: string;
url: string;
thumbnail: string;
}
7. 性能优化与最佳实践
7.1 内存管理
typescript
class OptimizedVideoPlayer extends AdvancedVideoPlayer {
private memoryMonitor: MemoryMonitor = new MemoryMonitor();
// 资源清理
async cleanup(): Promise<void> {
if (!this.avPlayer) return;
const currentState = this.avPlayer.state;
// 根据当前状态执行适当的清理
if (['prepared', 'playing', 'paused', 'completed'].includes(currentState)) {
await this.avPlayer.stop();
}
if (currentState !== 'idle' && currentState !== 'released') {
await this.avPlayer.reset();
}
if (currentState !== 'released') {
await this.avPlayer.release();
}
this.avPlayer = null;
}
// 监控内存使用
private monitorMemoryUsage(): void {
setInterval(() => {
const memoryInfo = this.memoryMonitor.getMemoryInfo();
if (memoryInfo.usage > memoryInfo.threshold * 0.8) {
console.warn('内存使用过高,考虑清理资源');
this.cleanupUnusedResources();
}
}, 5000);
}
}
7.2 网络优化
typescript
// 网络状态监控
private monitorNetworkStatus(): void {
// 监听网络状态变化
// 根据网络质量调整播放策略
}
// 自适应码率切换
private setupAdaptiveBitrate(): void {
// 根据网络条件动态切换视频质量
}
8. 错误处理与调试
8.1 常见错误处理
typescript
private handlePlaybackError(error: BusinessError): void {
console.error(`播放错误: code=${error.code}, message=${error.message}`);
switch (error.code) {
case 5400101: // 媒体格式不支持
this.showErrorMessage('视频格式不支持,请尝试其他格式');
break;
case 5400102: // 网络异常
this.showErrorMessage('网络连接异常,请检查网络设置');
break;
case 5400103: // 解码失败
this.showErrorMessage('视频解码失败');
break;
default:
this.showErrorMessage('播放失败,请重试');
break;
}
// 重置播放器状态
this.resetPlayer();
}
private resetPlayer(): void {
if (this.avPlayer && this.avPlayer.avPlayer) {
this.avPlayer.avPlayer.reset();
}
}
总结
本文详细介绍了在HarmonyOS中使用AVPlayer实现视频播放的完整流程,包括:
- AVPlayer实例创建与配置
- 状态机管理与事件监听
- 多种资源设置方式
- 与XComponent集成实现视频渲染
- 完整的播放控制功能
- 高级功能如缩放、循环播放等
- 性能优化和错误处理
通过以上实现,开发者可以构建功能完善、性能优异的视频播放应用。在实际开发中,建议根据具体需求选择合适的功能实现,并注意资源管理和错误处理,以提供更好的用户体验。