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/dev/vidply.esm.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import {
|
|
7
7
|
HTML5Renderer
|
|
8
|
-
} from "./vidply.chunk-
|
|
8
|
+
} from "./vidply.chunk-W2LSBD6Y.js";
|
|
9
9
|
import {
|
|
10
10
|
DOMUtils,
|
|
11
11
|
DraggableResizable,
|
|
@@ -91,6 +91,81 @@ function rafWithTimeout(callback, timeout = 100) {
|
|
|
91
91
|
setTimeout(execute, timeout);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// src/utils/VideoFrameCapture.js
|
|
95
|
+
async function captureVideoFrame(video, time, options = {}) {
|
|
96
|
+
if (!video || video.tagName !== "VIDEO") {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const {
|
|
100
|
+
restoreState = true,
|
|
101
|
+
quality = 0.9,
|
|
102
|
+
maxWidth,
|
|
103
|
+
maxHeight
|
|
104
|
+
} = options;
|
|
105
|
+
const wasPlaying = !video.paused;
|
|
106
|
+
const originalTime = video.currentTime;
|
|
107
|
+
const originalMuted = video.muted;
|
|
108
|
+
if (restoreState) {
|
|
109
|
+
video.muted = true;
|
|
110
|
+
}
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const captureFrame = () => {
|
|
113
|
+
try {
|
|
114
|
+
let width = video.videoWidth || 640;
|
|
115
|
+
let height = video.videoHeight || 360;
|
|
116
|
+
if (maxWidth && width > maxWidth) {
|
|
117
|
+
const ratio = maxWidth / width;
|
|
118
|
+
width = maxWidth;
|
|
119
|
+
height = Math.round(height * ratio);
|
|
120
|
+
}
|
|
121
|
+
if (maxHeight && height > maxHeight) {
|
|
122
|
+
const ratio = maxHeight / height;
|
|
123
|
+
height = maxHeight;
|
|
124
|
+
width = Math.round(width * ratio);
|
|
125
|
+
}
|
|
126
|
+
const canvas = document.createElement("canvas");
|
|
127
|
+
canvas.width = width;
|
|
128
|
+
canvas.height = height;
|
|
129
|
+
const ctx = canvas.getContext("2d");
|
|
130
|
+
ctx.drawImage(video, 0, 0, width, height);
|
|
131
|
+
const dataURL = canvas.toDataURL("image/jpeg", quality);
|
|
132
|
+
if (restoreState) {
|
|
133
|
+
video.currentTime = originalTime;
|
|
134
|
+
video.muted = originalMuted;
|
|
135
|
+
if (wasPlaying && !video.paused) {
|
|
136
|
+
video.play().catch(() => {
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
resolve(dataURL);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (restoreState) {
|
|
143
|
+
video.currentTime = originalTime;
|
|
144
|
+
video.muted = originalMuted;
|
|
145
|
+
if (wasPlaying && !video.paused) {
|
|
146
|
+
video.play().catch(() => {
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
resolve(null);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const onSeeked = () => {
|
|
154
|
+
video.removeEventListener("seeked", onSeeked);
|
|
155
|
+
requestAnimationFrame(() => {
|
|
156
|
+
requestAnimationFrame(captureFrame);
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
const timeDiff = Math.abs(video.currentTime - time);
|
|
160
|
+
if (timeDiff < 0.1 && video.readyState >= 2) {
|
|
161
|
+
captureFrame();
|
|
162
|
+
} else {
|
|
163
|
+
video.addEventListener("seeked", onSeeked);
|
|
164
|
+
video.currentTime = time;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
94
169
|
// src/controls/ControlBar.js
|
|
95
170
|
var ControlBar = class {
|
|
96
171
|
constructor(player) {
|
|
@@ -727,13 +802,117 @@ var ControlBar = class {
|
|
|
727
802
|
this.controls.progressTooltip = DOMUtils.createElement("div", {
|
|
728
803
|
className: `${this.player.options.classPrefix}-progress-tooltip`
|
|
729
804
|
});
|
|
805
|
+
this.controls.progressPreview = DOMUtils.createElement("div", {
|
|
806
|
+
className: `${this.player.options.classPrefix}-progress-preview`,
|
|
807
|
+
attributes: {
|
|
808
|
+
"aria-hidden": "true"
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
this.controls.progressTooltip.appendChild(this.controls.progressPreview);
|
|
812
|
+
this.controls.progressTooltipTime = DOMUtils.createElement("div", {
|
|
813
|
+
className: `${this.player.options.classPrefix}-progress-tooltip-time`
|
|
814
|
+
});
|
|
815
|
+
this.controls.progressTooltip.appendChild(this.controls.progressTooltipTime);
|
|
730
816
|
progressContainer.appendChild(this.controls.buffered);
|
|
731
817
|
progressContainer.appendChild(this.controls.played);
|
|
732
818
|
this.controls.played.appendChild(this.controls.progressHandle);
|
|
733
819
|
progressContainer.appendChild(this.controls.progressTooltip);
|
|
734
820
|
this.controls.progress = progressContainer;
|
|
821
|
+
this.initPreviewThumbnail();
|
|
735
822
|
this.setupProgressBarEvents();
|
|
736
823
|
}
|
|
824
|
+
/**
|
|
825
|
+
* Initialize preview thumbnail functionality for HTML5 video
|
|
826
|
+
*/
|
|
827
|
+
initPreviewThumbnail() {
|
|
828
|
+
this.previewThumbnailCache = /* @__PURE__ */ new Map();
|
|
829
|
+
this.previewVideo = null;
|
|
830
|
+
this.currentPreviewTime = null;
|
|
831
|
+
this.previewThumbnailTimeout = null;
|
|
832
|
+
this.previewSupported = false;
|
|
833
|
+
const isVideo = this.player.element && this.player.element.tagName === "VIDEO";
|
|
834
|
+
if (!isVideo) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
const renderer = this.player.renderer;
|
|
838
|
+
const hasVideoMedia = renderer && renderer.media && renderer.media.tagName === "VIDEO";
|
|
839
|
+
const isHTML5Renderer = renderer && (renderer.constructor.name === "HTML5Renderer" || renderer.constructor.name === "HLSRenderer" && hasVideoMedia);
|
|
840
|
+
this.previewSupported = isHTML5Renderer && hasVideoMedia;
|
|
841
|
+
if (this.previewSupported) {
|
|
842
|
+
this.previewVideo = document.createElement("video");
|
|
843
|
+
this.previewVideo.muted = true;
|
|
844
|
+
this.previewVideo.preload = "metadata";
|
|
845
|
+
this.previewVideo.style.position = "absolute";
|
|
846
|
+
this.previewVideo.style.visibility = "hidden";
|
|
847
|
+
this.previewVideo.style.width = "1px";
|
|
848
|
+
this.previewVideo.style.height = "1px";
|
|
849
|
+
this.previewVideo.style.top = "-9999px";
|
|
850
|
+
const mainVideo = renderer.media || this.player.element;
|
|
851
|
+
if (mainVideo.src) {
|
|
852
|
+
this.previewVideo.src = mainVideo.src;
|
|
853
|
+
} else {
|
|
854
|
+
const source = mainVideo.querySelector("source");
|
|
855
|
+
if (source) {
|
|
856
|
+
this.previewVideo.src = source.src;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
this.previewVideo.addEventListener("error", () => {
|
|
860
|
+
this.player.log("Preview video failed to load", "warn");
|
|
861
|
+
this.previewSupported = false;
|
|
862
|
+
});
|
|
863
|
+
if (this.player.container) {
|
|
864
|
+
this.player.container.appendChild(this.previewVideo);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Generate preview thumbnail for a specific time
|
|
870
|
+
* @param {number} time - Time in seconds
|
|
871
|
+
* @returns {Promise<string>} Data URL of the thumbnail
|
|
872
|
+
*/
|
|
873
|
+
async generatePreviewThumbnail(time) {
|
|
874
|
+
if (!this.previewSupported || !this.previewVideo) {
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
const cacheKey = Math.floor(time);
|
|
878
|
+
if (this.previewThumbnailCache.has(cacheKey)) {
|
|
879
|
+
return this.previewThumbnailCache.get(cacheKey);
|
|
880
|
+
}
|
|
881
|
+
const dataURL = await captureVideoFrame(this.previewVideo, time, {
|
|
882
|
+
restoreState: false,
|
|
883
|
+
quality: 0.8,
|
|
884
|
+
maxWidth: 160,
|
|
885
|
+
maxHeight: 90
|
|
886
|
+
});
|
|
887
|
+
if (dataURL) {
|
|
888
|
+
if (this.previewThumbnailCache.size > 20) {
|
|
889
|
+
const firstKey = this.previewThumbnailCache.keys().next().value;
|
|
890
|
+
this.previewThumbnailCache.delete(firstKey);
|
|
891
|
+
}
|
|
892
|
+
this.previewThumbnailCache.set(cacheKey, dataURL);
|
|
893
|
+
}
|
|
894
|
+
return dataURL;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Update preview thumbnail display
|
|
898
|
+
* @param {number} time - Time in seconds
|
|
899
|
+
*/
|
|
900
|
+
async updatePreviewThumbnail(time) {
|
|
901
|
+
if (!this.previewSupported) {
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
if (this.previewThumbnailTimeout) {
|
|
905
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
906
|
+
}
|
|
907
|
+
this.previewThumbnailTimeout = setTimeout(async () => {
|
|
908
|
+
const thumbnail = await this.generatePreviewThumbnail(time);
|
|
909
|
+
if (thumbnail && this.controls.progressPreview) {
|
|
910
|
+
this.controls.progressPreview.style.backgroundImage = `url(${thumbnail})`;
|
|
911
|
+
this.controls.progressPreview.style.display = "block";
|
|
912
|
+
}
|
|
913
|
+
this.currentPreviewTime = time;
|
|
914
|
+
}, 100);
|
|
915
|
+
}
|
|
737
916
|
setupProgressBarEvents() {
|
|
738
917
|
const progress = this.controls.progress;
|
|
739
918
|
const updateProgress = (clientX) => {
|
|
@@ -760,13 +939,19 @@ var ControlBar = class {
|
|
|
760
939
|
progress.addEventListener("mousemove", (e) => {
|
|
761
940
|
if (!this.isDraggingProgress) {
|
|
762
941
|
const { time } = updateProgress(e.clientX);
|
|
763
|
-
|
|
764
|
-
|
|
942
|
+
const rect = progress.getBoundingClientRect();
|
|
943
|
+
const left = e.clientX - rect.left;
|
|
944
|
+
this.controls.progressTooltipTime.textContent = TimeUtils.formatTime(time);
|
|
945
|
+
this.controls.progressTooltip.style.left = `${left}px`;
|
|
765
946
|
this.controls.progressTooltip.style.display = "block";
|
|
947
|
+
this.updatePreviewThumbnail(time);
|
|
766
948
|
}
|
|
767
949
|
});
|
|
768
950
|
progress.addEventListener("mouseleave", () => {
|
|
769
951
|
this.controls.progressTooltip.style.display = "none";
|
|
952
|
+
if (this.previewThumbnailTimeout) {
|
|
953
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
954
|
+
}
|
|
770
955
|
});
|
|
771
956
|
progress.addEventListener("keydown", (e) => {
|
|
772
957
|
if (e.key === "ArrowLeft") {
|
|
@@ -2513,6 +2698,20 @@ var ControlBar = class {
|
|
|
2513
2698
|
hide() {
|
|
2514
2699
|
this.element.style.display = "none";
|
|
2515
2700
|
}
|
|
2701
|
+
/**
|
|
2702
|
+
* Cleanup preview thumbnail resources
|
|
2703
|
+
*/
|
|
2704
|
+
cleanupPreviewThumbnail() {
|
|
2705
|
+
if (this.previewThumbnailTimeout) {
|
|
2706
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
2707
|
+
this.previewThumbnailTimeout = null;
|
|
2708
|
+
}
|
|
2709
|
+
if (this.previewVideo && this.previewVideo.parentNode) {
|
|
2710
|
+
this.previewVideo.parentNode.removeChild(this.previewVideo);
|
|
2711
|
+
this.previewVideo = null;
|
|
2712
|
+
}
|
|
2713
|
+
this.previewThumbnailCache.clear();
|
|
2714
|
+
}
|
|
2516
2715
|
destroy() {
|
|
2517
2716
|
if (this.hideTimeout) {
|
|
2518
2717
|
clearTimeout(this.hideTimeout);
|
|
@@ -2520,6 +2719,7 @@ var ControlBar = class {
|
|
|
2520
2719
|
if (this.overflowResizeObserver) {
|
|
2521
2720
|
this.overflowResizeObserver.disconnect();
|
|
2522
2721
|
}
|
|
2722
|
+
this.cleanupPreviewThumbnail();
|
|
2523
2723
|
if (this.element && this.element.parentNode) {
|
|
2524
2724
|
this.element.parentNode.removeChild(this.element);
|
|
2525
2725
|
}
|
|
@@ -5021,7 +5221,7 @@ var Player = class _Player extends EventEmitter {
|
|
|
5021
5221
|
const module = await import("./vidply.VimeoRenderer-VPH4RNES.js");
|
|
5022
5222
|
rendererClass = module.VimeoRenderer || module.default;
|
|
5023
5223
|
} else if (src.includes(".m3u8")) {
|
|
5024
|
-
const module = await import("./vidply.HLSRenderer-
|
|
5224
|
+
const module = await import("./vidply.HLSRenderer-UMPUDSYL.js");
|
|
5025
5225
|
rendererClass = module.HLSRenderer || module.default;
|
|
5026
5226
|
} else if (src.includes("soundcloud.com") || src.includes("api.soundcloud.com")) {
|
|
5027
5227
|
const module = await import("./vidply.SoundCloudRenderer-CD7VJKNS.js");
|
|
@@ -5126,6 +5326,64 @@ var Player = class _Player extends EventEmitter {
|
|
|
5126
5326
|
return posterPath;
|
|
5127
5327
|
}
|
|
5128
5328
|
}
|
|
5329
|
+
/**
|
|
5330
|
+
* Generate a poster image from video frame at specified time
|
|
5331
|
+
* @param {number} time - Time in seconds (default: 10)
|
|
5332
|
+
* @returns {Promise<string|null>} Data URL of the poster image or null if failed
|
|
5333
|
+
*/
|
|
5334
|
+
async generatePosterFromVideo(time = 10) {
|
|
5335
|
+
if (this.element.tagName !== "VIDEO") {
|
|
5336
|
+
return null;
|
|
5337
|
+
}
|
|
5338
|
+
const renderer = this.renderer;
|
|
5339
|
+
if (!renderer || !renderer.media || renderer.media.tagName !== "VIDEO") {
|
|
5340
|
+
return null;
|
|
5341
|
+
}
|
|
5342
|
+
const video = renderer.media;
|
|
5343
|
+
if (!video.duration || video.duration < time) {
|
|
5344
|
+
time = Math.min(time, Math.max(1, video.duration * 0.1));
|
|
5345
|
+
}
|
|
5346
|
+
let videoToUse = video;
|
|
5347
|
+
if (this.controlBar && this.controlBar.previewVideo && this.controlBar.previewSupported) {
|
|
5348
|
+
videoToUse = this.controlBar.previewVideo;
|
|
5349
|
+
}
|
|
5350
|
+
const restoreState = videoToUse === video;
|
|
5351
|
+
return await captureVideoFrame(videoToUse, time, {
|
|
5352
|
+
restoreState,
|
|
5353
|
+
quality: 0.9
|
|
5354
|
+
});
|
|
5355
|
+
}
|
|
5356
|
+
/**
|
|
5357
|
+
* Auto-generate poster from video if none is provided
|
|
5358
|
+
*/
|
|
5359
|
+
async autoGeneratePoster() {
|
|
5360
|
+
const hasPoster = this.element.getAttribute("poster") || this.element.poster || this.options.poster;
|
|
5361
|
+
if (hasPoster) {
|
|
5362
|
+
return;
|
|
5363
|
+
}
|
|
5364
|
+
if (this.element.tagName !== "VIDEO") {
|
|
5365
|
+
return;
|
|
5366
|
+
}
|
|
5367
|
+
if (!this.state.duration || this.state.duration === 0) {
|
|
5368
|
+
await new Promise((resolve) => {
|
|
5369
|
+
const onLoadedMetadata = () => {
|
|
5370
|
+
this.element.removeEventListener("loadedmetadata", onLoadedMetadata);
|
|
5371
|
+
resolve();
|
|
5372
|
+
};
|
|
5373
|
+
if (this.element.readyState >= 1) {
|
|
5374
|
+
resolve();
|
|
5375
|
+
} else {
|
|
5376
|
+
this.element.addEventListener("loadedmetadata", onLoadedMetadata);
|
|
5377
|
+
}
|
|
5378
|
+
});
|
|
5379
|
+
}
|
|
5380
|
+
const posterDataURL = await this.generatePosterFromVideo(10);
|
|
5381
|
+
if (posterDataURL) {
|
|
5382
|
+
this.element.poster = posterDataURL;
|
|
5383
|
+
this.log("Auto-generated poster from video frame at 10 seconds", "info");
|
|
5384
|
+
this.showPosterOverlay();
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
5129
5387
|
showPosterOverlay() {
|
|
5130
5388
|
if (!this.videoWrapper || this.element.tagName !== "VIDEO") {
|
|
5131
5389
|
return;
|
|
@@ -5134,7 +5392,7 @@ var Player = class _Player extends EventEmitter {
|
|
|
5134
5392
|
if (!poster) {
|
|
5135
5393
|
return;
|
|
5136
5394
|
}
|
|
5137
|
-
const resolvedPoster = this.resolvePosterPath(poster);
|
|
5395
|
+
const resolvedPoster = poster.startsWith("data:") ? poster : this.resolvePosterPath(poster);
|
|
5138
5396
|
this.videoWrapper.style.setProperty("--vidply-poster-image", `url("${resolvedPoster}")`);
|
|
5139
5397
|
this.videoWrapper.classList.add("vidply-forced-poster");
|
|
5140
5398
|
if (this._isAudioContent && this.container) {
|