#頭條創作挑戰賽#
本文同步本人掘金平台的文章:https://juejin.cn/post/7084784818247958535
上一篇文章是 Angular 項目實作權限控制。最近自己在網上看到别人使用 vue 進行自定義 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
}
複制代碼
聲音開 / 聲音關
聲音的開關使用 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); // 指派給視訊聲音
}
複制代碼
如圖:
效果示範
完成了上面的内容,我們以一個 gif 圖來展示效果:
全屏,聲音和畫中畫比較難截圖,Gif 上展現不來
詳細的代碼,請前往 video-ng 擷取。
【完】✅