HarmonyOS鸿蒙开发:在线短视频流畅切换最佳实践

简介

为了帮助开发者解决在应用中在线短视频快速切换时容易出现快速切换播放时延过长的问题,将提供对应场景的解决方案。

该解决方案使用:

  • 视频播放框架AVPlayer和滑块视图容器Swiper进行短视频滑动轮播切换。
  • 绘制组件XComponent的Surface类型动态渲染视频流。
  • 使用LazyForEach进行数据懒加载,设置cachedCount属性来指定缓存数量,同时搭配组件复用能力以达到高性能效果,(在冷启动过程中创建一个AVPlayer并进行数据初始化到prepared阶段,在轮播过程中,每次异步创建一个播放器为下一个视频播放做准备)。

最终实现短视频快速切换起播时延达到≤200ms的效果。

如果开发者使用自研播放器引擎而非AVPlayer,也可以参考该解决方案思路达成最佳实践

效果展示

在线短视频滑动切换

场景说明

适用范围

适用于应用中在线短视频快速切换,容易出现快速切换播放起播慢体验不佳的场景。

场景性能指标

起播时延计时标准

  1. 一般以用户滑动屏幕后抬手,手指离屏时刻为起点,以视频第二帧画面显示时刻为终点(不是封面帧)。

  2. 转场动画时长一般设置为300ms。

  3. 在动画开始时使用预先准备的播放器起播,起播时延一般在200ms内。

视频播放器时延优化前优化后
AVPlayer在线短视频切换播放起播时延1100ms200ms

场景分析

典型场景及优化方案

典型场景描述

短视频:一般小于5分钟,以移动状态和短时间休闲状态下观看为主

  1. 应用内滑动视频,新视频起播时延≤X(200ms/400ms/500ms)。
  2. 起点时间:滑动离手;时间终点:视频内容开始播放,画面发生变化。

场景优化方案

AVPlayer:

  1. 数据懒加载

    在线短视频预加载,冷启动时创建第一个播放器,播放当前视频时预加载下一个播放视频,绘制组件XComponent的Surface类型将视频流进行动态渲染、使用LazyForEach进行数据懒加载,设置cachedCount属性来指定缓存数量,同时搭配组件复用能力以达到高性能效果。

  2. 异步在线视频预加载

    在轮播过程中,对下一个视频提前进入AVPlayer的prepared状态。

  3. 在线视频播放预接力

    滑动过程中手指离开屏幕,此时滑动动效开始播放,在动效开始时就可以调用AVPlayer的play方法进行播放。

自研播放器:

  1. 数据懒加载

    在线短视频预加载,冷启动时创建第一个播放器,播放当前视频时预加载下一个播放视频,绘制组件XComponent的Surface类型将视频流进行动态渲染、使用LazyForEach进行数据懒加载,设置cachedCount属性来指定缓存数量,同时搭配组件复用能力以达到高性能效果

  2. 异步在线视频预加载

    在轮播过程中,对下一个视频提前初始化播放器所需内容(视频源下载、AudioRender初始化、解码器初始化等),并对视频提前预解析首帧画面。

  3. 在线视频播放预接力

    滑动过程中手指离开屏幕,此时滑动动效开始播放,在动效开始时就可以调用播放引擎进行播放。 为了保证用户的起播体验,在前几帧画面送显时应优先送显,而不是等AudioRender写入音频数据才送显,因为音频硬件时延比显示时延大。播放起始几帧建议不要做强音画同步,而是采用慢追帧策略进行同步,视频帧稍微增大送显间隔,直到完成音画同步。

场景实现

场景整体介绍

基于AVPlayer实现了在线流媒体的短视频流畅播放和控制功能。基于对应的播放器,使用滑块视图容器Swiper进行短视频滑动轮播切换、绘制组件XComponent的Surface类型将视频流进行动态渲染、懒加载,最终实现短视频快速切换,实现起播≤200ms,提供开发者解决此类问题的方案。

功能时序图

在线短视频快速切换

实现流程图

关键点

AVPlayer

AVPlayer可以将Audio/Video媒体资源(比如mp4/mp3/mkv/mpeg-ts等)转码为可供渲染的图像和可听见的音频模拟信号,并通过输出设备进行播放。

LazyForEach数据懒加载

LazyForEach数据懒加载可以通过设置cachedCount属性来指定缓存数量(目前设置为3),同时搭配组件复用能力以达到高性能效果。SurfaceID每次都会创建,不共用SurfaceID,AVPlayer也会同时创建,不共用AVPlayer,进而将提前加载好的视频(prepared阶段)放到缓存池中。 在通过Swiper切换时,会根据当前轮询滑动的窗口索引index到缓存池中找到对应的视频(prepared阶段),直接进行播放,从而提升切换性能。

异步视频预加载

异步视频预加载:在Swiper轮播过程中,在播放当前视频时,提前加载好下一个视频,在缓存中同时存在多个播放器实例,根据视频当前的索引来确定使用缓存中的哪个播放器来播放,从而达到流畅切换的效果。

(1)本地播放一个短视频的耗时。

(2)播放视频A的时候,提前预加载视频B。在切换短视频时,可以马上开始播放已预加载完成的视频B,从而减少了切换时间,提高了切换性能。

视频播放预启动能力

为了进一步提升滑动播放体验,在动效开始时就开始播放,做到动效和播放并行进行:

(1)在收到AnimationStart回调时开始播放,而不是动效结束再播放;

(2)不要用默认的弹簧曲线(弹簧动效有560ms,视频窗口在400ms左右已经全面铺开了,最后150ms位移随时间变化较小),可以把curve改成Curve.Ease,duration改为300ms(视APP UX确定);

视频播放预启动接力:类似于4*100接力赛,想要尽快完成接力赛,当第一个选手快到达终点时,第二个选手就提前起跑并且和第一个选手完美完成接力棒,从而减少整个接力赛过程中的时间。短视频切换也是如此,如下图所示:

关键代码片段

  1. 初始化AVPlayer播放器。

    async initAVPlayer() {Logger.info(TAG, 'createAVPlayer begin');media.createAVPlayer().then((video: media.AVPlayer) => {if (video !== null) {this.avPlayer = video;this.setAVPlayerCallback(this.avPlayer);// 设置播放源,使其进入initialized状态if (typeof this.curSource === 'string') {this.avPlayer.url = this.curSource;} else {this.avPlayer.fdSrc = this.curSource;}Loggor.info(TAG, 'createAVPlayer success');} else {Loggor.error(TAG, 'createAVPlayer fail');}}).catch((error: BusinessError) => {Logger.error(TAG, `AVPlayer catchCallback,error message:${error.message}`);})
    }
  2. 设置业务需要的监听事件。

    setAVPlayerCallback(avPlayer: media.avPlayer) {// 用于进度条,监听进度条当前位置,刷新当前时间avPlayer.on('timeUpdate', (time: number) => {if (!this.isSliderMoving) {this.currentTime = Math.floor(time * this.durationTime / this.duration);this.currentStringTime = secondToTime(Math.floor((time / CommConstants.SECOND_TO_MS)));}})// 适配一多,根据屏幕尺寸的变化同步更新视频的长宽avPlayer.on('videoSizeChange', (width: number, height: number) => {this.viewHeight = height;this.viewWidth = width;this.autoVideoSize();})// 必要事件,监听播放器的错误信息avPlayer.on('error', (error: BusinessError) => {Logger.error(TAG,`Invoke avPlayer failed, code is ${error.code},message is ${error.message}` + `---state:${avPlayer.state}`);avPlayer.reset();})this.setAVPlayerStateListen(avPlayer);
    }
  3. 设置状态机变化回调函数。

    setAVPlayerStateListen(avPlayer: media.AVPlayer) {avPlayer.on('stateChange', async (state: string) => {switch (state) {case 'idle': // 成功调用reset接口后触发该状态机上报Logger.info(TAG, 'AVPlayer state idle called.' + `this.curIndex:${this.curIndex}` + `this.index:${this.index}`);break;case 'initialized': // avplayer 设置播放源后触发该状态上报Logger.info(TAG,'AVPlayer state initialized called.' + `this.curIndex:${this.curIndex}` + `this.index:${this.index}`);avPlayer.surfaceId = this.surfaceID;avPlayer.prepare();break;case 'prepared': // prepare调用成功后上报该状态机Logger.info(TAG,'AVPlayer state prepared called.' + `this.curIndex:${this.curIndex}` + `this.index:${this.index}`);avPlayer.audioInterruptMode = audio.InterruptMode.INDEPENDENT_MODE; // 避免同时出现两个视频的声音this.flag = true;avPlayer.loop = true;this.duration = avPlayer.duration;this.durationTime = Math.floor(this.duration / CommConstants.SECOND_TO_MS);this.currentStringTime = secondToTime(this.durationTime);if (this.firstFlag && this.index === 0 && this.isPageShow) {avPlayer.play(); // 应用启动后的第一个视频启动播放this.firstFlag = false;}break;case 'completed': // 播放结束后触发该状态机上报Logger.info(TAG,'AVPlayer state completed called.' + `this.curIndex:${this.curIndex}` + `this.index:${this.index}`);this.isPlaying = false;break;case 'playing': // play成功调用后触发该状态机上报Logger.info(TAG,'AVPlayer state playing called.' + `this.curIndex:${this.curIndex}` + `this.index:${this.index}` +`source:${this.curSource}`);this.isPlaying = true;break;case 'paused': // pause成功调用后触发该状态机上报Logger.info(TAG,'AVPlayer state paused called.' + `this.curIndex:${this.curIndex}` + `this.index:${this.index}`);break;case 'stopped': // stop接口成功调用后触发该状态机上报Logger.info(TAG,'AVPlayer state stopped called.' + `this.curIndex:${this.curIndex}` + `this.index:${this.index}`);break;case 'released':Logger.info(TAG,'AVPlayer state released called.' + `this.curIndex:${this.curIndex}` + `this.index:${this.index}`);break;case 'error':Logger.info(TAG,'AVPlayer state released called.' + `this.curIndex:${this.curIndex}` + `this.index:${this.index}`);avPlayer.reset();break;default:Logger.info(TAG, 'AVPlayer state unknown called.' + state);break;}})
    }
  4. 视频轮播:使用Swiper组件进行视频轮播,设置cachedCount(3)缓存视频数量。

    build() {Swiper(this.swiperController) {LazyForEach(new MyDataSource(this.sources), (item: string, index: number) => {VideoPlayer({curSource: item,curIndex: this.curIndex,index: index,firstFlag: this.firstFlag,isPageShow: this.isPageShow,foldStatus: this.foldStatus})}, (item: string, index: number) => JSON.stringify(item) + index)}.cachedCount(3) // 缓存视频数量.width(CommComstants.WIDTH_FULL_PERCENT).height(CommComstants.HEIGHT_FULL_PERCENT).vertical(true).loop(true).curve(Curve.Ease).duration(CommComstants.DURATION_TIME).indicator(false).backgroundColor(Color.Black).onGestureSwipe((index: number, extraInfo: SwiperAnimationEvent) => {Logger.info(TAG, `onGestureSwipe index:${index}}`);}).onAnimationStart((index: number, targetIndex: number, extraInfo: SwiperAnimationEvent) => {this.curIndex = targetIndex; // 优化点:视频播放和动画启动同步进行,覆盖动画效果}).onAnimationEnd((index: number, extraInfo: SwiperAnimationEvent) => {Logger.info(TAG, `onAnimationEnd index:${index}}`);})
    }
  5. 窗口设置:设置XComponent组件用于视频流渲染,获取并设置SurfaceID,用户设置显示画面,在onLoad时异步创建并初始化AVPlayer播放器。

    XComponent({id: 'XComponent',type: XComponentType.SURFACE,controller: this.xComponentController}).width(this.XComponentWidth).height(this.XComponentHeight).onLoad(async () => {this.surfaceID = this.xComponentController.getXComponentSurfaceId();this.initAVPlayer(); // 优化点:创建AVPlayer的播放器放入到缓存池中,不可共用播放器。})
  6. 视频播放设置:监听Swiper轮播的this.curIndex值,在视频缓存流中跟this.index进行比较,从而判断视频流中哪个播放,其余的均暂停。

    onIndexChange() {if (this.curIndex !== this.index) {pauseVideo(this.avPlayer, this.curIndex, this.index);this.isPlaying = false;this.trackThicknessSize = CommConstants.TRACK_SIZE_MIN;} else {if (this.flag === true) {playVideo(this.avPlayer, this.curIndex, this.index);this.isPlaying = true;this.trackThicknessSize = CommConstants.TRACK_SIZE_MIN;} else {let countNum = 0;let interValFlag = setInterval(() => {countNum++;// 此处有必要再次判断索引,否则会出现索引错乱导致播放异常if (this.curIndex !== this.index) {clearInterval(interValFlag);}if (this.flag === true && this.isPageShow) {countNum = 0;playVideo(this.avPlayer, this.curIndex, this.index);this.isPlaying = true;this.trackThicknessSize = CommConstants.TRACK_SIZE_MIN;clearInterval(interValFlag);} else {if (countNum > 15) {countNum = 0;this.initAVPlayer();}}}, 100);}}
    }
  7. 设置AVPlayer监听关闭并释放资源。

    export function releaseVideo(avPlayer: media.AVPlayer | undefined, curIndex: number, index: number) {if (avPlayer) {Logger.info(TAG, 'releaseVideo:' + `state:${avPlayer.state}` + `curIndex:${curIndex},index:${index}`);avPlayer.off('timeUpdate');avPlayer.off('seekDone');avPlayer.off('speedDone');avPlayer.off('error');avPlayer.off('stateChange');avPlayer.release();}
    }
    aboutToDisappear(): void {releaseVideo(this.avPlayer, this.curIndex, this.index);
    }

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

希望这一份鸿蒙学习文档能够给大家带来帮助~


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频教程+学习PDF文档

(鸿蒙语法ArkTS、TypeScript、ArkUI教程……)

 纯血版鸿蒙全套学习文档(面试、文档、全套视频等)

                   

鸿蒙APP开发必备

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/414297.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

midwayjs 框架使用 rabbitmq 消息延迟

插件rabbitmq_delayed_message_exchange是RabbitMQ官方提供的一种用于实现延迟消息的解决方案。该插件将交换机类型扩展至x-delayed-message,这种类型的交换机能够将消息暂时挂起,直到设定的延迟时间到达,才将消息投递到绑定的队列中。这一特…

js做一个带模糊搜索、自动补全的select组件auto-input-select

效果图: 思路 原本是想弄一个输入框input,挡在原生select的前面,结果发现,原生select无论怎么弄,都无法js手动控制展开下拉选,必须点击select,这就很尴尬 然后就只能弄一个输入框input&#x…

python-变量声明、数据类型、标识符

一.变量 1.什么是变量 为什么需要变量呢? 一个程序就是一个世界,不论使用哪种高级程序语言编写代码,变量都是其程序的基本组成单位。如下图所示的sum和sub都是变量。 变量的定义: 变量相当于内存中一个数据存储空间的表示&#…

【Unity小工具】Image组件宽度、高度自适应

Unity开发中,用同一个Image进行动态加载不同尺寸的图片,在显示上会有形变此工具可以进行Image的宽度、高度自适应 实现原理 获取Image原始尺寸(sizeDelta)获取图片原始尺寸(spriteSizeDelta)公式&#xff…

Git 忽略已经提交的文件

对于未提交过的文件直接用ignore文件即可,不再赘述 对于已经提交过的文件,但是实际上不需要的,可以用git rm --cached命令 比如下图这个 .vsconfig被我误提交了或者忘了在ignore里添加了 但是我实际上不想要这个文件,那么在项目根目录打开git bash ,输入 git rm --cached .vsc…

【Hot100】LeetCode—34. 在排序数组中查找元素的第一个和最后一个位置

目录 1- 思路二分 - 左侧二分 右侧二分 2- 实现⭐34. 在排序数组中查找元素的第一个和最后一个位置——题解思路 3- ACM 实现 原题链接:34. 在排序数组中查找元素的第一个和最后一个位置 1- 思路 二分 - 左侧二分 右侧二分 右区间二分 ——> 找首次出现的位置…

unreal engine5.4.3动画重定向

UE5系列文章目录 文章目录 UE5系列文章目录前言 前言 ue5.4和ue3动画重定向之间存在差异,跟ue5.2差别更大一点,总之ue5.4越来越简化动画重定向,不想之前还需要制作RTG文件 这是ue5.3.2的制作动画重定向的界面 这是ue5.4.2的制作动画重定向…

编译FFmpeg动态库

编译FFmpeg动态库 环境 macOS High SierraFFmpeg 4.3android-ndk-r21b 编译so库 下载FFmpeg4.3源代码,进入源码目录创建build_android.sh脚本,ffmpeg从4.0起新增了target-osandroid,所以不用再修改configure文件。 注意: ndk…

k8s1.23 部署Prometheus-Operator集群监控

1. Prometheus-Operator介绍 Prometheus Operator 为 Kubernetes 提供了对 Prometheus 相关监控组件的本地部署和管理方案,该项目的目的是为了简化和自动化基于 Prometheus 的监控栈配置,主要包括以下几个功能: kubernetes自定义资源&#…

【网络安全】服务基础第一阶段——第八节:Windows系统管理基础---- Web服务与虚拟主机

目录 一、WWW概述 1.1 HTML 1.2 URI与URL 1.2.1 URL(统一资源标识符,Uniform Resource Locator) 1.3 HTTP 1.3.1 HTTP请求: 1.3.2 HTTP响应 1.3.3 状态码 1.4常见Web URL格式 实验一、网站搭建 1)访问失败可…

孩子自闭症的主要表现:探寻理解之门

自闭症,也称为孤独症,是一种复杂的神经发展障碍,它影响着孩子的社交互动、沟通能力以及行为模式。当家长注意到孩子出现自闭症倾向时,及时识别并寻求专业帮助至关重要。以下是孩子自闭症的一些主要表现,希望能为家长提…

温馨网站练习运用

第二次与团队一起制作网页虽然不进行商用,但是练习一下还是好的😊😊 我主要负责后端部分,该项目用了SpringBoot框架、SpringSecurity的安全框架、结合MyBatis-Plus的数据库查询。如果想看看,网站:温馨网登…

昇腾AI处理器的计算核心 - AI Core即DaVinci Core

昇腾AI处理器的计算核心 - AI Core即DaVinci Core flyfish 从一段代码的解释开始 template <typename T> class GlobalTensor { public:void setGlobalBuffer(T* buffer, uint32_t buffersize) {// 在这里实现设置全局缓冲区的逻辑} };语法的说明&#xff0c;主要用于…

SQLi-LABS靶场51-55通过攻略

第51关&#xff08;报错注入 闭合&#xff09; 查数据库 ?sort1%27%20and%20updatexml(1,concat(1,database()),3)-- 查表 ?sort1 and updatexml(1,concat(1,(select group_concat(table_name) from information_schema.tables where table_schemasecurity)),1)-- 第52关…

安装python软件

系统是32位还是64位 “此电脑"或者"我的电脑”&#xff0c;鼠标右键——属性&#xff0c;出现如下图查看电脑系统类型&#xff08;图中显示电脑系统类型是64位系统&#xff0c;安装Python则选择其名含有"adm64"字样的文件&#xff09;: 软件安装地址 全…

用AI生成旅游打卡照!FLUX假装去旅行lora的使用【附工作流】

hello&#xff01;今天我们来聊聊一个特别有意思的话题&#xff1a;如何用AI生成那些看起来像是去过世界各地的旅游打卡照&#xff0c;还能在朋友圈里炫耀一番。很多人看到这些照片都会问&#xff1a;“你真的去过这些地方吗&#xff1f;” 而且最主要的是这种图片做点自媒体旅…

提高工作效益方法(一)

目录 如何提高工作效率? 如何提高工作效率?&#xff08;每日工作安排&#xff09; 怎么在职场做好时间管理&#xff1f; 如何提高工作效率? 提高工作效率的关键在于采用一系列策略和方法&#xff0c;以确保工作能够高效、有序地进行。通过这些方法&#xff0c;可以有效地提…

银河麒麟编译opencv库并配置qt环境

1.opencv下载版本:opencv4.5.5,qt安装的是qt5.12.11,系统版本: 2.首先应该安装cmake工具: 下载地址:https://cmake.org/download/ 安装步骤: 1)解压; 2)进入解压后的文件夹cd cmake-3.30.2 3)./bootstrap 4)sudo make 5)sudo make install 3.下载opencv,下…

7个流行的开源数据治理工具

数字化时代&#xff0c;数据是已经成为最宝贵的资产之一。数据支撑着我们的政府、企业以及各类组织的所有流程&#xff0c;并为决策以及智能化服务提供支撑。大数据有大用途&#xff0c;但是也可能隐藏着巨大的风险&#xff0c;特别是如果我们对数据的情况不是很了解的时候&…

第二证券:两市成交不足5000亿元 小盘成长股逆势活跃

A股持续小幅颤动&#xff0c;银行等大盘蓝筹股呈现调整&#xff0c;小盘生长股则逆势反弹&#xff0c;创业板指、中证500、中证1000等指数小幅飘红。到收盘&#xff0c;沪指跌0.4%报2837.43点&#xff0c;深证成指跌0.31%报8078.82点&#xff0c;创业板指微涨0.05%报1531.45点&…