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.
@@ -32,6 +32,13 @@ export class HTML5Renderer {
32
32
  this.media.addEventListener('loadedmetadata', () => {
33
33
  this.player.state.duration = this.media.duration;
34
34
  this.player.emit('loadedmetadata');
35
+
36
+ // Auto-generate poster if none exists (for HTML5 video only)
37
+ if (this.media.tagName === 'VIDEO') {
38
+ this.player.autoGeneratePoster().catch(error => {
39
+ this.player.log('Failed to auto-generate poster:', error, 'warn');
40
+ });
41
+ }
35
42
  });
36
43
 
37
44
  this.media.addEventListener('play', () => {
@@ -793,11 +793,30 @@
793
793
  color: var(--vidply-white);
794
794
  display: none;
795
795
  font-size: 0.75rem;
796
- padding: 0.25rem 0.5rem;
796
+ padding: 0;
797
797
  pointer-events: none;
798
798
  position: absolute;
799
799
  transform: translateX(-50%);
800
800
  white-space: nowrap;
801
+ overflow: hidden;
802
+ }
803
+
804
+ .vidply-progress-preview {
805
+ background-size: cover;
806
+ background-position: center;
807
+ border-radius: 0.25rem 0.25rem 0 0;
808
+ display: none;
809
+ height: 5rem;
810
+ width: 8.888888889rem; /* 160px at 18px base */
811
+ min-width: 8.888888889rem;
812
+ }
813
+
814
+ .vidply-progress-tooltip-time {
815
+ background: var(--vidply-black-90);
816
+ border-radius: 0 0 0.25rem 0.25rem;
817
+ padding: 0.25rem 0.5rem;
818
+ text-align: center;
819
+ width: 100%;
801
820
  }
802
821
 
803
822
  /* Button Container */
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Utility for capturing frames from video elements
3
+ */
4
+
5
+ /**
6
+ * Capture a frame from a video element at a specific time
7
+ * @param {HTMLVideoElement} video - Video element to capture from
8
+ * @param {number} time - Time in seconds to capture
9
+ * @param {Object} options - Options for frame capture
10
+ * @param {boolean} [options.restoreState=true] - Whether to restore video state after capture
11
+ * @param {number} [options.quality=0.9] - JPEG quality (0-1)
12
+ * @param {number} [options.maxWidth] - Maximum width for thumbnail
13
+ * @param {number} [options.maxHeight] - Maximum height for thumbnail
14
+ * @returns {Promise<string|null>} Data URL of the captured frame or null if failed
15
+ */
16
+ export async function captureVideoFrame(video, time, options = {}) {
17
+ if (!video || video.tagName !== 'VIDEO') {
18
+ return null;
19
+ }
20
+
21
+ const {
22
+ restoreState = true,
23
+ quality = 0.9,
24
+ maxWidth,
25
+ maxHeight
26
+ } = options;
27
+
28
+ // Save original state if we need to restore it
29
+ const wasPlaying = !video.paused;
30
+ const originalTime = video.currentTime;
31
+ const originalMuted = video.muted;
32
+
33
+ // Ensure video is muted during capture to avoid audio playback
34
+ if (restoreState) {
35
+ video.muted = true;
36
+ }
37
+
38
+ return new Promise((resolve) => {
39
+ const captureFrame = () => {
40
+ try {
41
+ // Get video dimensions
42
+ let width = video.videoWidth || 640;
43
+ let height = video.videoHeight || 360;
44
+
45
+ // Scale down if max dimensions specified
46
+ if (maxWidth && width > maxWidth) {
47
+ const ratio = maxWidth / width;
48
+ width = maxWidth;
49
+ height = Math.round(height * ratio);
50
+ }
51
+ if (maxHeight && height > maxHeight) {
52
+ const ratio = maxHeight / height;
53
+ height = maxHeight;
54
+ width = Math.round(width * ratio);
55
+ }
56
+
57
+ // Create canvas to capture frame
58
+ const canvas = document.createElement('canvas');
59
+ canvas.width = width;
60
+ canvas.height = height;
61
+
62
+ const ctx = canvas.getContext('2d');
63
+ ctx.drawImage(video, 0, 0, width, height);
64
+
65
+ const dataURL = canvas.toDataURL('image/jpeg', quality);
66
+
67
+ // Restore original state if needed
68
+ if (restoreState) {
69
+ video.currentTime = originalTime;
70
+ video.muted = originalMuted;
71
+ if (wasPlaying && !video.paused) {
72
+ video.play().catch(() => {});
73
+ }
74
+ }
75
+
76
+ resolve(dataURL);
77
+ } catch (error) {
78
+ // Restore original state on error
79
+ if (restoreState) {
80
+ video.currentTime = originalTime;
81
+ video.muted = originalMuted;
82
+ if (wasPlaying && !video.paused) {
83
+ video.play().catch(() => {});
84
+ }
85
+ }
86
+ resolve(null);
87
+ }
88
+ };
89
+
90
+ const onSeeked = () => {
91
+ video.removeEventListener('seeked', onSeeked);
92
+ // Wait for frame to be ready (double RAF for better frame quality)
93
+ requestAnimationFrame(() => {
94
+ requestAnimationFrame(captureFrame);
95
+ });
96
+ };
97
+
98
+ // Check if video is already at the right time and ready
99
+ const timeDiff = Math.abs(video.currentTime - time);
100
+ if (timeDiff < 0.1 && video.readyState >= 2) {
101
+ // Video is already at the right position, capture immediately
102
+ captureFrame();
103
+ } else {
104
+ // Seek to the desired time
105
+ video.addEventListener('seeked', onSeeked);
106
+ video.currentTime = time;
107
+ }
108
+ });
109
+ }
110
+