实现短视频播放功能鸿蒙示例代码

鸿蒙场景化示例代码技术工程师
发布于 2025-3-19 10:43
浏览
0收藏

本文原创发布在华为开发者社区

介绍

本示例使用Swiper+AVPlayer构建了一个短视频上下滑动并播放的场景。

实现短视频播放功能源码链接

效果预览

实现短视频播放功能鸿蒙示例代码-鸿蒙开发者社区

使用说明

  • 打开应用,直接播放第一条短视频。
  • 向上滑动,即可进入第二条短视频,之后可以上下滑动播放短视频。

实现思路

数据处理

  1. 构造BasicDataSource类,实现对数据的处理函数。
class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];

  public totalCount(): number {
    return 0;
  }

  public getData(index: number): media.AVFileDescriptor | string | undefined {
    return undefined;
  }
  ···
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  1. 定义了一个名为MyDataSource的类,继承自BasicDataSource类。MyDataSource类会继承BasicDataSource类中已有的属性和方法,同时还可以在此基础上进行扩展和重写totalCount()、getData()等方法,以满足特定的业务需求。
export class MyDataSource extends BasicDataSource {
  public dataArray: media.AVFileDescriptor[] = [];
  ···
  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): media.AVFileDescriptor {
    return this.dataArray[index];
  }
  ···
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

短视频播放组件的构建

  1. 定义一个名为getVideoRatio的异步函数(通过async关键字标识),获取视频的宽高比。借助一些媒体相关的功能(比如media.createAVMetadataExtractor等)来提取视频的元数据信息,进而计算视频的宽高比。
  async getVideoRatio() {
    let aVMetadataExtractor = await media.createAVMetadataExtractor();
    if (typeof this.curSource !== 'string') {
      aVMetadataExtractor.fdSrc = this.curSource;
    }
    let aVMetadata = await aVMetadataExtractor.fetchMetadata();
    if (aVMetadata.videoWidth != undefined && aVMetadata.videoHeight != undefined) {
      this.videoRatio = Number.parseInt(aVMetadata.videoWidth) / Number.parseInt(aVMetadata.videoHeight);
    }
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  1. 构建onIndexChange()函数,用于处理索引变化,获取视频播放相关的上下文,在视频列表切换或者播放顺序改变等场景下,根据当前索引和新索引的对比情况,来决定视频的播放、暂停以及一些相关状态(如透明度、是否正在播放等)的设置,并且会根据 flag 标志来进一步判断是立即播放还是等待一定条件满足后再播放。
  onIndexChange() {
    hilog.info(0x0000, 'testTag', `enter onIndexChange. this.curIndex:${this.curIndex} this.index:${this.index}`);
    if() {
        ···
    }else {
        ···
    }
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  1. 构建一个播放控制相关的界面组件,呈现出包含播放/暂停按钮、当前播放时间显示、播放进度滑块以及总时长显示等常见播放控制功能的界面布局,并且根据不同的状态(如是否正在播放、是否有透明度等)来调整界面元素的显示样式和交互行为。
  PlayControl() {
    Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
      Image(this.isPlaying ? $r('app.media.ic_pause') : $r('app.media.ic_play'))
        .onClick(() => {
          this.iconOnclick();
        });
      Text(this.currentStringTime)

      Slider({
        value: this.currentTime,
        step: 1,
        min: 0,
        max: this.durationTime,
        style: SliderStyle.OutSet
      })
      ···
        .onChange((value: number, mode: SliderChangeMode) => {
          this.sliderOnchange(value, mode);
        });
      Text(this.durationStringTime)
    }
    ···
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  1. 构造iconOnclick()函数,作为播放 / 暂停按钮点击事件的处理逻辑,用于根据当前的播放状态以及视频加载相关的标志(this.flag)来决定是暂停正在播放的视频,还是启动视频播放,同时还会相应地更新与播放状态和显示透明度相关的属性(this.isPlaying 和 this.isOpacity)。
  iconOnclick() {
    if (this.isPlaying) {
      this.pause();
      this.isOpacity = false;
      this.isPlaying = false;
      return;
    }
    ···
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  1. 构造sliderOnchange()函数,用于处理播放进度滑块(Slider)的值改变事件。它会根据滑块变化的不同阶段(通过 mode 参数体现)以及滑块当前的值(value 参数)来更新相关的播放进度属性,同时调整与界面显示相关的透明度状态,并在滑块操作结束时执行具体的视频播放位置跳转(seek)操作,以实现根据滑块位置来控制视频播放进度的功能。
  sliderOnchange(value: number, mode: SliderChangeMode) {
    hilog.info(0x0000, 'testTag', `sliderOnchange. value is ${value}`);
    this.currentTime = value
    if (mode === SliderChangeMode.Begin || mode === SliderChangeMode.Moving) {
      this.isOpacity = false;
    }
    if (mode === SliderChangeMode.End) {
      let seekTime: number = value * this.duration / this.durationTime;
      this.currentStringTime = this.secondToTime(Math.floor(seekTime / 1000));
      hilog.info(0x0000, 'testTag', `sliderOnchange. time is ${seekTime}, currentTime is ${this.currentTime}`);
      this.setSeek(seekTime);
      this.isOpacity = true;
    }
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  1. 构造secondToTime()函数,将输入的以秒为单位的时间数值(seconds)转换为特定格式的时间字符串表示形式,格式会根据时间的长短有所不同,可能是 HH:MM:SS(小时:分钟:秒,当时间大于等于 1 小时时)、MM:SS(分钟:秒,当时间大于等于 1 分钟但小于 1 小时时)或者 00:SS(秒,当时间小于 1 分钟时),常用于将视频播放的时间长度等以一种直观易读的方式展示出来。
secondToTime(seconds: number): string {
    let hourUnit = 60 * 60;
    let hour: number = Math.floor(seconds / hourUnit);
    let minute: number = Math.floor((seconds - hour * hourUnit) / 60);
    let second: number = seconds - hour * hourUnit - minute * 60;
    let hourStr: string = hour < 10 ? `0${hour.toString()}` : `${hour.toString()}`
    let minuteStr: string = minute < 10 ? `0${minute.toString()}` : `${minute.toString()}`
    let secondStr: string = second < 10 ? `0${second.toString()}` : `${second.toString()}`
    ···
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

首页的构建

  1. Swiper构建上下滑动的短视频列表播放。
    Swiper(this.swiperController) {
      LazyForEach(new MyDataSource(this.videoFiles), (item: media.AVFileDescriptor, index: number) => {
        VideoPlayer({ curSource: item, curIndex: this.curIndex, index: index, firstFlag: this.firstFlag })
      }, (item: media.AVFileDescriptor, index: number) => JSON.stringify(item) + index)
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  1. 构造initFiles()函数,主要目的是初始化文件相关操作,具体是从特定的资源管理模块中获取视频相关文件的信息,将其整理成合适的格式(media.AVFileDescriptor 类型)后,存储到 this.videoFiles 数组中,为后续的视频播放等操作准备数据源。
  async initFiles() {
    let fileList: string[] = getContext(this).resourceManager.getRawFileListSync('video');
    fileList.forEach(async (fileStr: string) => {
      let fileDescriptor = getContext().resourceManager.getRawFdSync(`video/${fileStr}`);
      let avFileDescriptor: media.AVFileDescriptor = {
        fd: fileDescriptor.fd,
        offset: fileDescriptor.offset,
        length: fileDescriptor.length
      };
      this.videoFiles.push(avFileDescriptor)
    })
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

分类
收藏
回复
举报
回复
    相关推荐