鸿蒙学习笔记二十七:视频播放器

手撸了一个视频播放器,功能包含快进、快退、暂停、播放、进度条、全屏
难点

既然有视频播放器,那肯定需要全屏播放,如果按照常规思路,在全屏播放时需要将应用设置为横屏,用到的方法为:

 // 切换屏幕方向(横屏/竖屏)
  private async switchOrientation(landscape: boolean) {
    try {
      // 获取当前窗口实例
      const topWindow = await window.getTopWindow()getContext(this);
      if (landscape) {
        // 设置为横屏(左右横屏均可)
        await topWindow.setPreferredOrientation(window.Orientation.LANDSCAPE);
        this.isLandscape = true;
      } else {
        // 恢复竖屏
        await topWindow.setPreferredOrientation(window.Orientation.PORTRAIT);
        this.isLandscape = false;
      }
    } catch (err) {
      hilog.error(0x0000, 'VideoDemo', `切换方向失败: ${(err as BusinessError).message}`);
    }
  }

这种方法的弊端在于会横竖屏切换时会重新走声明周期,这样就导致播放进度、是否展示了控制面板这些的状态失效,而如果想保留状态,就需要进行一大堆操作,非常麻烦,因此咱们放弃这种方法,改成直接将播放器组件旋转90度,然后将播放器组件的宽度设置为屏幕的高度,这样也能达到效果,而且无需重走生命周期,非常简单,下面是完整demo

import { window } from '@kit.ArkUI';
import display from '@ohos.display'


@Entry
@Component
export struct VideoPlayer {
  @State isPlaying: boolean = false;
  @State currentTime: number = 0; // 当前播放时间(秒)
  @State duration: number = 0; // 总时长(秒)
  @State isFullScreen: boolean = false; // 是否全屏
  @State showControls: boolean = true; // 是否显示控制栏
  @State controlTimer: number = 0; // 控制栏自动隐藏计时器
  private videoController: VideoController = new VideoController();
  private totalWidth: number = 0 //横屏时的宽度
  private totalHeight: number = 0 //横屏时的高度
  // 视频源(支持网络URL或本地文件路径)
  videoSrc: string = ''

  aboutToAppear(): void {
    this.getScreenSize()
  }

  aboutToDisappear(): void {
    this.clearControlTimer(); // 组件消失时清理计时器
  }

  private async getScreenSize() {
    const win = await window.getLastWindow(getContext(this));
    const size = await win.getWindowProperties();
    //屏幕宽高除以屏幕密度,得到vp;高度需要+5进行微调,否则横屏时顶部会有一个小白条
    this.totalWidth = size.windowRect.width / display.getDefaultDisplaySync().densityPixels
    this.totalHeight = size.windowRect.height / display.getDefaultDisplaySync().densityPixels + 5
  }

  build() {
    Row() {
      RelativeContainer() {
        this.VideoView()
        this.ControlView()
        this.RemindView()
      }
      .width('100%')
      .height(this.isFullScreen ? this.totalWidth : 300)
      .rotate({
        x: 0,
        y: 0,
        z: 1,
        centerX: '50%',
        centerY: '50%',
        angle: this.isFullScreen ? 90 : 0 // 旋转角度:90度
      })
    }
    .alignItems(VerticalAlign.Center)
    .width('100%')
    .height('100%')
    .backgroundColor('#80000000')
  }

  //播放器
  @Builder
  VideoView() {
    // 视频播放区域
    Video({
      src: this.videoSrc,
      controller: this.videoController
    })
      .width(this.isFullScreen ? this.totalHeight : '100%')
      .height(this.isFullScreen ? this.totalWidth : 300)
      .objectFit(ImageFit.Contain)
      .controls(false)
      .onStart(() => {
        this.isPlaying = true;
        this.startControlTimer(); // 播放时启动控制栏隐藏计时器
      })
      .onPause(() => {
        this.showControls = true; // 暂停时显示控制栏
      })
      .onFinish(() => {
        this.isPlaying = false;
        this.currentTime = 0;
        this.showControls = true;
      })
      .onPrepared((callBackDuration) => {
        this.duration = callBackDuration.duration; // 更新总时长
      })
      .autoPlay(true)
      .onUpdate((callBackPlayTime) => {
        this.currentTime = callBackPlayTime.time; // 更新当前播放时间
      })
      .onClick(() => {
        this.toggleControls(); // 点击视频切换控制栏显示状态
      })
      .alignRules({
        middle: { anchor: '__container__', align: HorizontalAlign.Center },
        center: { anchor: '__container__', align: VerticalAlign.Center },
      })
  }

  //快进、快退、暂停、播放、进度条
  @Builder
  ControlView() {
    // 控制栏(底部显示)
    Column() {
      // 控制按钮区域
      Row() {
        // 快退按钮(10秒)
        Button('快退10s')
          .fontSize(14)
          .padding({ left: 12, right: 12 })
          .margin({ left: 10 })
          .onClick(() => this.rewind(10));

        Text().layoutWeight(1)

        // 快进按钮(10秒)
        Button('快进10s')
          .fontSize(14)
          .padding({ left: 12, right: 12 })
          .margin({ left: 10 })
          .onClick(() => this.forward(10));
      }

      //进度条
      Row() {
        if (this.isFullScreen) {
          Text().width(15)
        }

        Text(`${this.formatTime(this.currentTime)}`)
          .fontSize(14)
          .margin({ left: 5, right: 5 })
          .fontColor('#fff')

        Slider({
          value: this.currentTime,
          min: 0,
          max: this.duration,
          step: 1
        })
          .layoutWeight(1)
          .blockColor('#ffffff')
          .selectedColor('#ffffff')
          .trackColor('#cccccc')
          .onChange((value) => {
            this.videoController.setCurrentTime(value); // 拖动进度条跳转播放位置
          })
          .onTouch((event) => {
            if (event.type === TouchType.Down) {
              this.clearControlTimer(); // 触摸进度条时暂停隐藏计时器
            } else if (event.type === TouchType.Up) {
              this.startControlTimer(); // 结束触摸后重启计时器
            }
          })

        Text(`${this.formatTime(this.duration)}`)
          .fontSize(14)
          .margin({ left: 5, right: 5 })
          .fontColor('#fff')

        Button(this.isFullScreen ? '退出全屏' : '全屏')
          .fontSize(14)
          .margin({ left: 5, right: 5 })
          .onClick(() => this.toggleFullScreen())

        if (this.isFullScreen) {
          Text().width(15)
        }
      }
    }
    .alignItems(HorizontalAlign.Start)
    .width(this.isFullScreen ? this.totalHeight : '100%')
    .backgroundColor('#80000000')
    .visibility(this.showControls ? Visibility.Visible : Visibility.Hidden)
    .padding({ top: 5, bottom: 5 })
    .alignRules({
      middle: { anchor: '__container__', align: HorizontalAlign.Center },
      bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
    })
  }

  //加载中
  @Builder
  RemindView() {
    // 加载提示--暂不需要
    // if (this.duration == 0) {
    //   Text('加载中...')
    //     .fontSize(14)
    //     .fontColor('#fff')
    //     .alignRules({
    //       middle: { anchor: '__container__', align: HorizontalAlign.Center },
    //       center: { anchor: '__container__', align: VerticalAlign.Center },
    //     })
    // }

    if (this.isPlaying) {
      Button('暂停')
        .fontSize(14)
        .padding({ left: 12, right: 12 })
        .onClick(() => this.togglePlay())
        .visibility(this.showControls ? Visibility.Visible : Visibility.Hidden)
        .alignRules({
          middle: { anchor: '__container__', align: HorizontalAlign.Center },
          center: { anchor: '__container__', align: VerticalAlign.Center },
        })
    } else {
      Button('播放')
        .fontSize(14)
        .padding({ left: 12, right: 12 })
        .onClick(() => this.togglePlay())
        .visibility(this.showControls ? Visibility.Visible : Visibility.Hidden)
        .alignRules({
          middle: { anchor: '__container__', align: HorizontalAlign.Center },
          center: { anchor: '__container__', align: VerticalAlign.Center },
        })
    }
  }

  // 切换播放/暂停
  private togglePlay() {
    if (this.duration == 0) {
      return
    }
    if (this.isPlaying) {
      this.videoController.pause();
    } else {
      this.videoController.start();
    }
    this.isPlaying = !this.isPlaying;
  }

  // 快进n秒
  private forward(seconds: number) {
    if (this.duration == 0) {
      return
    }
    const newTime = this.currentTime + seconds;
    this.videoController.setCurrentTime(Math.min(newTime, this.duration)); // 不超过总时长
  }

  // 快退n秒
  private rewind(seconds: number) {
    if (this.duration == 0) {
      return
    }
    const newTime = this.currentTime - seconds;
    this.videoController.setCurrentTime(Math.max(newTime, 0)); // 不小于0
  }

  // 切换全屏/窗口模式
  private async toggleFullScreen() {
    this.isFullScreen = !this.isFullScreen
  }

  // 切换控制栏显示状态
  private toggleControls() {
    this.showControls = !this.showControls;
    if (this.showControls && this.isPlaying) {
      this.startControlTimer(); // 显示时启动计时器(仅在播放中)
    } else {
      this.clearControlTimer();
    }
  }

  // 启动控制栏自动隐藏计时器(3秒后隐藏)
  private startControlTimer() {
    this.clearControlTimer();
    this.controlTimer = setTimeout(() => {
      this.showControls = false;
    }, 3000)
  }

  // 清除计时器
  private clearControlTimer() {
    if (this.controlTimer) {
      clearTimeout(this.controlTimer);
      this.controlTimer = 0;
    }
  }

  // 格式化时间(秒 -> mm:ss)
  private formatTime(seconds: number): string {
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  }
}

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容