天天看點

Angular 中自定義 Video 操作

作者:吉米龐

#頭條創作挑戰賽#

本文同步本人掘金平台的文章:https://juejin.cn/post/7084784818247958535

Angular 中自定義 Video 操作

上一篇文章是 Angular 項目實作權限控制。最近自己在網上看到别人使用 vue 進行自定義 video 的操縱。加上不久前實作了 angular 自定義 video 的相關需求, 遂來記錄一下,作為交流思考

實作的功能如下:

  • 播放 / 停止
  • 快退 / 快進 / 倍速
  • 聲音開 / 聲音關
  • 進入全屏 / 退出全屏
  • 進入畫中畫 / 退出畫中畫 【安卓平闆不支援,不建議使用】
  • 經過時長 / 總時長
  • 播放進度條功能:支援點選,拖拽進度
  • 聲音進度條功能:支援點選,拖拽進度

如圖:

Angular 中自定義 Video 操作

下面我們來一一實作:

這裡的重點不在布局,我們簡單來定義一下:

<!-- 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
}
複制代碼           

這裡自定義的 play 和 pause 方法加上了一個标志,對下下面要講的進度條的控制有幫助,上面的代碼可以更加簡潔,讀者可以簡寫下。

快退 / 快進 / 倍速

這裡的快退,快進和倍速設定了不同的選項,通過參數進行傳遞:

// 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
}
複制代碼           
Angular 中自定義 Video 操作

聲音開 / 聲音關

聲音的開關使用 video 的 muted 屬性即可:

// 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); // 指派給視訊聲音
}
複制代碼           

如圖:

Angular 中自定義 Video 操作

效果示範

完成了上面的内容,我們以一個 gif 圖來展示效果:

Angular 中自定義 Video 操作
全屏,聲音和畫中畫比較難截圖,Gif 上展現不來

詳細的代碼,請前往 video-ng 擷取。

【完】✅