鸿蒙系统下使用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 = true
      data.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).size
            if (fileAsset.photoType == photoAccessHelper.PhotoType.VIDEO) {


              let mVideoFile = new VideoFile()


              mVideoFile.fileFD = fd
              mVideoFile.fileSize = size

              let filePath = this.getFileNamePath(fileAsset.uri) + fileAsset.displayName
              mVideoFile.filePath = filePath

              mVideoFile.uri = fileAsset.uri

              PersistentStorage.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.photoType
              mVideoFile.photoType = 'video/mp4'
              mVideoFile.videoWidth = fileAsset.get(photoAccessHelper.PhotoKeys.WIDTH) as number
              mVideoFile.videoHeight = fileAsset.get(photoAccessHelper.PhotoKeys.HEIGHT) as number
              mVideoFile.size = fileAsset.get(photoAccessHelper.PhotoKeys.SIZE) as Number
              mVideoFile.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 = video
        this.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 = state
      if (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布局
@Builder
  video() {
    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 = value
        this.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 = true
      this.seek(0)
 })

    }
  }

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

播放器效果图:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

城电科技|太阳能折叠灯:点亮你的便捷之光

朋友们&#xff0c;今天要给你们介绍一款能让生活变得更加美好的神器 —— 太阳能折叠灯&#xff01; 【超便捷折叠设计】 它就像一个百变精灵&#xff0c;轻松折叠起来后小巧玲珑。可以随意塞进背包的缝隙&#xff0c;或者放在车载储物箱里&#xff0c;完全不占地方&#xff…

二次封装的天气时间日历选择组件

这个接口没调通 没有数据展示~ userStore.badgeDate是VUEX全部存的日历数据 <template><!-- 日历组件 --><el-date-pickerref"elPicker":size"size"v-model"dateTimeValue":type"dateType":range-separator"rang…

PLC与PLC跨网段通讯的几种方法:厂区组网实践

PLC通常通过以太网或其他工业网络协议&#xff08;如PROFINET、Modbus TCP等&#xff09;进行通信。当PLC位于不同的网段时&#xff0c;它们不能直接通信&#xff0c;需要特殊的配置或设备来实现通信&#xff0c;不同网段的PLC通讯变得尤为重要。 随着工业网络的发展和工业4.0概…

常见的上网方式:PPPoE、静态IP、动态IP地址

常见的上网方式有&#xff1a;PPPoE、静态IP、动态IP地址三种。本文给予简单的介绍&#xff1a; 1.PPPoE PPPoE也叫宽带拨号上网&#xff0c;拨号宽带接入是当前最广泛的宽带接入方式&#xff0c;运营商分配宽带用户名和密码&#xff0c;通过用户名和密码进行用户身份认证。如…

elasticsearch介绍和部署

1 elasticsearch介绍 Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。可以很方便的使大量数据具有搜索、分析和探索的能力。充分利用Elasticsearch的水平伸缩性。Elasticsearch 的实现原理主要分为以下几个步骤&#xff0c;首先用户将数据提交到Elasticsea…

在SpringBoot项目中集成MongoDB

文章目录 1. 准备工作2. 在SpringBoot项目中集成MongoDB2.1 引入依赖2.2 编写配置文件2.3 实体类 3. 测试4. 文档操作4.1 插入操作4.1.1 单次插入4.1.2 批量插入 4.2 查询操作4.2.1 根据id查询4.2.2 根据特定条件查询4.2.3 正则查询4.2.4 查询所有文档4.2.5 排序后返回 4.3 删除…

Linux相关概念和易错知识点(21)(软硬链接、动静态库)

目录 1.软硬链接 &#xff08;1&#xff09;软链接 &#xff08;2&#xff09;硬链接 ①实现方式及其功能 ②硬链接在目录中的运用 ③计算子目录数量 2.动静态库 &#xff08;1&#xff09;动态库 ①动态链接和静态链接 ②动态库的实现 ③系统查找动态库问题 ④解决…

Leetcode 组合

使用回溯来解决此问题。 提供的代码使用了回溯法&#xff08;Backtracking&#xff09;&#xff0c;这是一种通过递归探索所有可能解的算法思想。以下是对算法思想的详细解释&#xff1a; 核心思想&#xff1a; 回溯法通过以下步骤解决问题&#xff1a; 路径选择&#xff1a…

工具学习_Docker

0. Docker 简介 Docker 是一个开源平台&#xff0c;旨在帮助开发者构建、运行和交付应用程序。它通过容器化技术将应用程序及其所有依赖项打包在一个标准化的单元&#xff08;即容器&#xff09;中&#xff0c;使得应用程序在任何环境中都能保持一致的运行效果。Docker 提供了…

【从零开始的LeetCode-算法】3233. 统计不是特殊数字的数字数量

给你两个 正整数 l 和 r。对于任何数字 x&#xff0c;x 的所有正因数&#xff08;除了 x 本身&#xff09;被称为 x 的 真因数。 如果一个数字恰好仅有两个 真因数&#xff0c;则称该数字为 特殊数字。例如&#xff1a; 数字 4 是 特殊数字&#xff0c;因为它的真因数为 1 和…

day06(单片机高级)PCB设计

目录 PCB设计 PCB设计流程 元器件符号设计 原理图设计 元器件封装设计 元器件库使用 PCB设计 目的&#xff1a;学习从画原理图到PCB设计的整个流程 PCB设计流程 元器件符号设计 元器件符号&#xff1a;这是电子元器件的图形表示&#xff0c;用于在原理图中表示特定的元器件。例…

Oracle JDK(通常简称为 JDK)和 OpenJDK区别

Java 的开发和运行时环境主要由两种实现主导&#xff1a;Oracle JDK&#xff08;通常简称为 JDK&#xff09;和 OpenJDK。尽管它们都基于同一个代码库&#xff0c;但在一些关键点上有所区别。以下是详细的对比&#xff1a; 1. 基础代码 Oracle JDK&#xff1a; 基于 OpenJD…

LeetCode 101题集(随时更新)

题集来源&#xff1a;GitHub - changgyhub/leetcode_101: LeetCode 101&#xff1a;力扣刷题指南 使用C完成相关题目&#xff0c;以训练笔试 贪心 采用贪心的策略&#xff0c;保证每次操作都是局部最优的&#xff0c;从而使最后得到的结果是全局最优的。 分配问题 455. 分发饼…

渗透测试笔记——shodan(4)

声明&#xff1a; 学习视频来自B站up主 【泷羽sec】有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&am…

06 —— Webpack优化—压缩过程

css代码提取后想要压缩 —— 使用css-minimizer-webpack-plugin插件 下载 css-minimizer-webpack-plugin 本地软件包 npm install css-minimizer-webpack-plugin --save-dev 配置 webpack.config.js 让webpack拥有该功能 const CssMinimizerPlugin require(css-minimizer-…

【Android】android compat理解

1&#xff0c;前提 即便是在同一手机上安装的不同apk&#xff0c;其编译的apk不同&#xff0c;也会导致行为上的差异。如SDK34有限制后台启动&#xff0c;但如果安装的apk所依赖的sdk是33&#xff0c;则不会表现出此差异。这是如何实现的呢&#xff1f;其实&#xff0c;本质是…

蓝桥杯每日真题 - 第21天

题目&#xff1a;(空间) 题目描述&#xff08;12届 C&C B组A题&#xff09; 解题思路&#xff1a; 转换单位&#xff1a; 内存总大小为 256MB&#xff0c;换算为字节&#xff1a; 25610241024268,435,456字节 计算每个整数占用空间&#xff1a; 每个 32 位整数占用…

MongoDB进阶篇-索引(索引概述、索引的类型、索引相关操作、索引的使用)

文章目录 1. 索引概述2. 索引的类型2.1 单字段索引2.2 复合索引2.3 其他索引2.3.1 地理空间索引&#xff08;Geospatial Index&#xff09;2.3.2 文本索引&#xff08;Text Indexes&#xff09;2.3.3 哈希索引&#xff08;Hashed Indexes&#xff09; 3. 索引相关操作3.1 查看索…

做一个FabricJS.cc的中文文档网站——面向markdown编程

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;新的征程&#xff0c;用爱发电&#…

自动驾驶之激光雷达

这里写目录标题 1 什么是激光雷达2 激光雷达的关键参数3 激光雷达种类4 自动驾驶感知传感器5 激光雷达感知框架5.1 pointcloud_preprocess5.2 pointcloud_map_based_roi5.3 pointcloud_ground_detection5.4 lidar_detection5.5 lidar_detection_filter5.6 lidar_tracking 1 什么…