Vinc3nt's Life

POC - Player 實作時間軸預覽懸停

2024-03-27
develop
player
research
最後更新:2025-01-26
7分鐘
1285字

概論

本文將研究並驗證,在 Video Player 實現:當鼠標在進度條(timeline)移動時,隨著鼠標指向的時間區段,顯示對應的預覽縮圖。

研究

FFmpeg

FFmpeg 是一種開源、跨平台的錄影、轉檔和串流方案,提供了一套完整、強大且靈活的工具和庫,用於處理各種多媒體資料。它主要包括三個關鍵部分:libavcodec(音頻/影片編碼/解碼庫)、libavformat(音頻/影片封裝庫)和 ffmpeg 命令行工具。

利用 FFmpeg,使用者可以輕鬆地對視訊進行操作,如更改規模、轉換格式、裁剪、添加過濾器等等。此外,其豐富的API集合使開發者可以根據自己的需求進行客製化開發。

本研究將利用 FFmpeg,依照來源影片之 timeline,進行截圖。

Montage

Montage 是一款由 ImageMagick 開發的工具,用於將多個圖片合併成一個或多個圖片。此工具可以根據需求定義輸入圖像的佈局,調整邊框和間距,以及添加標籤等等。

本研究將利用 Montage,將複數截圖合併為一張等高的 timeline 預覽圖。

Video Player Progress Control

影片播放器進度控制是影片播放過程中一個重要的特性,它不僅讓使用者能直觀地了解當前播放進度,也能讓使用者自由地在影片中跳躍至指定的播放點。此功能的實現需要在播放器內部進行精確的時間與播放位置的映射,並且在使用者進行操作時提供流暢且快速的反應。

本研究將會嘗試在 Video Player 上,基於監聽 Progress Control 實現預覽圖功能。

Dplayer integrate timeline preview

Dplayer 已經提供了這個功能的接口,因此只需要將縮圖網址跟影片原址一起給 Player 便可使用。

要注意的事,Dplayer 要求縮圖為 160 寬度,並且會檢查此縮圖是否為當前影片所生,其檢查機制暫且不明。

Videojs integrate timeline preview

Videojs 並未提供該功能,不過我們可以透過對 Progress Control 的元件添加監聽器事件,透過 css 來顯示對應縮圖。

流程設計

default

實作及驗證

Thumbnails

使用以下指令建立 FFmpeg Docker Container:

Terminal window
1
docker run -it --name app_ffmpeg -p 8080:8080 -v ~/Downloads/ffmpeg:/storage -w /storage --entrypoint='bash' jrottenberg/ffmpeg

以下在 Docker Container操作

首先擷取縮圖

由於 docker 環境並沒有預裝 bc,所以先安裝它:

Terminal window
1
sudo apt install bc

將影片放置在 ~/Downloads/ffmpeg ,建立腳本檔 run.sh 包含以下內容:

1
#!/bin/bash
2
3
input="swyg.mp4"
4
5
## Get duration in seconds
6
duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $input)
7
8
## Calculate interval
9
interval=$(echo "$duration/100" | bc -l)
10
11
for i in {0..99}; do
12
ss=$(echo "$interval*$i/1" | bc)
13
ss=$(printf "%02d:%02d:%02d" $((ss/3600)) $((ss%3600/60)) $((ss%60)))
14
ffmpeg -ss $ss -i $input -vframes 1 -vf scale=160:-1 screenshot$(printf "%03d" $i).png
15
done

執行 run.sh,ffmpeg 會依照 timeline 長度,均勻的在 100 個時間點擷取縮圖,並依照 screenshot*.png 格式命名。

接著,需要把 100 張縮圖整合成一個超長的縮圖

安裝 imagemagick:

Terminal window
1
sudo apt-get update
2
sudo apt-get install imagemagick

因為 docker 並沒有安裝字型檔,在執行 montage 會有問題,所以需要在 docker 安裝設定字體。

執行以下指令:

Terminal window
1
sudo apt-get install fontconfig
2
sudo dpkg-reconfigure fontconfig
3
export FONTCONFIG_PATH=/etc/fonts

使用 montage 合併圖片:

Terminal window
1
montage -geometry +0+0 -tile x1 screenshot*.png timeline.png
2
3
###
4
## -geometry +0+0 選項表示圖像之間的間距為 0。
5
## -tile x1 選項表示所有的圖像都排列在一行。
6
## screenshot*.png 是你的縮圖文件名模式。
7
## timeline.png 是輸出的圖片文件名。
8
###

到這裡,我們得到了最後的縮圖檔 timeline.png

Docker Container 內部操作結束


應用於 Dplayer

1
<!doctype html>
2
<html lang="en">
3
4
<head>
5
<meta charset="UTF-8">
6
<meta name="viewport"
7
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
8
<meta http-equiv="X-UA-Compatible" content="ie=edge">
9
<script src="https://jsdelivr.fusioncdn.com/npm/dplayer@1.26.0/dist/DPlayer.min.js"></script>
10
<!-- Configuration might be different along with Stream. -->
11
<script src="https://vsp.mlytics.co/{TOKEN}.js"></script>
12
<script src="https://jsdelivr.fusioncdn.com/npm/@mlytics/p2sp-sdk@latest/bundle/driver.min.js"></script>
13
<script
14
src="https://jsdelivr.fusioncdn.com/npm/@mlytics/p2sp-sdk@latest/bundle/peripheral/player/dplayer-hls.min.js"></script>
15
</head>
24 collapsed lines
16
17
<body>
18
<div style="width: 800px; height: 640px">
19
<div id="video" style="width: 800px; height: 640px; background-color: black;"></div>
20
</div>
21
<script>
22
const driver = mlysdk.driver.initialize()
23
24
const src = "PATH_TO_YOUR_M3U8"
25
const video = document.getElementById('video')
26
const adapter = driver.extensions.DPlayerHlsPlayerPlugin.create({
27
container: video,
28
autoplay: true,
29
controls: true,
30
video: {
31
url: src,
32
thumbnails: 'PATH_TO_YOUR_THUMBAILS',
33
}
34
})
35
const dp = adapter.player
36
</script>
37
</body>
38
39
</html>

應用於 Videojs

1
<head>
2
<link href="https://vjs.zencdn.net/8.3.0/video-js.css" rel="stylesheet" />
3
4
<!-- If you'd like to support IE8 (for Video.js versions prior to v7) -->
5
<!-- <script src="https://vjs.zencdn.net/ie8/1.1.2/videojs-ie8.min.js"></script> -->
6
</head>
7
8
<body>
9
<video id="my-video" class="video-js" controls preload="auto" width="640" height="264" data-setup="{}">
10
<source src="http://archive.org/download/thethreeagesbusterkeaton/Buster.Keaton.The.Three.Ages.ogv">
11
<p class="vjs-no-js">
12
To view this video please enable JavaScript, and consider upgrading to a
13
web browser that
14
<a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
15
</p>
65 collapsed lines
16
</video>
17
18
<script src="https://vjs.zencdn.net/8.3.0/video.min.js"></script>
19
20
<script>
21
class ThumbnailTip extends videojs.getComponent('Component') {
22
constructor(player, options) {
23
super(player, options);
24
}
25
26
createEl() {
27
return videojs.dom.createEl('div', {
28
className: 'vjs-thumbnail-tip',
29
innerHTML: '<div class="thumbnail" style="width:160px; height:90px; background:url(https://static.gordon.cf/p2sp-test/thumbnails-swyg.png) no-repeat;"></div>',
30
style: 'position: absolute; z-index: 100;'
31
});
32
}
33
34
showAt(time, totalDuration, totalThumbnails) {
35
var backgroundPosition = getBackgroundPositionForTime(time, totalDuration, totalThumbnails);
36
this.el().querySelector('.thumbnail').style.backgroundPosition = backgroundPosition;
37
this.show();
38
}
39
}
40
41
videojs.registerComponent('ThumbnailTip', ThumbnailTip);
42
43
var player = videojs('my-video');
44
45
let thumbnailTip = player.addChild('ThumbnailTip', {});
46
47
var progressControl = player.controlBar.progressControl;
48
49
progressControl.on('mousemove', function (event) {
50
var mousePosition = videojs.dom.getPointerPosition(progressControl.el(), event);
51
var time = mousePosition.x * player.duration();
52
53
// Calculate the left offset in pixels.
54
var leftOffsetPixels = mousePosition.x * progressControl.currentWidth();
55
56
// Apply the left offset to the thumbnailTip
57
thumbnailTip.el().style.left = leftOffsetPixels + 'px';
58
59
// Calculate the top offset in pixels.
60
// var topOffsetPixels = progressControl.el().getBoundingClientRect().top - thumbnailTip.el().offsetHeight;
61
var topOffsetPixels = progressControl.el().getBoundingClientRect().top - 90 - 10;
62
63
// Apply the top offset to the thumbnailTip
64
thumbnailTip.el().style.top = topOffsetPixels + 'px';
65
66
thumbnailTip.showAt(time, player.duration(), 100);
67
});
68
69
70
progressControl.on('mouseout', function () {
71
thumbnailTip.hide();
72
});
73
74
function getBackgroundPositionForTime(time, totalDuration, totalThumbnails) {
75
var thumbnailIndex = Math.floor((time / totalDuration) * totalThumbnails);
76
return (-thumbnailIndex * 160) + 'px 0';
77
}
78
</script>
79
80
</body>

結論

Video Player 要實現縮圖預覽功能並不困難,不過考量到我們的產品並不介入 Player 的人機互動,因此建議在影片轉檔時,一併產生縮圖檔並提供 url 即可。

本文標題:POC - Player 實作時間軸預覽懸停
文章作者:Vincent Lin
發布時間:2024-03-27