vidply 1.0.29 → 1.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/vidply.HLSRenderer-UMPUDSYL.js +266 -0
- package/dist/dev/vidply.HLSRenderer-UMPUDSYL.js.map +7 -0
- package/dist/dev/vidply.HTML5Renderer-FXBZQL6Y.js +12 -0
- package/dist/dev/vidply.HTML5Renderer-FXBZQL6Y.js.map +7 -0
- package/dist/dev/vidply.chunk-W2LSBD6Y.js +251 -0
- package/dist/dev/vidply.chunk-W2LSBD6Y.js.map +7 -0
- package/dist/dev/vidply.esm.js +263 -5
- package/dist/dev/vidply.esm.js.map +3 -3
- package/dist/legacy/vidply.js +266 -3
- package/dist/legacy/vidply.js.map +3 -3
- package/dist/legacy/vidply.min.js +1 -1
- package/dist/legacy/vidply.min.meta.json +25 -7
- package/dist/prod/vidply.HLSRenderer-3CG7BZKA.min.js +6 -0
- package/dist/prod/vidply.HTML5Renderer-KKW3OLHM.min.js +6 -0
- package/dist/prod/vidply.chunk-34RH2THY.min.js +6 -0
- package/dist/prod/vidply.esm.min.js +4 -4
- package/dist/vidply.css +20 -1
- package/dist/vidply.esm.min.meta.json +34 -16
- package/dist/vidply.min.css +1 -1
- package/package.json +2 -2
- package/src/controls/ControlBar.js +179 -3
- package/src/core/Player.js +4868 -4776
- package/src/renderers/HTML5Renderer.js +7 -0
- package/src/styles/vidply.css +20 -1
- package/src/utils/VideoFrameCapture.js +110 -0
package/dist/legacy/vidply.js
CHANGED
|
@@ -1741,6 +1741,11 @@
|
|
|
1741
1741
|
this.media.addEventListener("loadedmetadata", () => {
|
|
1742
1742
|
this.player.state.duration = this.media.duration;
|
|
1743
1743
|
this.player.emit("loadedmetadata");
|
|
1744
|
+
if (this.media.tagName === "VIDEO") {
|
|
1745
|
+
this.player.autoGeneratePoster().catch((error) => {
|
|
1746
|
+
this.player.log("Failed to auto-generate poster:", error, "warn");
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1744
1749
|
});
|
|
1745
1750
|
this.media.addEventListener("play", () => {
|
|
1746
1751
|
this.player.state.playing = true;
|
|
@@ -5574,6 +5579,81 @@
|
|
|
5574
5579
|
setTimeout(execute, timeout);
|
|
5575
5580
|
}
|
|
5576
5581
|
|
|
5582
|
+
// src/utils/VideoFrameCapture.js
|
|
5583
|
+
async function captureVideoFrame(video, time, options = {}) {
|
|
5584
|
+
if (!video || video.tagName !== "VIDEO") {
|
|
5585
|
+
return null;
|
|
5586
|
+
}
|
|
5587
|
+
const {
|
|
5588
|
+
restoreState = true,
|
|
5589
|
+
quality = 0.9,
|
|
5590
|
+
maxWidth,
|
|
5591
|
+
maxHeight
|
|
5592
|
+
} = options;
|
|
5593
|
+
const wasPlaying = !video.paused;
|
|
5594
|
+
const originalTime = video.currentTime;
|
|
5595
|
+
const originalMuted = video.muted;
|
|
5596
|
+
if (restoreState) {
|
|
5597
|
+
video.muted = true;
|
|
5598
|
+
}
|
|
5599
|
+
return new Promise((resolve) => {
|
|
5600
|
+
const captureFrame = () => {
|
|
5601
|
+
try {
|
|
5602
|
+
let width = video.videoWidth || 640;
|
|
5603
|
+
let height = video.videoHeight || 360;
|
|
5604
|
+
if (maxWidth && width > maxWidth) {
|
|
5605
|
+
const ratio = maxWidth / width;
|
|
5606
|
+
width = maxWidth;
|
|
5607
|
+
height = Math.round(height * ratio);
|
|
5608
|
+
}
|
|
5609
|
+
if (maxHeight && height > maxHeight) {
|
|
5610
|
+
const ratio = maxHeight / height;
|
|
5611
|
+
height = maxHeight;
|
|
5612
|
+
width = Math.round(width * ratio);
|
|
5613
|
+
}
|
|
5614
|
+
const canvas = document.createElement("canvas");
|
|
5615
|
+
canvas.width = width;
|
|
5616
|
+
canvas.height = height;
|
|
5617
|
+
const ctx = canvas.getContext("2d");
|
|
5618
|
+
ctx.drawImage(video, 0, 0, width, height);
|
|
5619
|
+
const dataURL = canvas.toDataURL("image/jpeg", quality);
|
|
5620
|
+
if (restoreState) {
|
|
5621
|
+
video.currentTime = originalTime;
|
|
5622
|
+
video.muted = originalMuted;
|
|
5623
|
+
if (wasPlaying && !video.paused) {
|
|
5624
|
+
video.play().catch(() => {
|
|
5625
|
+
});
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
resolve(dataURL);
|
|
5629
|
+
} catch (error) {
|
|
5630
|
+
if (restoreState) {
|
|
5631
|
+
video.currentTime = originalTime;
|
|
5632
|
+
video.muted = originalMuted;
|
|
5633
|
+
if (wasPlaying && !video.paused) {
|
|
5634
|
+
video.play().catch(() => {
|
|
5635
|
+
});
|
|
5636
|
+
}
|
|
5637
|
+
}
|
|
5638
|
+
resolve(null);
|
|
5639
|
+
}
|
|
5640
|
+
};
|
|
5641
|
+
const onSeeked = () => {
|
|
5642
|
+
video.removeEventListener("seeked", onSeeked);
|
|
5643
|
+
requestAnimationFrame(() => {
|
|
5644
|
+
requestAnimationFrame(captureFrame);
|
|
5645
|
+
});
|
|
5646
|
+
};
|
|
5647
|
+
const timeDiff = Math.abs(video.currentTime - time);
|
|
5648
|
+
if (timeDiff < 0.1 && video.readyState >= 2) {
|
|
5649
|
+
captureFrame();
|
|
5650
|
+
} else {
|
|
5651
|
+
video.addEventListener("seeked", onSeeked);
|
|
5652
|
+
video.currentTime = time;
|
|
5653
|
+
}
|
|
5654
|
+
});
|
|
5655
|
+
}
|
|
5656
|
+
|
|
5577
5657
|
// src/controls/ControlBar.js
|
|
5578
5658
|
var ControlBar = class {
|
|
5579
5659
|
constructor(player) {
|
|
@@ -6211,13 +6291,117 @@
|
|
|
6211
6291
|
this.controls.progressTooltip = DOMUtils.createElement("div", {
|
|
6212
6292
|
className: "".concat(this.player.options.classPrefix, "-progress-tooltip")
|
|
6213
6293
|
});
|
|
6294
|
+
this.controls.progressPreview = DOMUtils.createElement("div", {
|
|
6295
|
+
className: "".concat(this.player.options.classPrefix, "-progress-preview"),
|
|
6296
|
+
attributes: {
|
|
6297
|
+
"aria-hidden": "true"
|
|
6298
|
+
}
|
|
6299
|
+
});
|
|
6300
|
+
this.controls.progressTooltip.appendChild(this.controls.progressPreview);
|
|
6301
|
+
this.controls.progressTooltipTime = DOMUtils.createElement("div", {
|
|
6302
|
+
className: "".concat(this.player.options.classPrefix, "-progress-tooltip-time")
|
|
6303
|
+
});
|
|
6304
|
+
this.controls.progressTooltip.appendChild(this.controls.progressTooltipTime);
|
|
6214
6305
|
progressContainer.appendChild(this.controls.buffered);
|
|
6215
6306
|
progressContainer.appendChild(this.controls.played);
|
|
6216
6307
|
this.controls.played.appendChild(this.controls.progressHandle);
|
|
6217
6308
|
progressContainer.appendChild(this.controls.progressTooltip);
|
|
6218
6309
|
this.controls.progress = progressContainer;
|
|
6310
|
+
this.initPreviewThumbnail();
|
|
6219
6311
|
this.setupProgressBarEvents();
|
|
6220
6312
|
}
|
|
6313
|
+
/**
|
|
6314
|
+
* Initialize preview thumbnail functionality for HTML5 video
|
|
6315
|
+
*/
|
|
6316
|
+
initPreviewThumbnail() {
|
|
6317
|
+
this.previewThumbnailCache = /* @__PURE__ */ new Map();
|
|
6318
|
+
this.previewVideo = null;
|
|
6319
|
+
this.currentPreviewTime = null;
|
|
6320
|
+
this.previewThumbnailTimeout = null;
|
|
6321
|
+
this.previewSupported = false;
|
|
6322
|
+
const isVideo = this.player.element && this.player.element.tagName === "VIDEO";
|
|
6323
|
+
if (!isVideo) {
|
|
6324
|
+
return;
|
|
6325
|
+
}
|
|
6326
|
+
const renderer = this.player.renderer;
|
|
6327
|
+
const hasVideoMedia = renderer && renderer.media && renderer.media.tagName === "VIDEO";
|
|
6328
|
+
const isHTML5Renderer = renderer && (renderer.constructor.name === "HTML5Renderer" || renderer.constructor.name === "HLSRenderer" && hasVideoMedia);
|
|
6329
|
+
this.previewSupported = isHTML5Renderer && hasVideoMedia;
|
|
6330
|
+
if (this.previewSupported) {
|
|
6331
|
+
this.previewVideo = document.createElement("video");
|
|
6332
|
+
this.previewVideo.muted = true;
|
|
6333
|
+
this.previewVideo.preload = "metadata";
|
|
6334
|
+
this.previewVideo.style.position = "absolute";
|
|
6335
|
+
this.previewVideo.style.visibility = "hidden";
|
|
6336
|
+
this.previewVideo.style.width = "1px";
|
|
6337
|
+
this.previewVideo.style.height = "1px";
|
|
6338
|
+
this.previewVideo.style.top = "-9999px";
|
|
6339
|
+
const mainVideo = renderer.media || this.player.element;
|
|
6340
|
+
if (mainVideo.src) {
|
|
6341
|
+
this.previewVideo.src = mainVideo.src;
|
|
6342
|
+
} else {
|
|
6343
|
+
const source = mainVideo.querySelector("source");
|
|
6344
|
+
if (source) {
|
|
6345
|
+
this.previewVideo.src = source.src;
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
this.previewVideo.addEventListener("error", () => {
|
|
6349
|
+
this.player.log("Preview video failed to load", "warn");
|
|
6350
|
+
this.previewSupported = false;
|
|
6351
|
+
});
|
|
6352
|
+
if (this.player.container) {
|
|
6353
|
+
this.player.container.appendChild(this.previewVideo);
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
6356
|
+
}
|
|
6357
|
+
/**
|
|
6358
|
+
* Generate preview thumbnail for a specific time
|
|
6359
|
+
* @param {number} time - Time in seconds
|
|
6360
|
+
* @returns {Promise<string>} Data URL of the thumbnail
|
|
6361
|
+
*/
|
|
6362
|
+
async generatePreviewThumbnail(time) {
|
|
6363
|
+
if (!this.previewSupported || !this.previewVideo) {
|
|
6364
|
+
return null;
|
|
6365
|
+
}
|
|
6366
|
+
const cacheKey = Math.floor(time);
|
|
6367
|
+
if (this.previewThumbnailCache.has(cacheKey)) {
|
|
6368
|
+
return this.previewThumbnailCache.get(cacheKey);
|
|
6369
|
+
}
|
|
6370
|
+
const dataURL = await captureVideoFrame(this.previewVideo, time, {
|
|
6371
|
+
restoreState: false,
|
|
6372
|
+
quality: 0.8,
|
|
6373
|
+
maxWidth: 160,
|
|
6374
|
+
maxHeight: 90
|
|
6375
|
+
});
|
|
6376
|
+
if (dataURL) {
|
|
6377
|
+
if (this.previewThumbnailCache.size > 20) {
|
|
6378
|
+
const firstKey = this.previewThumbnailCache.keys().next().value;
|
|
6379
|
+
this.previewThumbnailCache.delete(firstKey);
|
|
6380
|
+
}
|
|
6381
|
+
this.previewThumbnailCache.set(cacheKey, dataURL);
|
|
6382
|
+
}
|
|
6383
|
+
return dataURL;
|
|
6384
|
+
}
|
|
6385
|
+
/**
|
|
6386
|
+
* Update preview thumbnail display
|
|
6387
|
+
* @param {number} time - Time in seconds
|
|
6388
|
+
*/
|
|
6389
|
+
async updatePreviewThumbnail(time) {
|
|
6390
|
+
if (!this.previewSupported) {
|
|
6391
|
+
return;
|
|
6392
|
+
}
|
|
6393
|
+
if (this.previewThumbnailTimeout) {
|
|
6394
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
6395
|
+
}
|
|
6396
|
+
this.previewThumbnailTimeout = setTimeout(async () => {
|
|
6397
|
+
const thumbnail = await this.generatePreviewThumbnail(time);
|
|
6398
|
+
if (thumbnail && this.controls.progressPreview) {
|
|
6399
|
+
this.controls.progressPreview.style.backgroundImage = "url(".concat(thumbnail, ")");
|
|
6400
|
+
this.controls.progressPreview.style.display = "block";
|
|
6401
|
+
}
|
|
6402
|
+
this.currentPreviewTime = time;
|
|
6403
|
+
}, 100);
|
|
6404
|
+
}
|
|
6221
6405
|
setupProgressBarEvents() {
|
|
6222
6406
|
const progress = this.controls.progress;
|
|
6223
6407
|
const updateProgress = (clientX) => {
|
|
@@ -6244,13 +6428,19 @@
|
|
|
6244
6428
|
progress.addEventListener("mousemove", (e) => {
|
|
6245
6429
|
if (!this.isDraggingProgress) {
|
|
6246
6430
|
const { time } = updateProgress(e.clientX);
|
|
6247
|
-
|
|
6248
|
-
|
|
6431
|
+
const rect = progress.getBoundingClientRect();
|
|
6432
|
+
const left = e.clientX - rect.left;
|
|
6433
|
+
this.controls.progressTooltipTime.textContent = TimeUtils.formatTime(time);
|
|
6434
|
+
this.controls.progressTooltip.style.left = "".concat(left, "px");
|
|
6249
6435
|
this.controls.progressTooltip.style.display = "block";
|
|
6436
|
+
this.updatePreviewThumbnail(time);
|
|
6250
6437
|
}
|
|
6251
6438
|
});
|
|
6252
6439
|
progress.addEventListener("mouseleave", () => {
|
|
6253
6440
|
this.controls.progressTooltip.style.display = "none";
|
|
6441
|
+
if (this.previewThumbnailTimeout) {
|
|
6442
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
6443
|
+
}
|
|
6254
6444
|
});
|
|
6255
6445
|
progress.addEventListener("keydown", (e) => {
|
|
6256
6446
|
if (e.key === "ArrowLeft") {
|
|
@@ -7997,6 +8187,20 @@
|
|
|
7997
8187
|
hide() {
|
|
7998
8188
|
this.element.style.display = "none";
|
|
7999
8189
|
}
|
|
8190
|
+
/**
|
|
8191
|
+
* Cleanup preview thumbnail resources
|
|
8192
|
+
*/
|
|
8193
|
+
cleanupPreviewThumbnail() {
|
|
8194
|
+
if (this.previewThumbnailTimeout) {
|
|
8195
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
8196
|
+
this.previewThumbnailTimeout = null;
|
|
8197
|
+
}
|
|
8198
|
+
if (this.previewVideo && this.previewVideo.parentNode) {
|
|
8199
|
+
this.previewVideo.parentNode.removeChild(this.previewVideo);
|
|
8200
|
+
this.previewVideo = null;
|
|
8201
|
+
}
|
|
8202
|
+
this.previewThumbnailCache.clear();
|
|
8203
|
+
}
|
|
8000
8204
|
destroy() {
|
|
8001
8205
|
if (this.hideTimeout) {
|
|
8002
8206
|
clearTimeout(this.hideTimeout);
|
|
@@ -8004,6 +8208,7 @@
|
|
|
8004
8208
|
if (this.overflowResizeObserver) {
|
|
8005
8209
|
this.overflowResizeObserver.disconnect();
|
|
8006
8210
|
}
|
|
8211
|
+
this.cleanupPreviewThumbnail();
|
|
8007
8212
|
if (this.element && this.element.parentNode) {
|
|
8008
8213
|
this.element.parentNode.removeChild(this.element);
|
|
8009
8214
|
}
|
|
@@ -10635,6 +10840,64 @@
|
|
|
10635
10840
|
return posterPath;
|
|
10636
10841
|
}
|
|
10637
10842
|
}
|
|
10843
|
+
/**
|
|
10844
|
+
* Generate a poster image from video frame at specified time
|
|
10845
|
+
* @param {number} time - Time in seconds (default: 10)
|
|
10846
|
+
* @returns {Promise<string|null>} Data URL of the poster image or null if failed
|
|
10847
|
+
*/
|
|
10848
|
+
async generatePosterFromVideo(time = 10) {
|
|
10849
|
+
if (this.element.tagName !== "VIDEO") {
|
|
10850
|
+
return null;
|
|
10851
|
+
}
|
|
10852
|
+
const renderer = this.renderer;
|
|
10853
|
+
if (!renderer || !renderer.media || renderer.media.tagName !== "VIDEO") {
|
|
10854
|
+
return null;
|
|
10855
|
+
}
|
|
10856
|
+
const video = renderer.media;
|
|
10857
|
+
if (!video.duration || video.duration < time) {
|
|
10858
|
+
time = Math.min(time, Math.max(1, video.duration * 0.1));
|
|
10859
|
+
}
|
|
10860
|
+
let videoToUse = video;
|
|
10861
|
+
if (this.controlBar && this.controlBar.previewVideo && this.controlBar.previewSupported) {
|
|
10862
|
+
videoToUse = this.controlBar.previewVideo;
|
|
10863
|
+
}
|
|
10864
|
+
const restoreState = videoToUse === video;
|
|
10865
|
+
return await captureVideoFrame(videoToUse, time, {
|
|
10866
|
+
restoreState,
|
|
10867
|
+
quality: 0.9
|
|
10868
|
+
});
|
|
10869
|
+
}
|
|
10870
|
+
/**
|
|
10871
|
+
* Auto-generate poster from video if none is provided
|
|
10872
|
+
*/
|
|
10873
|
+
async autoGeneratePoster() {
|
|
10874
|
+
const hasPoster = this.element.getAttribute("poster") || this.element.poster || this.options.poster;
|
|
10875
|
+
if (hasPoster) {
|
|
10876
|
+
return;
|
|
10877
|
+
}
|
|
10878
|
+
if (this.element.tagName !== "VIDEO") {
|
|
10879
|
+
return;
|
|
10880
|
+
}
|
|
10881
|
+
if (!this.state.duration || this.state.duration === 0) {
|
|
10882
|
+
await new Promise((resolve) => {
|
|
10883
|
+
const onLoadedMetadata = () => {
|
|
10884
|
+
this.element.removeEventListener("loadedmetadata", onLoadedMetadata);
|
|
10885
|
+
resolve();
|
|
10886
|
+
};
|
|
10887
|
+
if (this.element.readyState >= 1) {
|
|
10888
|
+
resolve();
|
|
10889
|
+
} else {
|
|
10890
|
+
this.element.addEventListener("loadedmetadata", onLoadedMetadata);
|
|
10891
|
+
}
|
|
10892
|
+
});
|
|
10893
|
+
}
|
|
10894
|
+
const posterDataURL = await this.generatePosterFromVideo(10);
|
|
10895
|
+
if (posterDataURL) {
|
|
10896
|
+
this.element.poster = posterDataURL;
|
|
10897
|
+
this.log("Auto-generated poster from video frame at 10 seconds", "info");
|
|
10898
|
+
this.showPosterOverlay();
|
|
10899
|
+
}
|
|
10900
|
+
}
|
|
10638
10901
|
showPosterOverlay() {
|
|
10639
10902
|
if (!this.videoWrapper || this.element.tagName !== "VIDEO") {
|
|
10640
10903
|
return;
|
|
@@ -10643,7 +10906,7 @@
|
|
|
10643
10906
|
if (!poster) {
|
|
10644
10907
|
return;
|
|
10645
10908
|
}
|
|
10646
|
-
const resolvedPoster = this.resolvePosterPath(poster);
|
|
10909
|
+
const resolvedPoster = poster.startsWith("data:") ? poster : this.resolvePosterPath(poster);
|
|
10647
10910
|
this.videoWrapper.style.setProperty("--vidply-poster-image", 'url("'.concat(resolvedPoster, '")'));
|
|
10648
10911
|
this.videoWrapper.classList.add("vidply-forced-poster");
|
|
10649
10912
|
if (this._isAudioContent && this.container) {
|