【OpenHarmony】多媒体开发:ohos_ijkplayer

简介

ijkplayer是OpenHarmony环境下可用的一款基于FFmpeg的视频播放器。

演示

编译运行

ffmpeg soundtouch yuv openh264依赖

  1. FFmpeg:基于B站的FFmpeg版本(ff4.0--ijk0.8.8--20210426--001):FFmpeg源码链接, FFmpeg可以在交叉编译出库文件和头文件,编译可参考FFmpeg-ff4.0编译指导。

    1. 编译成功后会在lycium\usr生成FFmpeg-ff4.0文件夹改名为ffmpeg。
  2. soudtouch:基于B站的soudtouch版本(ijk-r0.1.2-dev):soundtouch源码链接,soundtouch须在交叉编译出库文件和头文件。

    1. 把doc目录下的soundtouch-ijk文件夹拷贝到thirdparty下在lycium文件夹执行./build.sh soundtouch-ijk可以在lycium\usr目录下编译出soundtouch的静态库和头文件
  3. yuv:基于B站的yuv版本(ijk-r0.2.1-dev):yuv源码链接,yuv须在交叉编译出库文件和头文件。

    1. 把doc目录下的libyuv-ijk文件夹拷贝到thirdparty下在lycium文件夹执行./build.sh libyuv-ijk可以在lycium\usr目录下编译出yuv的静态库和头文件
  4. openh264:基于版本(openh264-2.4.1):openh264源码链接,openh264须在交叉编译出库文件和头文件。编译脚本可参考openh264。编译完成后,输出文件在lycium\usr目录下openh264文件夹。

  5. 把编译生成的ffmpeg文件夹拷贝到ijkplayer/src/main/cpp/third_party/ffmpeg下

  6. 把编译生成的openssl、soundtouch、yuv、openh264的文件夹,拷贝到工程的ijkplayer/src/main/cpp/third_party下,如图所示:

IDE编译运行

1、通过IDE工具下载依赖SDK,Tools->SDK Manager->OpenHarmony SDK 把native选项勾上下载,API版本>=9

2、开发板选择RK3568,ROM下载地址. 选择开发板类型是rk3568,请使用最新的版本

3、使用git clone下载源码,不要直接通过gitee网页的方式下载

下载安装

shell 复制代码
ohpm install @ohos/ijkplayer

使用说明

python 复制代码
   import { IjkMediaPlayer } from "@ohos/ijkplayer";
   import type { OnPreparedListener } from "@ohos/ijkplayer";
   import type { OnVideoSizeChangedListener } from "@ohos/ijkplayer";
   import type { OnCompletionListener } from "@ohos/ijkplayer";
   import type { OnBufferingUpdateListener } from "@ohos/ijkplayer";
   import type { OnErrorListener } from "@ohos/ijkplayer";
   import type { OnInfoListener } from "@ohos/ijkplayer";
   import type { OnSeekCompleteListener } from "@ohos/ijkplayer";
   import { LogUtils } from "@ohos/ijkplayer";

在UI中配置XComponent控件

javascript 复制代码
    XComponent({
      id: 'xcomponentId',
      type: 'surface',
      libraryname: 'ijkplayer_napi'
    })
    .onLoad((context) => {
      this.initDelayPlay(context);
     })
     .onDestroy(() => {
     })
     .width('100%')
     .aspectRatio(this.aspRatio)

播放

typescript 复制代码
    //单例模式
    let mIjkMediaPlayer = IjkMediaPlayer.getInstance();
    //多实例模式
    let mIjkMediaPlayer = new IjkMediaPlayer();
    // 如果播放视频,调用setContext接口,参数1为XComponent回调的context, 可选参数2为XComponent的id属性值
    mIjkMediaPlayer.setContext(this.mContext, "xcomponentId");
    // 如果只播放音频,则调用setAudioId接口,参数为音频对象的id
    // mIjkMediaPlayer.setAudioId('audioIjkId');
    // 设置debug模式
    mIjkMediaPlayer.setDebug(true);
    // 初始化配置
    mIjkMediaPlayer.native_setup();
    // 设置视频源
    mIjkMediaPlayer.setDataSource(url); 
    // 设置视频源http请求头
    let headers =  new Map([
      ["user_agent", "Mozilla/5.0 BiliDroid/7.30.0 ([email protected])"],
      ["referer", "https://www.bilibili.com"]
    ]);
    mIjkMediaPlayer.setDataSourceHeader(headers);
    // 使用精确寻帧 例如,拖动播放后,会寻找最近的关键帧进行播放,很有可能关键帧的位置不是拖动后的位置,而是较前的位置.可以设置这个参数来解决问题
    mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "enable-accurate-seek", "1");
    // 预读数据的缓冲区大小
    mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", "102400");
    // 停止预读的最小帧数
    mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", "100");
    // 启动预加载
    mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", "1");
    // 设置无缓冲,这是播放器的缓冲区,有数据就播放
    mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", "0");
    // 跳帧处理,放CPU处理较慢时,进行跳帧处理,保证播放流程,画面和声音同步
    mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", "5");
    // 最大缓冲cache是3s, 有时候网络波动,会突然在短时间内收到好几秒的数据
    // 因此需要播放器丢包,才不会累积延时
    // 这个和第三个参数packet-buffering无关。
    mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", "3000");
    // 无限制收流
    mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", "1");
    // 屏幕常亮
    mIjkMediaPlayer.setScreenOnWhilePlaying(true);
    // 设置超时
    mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "timeout", "10000000");
    mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "connect_timeout", "10000000");
    mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "listen_timeout", "10000000");
    mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "addrinfo_timeout", "10000000");
    mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_timeout", "10000000");
    
    let mOnVideoSizeChangedListener: OnVideoSizeChangedListener = {
      onVideoSizeChanged(width: number, height: number, sar_num: number, sar_den: number) {
        that.aspRatio = width / height;
        LogUtils.getInstance()
          .LOGI("setOnVideoSizeChangedListener-->go:" + width + "," + height + "," + sar_num + "," + sar_den)
        that.hideLoadIng();
      }
    }
    mIjkMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener);
    let mOnPreparedListener: OnPreparedListener = {
      onPrepared() {
        LogUtils.getInstance().LOGI("setOnPreparedListener-->go");
      }
    }
    mIjkMediaPlayer.setOnPreparedListener(mOnPreparedListener);

    let mOnCompletionListener: OnCompletionListener = {
      onCompletion() {
        LogUtils.getInstance().LOGI("OnCompletionListener-->go")
        that.currentTime = that.stringForTime(mIjkMediaPlayer.getDuration());
        that.progressValue = PROGRESS_MAX_VALUE;
        that.stop();
      }
    }
    mIjkMediaPlayer.setOnCompletionListener(mOnCompletionListener);

    let mOnBufferingUpdateListener: OnBufferingUpdateListener = {
      onBufferingUpdate(percent: number) {
        LogUtils.getInstance().LOGI("OnBufferingUpdateListener-->go:" + percent)
      }
    }
    mIjkMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener);

    let mOnSeekCompleteListener: OnSeekCompleteListener = {
      onSeekComplete() {
        LogUtils.getInstance().LOGI("OnSeekCompleteListener-->go")
        that.startPlayOrResumePlay();
      }
    }
    mIjkMediaPlayer.setOnSeekCompleteListener(mOnSeekCompleteListener);

    let mOnInfoListener: OnInfoListener = {
      onInfo(what: number, extra: number) {
        LogUtils.getInstance().LOGI("OnInfoListener-->go:" + what + "===" + extra)
      }
    }
    mIjkMediaPlayer.setOnInfoListener(mOnInfoListener);

    let mOnErrorListener: OnErrorListener = {
      onError(what: number, extra: number) {
        LogUtils.getInstance().LOGI("OnErrorListener-->go:" + what + "===" + extra)
        that.hideLoadIng();
        prompt.showToast({
          message:"亲,视频播放异常,系统开小差咯"
        });
      }
    }
    mIjkMediaPlayer.setOnErrorListener(mOnErrorListener);

    mIjkMediaPlayer.setMessageListener();

    mIjkMediaPlayer.prepareAsync();

    mIjkMediaPlayer.start();

DD一下: 欢迎大家关注工粽号<程序猿百晓生>,可以了解到以下知识点。

erlang 复制代码
`欢迎大家关注工粽号<程序猿百晓生>,可以了解到以下知识点。`
1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案) 
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......

暂停

ini 复制代码
   mIjkMediaPlayer.pause();

停止

ini 复制代码
   mIjkMediaPlayer.stop();

重置

ini 复制代码
   mIjkMediaPlayer.reset();

释放

ini 复制代码
   mIjkMediaPlayer.release();

快进、后退

ini 复制代码
   mIjkMediaPlayer.seekTo(msec);

倍数播放

arduino 复制代码
   mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "soundtouch", "1");
   mIjkMediaPlayer.setSpeed("2f");

屏幕常亮

arduino 复制代码
   mIjkMediaPlayer.setScreenOnWhilePlaying(true);

循环播放

arduino 复制代码
   mIjkMediaPlayer.setLoopCount(true);

设置音量

ini 复制代码
   mIjkMediaPlayer.setVolume(leftVolume, rightVolume);

音频焦点监控

csharp 复制代码
   import { InterruptEvent, InterruptHintType } from '@ohos/ijkplayer/src/main/ets/ijkplayer/IjkMediaPlayer';
   import { Callback } from '@ohos.base';
   // 音频焦点变化回调处理
   let event:  Callback<InterruptEvent> = (event) => {
     console.info(`event: ${JSON.stringify(event)}`);
     if (event.hintType === InterruptHintType.INTERRUPT_HINT_PAUSE) {
       this.pause();
     } else if (event.hintType === InterruptHintType.INTERRUPT_HINT_RESUME) {
       this.startPlayOrResumePlay();
     } else if (event.hintType === InterruptHintType.INTERRUPT_HINT_STOP) {
       this.stop();
     }
   }
   // 设置监听音频中断事件
   mIjkMediaPlayer.on('audioInterrupt', event);

   // 取消订阅音频中断事件
   mIjkMediaPlayer.off('audioInterrupt');

开启硬解码

arduino 复制代码
   // 开启h264与h265硬解码
   ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-all-videos", "1");
   // 开启h265硬解码
   ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-hevc", "1");

开启视频录制

ini 复制代码
    let recordSaveFilePath = getContext(this).cacheDir + "/record.mp4";
    let result = this.mIjkMediaPlayer.startRecord(recordSaveFilePath);
    prompt.showToast({
      message: result ? "开启录制成功" : "开启录制失败"
    });

获取视频录制状态

csharp 复制代码
    let isRecord = this.mIjkMediaPlayer.isRecord();
    prompt.showToast({
      message: isRecord ? "正在录制中" : "录制没有开启哦!"
    });

停止视频录制

ini 复制代码
    // 保存相册需要申请权限: ohos.permission.WRITE_IMAGEVIDEO
    this.mIjkMediaPlayer.stopRecord().then((result) => {
      if(!result){
        prompt.showToast({
          message: "停止录制失败"
        });
        return;
      }
      let atManager = abilityAccessCtrl.createAtManager();
      atManager.requestPermissionsFromUser(getContext(that), ['ohos.permission.WRITE_IMAGEVIDEO']).then(async () => {
        let photoType: photoAccessHelper.PhotoType = photoAccessHelper.PhotoType.VIDEO;
        let extension:string = 'mp4';
        let options: photoAccessHelper.CreateOptions = {
          title: "record_"+new Date().getTime()+""
        }
        let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(getContext(that));
        phAccessHelper.createAsset(photoType, extension, options, (err, uri) => {
          if (uri !== undefined) {
            let recordFile = fs.openSync(that.recordSaveFilePath);
            let albumFile = fs.openSync(uri,fs.OpenMode.READ_WRITE);
            fs.copyFileSync(recordFile.fd,albumFile.fd);
            fs.closeSync(recordFile);
            fs.closeSync(albumFile);
            prompt.showToast({
              message: "停止录制成功"
            });
          } else {
            prompt.showToast({
              message: "停止录制失败"
            });
          }
        });
      })
    })

DD一下: 欢迎大家关注工粽号<程序猿百晓生>,可以了解到以下知识点。

erlang 复制代码
`欢迎大家关注工粽号<程序猿百晓生>,可以了解到以下知识点。`
1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案) 
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......

截屏

ini 复制代码
    // 保存相册需要申请权限: ohos.permission.WRITE_IMAGEVIDEO
    let saveFilePath = getContext(this).cacheDir + "/screen.jpg";
    this.mIjkMediaPlayer.screenshot(saveFilePath).then((result) => {
      if(!result) {
        prompt.showToast({
          message: "截屏失败"
        });
        return;
      }
      let atManager = abilityAccessCtrl.createAtManager();
      atManager.requestPermissionsFromUser(getContext(that), ['ohos.permission.WRITE_IMAGEVIDEO']).then(async () => {
        let photoType: photoAccessHelper.PhotoType = photoAccessHelper.PhotoType.IMAGE;
        let extension:string = 'jpg';
        let options: photoAccessHelper.CreateOptions = {
          title: "screenshot_"+new Date().getTime()+""
        }
        let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(getContext(that));
        phAccessHelper.createAsset(photoType, extension, options, (err, uri) => {
          if (uri !== undefined) {
            let screenshotFile = fs.openSync(saveFilePath);
            let albumFile = fs.openSync(uri,fs.OpenMode.READ_WRITE);
            fs.copyFileSync(screenshotFile.fd,albumFile.fd);
            fs.closeSync(screenshotFile);
            fs.closeSync(albumFile);
            prompt.showToast({
              message: "截屏成功"
            });
          } else {
            prompt.showToast({
              message: "截屏失败"
            });
          }
        });
      })
    });

接口说明

IjkMediaPlayer.getInstance()

接口名 参数 返回值 说明
setContext context: object, id?: string void 设置XComponent回调的context, 设置XComponent的id属性值(可选), 播放视频时需要调用该接口
setDebug open: boolean void 设置日志开关
native_setup void 初始化配置
setDataSource url: string void 设置视频源地址
setDataSourceHeader headers: Map<string, string> void 设置视频源的HTTP请求头
setOption category:string, key: string, value: string void 设置播放前预设参数(用于设置char类型参数)
setOptionLong category:string, key: string, value: string void 设置播放前预设参数(用于设置int类型参数)
prepareAsync void 加载视频
start void 播放视频
stop void 停止播放
pause void 暂停播放
reset void 视频重置
release void 释放资源
seekTo msec: string void 快进、后退
setScreenOnWhilePlaying on: boolean void 设置屏幕常亮
setSpeed speed: string void 设置播放倍数
getSpeed number 获取设置的倍数
isPlaying boolean 查看是否正在播放状态
setOnVideoSizeChangedListener listener: OnVideoSizeChangedListener void 设置获取视频宽高回调监听
setOnPreparedListener listener: OnPreparedListener void 设置视频准备就绪回调监听
setOnInfoListener listener: OnInfoListener void 设置播放器的各种状态回调监听
setOnErrorListener listener: OnErrorListener void 设置播放异常回调监听
setOnBufferingUpdateListener listener: OnBufferingUpdateListener void 设置buffer缓冲回调监听
setOnSeekCompleteListener listener: OnSeekCompleteListener void 设置快进后退回调监听
setMessageListener void 设置视频监听器到napi用于接收回调
getVideoWidth number 获取视频宽度
getVideoHeight number 获取视频高度
getVideoSarNum number 获取视频宽高比的分子
getVideoSarDen number 获取视频宽高比的分母
getDuration number 获取视频总的时长
getCurrentPosition number 获取视频播放当前位置
getAudioSessionId number 获取音频sessionID
setVolume leftVolume: string,rightVolume:string void 设置音量
setLoopCount looping: boolean void 设置循环播放
isLooping boolean 查看当前是否循环播放
selectTrack track: string void 选择轨道
deselectTrack track: string void 删除选择轨道
getMediaInfo object 获取媒体信息
setAudioId id: string void 设置创建音频对象,设置id
on type: 'audioInterrupt', callback: Callback< InterruptEvent > void 监听音频中断事件,使用callback方式返回结果
off type: 'audioInterrupt' void 取消订阅音频中断事件
startRecord saveFilePath: string boolean 开启视频录制
isRecord boolean 获取视频录制状态
stopRecord Promise 停止视频录制
screenshot saveFilePath: string Promise 截屏

参数说明

  1. InterruptEvent 播放中断时,应用接收的中断事件。
名称 类型 必填 说明
forceType InterruptForceType 操作是由系统执行或是由应用程序执行。
hintType InterruptHint 中断提示。
  1. InterruptForceType 枚举,强制打断类型。
名称 说明
INTERRUPT_FORCE 0 由系统进行操作,强制打断音频播放。
INTERRUPT_SHARE 1 由应用进行操作,可以选择打断或忽略。
  1. InterruptHint 枚举,中断提示。
名称 说明
INTERRUPT_HINT_NONE 0 无提示。
INTERRUPT_HINT_RESUME 1 提示音频恢复。
INTERRUPT_HINT_PAUSE 2 提示音频暂停。
INTERRUPT_HINT_STOP 3 提示音频停止。
INTERRUPT_HINT_DUCK 4 提示音频躲避。(躲避:音量减弱,而不会停止)
INTERRUPT_HINT_UNDUCK 5 提示音量恢复。

DD一下: 欢迎大家关注工粽号<程序猿百晓生>,可以了解到以下知识点。

erlang 复制代码
`欢迎大家关注工粽号<程序猿百晓生>,可以了解到以下知识点。`
1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案) 
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......

约束与限制

在下述版本验证通过:

  • DevEco Studio: NEXT Beta1-5.0.3.806, SDK: API12 Release (5.0.0.66)
  • DevEco Studio NEXT 5.0(5.0.3.427)--SDK:API12

监听音频中断事件需保证设备系统版本在22以上。 设置音量需保证SDK版本在12及以上。

目录结构

javascript 复制代码
|---- ijkplayer  
|     |---- entry  # 示例代码文件夹
|     |---- ijkplayer  # ijkplayer 库文件夹
|			|---- cpp  # native模块
|                  |----- ijkplayer # ijkplayer内部业务
|                  |----- ijksdl    # ijkplayer内部业务
|                  |----- napi      # 封装NAPI接口
|                  |----- proxy     # 代理提供给NAPI调用处理ijkplayer内部业务
|                  |----- third_party #三方库依赖 
|                  |----- utils     #工具
|            |---- ets  # ets接口模块
|                  |----- callback  #视频回调接口
|                  |----- common    #常量
|                  |----- utils     #工具  
|                  |----- IjkMediaPlayer.ets #ijkplayer暴露的napi调用接口
|     |---- README_zh.MD  # 安装使用方法                   
相关推荐
二流小码农2 小时前
鸿蒙开发:基于最新API,如何实现组件化运行
harmonyos
Aisanyi4 小时前
【鸿蒙开发】使用HMRouter路由的使用
前端·harmonyos
暗雨4 小时前
鸿蒙元服务游戏化开发实战(Godot+ArkTS)
harmonyos
暗雨4 小时前
鸿蒙游戏引擎生态发展观察:Godot的机遇与挑战
harmonyos
HMSCore5 小时前
用户通知服务,轻松实现应用与用户的多场景交互
harmonyos
暗雨5 小时前
鸿蒙游戏引擎 Godot技术演进AI 原生游戏
harmonyos
zhanshuo6 小时前
鸿蒙国际化实战:3步实现多语言天气应用,让你的应用走向全球!
harmonyos
塞尔维亚大汉6 小时前
鸿蒙内核源码分析(内存映射篇) | 虚拟内存虚在哪里
源码·harmonyos
暗雨7 小时前
方舟图形引擎
harmonyos