HarmonyOS简易视频播放器

使用ArkTS语言实现视频播放器,主要包括主界面和视频播放界面功能:

  1. 主界面顶部使用Swiper组件实现视频海报轮播。

  2. 主界面下方使用List组件实现视频列表。

  3. 播放界面使用Video组件实现视频播放。

  4. 在不使用视频组件默认控制器的前提下,实现自定义控制器。

  5. 播放界面底部使用图标控制视频播放/暂停。

  6. 播放界面底部使用Slider组件控制和实现视频播放进度。

  7. 播放界面使用Stack容器组件的Z序控制在视频播放画面上展示开始/暂停/加载图标。

注意:主界面中最近播放和为你推荐列表播放网络视频,需将CommonConstants.ets中的NET属性修改为网络视频地址,网络视频要在真机上运行。

完整示例

HarmonyOS-UI: HarmonyOS一些UI示例

图片展示

软件要求

硬件要求

  • 设备类型:华为手机或运行在DevEco Studio上的华为手机设备模拟器。
  • HarmonyOS系统:3.1.0 Developer Release。

代码结构

  1. ├──entry/src/main/ets // 代码区
  2. │ ├──common
  3. │ │ └──constants
  4. │ │     └──CommonConstants.ets // 样式常量类
  5. │ ├──entryability
  6. │ │ └──EntryAbility.ts // 程序入口类
  7. │ ├──pages
  8. │ │ ├──SimpleVideoIndex.ets // 主界面
  9. │ │ └──SimpleVideoPlay.ets // 视频播放界面
  10. │ ├──view
  11. │ │ ├──VideoModule.ets // 自定义首页List模块组件文件
  12. │ │ ├──SwiperVideo.ets // 自定义首页Swiper组件文件
  13. │ │ ├──VideoPlayer.ets // 自定义播放界面视频组件文件
  14. │ │ └──VideoPlaySlider.ets // 自定义播放界面视频进度条组件文件
  15. │ └──viewmodel
  16. │ ├──HorizontalVideoItem.ets // 水平视频类
  17. │ ├──ParamItem.ets // 参数类
  18. │ ├──SwiperVideoItem.ets // banner视频类
  19. │ └──SwiperVideoModelets // 首页相关数据
  20. └──entry/src/main/resource // 应用静态资源目录

构建主界面

主界面由视频轮播模块和多个视频列表模块组成

主界面代码 SimpleVideoIndex

import { COMMON_PERCENT, COMMON_SIZE, LIST } from '../common/constants/CommonConstants'
import { SwiperVideo } from '../view/SwiperVideo'
import { VideoModule } from '../view/VideoModule'

@Entry
@Component
struct SimpleVideoIndex {
  build() {
    Column({ space: COMMON_SIZE.SIZE_24 }) {

      SwiperVideo()

      List() {
        ForEach(LIST, (item: string) => {
          ListItem() {
            VideoModule({ moduleName: item })
              .margin({ top: COMMON_SIZE.SIZE_12 })
          }
        }, (item: string) => JSON.stringify(item))
      }
      .margin({ top: COMMON_SIZE.SIZE_18 })
    }
    .width(COMMON_PERCENT.PERCENT_100)
    .height(COMMON_PERCENT.PERCENT_100)
    .backgroundColor($r("app.color.index_backgroundColor"))
  }
}

视频播放核心代码及流程

以视频轮播为例

1.获取视频数据源

在SwiperVideoModel类中定义,通过SwiperVideoModel.getSwiperVideoItems()拿到数据源

2.跳转播放页面

点击视频通过Navigator的target携带跳转路径

Navigator({ target: 'pages/SimpleVideoPlay', type: NavigationType.Push }) {
      Image(this.imageSrc)
        .borderRadius(COMMON_SIZE.SIZE_12)
    }
    .params(this.paramItem)

3.构建视频播放界面

3.1  通过Stack()容器以及zIndex属性进行组件堆叠,包括图片、播放图标、视频加载图标、播放控制

3.2 视频播放子组件VideoPlayer ,onPrepared回调方法中可以获取视频总时长,onUpdate回调方法中可实时获取到视频播放的当前时间戳,onFinish是视频播放结束后的回调方法,onError是视频播放出错的回调方法。

3.3  在自定义组件VideoPlayer底部使用了自定义子组件VideoSlider,VideoSlider自定义组件中显示和控制视频播放进度、播放、暂停、快进功能。

SimpleVideoPlay代码
import router from '@ohos.router';
import {
  COMMON_NUM_FONT_WEIGHT,
  COMMON_PERCENT,
  COMMON_SIZE,
  STACK_STYLE
} from '../common/constants/CommonConstants';
import { VideoPlayer } from '../view/VideoPlayer';

@Entry
@Component
struct SimpleVideoPlay {
  private source: string = (router.getParams() as Record<string, Object>).source as string;
  private startIconResource: Resource = $r('app.media.ic_public_play');
  private backIconResource: Resource = $r('app.media.ic_back');
  @Provide isPlay: boolean = false;
  @Provide isOpacity: boolean = false;
  private controller: VideoController = new VideoController();
  @Provide isLoading: boolean = false;
  @Provide progressVal: number = 0;
  @Provide flag: boolean = false;

  aboutToAppear() {
    this.source;
  }

  onPageHide() {
    this.controller.pause();
  }

  build() {
    Column() {
      Row() {
        Image(this.backIconResource)
          .width(COMMON_SIZE.SIZE_24)
          .height(COMMON_SIZE.SIZE_24)
          .margin({ left: COMMON_SIZE.SIZE_24 })
          .onClick(() => {
            router.back();
          })
        Text($r('app.string.back'))
          .fontColor(Color.White)
          .fontSize(COMMON_SIZE.SIZE_24)
          .fontWeight(COMMON_NUM_FONT_WEIGHT)
          .margin({ left: COMMON_SIZE.SIZE_12 })
      }
      .width(COMMON_PERCENT.PERCENT_100)
      .margin({
        left: COMMON_SIZE.SIZE_12,
        top: COMMON_SIZE.SIZE_12
      })
      .justifyContent(FlexAlign.Start)

      Stack() {
        if (!this.isPlay && !this.isLoading) {
          Image(this.startIconResource)
            .width(COMMON_SIZE.SIZE_50)
            .height(COMMON_SIZE.SIZE_50)
            .zIndex(STACK_STYLE.IMAGE_Z_INDEX)
            .onClick(() => {
              if (this.flag === true) {
                this.isPlay = true
                this.controller.start()
              }
            })
        }
        if (this.isLoading) {
          Progress({
            value: STACK_STYLE.PROGRESS_VALUE,
            total: STACK_STYLE.PROGRESS_TOTAL,
            type: ProgressType.ScaleRing
          })
            .color(Color.Grey)
            .value(this.progressVal)
            .width(STACK_STYLE.PROGRESS_WIDTH)
            .style({
              strokeWidth: STACK_STYLE.PROGRESS_STROKE_WIDTH,
              scaleCount: STACK_STYLE.PROGRESS_SCALE_COUNT,
              scaleWidth: STACK_STYLE.PROGRESS_SCALE_WIDTH
            })
            .zIndex(STACK_STYLE.PROGRESS_Z_INDEX)
        }
        VideoPlayer({
          source: this.source,
          controller: this.controller
        })
          .zIndex(0)
      }
    }
    .height(COMMON_PERCENT.PERCENT_100)
    .backgroundColor(Color.Black)
  }
}
VideoPlayer代码

import prompt from '@ohos.promptAction';
import { COMMON_NUM, COMMON_PERCENT, MESSAGE, SPLIT, START_TIME, ZERO_STR } from '../common/constants/CommonConstants';
import { VideoPlaySlider } from './VideoPlaySlider';

/**
 * video controller component
 */
@Component
export struct VideoPlayer {
  private source: string | Resource = '';
  private controller: VideoController = new VideoController();
  private previewUris: Resource = $r('app.media.preview');
  @Provide currentTime: number = 0;
  @Provide durationTime: number = 0;
  @Provide durationStringTime: string = START_TIME;
  @Provide currentStringTime: string = START_TIME;
  @Consume isPlay: boolean;
  @Consume isOpacity: boolean;
  @Consume flag: boolean;
  @Consume isLoading: boolean;
  @Consume progressVal: number;

  build() {
    Column() {
      Video({
        src: this.source,
        previewUri: this.previewUris,
        controller: this.controller
      })
        .width(COMMON_PERCENT.PERCENT_100)
        .height(COMMON_PERCENT.PERCENT_88)
        .controls(false)
        .autoPlay(false)
        .objectFit(ImageFit.Contain)
        .loop(false)
        .onUpdate((event) => {
          if (event) {
            this.currentTime = event.time;
            this.currentStringTime = this.changeSliderTime(this.currentTime);
          }
        })
        .onPrepared((event) => {
          this.prepared(event?.duration);
        })
        .onFinish(() => {
          this.finish();
        })
        .onError(() => {
          prompt.showToast({
            duration: COMMON_NUM.NUM_5000,
            message: MESSAGE
          });
        })
      VideoPlaySlider({ controller: this.controller })
    }
  }

  /**
   * video component prepared callback
   */
  prepared(duration: number) {
    this.durationTime = duration;
    let second: number = duration % COMMON_NUM.NUM_60;
    let min: number = Number.parseInt((duration / COMMON_NUM.NUM_60).toString());
    let head = min < COMMON_NUM.NUM_10 ? `${ZERO_STR}${min}` : min;
    let end = second < COMMON_NUM.NUM_10 ? `${ZERO_STR}${second}` : second;
    this.durationStringTime = `${head}${SPLIT}${end}`;
    this.flag = true;
  }
  /**
   * Get video string of current time.
   * @param the number of current time
   * @return the string of current time
   */
  changeSliderTime(value: number): string {
    let second: number = value % COMMON_NUM.NUM_60;
    let min: number = Number.parseInt((value / COMMON_NUM.NUM_60).toString());
    let head = min < COMMON_NUM.NUM_10 ? `${ZERO_STR}${min}` : min;
    let end = second < COMMON_NUM.NUM_10 ? `${ZERO_STR}${second}` : second;
    let nowTime = `${head}${SPLIT}${end}`;
    return nowTime;
  }

  /**
   * video component finish callback
   */
  finish() {
    this.isPlay = false;
    this.isOpacity = false;
  }
}

总结

  1. Swiper组件的使用。
  2. List组件的使用。
  3. Video组件的使用。
  4. Slider组件的使用。
  5. 如何实现自定义视频控制器。

  • 29
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值