鸿蒙系统下使用AVPlay播放视频,封装播放器

鸿蒙系统下使用AVPlay开发一款视频播放器流程

一. 申请权限

申请相关权限,主要是读取存储卡权限,方便后面扫描视频用:

getPermission(): void {let array: Array<Permissions> = ['ohos.permission.WRITE_DOCUMENT','ohos.permission.READ_DOCUMENT','ohos.permission.READ_MEDIA','ohos.permission.WRITE_MEDIA','ohos.permission.MEDIA_LOCATION','ohos.permission.READ_IMAGEVIDEO','ohos.permission.WRITE_IMAGEVIDEO','ohos.permission.DISTRIBUTED_DATASYNC','ohos.permission.DISTRIBUTED_SOFTBUS_CENTER',];let context = this.context;let atManager = abilityAccessCtrl.createAtManager();atManager.requestPermissionsFromUser(context, array).then((data) => {let isAgreeAllPermissions = truedata.authResults.forEach((result: number) => {if (result != 0) {isAgreeAllPermissions = false}})if (isAgreeAllPermissions) {this.updatePlayStatus()}})}

二. 获取本地视频数据

使用 phAccessHelper 扫描本地视频列表,然后将视频相关信息封装起来

 //获取本地视频列表async getRawFileList(callback: Function) {let videoListSrc: Array<VideoFile> = []const context = getContext(this);let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);// console.log('console is  == phAccessHelper', JSON.stringify(phAccessHelper))let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();let fetchOptions: photoAccessHelper.FetchOptions = {// fetchColumns: [],fetchColumns: [photoAccessHelper.PhotoKeys.SIZE,photoAccessHelper.PhotoKeys.DATE_ADDED,photoAccessHelper.PhotoKeys.DATE_MODIFIED,photoAccessHelper.PhotoKeys.POSITION,photoAccessHelper.PhotoKeys.WIDTH,photoAccessHelper.PhotoKeys.HEIGHT,],predicates: predicates};phAccessHelper.getAssets(fetchOptions, async (err, fetchResult) => {if (fetchResult != undefined) {let sortList: Array<string> = []for (let i = 0; i < fetchResult.getCount(); i++) {let fileAsset: photoAccessHelper.PhotoAsset = await fetchResult.getNextObject();if (fileAsset == undefined) {continue}await fileAsset.open('r').then((fd: number) => {let size = fs.statSync(fd).sizeif (fileAsset.photoType == photoAccessHelper.PhotoType.VIDEO) {let mVideoFile = new VideoFile()mVideoFile.fileFD = fdmVideoFile.fileSize = sizelet filePath = this.getFileNamePath(fileAsset.uri) + fileAsset.displayNamemVideoFile.filePath = filePathmVideoFile.uri = fileAsset.uriPersistentStorage.persistProp(filePath,0)mVideoFile.duration = AppStorage.get(filePath) as number// LogUtil.info('读取的key: '+filePath+ '| 视频时长: '+mVideoFile.duration)mVideoFile.displayName = this.getShowFileName(fileAsset.displayName)// mVideoFile.photoType = fileAsset.photoTypemVideoFile.photoType = 'video/mp4'mVideoFile.videoWidth = fileAsset.get(photoAccessHelper.PhotoKeys.WIDTH) as numbermVideoFile.videoHeight = fileAsset.get(photoAccessHelper.PhotoKeys.HEIGHT) as numbermVideoFile.size = fileAsset.get(photoAccessHelper.PhotoKeys.SIZE) as NumbermVideoFile.dimensions = fileAsset.get(photoAccessHelper.PhotoKeys.WIDTH).toString() + 'x' + fileAsset.get(photoAccessHelper.PhotoKeys.HEIGHT).toString()videoListSrc.push(mVideoFile)sortList.push(fileAsset.displayName)}})}if (callback != null) {callback(videoListSrc)}}});}

三.封装AVPlay相关接口

初始化AVPlay,并封装相关接口,建议单独封装一个AVPlayViewModel,处理视频相关业务

1、 初始化AVPlay
 initAVPlay() {media.createAVPlayer((error: BusinessError, video: media.AVPlayer) => {if (video != null) {this.avPlayer = video;avPlayer = videothis.setAVPlayerCallBack(this.avPlayer)this.setScreenOnWhilePlaying(true)} else {}});}
2. 封装播放、暂停、停止等相关接口
 prepared(): Promise<void> {return this.avPlayer.prepare();}start() {this.avPlayer.play()}play() {this.avPlayer.play()}pause(): Promise<void> {return this.avPlayer.pause()}stop(): Promise<void> {return this.avPlayer.stop();}reset(): Promise<void> {return this.avPlayer.reset()}release() {this.avPlayer.release()}isPlaying() {return this.mCurrentPlayStatus == AvplayerStatus.PLAYING}getDuration(): number {return this.avPlayer.duration}
3. seek相关
// 设置当前播放位置setSeekTime(value: number) {this.seekTime = value * this.duration / CommonConstants.ONE_HUNDRED;if (this.avPlayer !== null) {this.avPlayer.seek(value, media.SeekMode.SEEK_NEXT_SYNC);}}
4. 设置播放路径
 async setDataSrc(fileSize: number, fileFD: number) {let src: media.AVDataSrcDescriptor = {fileSize: fileSize,callback: (buf: ArrayBuffer, length: number, pos: number | undefined) => {let num = 0;if (buf == undefined || length == undefined || pos == undefined) {return -1;}num = fileIo.readSync(fileFD, buf, { offset: pos, length: length });if (num > 0 && (fileSize >= pos)) {return num;}return -1;}}this.isSeek = true; // 支持seek操作avPlayer.dataSrc = src;}
5. 设置相关播放状态监听
 setOnSeekCompleteListener(listener: OnSeekCompleteListener) {this.avPlayer.on('seekDone', (seekDoneTime: number) => {listener.onSeekComplete()})}setOnErrorListener(listener: OnErrorListener): void {this.avPlayer.on('error', (err: BusinessError) => {listener.onError(err.code, err.message)});}setOnDurationUpdateListener(listener: OnDurationUpdateListener) {avPlayer.on('durationUpdate', (duration: number) => {listener.onDurationUpdate(duration)})}setOnTimeUpdateListener(listener: OnTimedTextListener) {this.avPlayer.on('timeUpdate', (seekDoneTime: number) => { //设置'timeUpdate'事件回调if (seekDoneTime == null) {return;}listener.onTimedText(seekDoneTime + '')});}setOnVideoSizeChangeListener(listener: OnVideoSizeChangedListener): void {this.avPlayer.on('videoSizeChange', (width: number, height: number) => {listener.onVideoSizeChanged(width, height)})}setOnStartRenderFrameListener(listener: OnTimedTextListener) {this.avPlayer.on('startRenderFrame', () => {});}
6. 设置播放相关监听Callback
  avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {this.mCurrentPlayStatus = stateif (this.mOnStateChangeListener != null) {this.mOnStateChangeListener.onStateChange(state)}switch (state) {case AvplayerStatus.IDLE: // 成功调用reset接口后触发该状态机上报LogUtil.info('AVPlayer state idle called.');// avPlayer.release(); // 调用release接口销毁实例对象break;case AvplayerStatus.INITIALIZED: // avplayer 设置播放源后触发该状态上报LogUtil.info('AVPlayer state initialized called.  surfaceID: ' + this.surfaceID);avPlayer.surfaceId = this.surfaceID; // 设置显示画面,当播放的资源为纯音频时无需设置avPlayer.prepare();break;case AvplayerStatus.PREPARED: // prepare调用成功后上报该状态机LogUtil.info('AVPlayer state prepared called.');this.duration = avPlayer.duration// this.play(); // 调用播放接口开始播放LogUtil.info('video duration; ' + this.duration)break;case AvplayerStatus.PLAYING: // play成功调用后触发该状态机上报LogUtil.info('AVPlayer state playing called.');if (this.count !== 0) {if (this.isSeek) {LogUtil.info('AVPlayer start to seek.');// avPlayer.seek(avPlayer.duration); //seek到视频末尾} else {// 当播放模式不支持seek操作时继续播放到结尾LogUtil.info('AVPlayer wait to play end.');}} else {// avPlayer.pause(); // 调用暂停接口暂停播放}this.count++;break;case AvplayerStatus.PAUSED: // pause成功调用后触发该状态机上报LogUtil.info('AVPlayer state paused called.');// avPlayer.play(); // 再次播放接口开始播放break;case AvplayerStatus.COMPLETED: // 播放结束后触发该状态机上报LogUtil.info('AVPlayer state completed called.');// this.stop()break;case AvplayerStatus.STOPPED: // stop接口成功调用后触发该状态机上报LogUtil.info('AVPlayer state stopped called.');this.reset(); // 调用reset接口初始化avplayer状态break;case AvplayerStatus.RELEASED:LogUtil.info('AVPlayer state released called.');break;default:LogUtil.info('AVPlayer state unknown called.');break;}})

三. 绘制页面,使用XComponent渲染视频

1.主界面布局
 build() {Column() {Stack() {Column() {this.video()}.justifyContent(this.isLand ? FlexAlign.Center : FlexAlign.Start).padding({ top: this.isLand ? 0 : 50 }).height(CommonConstants.FULL_PERCENT)if (this.isLand) {this.LandScreenView() //横屏} else {this.VerticalScreenView() //竖屏}this.buildLoading()}.backgroundColor($r('app.color.black')).height(CommonConstants.FULL_PERCENT).width(CommonConstants.FULL_PERCENT)}.backgroundColor($r('app.color.black')).height(CommonConstants.FULL_PERCENT).width(CommonConstants.FULL_PERCENT)}
2. 视频ivideo布局
@Buildervideo() {Row() {XComponent({id: 'xComponentId',type: XComponentType.SURFACE,libraryname: 'nativerender',controller: this.mXComponentController}).width(this.isLand ? this.isVideoFullScreen ? '100%' : '75%' : CommonConstants.FULL_PERCENT).height(this.isLand ?this.isVideoFullScreen ? mScreenUtils.getScreenWidth() * this.videoHeight / this.videoWidth : mScreenUtils.getScreenWidth() * 0.75 * this.videoHeight / this.videoWidth :mScreenUtils.getScreenWidth() * this.videoHeight / this.videoWidth).onLoad(() => {//设置surfaceID this.surfaceID = this.mXComponentController.getXComponentSurfaceId()mVideoPlayVM.setSurfaceID(this.surfaceID)})if (this.isLand) {Blank()}}.justifyContent(FlexAlign.Start).width(CommonConstants.FULL_PERCENT)}
3. seek相关
  Slider({ value: this.currentProgress, min: 0, max: this.duration }).layoutWeight(1).trackColor('#eeeeee').selectedColor('#ff0c4ae7').onChange(this.sliderChangeCallback)sliderChangeCallback = (value: number, mode: SliderChangeMode) => {this.stopProgressTask();this.currentProgress = value;LogUtil.info(`currentprogress: ${this.currentProgress}`)if (mode === SliderChangeMode.End || mode === SliderChangeMode.Moving) {if (mVideoPlayVM.getCurrentPlayState() == AvplayerStatus.PREPARED ||mVideoPlayVM.getCurrentPlayState() == AvplayerStatus.PLAYING ||mVideoPlayVM.getCurrentPlayState() == AvplayerStatus.PAUSED) {this.seek(value)} else if (mVideoPlayVM.getCurrentPlayState() == AvplayerStatus.IDLE) {this.tempOnStopSeekValue = valuethis.onPlayClick()} else if (mVideoPlayVM.getCurrentPlayState() == AvplayerStatus.COMPLETED) {this.seek(value)this.startPlayOrResumePlay()}}}
4. 播放、暂停相关
// 点击播放暂停onPlayClick() {LogUtil.info(`onPlayClick isPlaying= ${this.isPlaying}`)if (this.isPlaying) {this.pause()} else {this.startPlayOrResumePlay()}}private startPlayOrResumePlay() {this.mDestroyPage = false;this.videoPlayStateImage = $r('app.media.icon_video_pause')this.stopProgressTask();this.startProgressTask();this.stopHideVideoControlViewTask()this.isPlaying = true;if (mVideoPlayVM.getCurrentPlayState() == AvplayerStatus.IDLE) {this.play();}if (mVideoPlayVM.getCurrentPlayState() == AvplayerStatus.PAUSED ||mVideoPlayVM.getCurrentPlayState() == AvplayerStatus.COMPLETED) {mVideoPlayVM.start();}}//播放private play() {this.showLoadIng()this.setListener()if (mVideoPlayVM.getCurrentPlayState() == AvplayerStatus.INITIALIZED) {mVideoPlayVM.reset().then(() => {mVideoPlayVM.setDataSrc(this.fileSize, this.fileFD)})} else {mVideoPlayVM.setDataSrc(this.fileSize, this.fileFD)}}//停止private stop() {if (mVideoPlayVM.getCurrentPlayState() == AvplayerStatus.PREPARED ||mVideoPlayVM.getCurrentPlayState() == AvplayerStatus.PLAYING ||mVideoPlayVM.getCurrentPlayState() == AvplayerStatus.PAUSED) {this.isClickStopSeek = truethis.seek(0)})}}

最后处理一些细节,比如进度条、音量、异常等,一个基于AVPlay简单的鸿蒙播放器就实现了

播放器效果图:

在这里插入图片描述

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

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

相关文章

编程语言05:面向对象

一、定义 使用步骤&#xff1a; 1.定义类 2.创建对象 3.调用类的属性和方法 (一)定义类 1.java 一个java文件中可以定义多个class类&#xff0c;且只能一个类是public修饰&#xff0c;而且public修饰的类名必须成为代码文件名。 实际开发中建议还是一个文件定义一个…

低速接口项目之串口Uart开发(二)——FIFO实现串口数据的收发回环测试

本节目录 一、设计思路 二、loop环回模块 三、仿真模块 四、仿真验证 五、上板验证 六、往期文章链接本节内容 一、设计思路 串口数据的收发回环测试&#xff0c;最简单的硬件测试是把Tx和Rx连接在一起&#xff0c;然后上位机进行发送和接收测试&#xff0c;但是需要考虑到串…

机器学习系列----关联分析

目录 1. 关联分析的基本概念 1.1定义 1.2常用算法 2.Apriori 算法的实现 2.1 工作原理 2.2 算法步骤 2.3 优缺点 2.4 时间复杂度 2.5实际运用----市场购物篮分析 3. FP-Growth 算法 3.1 工作原理 3.2 算法步骤 3.3 优缺点 3.4 时间复杂度 3.5实际运用——网页点…

二叉树路径相关算法题|带权路径长度WPL|最长路径长度|直径长度|到叶节点路径|深度|到某节点的路径非递归(C)

带权路径长度WPL 二叉树的带权路径长度(WPL)是二叉树所有叶节点的带权路径长度之和&#xff0c;给定一棵二叉树T&#xff0c;采用二叉链表存储&#xff0c;节点结构为 其中叶节点的weight域保存该节点的非负权值&#xff0c;设root为指向T的根节点的指针&#xff0c;设计求W…

Ubuntu ESP32开发环境搭建

文章目录 ESP32开发环境搭建安装ESP-IDF搭建一个最小工程现象 ESP32开发环境搭建 最近有个小项目需要用到能够联网的mcu驱动&#xff0c;准备玩玩esp的芯片&#xff0c;记录下ESP32开发环境搭建的过程。 ESP-IDF 是乐鑫科技为其 ESP32 系列芯片提供的官方开发框架。这个框架主…

2024.5 AAAiGLaM:通过邻域分区和生成子图编码对领域知识图谱对齐的大型语言模型进行微调

GLaM: Fine-Tuning Large Language Models for Domain Knowledge Graph Alignment via Neighborhood Partitioning and Generative Subgraph Encoding 问题 如何将特定领域知识图谱直接整合进大语言模型&#xff08;LLM&#xff09;的表示中&#xff0c;以提高其在图数据上自…

《SpringBoot、Vue 组装exe与套壳保姆级教学》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

django宠物服务管理系统

摘 要 宠物服务管理系统是一种专门为宠物主人和宠物服务提供商设计的软件。它可以帮助用户快速找到附近的宠物医院、宠物美容店、宠物寄养中心等服务提供商&#xff0c;并预订相关服务。该系统还提供了一系列实用的功能。通过使用宠物服务管理系统&#xff0c;用户可以更加方便…

docker 容器运行Ruoyi-cloud

1&#xff0c;linux系统安装openjdk1.8,mvn,dokcer,node,git 2&#xff0c;拉取代码 1&#xff09;查看gitee仓库地址 2&#xff09;创建/app文件夹&#xff0c;进入app目录 mkdir /app cd /app 3&#xff09;clone代码 4&#xff09;修改配置文件中nacos地址 # 修改注…

Linux运维篇-iscsi存储搭建

目录 概念实验介绍环境准备存储端软件安装使用targetcli来管理iSCSI共享存储 客户端软件安装连接存储 概念 iSCSI是一种在Internet协议上&#xff0c;特别是以太网上进行数据块传输的标准&#xff0c;它是一种基于IP Storage理论的存储技术&#xff0c;该技术是将存储行业广泛…

《Spring 数据访问:高效整合数据库与 ORM》

一、Spring 数据访问概述 Spring 在数据访问方面具有至关重要的地位&#xff0c;它为开发者提供了强大而高效的数据访问解决方案。 &#xff08;一&#xff09;强大的数据访问支持 Spring 提供了多种数据访问方式&#xff0c;以满足不同项目的需求。JDBC 是一种传统的数据访问…

AMD(Xilinx) FPGA配置Flash大小选择

目录 1 FPGA配置Flash大小的决定因素2 为什么选择的Flash容量大小为最小保证能够完成整个FPGA的配置呢&#xff1f; 1 FPGA配置Flash大小的决定因素 在进行FPGA硬件设计时&#xff0c;选择合适的配置Flash是我们进行硬件设计必须考虑的&#xff0c;那么配置Flash大小的选择由什…

解读缓存问题的技术旅程

目录 前言1. 问题的突发与初步猜测2. 缓存的“隐身术”3. 缓存策略的深层优化4. 反思与感悟结语 前言 那是一个普通的工作日&#xff0c;团队例行的早会刚刚结束&#xff0c;我正准备继续优化手头的模块时&#xff0c;突然收到了用户反馈。反馈的内容是部分数据显示异常&#…

Block Successive Upper Bound Minimization Method(BSUM)算法

BSUM优化方法学习 先验知识参考资料1 A Unified Convergence Analysis of Block Successive Minimization Methods for Nonsmooth OptimizationSUCCESSIVE UPPER-BOUND MINIMIZATION (SUM) 连续上限最小化算法THE BLOCK SUCCESSIVE UPPER-BOUND MINIMIZATION ALGORITHM 块连续上…

开源 AI 智能名片 2+1 链动模式商城小程序:场景驱动的商业创新与用户价值挖掘

摘要&#xff1a;本文围绕开源 AI 智能名片 21 链动模式商城小程序源码&#xff0c;深入分析了场景中的时间、空间、设备、社交和状态五大核心元素。阐述了各元素的表现形式、应用策略及价值&#xff0c;包括时间元素对业务周期和用户行为的影响及相应营销策略&#xff1b;空间…

【PyTorch】Pytorch中torch.nn.Conv1d函数详解

1. 函数定义 torch.nn.Conv1d 是 PyTorch 中用于一维卷积操作的类。定义如下&#xff1a; 官方文档&#xff1a;https://pytorch.ac.cn/docs/stable/generated/torch.nn.Conv1d.html#torch.nn.Conv1d torch.nn.Conv1d(in_channels, out_channels, kernel_size, stride1,paddi…

绿光一字线激光模组:工业制造与科技创新的得力助手

在现代工业制造和科技创新领域&#xff0c;绿光一字线激光模组以其独特的性能和广泛的应用前景&#xff0c;成为了不可或缺的关键设备。这种激光模组能够发射出一条明亮且精确的绿色激光线&#xff0c;具有高精度、高稳定性和长寿命的特点&#xff0c;为各种精密加工和测量需求…

【Linux】【Shell】Shell 基础与变量

Shell 基础 Shell 基础查看可用的 Shell判断当前 Shell 类型 变量环境变量查看环境变量临时环境变量永久环境变量PATH 变量 自定义变量特殊赋值(双引号、单引号、反撇号) 预定义变量bashrc Shell 基础 Shell 是一个用 C 语言编写的程序&#xff0c;相当于是一个翻译&#xff0c…

【SQL50】day 2

目录 1.每位经理的下属员工数量 2.员工的直属部门 3.判断三角形 4.上级经理已离职的公司员工 5.换座位 6.电影评分 7.修复表中的名字 8.患某种疾病的患者 9.删除重复的电子邮箱 1.每位经理的下属员工数量 # Write your MySQL query statement below #e1是经理&#xff0c;…

FIFO和LRU算法实现操作系统中主存管理

FIFO&#xff0c;用数组实现 1和2都是使用nextReplace实现新页面位置的更新 1、不精确时间&#xff1a;用ctime输出运行时间都是0.00秒 #include <iostream> #include <iomanip> #include<ctime>//用于计算时间 using namespace std;// 页访问顺序 int pa…