基于鸿蒙API10的RTSP播放器(一:基本界面的实现)

打印 上一主题 下一主题

主题 799|帖子 799|积分 2397

ijkplayer简介:
ijkplayer 自己是一个开源的 Android 媒体播放库,它重要用于播放视频和音频文件,现在已有先辈将其引入到鸿蒙当中,通过XComponent组件完成适配。向开源致敬!
支持格式:
它支持多种格式,包罗 MP4、M3U8、FLV 等,并且具有高性能和低功耗的特点。ijkplayer 基于 FFmpeg 进行开辟,通过 JNI 技术将 FFmpeg 的功能封装在 Java 代码中,从而实现了对 FFmpeg 的高效调用。
ijkplayer 的关键特点:

  • 高性能:ijkplayer 利用 FFmpeg 的强盛功能,能够快速、高效地播放视频和音频文件。
  • 低功耗:与一些其他播放器相比,ijkplayer 在播放过程中斲丧的电量较少,更适合长时间利用的场景。
  • 丰富的格式支持:ijkplayer 支持多种视频和音频格式,包罗但不限于 MP4、M3U8、FLV 等。
  • 自定义配置:开辟者可以根据必要自定义播放器的各种参数,如音量、播放速度、视频宽高比等。
  • 丰富的监听器:ijkplayer 提供了一系列的监听器,如 OnPreparedListener、OnCompletionListener 等,方便开辟者监听播放过程中的各种事件。
  • 支持硬解码:在一些硬件装备上,ijkplayer 能够利用硬件加速器进行视频解码,从而提高播放效率。
  • 开源免费:ijkplayer 是一个开源项目,可以在 GitHub 上找到其源代码,并且免费利用。
鸿蒙版ijkplayer
已经更新到2.0.4,在鸿蒙三方库中心堆栈可以找到,在DevEco终端中,可以通过命令 ohpm install @ijkplayer 直接下载。鸿蒙版ijkplayer2.0.3-rc.2已经可以支持X86架构的模拟器,并且,2.0.3-rc.3可以支持H265硬编码,以及直播流RTSP。

鸿蒙三方库中心仓地址:鸿蒙版ijkplayer地址
修改API版本:
API10的鸿蒙项目文件中,在引入鸿蒙版ijkplayer后,修改三方库当中的src中的module.json文件,将API 12 改成 API 10 。此中依赖引入有一些小bug,会随机报错,直接忽略。

运行结果:

页面代码:
  1. // 1.这个页面仅用于ijkplayer测试运行
  2. // 2.编写这个页面进行测试,包括画面 声音 颜色的测试,也包括暂停、开始、返回的测试
  3. // 3.这是一个简单的页面,如需复杂功能,比如添加视频播放轨道,参见三方库中心仓的API说明
  4. // 4.致敬开源
  5. import {  
  6.   IjkMediaPlayer,  
  7.   OnErrorListener,  
  8.   OnInfoListener,  
  9.   OnSeekCompleteListener } from "@ohos/ijkplayer";  
  10. import type { OnPreparedListener } from "@ohos/ijkplayer";  
  11. import type { OnVideoSizeChangedListener } from "@ohos/ijkplayer";  
  12. import { LogUtils } from "@ohos/ijkplayer";  
  13. import { promptAction, router } from '@kit.ArkUI';  
  14.   
  15.   
  16. // 获取ijkplayer实例  
  17. let mIjkMediaPlayer = IjkMediaPlayer.getInstance();  
  18.   
  19. @Entry  
  20. @Component  
  21. struct Index {  
  22.   // 苹果视频流  
  23.   @State private videoUrl: string = "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8";  
  24.   @State private aspRatio: number = 16 / 9; // 默认宽高比  
  25.   // XComponent的上下文  
  26.   @State mContext: object | undefined = undefined;  
  27.   
  28.   
  29.   build() {  
  30.     Column() {  
  31.       // 视频播放组件  
  32.       Row() {  
  33.         XComponent({  
  34.           id: 'xcomponentId',  
  35.           type: 'surface',  
  36.           libraryname: 'ijkplayer_napi'  
  37.         })  
  38.           .onLoad((context?: object) => {  
  39.             if (!!context) {  
  40.               this.mContext = context;  
  41.               this.startPlay();  
  42.             }  
  43.           })  
  44.           .onDestroy(() => {  
  45.             this.releaseMediaPlayer();  
  46.           })  
  47.           .width('100%')  
  48.       }  
  49.       .height("900px")  
  50.       .width('100%')  
  51.   
  52.       // 控制按钮组件  
  53.       Row({space:"60px"}) {  
  54.         Button( 'Play')  
  55.           .width('20%')  
  56.           .height('50vp')  
  57.           .onClick(() => {  
  58.               this.restartPlay();  
  59.           });  
  60.         Button('Pause')  
  61.           .width('20%')  
  62.           .height('50vp')  
  63.           .onClick(() => {  
  64.               this.pause();  
  65.           });  
  66.         Button('Back')  
  67.           .width('20%')  
  68.           .height('50vp')  
  69.           .onClick(() => {  
  70.             router.back();  
  71.           })  
  72.       }  
  73.       .width('100%')  
  74.       .justifyContent(FlexAlign.Center)  
  75.       .margin("50px")  
  76.     }  
  77.     .height('100%')  
  78.   }  
  79.   
  80.   // 暂停后恢复播放  
  81.   restartPlay() {  
  82.     mIjkMediaPlayer.start();  
  83.   }  
  84.   // 退出XComponent后释放资源,避免退出组件后仍然在持续接受流的情况  
  85.   releaseMediaPlayer() {  
  86.     mIjkMediaPlayer.stop();  
  87.     mIjkMediaPlayer.release();  
  88.   }  
  89.   // 暂停  
  90.   pause() {  
  91.     mIjkMediaPlayer.pause();  
  92.   }  
  93.   
  94.   
  95.   startPlay() {  
  96.       this.play(this.videoUrl);// 调用play方法播放视频  
  97.   }  
  98.   
  99.   // 播放方法,调用ijkplayer库中已经写好的方法  
  100.   private play(url: string) {  
  101.   
  102.     let that = this  
  103.   
  104.     // 以下内容为配置mIjkplayer对象的属性!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  
  105.   
  106.     // 设置XComponent回调的context  
  107.     mIjkMediaPlayer.setContext(this.mContext);  
  108.     //初始化配置  
  109.     mIjkMediaPlayer.native_setup();  
  110.     // 设置debug模式,可以根基自己需要,调整成true,会打印更多内容便于调试  
  111.         mIjkMediaPlayer.setDebug(false);
  112.     //设置视频源  
  113.     mIjkMediaPlayer.setDataSource(url);  
  114.     //设置视频源http请求头  
  115.     let headers = new Map([  
  116.       ["user_agent", "Mozilla/5.0 BiliDroid/7.30.0 (bbcallen@gmail.com)"],  
  117.       ["referer", "https://www.bilibili.com"]  
  118.     ]);  
  119.     mIjkMediaPlayer.setDataSourceHeader(headers);  
  120.     //使用精确寻帧 例如,拖动播放后,会寻找最近的关键帧进行播放,很有可能关键帧的位置不是拖动后的位置,而是较前的位置.可以设置这个参数来解决问题  
  121.     mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "enable-accurate-seek", "1");  
  122.     //预读数据的缓冲区大小  
  123.     mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", "102400");  
  124.     //停止预读的最小帧数  
  125.     mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", "100");  
  126.     //启动预加载  
  127.     mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", "1");  
  128.     // 设置无缓冲,这是播放器的缓冲区,有数据就播放  
  129.     mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", "0");  
  130.     //跳帧处理,放CPU处理较慢时,进行跳帧处理,保证播放流程,画面和声音同步  
  131.     mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", "5");  
  132.     // 最大缓冲cache是3s, 有时候网络波动,会突然在短时间内收到好几秒的数据  
  133.     // 因此需要播放器丢包,才不会累积延时  
  134.     // 这个和第三个参数packet-buffering无关。  
  135.     mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", "3000");  
  136.     // 无限制收流  
  137.     mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", "1");  
  138.     mIjkMediaPlayer.setOptionLong(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", "1")  
  139.     // 屏幕常亮  
  140.     mIjkMediaPlayer.setScreenOnWhilePlaying(true);  
  141.     // 设置超时  
  142.     mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "timeout", "10000000");  
  143.     mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "connect_timeout", "10000000");  
  144.     mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "addrinfo_timeout", "10000000");  
  145.     mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_timeout", "10000000");  
  146.     // 设置音量  
  147.     mIjkMediaPlayer.setVolume("0.5", "0.5");  
  148.     // 变速播放  
  149.     mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "soundtouch", "1");  
  150.     // 获取并打印播放速度  
  151.     let Speed = mIjkMediaPlayer.getSpeed()  
  152.     LogUtils.getInstance().LOGI('getSpeed--' + Speed)  
  153.     //是否开启循环播放  
  154.     mIjkMediaPlayer.setLoopCount(true);  
  155.   
  156.     // 以下内容为配置mIjkplayer对象的监听器!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  
  157.   
  158.     // 设置视频尺寸变化的监听器  
  159.     let mOnVideoSizeChangedListener: OnVideoSizeChangedListener = {  
  160.       onVideoSizeChanged: (width: number, height: number, sar_num: number, sar_den: number) => {  
  161.         that.aspRatio = width / height;  
  162.         LogUtils.getInstance()  
  163.           .LOGI("setOnVideoSizeChangedListener-->go:" + width + "," + height + "," + sar_num + "," + sar_den)  
  164.       }  
  165.     }    mIjkMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener);  
  166.   
  167.     // 设置视频准备完成的监听器  
  168.     let mOnPreparedListener: OnPreparedListener = {  
  169.       onPrepared: () => {  
  170.         LogUtils.getInstance().LOGI("setOnPreparedListener-->go");  
  171.       }  
  172.     }    mIjkMediaPlayer.setOnPreparedListener(mOnPreparedListener);  
  173.   
  174.   
  175.     // 设置拖动播放完成的监听器  
  176.     let mOnSeekCompleteListener: OnSeekCompleteListener = {  
  177.       onSeekComplete: () => {  
  178.         LogUtils.getInstance().LOGI("OnSeekCompleteListener-->go");  
  179.         that.startPlay();  
  180.       }  
  181.     }    mIjkMediaPlayer.setOnSeekCompleteListener(mOnSeekCompleteListener);  
  182.   
  183.     // 设置信息监听器  
  184.     let mOnInfoListener: OnInfoListener = {  
  185.       onInfo: (what: number, extra: number) => {  
  186.         LogUtils.getInstance().LOGI("OnInfoListener-->go:" + what + "===" + extra);  
  187.       }  
  188.     }    mIjkMediaPlayer.setOnInfoListener(mOnInfoListener);  
  189.   
  190.     // 设置错误监听器  
  191.     let mOnErrorListener: OnErrorListener = {  
  192.       onError: (what: number, extra: number) => {  
  193.         LogUtils.getInstance().LOGI("OnErrorListener-->go:" + what + "===" + extra)  
  194.         promptAction.showToast({  
  195.           message: "视频播放异常"  
  196.         });  
  197.       }  
  198.     }    mIjkMediaPlayer.setOnErrorListener(mOnErrorListener);  
  199.   
  200.     // 设置消息监听器  
  201.     mIjkMediaPlayer.setMessageListener();  
  202.     // 加载视频  
  203.     mIjkMediaPlayer.prepareAsync();  
  204.     // 开始播放(真正播放的入口,通过NAPI调用底层)  
  205.     mIjkMediaPlayer.start();  
  206.   }  
  207.   
  208. }
复制代码
关闭mijkplayer对象的debug后,会继续打印以下日志:
根据您提供的日志信息,这些日志似乎是来自一个应用步调的错误和性能监控输出。下面是对这些日志的逐条解释:



    • 这条日志表明在处理名为 com.example.app.forrtsp 的应用步调时,出现了 getaddrinfo_ext 函数调用失败的环境。错误代码 218 通常与DNS查询失败有关,而 -2 可能是表现一个详细的错误状态。可能意味着应用步调无法分析视频播放地址的DNS。



    • 这条日志是关于EGL(Embedded System Grapfics Library嵌入式图形库)的输出缓冲区交换操纵。d_eglSwapBuffers_special 是EGL的一个函数,它负责在OpenGL ES上下文中交换渲染缓冲区。thread output size 14420760 表现线程输出的缓冲区大小,而 speed 14028k/s 表现每秒交换的次数。API_num 450 可能是表现利用的是OpenGL ES 3.0。



    • 这条日志来自应用步调的渲染器部分,可能是在尝试写入缓存数据时状态不正确。这可能是一个同步问题大概状态管理错误。
    • WriteInner Status changed while write这条日志也是关于渲染器的,可能是在写入操纵期间状态发生了变化。

EGL(嵌入式图形库)
是OpenGL ES的一部分,用于管理图形上下文和外貌。提供了一个与图形硬件和窗口体系无关的接口,答应开辟者在多种平台上编写图形应用步调。EGL的重要功能:

  • 创建和管理图形上下文:EGL提供了一种机制,可以在多种图形硬件和窗口体系上创建和管理OpenGL ES或OpenVG上下文。
  • 管理外貌:EGL可以创建和管理差别类型的图形外貌,如窗口、PBuffer(内存中的外貌)和帧缓冲区。
  • 同步机制:EGL提供了同步机制,如等待交换完成和同步上下文,以制止在渲染过程中出现竞态条件。
  • 加载OpenGL ES或OpenVG函数:EGL可以加载和卸载OpenGL ES或OpenVG函数,以便在差别的上下文中利用。
  • 缓冲区交换:EGL提供了缓冲区交换功能,答应开辟者控制何时将渲染结果呈现到屏幕上。
    在Android和鸿蒙等嵌入式平台上,EGL是图形开辟中不可或缺的一部分,它为开辟者提供了一个跨平台的图形接口,使得在差别的硬件和操纵体系上编写图形应用步调变得更加轻易。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

半亩花草

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表