鸿蒙系统下的播放器ijkPlayer是OpenHarmony下的基于FFmpeg的视频播放器,本文对ijkPlayer进行了概况分析并基于ijkPlayer封装一个播放器组件,可以进行视频流的播放。
1,导入播放器模块
import { IjkMediaPlayer } from '@ohos/ijkplayer';
此处为具名导入,因为在ijkplayer代码中的index.ets中有
export { IjkMediaPlayer } from "./src/main/ets/ijkplayer/IjkMediaPlayer";
2,ijkplayer 基本分析
在ijkPlayer代码中的IjkMediaPlayer.ets中,有prepareAsync(),start(),stop(),pause(),reset(),release()等播放处理。
import { IjkPlayerNapi } from "./utils/IjkPlayerNapi";
export class IjkMediaPlayer {
private static instance: IjkMediaPlayer; // 单例模式设计,确保全局唯一实例
// 使用私有构造函数防止外部实例化
private constructor() {
};
// 静态 getInstance 方法确保全局唯一实例,懒加载模式,首次调用时创建实例。
public static getInstance(): IjkMediaPlayer {
if (!IjkMediaPlayer.instance) {
IjkMediaPlayer.instance = new IjkMediaPlayer();
}
return IjkMediaPlayer.instance;
}
// 底层原生播放器的 NAPI 接口实例, 通过 setContext 方法注入.
private ijkplayer_napi: IjkPlayerNapi | null = null;
// 将 XComponent 的上下文转换为 IjkPlayerNapi 实例, 建立与原生播放器的连接。
setContext(context: object): void {
this.ijkplayer_napi = context as IjkPlayerNapi;
}
// 设置视频数据源 URL
setDataSource(url: string): void {
if (!!this.ijkplayer_napi)
this.ijkplayer_napi._setDataSource(url);
}
// 提供完整的播放器生命周期控制,所有方法都先检查 ijkplayer_napi 是否存在。
prepareAsync(): void {
if (!!this.ijkplayer_napi)
this.ijkplayer_napi._prepareAsync();
}
start(): void {
if (!!this.ijkplayer_napi)
this.ijkplayer_napi._start();
}
stop(): void {
if (!!this.ijkplayer_napi)
this.ijkplayer_napi._stop();
}
pause(): void {
if (!!this.ijkplayer_napi)
this.ijkplayer_napi._pause();
}
reset(): void {
if (!!this.ijkplayer_napi)
this.ijkplayer_napi._reset();
}
release(): void {
if (!!this.ijkplayer_napi)
this.ijkplayer_napi._release();
}
seekTo(msec: string): void {
if (!!this.ijkplayer_napi)
this.ijkplayer_napi._seekTo(msec);
}
// 设置监听,为各种播放器事件设置监听器, 应用观察者模式,通过监听器机制实现事件通知,解耦播放器和 UI 组件。
setOnVideoSizeChangedListener(listener: OnVideoSizeChangedListener): void {
this.mOnVideoSizeChangedListener = listener;
}
setOnPreparedListener(listener: OnPreparedListener): void {
this.mOnPreparedListener = listener;
}
setOnCompletionListener(listener: OnCompletionListener): void {
this.mOnCompletionListener = listener;
}
setOnInfoListener(listener: OnInfoListener): void {
this.mOnInfoListener = listener;
}
setOnErrorListener(listener: OnErrorListener): void {
this.mOnErrorListener = listener;
}
setOnBufferingUpdateListener(listener: OnBufferingUpdateListener): void {
this.mOnBufferingUpdateListener = listener;
}
setOnSeekCompleteListener(listener: OnSeekCompleteListener): void {
this.mOnSeekCompleteListener = listener;
}
setOnTimedTextListener(listener: OnTimedTextListener): void {
this.mOnTimedTextListener = listener;
}
// 消息监听机制,
setMessageListener(): void {
// 获取相关监听器
let onPreparedListener = this.mOnPreparedListener;
let onCompletionListener = this.mOnCompletionListener;
...
let messageCallBack = (what: number, arg1: number, arg2: number, obj: string) => {
// 核心的事件分发机制,将原生播放器的消息转换为上层回调,支持多种消息类型的处理。
if (what == MessageType.MEDIA_PREPARED) {
if (onPreparedListener !== null)
onPreparedListener.onPrepared();
}
if (what == MessageType.MEDIA_PLAYBACK_COMPLETE && onCompletionListener != null) {
onCompletionListener.onCompletion();
}
};
if (!!this.ijkplayer_napi)
this.ijkplayer_napi._setMessageListener(messageCallBack);
}
}
3,定义接口
export interface EricMPInterface {
player: IjkMediaPlayer
setContext(context: object): void
play(url: string): void
pause(): void
...
}
4,实现接口
const player: IjkMediaPlayer = IjkMediaPlayer.getInstance()
export class EricMPImpl implements EricMPInterface {
context?: object
onPreparedListener?: () => void
setContext(context: object): void {
this.context = context
}
setPreparedListener(onPreparedListener: () => void): void {
this.onPreparedListener = onPreparedListener
}
play(url: string): void {
player.setContext(this.context)
player.setDataSource(url)
...
player.setMessageListener()
player.prepareAsync()
player.start()
}
}
export const ericPlayer = new EricMPImpl()
5,自定义播放器组件
// EricVideoView 是一个 HarmonyOS ArkUI 框架的自定义组件,使用了 TypeScript 语法.
@Component
export struct EricVideoView {
// @Prop 装饰器表示这是一个父组件传递过来的不可变属性,url属性用于接收视频的播放地址.
@Prop
url: string
// 创建 XComponent 控制器,用于控制底层原生组件.
controller = new XComponentController()
// @State 装饰器表示这是组件内部的状态变量,preparing用于控制加载状态的显示,初始值为 true.
@State
preparing: boolean = true
aboutToDisappear(): void {
ericPlayer.release()
}
build() {
// 使用 Stack 布局实现组件叠加效果,底层是视频播放器,顶层是加载状态显示
Stack() {
// XComponent是 HarmonyOS 中用于集成原生组件的容器;type: 'surface'表示使用 Surface 类型的原生组件;libraryname: 'ijkplayer_napi'指定了使用的原生库名称;controller关联了之前创建的控制器
XComponent({
id: 'xcomponentId',
type: 'surface',
libraryname: 'ijkplayer_napi',
controller: this.controller
})
.width('100%')
.height('100%')
.alignSelf(ItemAlign.Center)
.onLoad((context) => {
if (!context) {
return
}
// 将原生组件上下文绑定到媒体播放器
ericPlayer.setContext(context)
// 设置准备完成监听器,准备完成后隐藏加载状态
ericPlayer.setPreparedListener(() => {
this.preparing = false
})
setTimeout(() => {
ericPlayer.play(this.url)
}, 100)
})
if (this.preparing) {
Image($r('app.media.preparing'))
.width('100%')
.height('100%')
}
}
.alignContent(Alignment.Center)
}
}
6,使用播放器组件
EricVideoView(
{ url: this.getVideoUrl()}
)
在调用时只需要传入url就可以使用播放器组件,这是因为在播放器组件定义中的 @Prop 装饰器声明了url是一个父组件传递的属性,并且是唯一的必填属性。其他属性如controller和preparing都是组件内部定义的,不需要外部传入。另外播放器组件也没有定义自定义的构造函数,这样也就意味着只需要传递包含url属性的参数对象即可创建组件实例。
7,XComponent 分析
XComponent 是 HarmonyOS ArkUI 框架中的一个重要组件,用于实现 ArkUI 与原生代码(C/C++)的桥接。
基本概念:
XComponent(扩展 Component)是 HarmonyOS 提供的一种特殊组件,它允许开发者在 ArkUI 应用中集成和使用原生代码实现的组件。它充当了 ArkUI 框架与底层原生代码之间的桥梁。
核心作用:
(1) 跨语言集成
连接 ArkUI(TypeScript/ArkTS)与原生代码(C/C++),实现高性能的图形渲染和复杂计算,集成第三方原生库(如媒体播放器、地图 SDK 等)。
(2) 功能扩展
突破 ArkUI 框架的功能限制,访问系统底层能力,实现高性能的自定义组件。
工作原理:
ArkUI应用层 (TypeScript/ArkTS)
↓
XComponent组件
↓
原生能力适配层 (NAPI)
↓
系统底层能力 (C/C++)
XComponent 通过 NAPI(Node-API)实现与原生代码的通信,支持双向数据传递和方法调用。
基本用法:
XComponent({
id: 'uniqueId', // 组件唯一标识
type: 'surface', // 组件类型
libraryname: 'library', // 原生库名称
controller: this.controller // 控制器实例
})
.width('100%')
.height('100%')
.onLoad((context) => {
// 组件加载完成回调
})
.onDestroy(() => {
// 组件销毁回调
})
重要属性:
id:组件的唯一标识符,用于在原生代码中识别和操作特定的 XComponent 实例。
type:surface: 用于图形渲染,提供 Surface 画布;component: 用于非图形的原生组件。
libraryname:指定要加载的原生动态库名称,原生库需要提前编译并放置在指定位置。
controller:XComponentController实例,用于在 ArkUI 层控制原生组件。
生命周期回调:
onLoad:当原生组件加载完成时调用,接收原生组件的上下文对象,通常在这里进行初始化和绑定操作。
onDestroy:当原生组件即将销毁时调用,用于释放原生资源,防止内存泄漏。