目前(2019 年 12 月),支持在视频进度条上悬停时添加缩略图的 javascript(免费版和付费版)库并不多。
但是你可以在videojs的道路上跟随。他们已经支持在视频进度条上悬停时添加工具提示。您可以做的其他事情就是生成视频缩略图并将其添加到控制栏中进行预览。
在这个例子中,我们将解释如何从<input type="file" /> 生成视频缩略图。虽然我们可以使用直接链接的视频源,但在测试期间,由于使用canvas.toDataURL()Tainted canvases may not be exported,我们遇到了一些问题
videojs 完成初始化后,您可以从源中克隆一个新视频并将其附加到正文中。只是为了玩和赶上loadeddata事件:
videojs('video').ready(function () {
var that = this;
var videoSource = this.player_.children_[0];
var video = $(videoSource).clone().css('display', 'none').appendTo('body')[0];
video.addEventListener('loadeddata', async function() {
// asynchronous code...
});
video.play();
});
与 YouTube 视频缩略图一样,我们会将缩略图文件生成为图像。此图片的尺寸:
(horizontalItemCount*thumbnailWidth)x(verticalItemCount*thumbnailHeight) = (5*158)x(5*90)
所以790x450 是包含 25 个子缩略图的图像大小(YouTube 使用 158 作为缩略图的宽度,90 作为缩略图的高度)。像这样:
然后,我们将根据视频时长拍摄视频快照。在这个例子中,我们每秒生成一个缩略图(每一秒都有一个缩略图)。
由于根据视频时长和质量生成视频缩略图需要很长时间,所以我们可以制作一个带有深色主题的默认缩略图等待。
.vjs-control-bar .vjs-thumbnail {
position: absolute;
width: 158px;
height: 90px;
top: -100px;
background-color: #000;
display: none;
}
获取视频时长后:
var duration = parseInt(that.duration());
在循环中使用之前,我们需要将其解析为int,因为该值可能是14.036。
其他都是:设置新视频的currentTime 值并将视频转换为画布。
因为 1 个 canvas 元素默认最多可以包含 25 个缩略图,所以我们必须将 25 个缩略图一个接一个地添加到画布上(从左到右,从上到下)。然后我们将它存储在一个数组中。
如果还有另一个缩略图,我们创建另一个画布并重复操作
var thumbnails = [];
var thumbnailWidth = 158;
var thumbnailHeight = 90;
var horizontalItemCount = 5;
var verticalItemCount = 5;
var init = function () {
videojs('video').ready(function() {
var that = this;
var videoSource = this.player_.children_[0];
var video = $(videoSource).clone().css('display', 'none').appendTo('body')[0];
// videojs element
var root = $(videoSource).closest('.video-js');
// control bar element
var controlBar = root.find('.vjs-control-bar');
// thumbnail element
controlBar.append('<div class="vjs-thumbnail"></div>');
//
controlBar.on('mousemove', '.vjs-progress-control', function() {
// getting time
var time = $(this).find('.vjs-mouse-display .vjs-time-tooltip').text();
//
var temp = null;
// format: 09
if (/^\d+$/.test(time)) {
// re-format to: 0:0:09
time = '0:0:' + time;
}
// format: 1:09
else if (/^\d+:\d+$/.test(time)) {
// re-format to: 0:1:09
time = '0:' + time;
}
//
temp = time.split(':');
// calculating to get seconds
time = (+temp[0]) * 60 * 60 + (+temp[1]) * 60 + (+temp[2]);
//
for (var item of thumbnails) {
//
var data = item.sec.find(x => x.index === time);
// thumbnail found
if (data) {
// getting mouse position based on "vjs-mouse-display" element
var position = controlBar.find('.vjs-mouse-display').position();
// updating thumbnail css
controlBar.find('.vjs-thumbnail').css({
'background-image': 'url(' + item.data + ')',
'background-position-x': data.backgroundPositionX,
'background-position-y': data.backgroundPositionY,
'left': position.left + 10,
'display': 'block'
});
// exit
return;
}
}
});
// mouse leaving the control bar
controlBar.on('mouseout', '.vjs-progress-control', function() {
// hidding thumbnail
controlBar.find('.vjs-thumbnail').css('display', 'none');
});
video.addEventListener('loadeddata', async function() {
//
video.pause();
//
var count = 1;
//
var id = 1;
//
var x = 0, y = 0;
//
var array = [];
//
var duration = parseInt(that.duration());
//
for (var i = 1; i <= duration; i++) {
array.push(i);
}
//
var canvas;
//
var i, j;
for (i = 0, j = array.length; i < j; i += horizontalItemCount) {
//
for (var startIndex of array.slice(i, i + horizontalItemCount)) {
//
var backgroundPositionX = x * thumbnailWidth;
//
var backgroundPositionY = y * thumbnailHeight;
//
var item = thumbnails.find(x => x.id === id);
if (!item) {
//
//
canvas = document.createElement('canvas');
//
canvas.width = thumbnailWidth * horizontalItemCount;
canvas.height = thumbnailHeight * verticalItemCount;
//
thumbnails.push({
id: id,
canvas: canvas,
sec: [{
index: startIndex,
backgroundPositionX: -backgroundPositionX,
backgroundPositionY: -backgroundPositionY
}]
});
} else {
//
//
canvas = item.canvas;
//
item.sec.push({
index: startIndex,
backgroundPositionX: -backgroundPositionX,
backgroundPositionY: -backgroundPositionY
});
}
//
var context = canvas.getContext('2d');
//
video.currentTime = startIndex;
//
await new Promise(function(resolve) {
var event = function() {
//
context.drawImage(video, backgroundPositionX, backgroundPositionY,
thumbnailWidth, thumbnailHeight);
//
x++;
// removing duplicate events
video.removeEventListener('canplay', event);
//
resolve();
};
//
video.addEventListener('canplay', event);
});
// 1 thumbnail is generated completely
count++;
}
// reset x coordinate
x = 0;
// increase y coordinate
y++;
// checking for overflow
if (count > horizontalItemCount * verticalItemCount) {
//
count = 1;
//
x = 0;
//
y = 0;
//
id++;
}
}
// looping through thumbnail list to update thumbnail
thumbnails.forEach(function(item) {
// converting canvas to blob to get short url
item.canvas.toBlob(blob => item.data = URL.createObjectURL(blob), 'image/jpeg');
// deleting unused property
delete item.canvas;
});
console.log('done...');
});
// playing video to hit "loadeddata" event
video.play();
});
};
$('[type=file]').on('change', function() {
var file = this.files[0];
$('video source').prop('src', URL.createObjectURL(file));
init();
});
.vjs-control-bar .vjs-thumbnail {
position: absolute;
width: 158px;
height: 90px;
top: -100px;
background-color: #000;
display: none;
}
<link rel="stylesheet" href="https://vjs.zencdn.net/7.5.5/video-js.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="https://vjs.zencdn.net/7.5.5/video.js"></script>
<input type="file" accept=".mp4" />
<video id="video" class="video-js vjs-default-skin" width="500" height="250" controls>
<source src="" type='video/mp4'>
</video>
Fiddle