Angular 中自定义 Video 操作

简介: 上一篇文章是 Angular 项目实现权限控制。最近自己在网上看到别人使用 vue 进行自定义 video 的操纵。加上不久前实现了 angular 自定义 video 的相关需求, 遂来记录一下,作为交流思考🤔

image.png


一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情


上一篇文章是 Angular 项目实现权限控制。最近自己在网上看到别人使用 vue 进行自定义 video 的操纵。加上不久前实现了 angular 自定义 video 的相关需求, 遂来记录一下,作为交流思考🤔


实现的功能如下:


  • 播放 / 停止
  • 快退 / 快进 / 倍速
  • 声音开 / 声音关
  • 进入全屏 / 退出全屏
  • 进入画中画 / 退出画中画 【安卓平板不支持,不建议使用】
  • 经过时长 / 总时长
  • 播放进度条功能:支持点击,拖拽进度
  • 声音进度条功能:支持点击,拖拽进度


如图:


image.png


下面我们来一一实现:


这里的重点不在布局,我们简单来定义一下:


<!-- app.component.html -->
<div class="video-page">
  <div class="video-tools">
    <button nz-button nzType="primary" (click)="play('btn')" style="margin-right: 12px;">播放 ✅</button>
    <button nz-button nzType="primary" (click)="pause('btn')">暂停 ✅</button>
    <ng-container>
      <button nz-button nz-dropdown [nzDropdownMenu]="menuForward" nzPlacement="bottomCenter" style="margin: 0 12px;">快进 ✅</button>
        <nz-dropdown-menu #menuForward="nzDropdownMenu">
          <ul nz-menu>
            <li nz-menu-item (click)="forwardSecond(10)">快进 10 s</li>
            <li nz-menu-item (click)="forwardSecond(20)">快进 20 s</li>
          </ul>
        </nz-dropdown-menu>
    </ng-container>
    <ng-container>
      <button nz-button nz-dropdown [nzDropdownMenu]="menuBack" nzPlacement="bottomCenter">快退 ✅</button>
        <nz-dropdown-menu #menuBack="nzDropdownMenu">
          <ul nz-menu>
            <li nz-menu-item (click)="retreatSecond(10)">快退 10 s</li>
            <li nz-menu-item (click)="retreatSecond(20)">快退 20 s</li>
          </ul>
        </nz-dropdown-menu>
    </ng-container>
    <ng-container>
      <button nz-button nz-dropdown [nzDropdownMenu]="speedUp" nzPlacement="bottomCenter" style="margin: 0 12px;">倍速 ✅</button>
        <nz-dropdown-menu #speedUp="nzDropdownMenu">
          <ul nz-menu>
            <li nz-menu-item (click)="speedUpVideo(1)">正常</li>
            <li nz-menu-item (click)="speedUpVideo(2)">2 倍</li>
            <li nz-menu-item (click)="speedUpVideo(4)">4 倍</li>
          </ul>
        </nz-dropdown-menu>
    </ng-container>
    <button nz-button nzType="primary" (click)="openOrCloseVoice()">声音开 / 声音关 ✅</button>
    <button nz-button nzType="primary" style="margin: 0 12px;" (click)="toFullScreen()">全屏 ✅</button>
    <br />
    <button nz-button nzType="primary" style="margin-top: 12px;" (click)="entryInPicture()">进入画中画 ⚠️ 安卓平板不支持</button>
    <button nz-button nzType="primary" style="margin: 12px 12px 0 12px;" (click)="exitInPicture()">退出画中画 ⚠️ 安卓平板不支持</button>
    <br />
    <div style="display: flex; justify-content: flex-start; align-items: center; margin: 12px 0;">
      经过时长 / 总时长 : ✅ {{ currentTime }}  / {{ totalTime }}
    </div>
    <!-- 进度条 -->
    <div style="display: flex; justify-content: flex-start; align-items: center; margin: 12px 0;">
      进度条:✅
      <div
        class="custom-video_control-bg"
        (mousedown)="handleProgressDown($event)"
        (mousemove)="handleProgressMove($event)"
        (mouseup)="handleProgressUp($event)"
      >
        <div
          class="custom-video_control-bg-outside"
          id="custom-video_control-bg-outside"
        >
          <span
            class="custom-video_control-bg-inside"
            id="custom-video_control-bg-inside"
          ></span>
          <span
            class="custom-video_control-bg-inside-point"
            id="custom-video_control-bg-inside-point"
          ></span>
        </div>
      </div>
    </div>
    <div style="display: flex; justify-content: flex-start; align-items: center; margin: 12px 0;">
      声音条:✅
      <div class="custom-video_control-voice">
        <span class="custom-video_control-voice-play">
          <i nz-icon nzType="sound" nzTheme="outline"></i>
        </span>
        <div
          class="custom-video_control-voice-bg"
          id="custom-video_control-voice-bg"
          (mousedown)="handleVolProgressDown($event)"
          (mousemove)="handleVolProgressMove($event)"
          (mouseup)="handleVolProgressUp($event)"
        >
          <div 
            class="custom-video_control-voice-bg-outside"
            id="custom-video_control-voice-bg-outside"
          >
            <span 
              class="custom-video_control-voice-bg-inside"
              id="custom-video_control-voice-bg-inside"
            ></span>
            <span 
              class="custom-video_control-voice-bg-point"
              id="custom-video_control-voice-bg-point"
            ></span>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="video-content">
    <video id="video" class="video" style="width: 100%" poster="assets/poster.png">
      <source type="video/mp4" src="assets/demo.mp4">
      Sorry, your browser doesn't support.
    </video>
  </div>
</div>
复制代码


这里使用了 angular ant design,之前写了一篇相关文章,还不熟悉的读者可前往 Angular 结合 NG-ZORRO 快速开发


播放 / 停止



这里直接调用 video 对象的方法 play()pause():


// app.component.ts
// 播放按钮事件
play(flag: string | undefined) {
  if(flag) this.videoState.playState = true
  this.videoState.play = true
  this.video.play()
}
// 暂停按钮事件
pause(flag: string | undefined): void {
  if(flag) this.videoState.playState = false
  this.video.pause()
  this.videoState.play = false
}
复制代码


这里自定义的 playpause 方法加上了一个标志,对下下面要讲的进度条的控制有帮助,上面的代码可以更加简洁,读者可以简写下。


快退 / 快进 / 倍速



这里的快退,快进和倍速设置了不同的选项,通过参数进行传递:


// app.component.ts
// 快进指定的时间
forwardSecond(second: number): void {
  this.video.currentTime += second; // 定位到当前的播放时间 currentTime
}
// 后退指定的时间
retreatSecond(second: number): void {
  this.video.currentTime -= second
}
// 倍速
speedUpVideo(multiple: number): void {
  this.video.playbackRate = multiple; // 设定当前的倍速 playbackRate
}
复制代码


image.png


声音开 / 声音关



声音的开关使用 videomuted 属性即可:


// app.component.ts
// 开或关声音
openOrCloseVoice(): void {
  this.video.muted = !this.video.muted;
}
复制代码


进入全屏 / 退出全屏



全屏的操作也是很简单,使用 webkitRequestFullScreen


// app.component.ts
// 全屏操作
toFullScreen(): void {
  this.video.webkitRequestFullScreen()
}
复制代码


全屏后,按 esc 可退出全屏


进入画中画 / 退出画中画



画中画相当于弹窗缩小视频~


// app.component.ts
// 进入画中画
entryInPicture(): void {
  this.video.requestPictureInPicture()
  this.video.style.display = "none"
}
// 退出画中画
exitInPicture(): void {
  if(this.document.pictureInPictureElement) {
    this.document.exitPictureInPicture()
    this.video.style.display = "block"
  }
}
复制代码


设置 video 的样式,是为了看起来不突兀...


经过时长 / 总时长



记录视频的总时长和视频当前的播放时长。我们已经来组件的时候就获取视频的元信息,得到总时长;在视频播放的过程中,更新当前时长。


// app.component.ts
// 初始化 video 的相关的事件
initVideoData(): void {
  // 获取视频的总时长
  this.video.addEventListener('loadedmetadata', () => {
    this.totalTime = this.formatTime(this.video.duration)
  })
  // 监听时间发生更改
  this.video.addEventListener('timeupdate', () => {
    this.currentTime = this.formatTime(this.video.currentTime) // 当前播放的时间
  })
}
复制代码


formatTime 是格式化函数


播放进度条功能



监听鼠标的点击,移动,松开的事件,对视频的播放时间和总事件进行相除,计算百分比。


// app.component.ts
// 进度条鼠标按下
handleProgressDown(event: any): void {
  this.videoState.downState = true
  this.pause(undefined);
  this.videoState.distance = event.clientX + document.documentElement.scrollLeft - this.videoState.leftInit;
}
// 进度条 滚动条移动
handleProgressMove(event: any): void {
  if(!this.videoState.downState) return
  let distanceX = (event.clientX + document.documentElement.scrollLeft) - this.videoState.leftInit
  if(distanceX > this.processWidth) { // 容错处理
    distanceX = this.processWidth;
  }
  if(distanceX < 0) { // 容错处理
    distanceX = 0
  }
  this.videoState.distance = distanceX
  this.video.currentTime = this.videoState.distance / this.processWidth * this.video.duration
}
// 进度条 鼠标抬起
handleProgressUp(event: any): void {
  this.videoState.downState = false
  // 视频播放
  this.video.currentTime = this.videoState.distance / this.processWidth * this.video.duration
  this.currentTime = this.formatTime(this.video.currentTime)
  if(this.videoState.playState) {
    this.play(undefined)
  }
}
复制代码


这里需要计算进度条的位置,来获取点击进度条的百分比,之后更新视频的当前播放时间。当然,我们还得有容错处理,比如进度条为负数时候,当前播放时间为0。


声音进度条



我们实现了播放进度条的操作,对声音进度条的实现就很容易上手了。声音进度条也是监听鼠标的点击,移动,松开。不过,这次我们处理的是已知声音 div 的高度。


// app.component.ts
// 声音条 鼠标按下
handleVolProgressDown(event: any) {
  this.voiceState.topInit = this.getOffset(this.voiceProOut, undefined).top
  this.volProcessHeight = this.voiceProOut.clientHeight
  this.voiceState.downState = true //按下鼠标标志
  this.voiceState.distance = this.volProcessHeight - (event.clientY + document.documentElement.scrollTop - this.voiceState.topInit) 
}
// 声音 滚动条移动
handleVolProgressMove(event: any) {
  if(!this.voiceState.downState) return
    let disY = this.voiceState.topInit + this.volProcessHeight - (event.clientY + document.documentElement.scrollTop)
    if(disY > this.volProcessHeight - 2) { // 容错处理
      disY = this.volProcessHeight - 2
    }
    if(disY < 0) { // 容错处理
      disY = 0
    }
    this.voiceState.distance = disY
    this.video.volume = this.voiceState.distance / this.volProcessHeight
    this.videoOption.volume = Math.round(this.video.volume * 100)
}
// 声音 鼠标抬起
handleVolProgressUp(event: any) {
  this.voiceState.downState = false //按下鼠标标志
  let voiceRate =  this.voiceState.distance / this.volProcessHeight
  if(voiceRate > 1) {
    voiceRate = 1
  }
  if(voiceRate < 0) {
    voiceRate = 0
  }
  this.video.volume = voiceRate
  this.videoOption.volume = Math.round(this.video.volume * 100); // 赋值给视频声音
}
复制代码


如图:


image.png


效果演示



完成了上面的内容,我们以一个 gif 图来展示效果:


image.png


全屏,声音和画中画比较难截图,Gif 上体现不来


相关文章
|
3月前
|
前端开发 开发者 C#
深度解析 Uno Platform 中的 MVVM 模式:从理论到实践的全方位指南,助你轻松掌握通过 C# 与 XAML 构建高效可维护的跨平台应用秘籍
【8月更文挑战第31天】本文详细介绍如何在优秀的跨平台 UI 框架 Uno Platform 中实施 MVVM(Model-View-ViewModel)模式,通过一个简单的待办事项列表应用演示其实现过程。MVVM 模式有助于分离视图层与业务逻辑层,提升代码组织性、易测性和可维护性。Uno Platform 的数据绑定机制使视图与模型间的同步变得高效简便。文章通过构造 `TodoListViewModel` 类及其相关视图,展示了如何解耦视图与模型,实现动态数据绑定及命令处理,从而提高代码质量和开发效率。通过这一模式,开发者能更轻松地构建复杂的跨平台应用。
48 0
|
3月前
|
JavaScript 前端开发
如何在 Angular 中为响应式表单创建自定义验证器
如何在 Angular 中为响应式表单创建自定义验证器
24 0
|
3月前
|
JavaScript
如何在自定义 Angular 指令中使用 @HostBinding 和 @HostListener
如何在自定义 Angular 指令中使用 @HostBinding 和 @HostListener
33 0
|
3月前
在Angular中创建自定义管道
在Angular中创建自定义管道
16 0
|
3月前
|
JavaScript 安全
如何在 Angular Material 中使用自定义 SVG 图标
如何在 Angular Material 中使用自定义 SVG 图标
64 0
|
3月前
|
JavaScript
如何使用 ControlValueAccessor 在 Angular 中创建自定义表单控件
如何使用 ControlValueAccessor 在 Angular 中创建自定义表单控件
22 0
|
JavaScript 定位技术
Angular1.x入门级自定义组件(导航条)
Angular1.x入门级自定义组件(导航条)
|
JavaScript 前端开发 API
让Angular自定义组件支持form表单验证
让Angular自定义组件支持form表单验证
160 0
|
Perl
Angular最新教程-第十三节 管道Pipes 自定义管道
Angular最新教程-第十三节 管道Pipes 自定义管道
144 0
Angular最新教程-第十三节 管道Pipes 自定义管道
|
JSON Linux 数据格式
Angular 2.x折腾记 :(5) 动手实现一个自定义管道
管道这东西,可以让用户的体验变得好,也可以省去我们一些重复性的工作; 官方的内置管道就不解释了,自行看文档吧
151 0