vidply 1.0.5 → 1.0.7

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.
@@ -1,298 +1,298 @@
1
- /**
2
- * HTML5 Media Renderer
3
- */
4
-
5
- export class HTML5Renderer {
6
- constructor(player) {
7
- this.player = player;
8
- this.media = player.element;
9
- }
10
-
11
- async init() {
12
- // Hide native controls
13
- this.media.controls = false;
14
- this.media.removeAttribute('controls');
15
-
16
- this.attachEvents();
17
-
18
- // Set preload
19
- this.media.preload = this.player.options.preload;
20
-
21
- // Load media
22
- this.media.load();
23
- }
24
-
25
- attachEvents() {
26
- // Playback events
27
- this.media.addEventListener('loadedmetadata', () => {
28
- this.player.state.duration = this.media.duration;
29
- this.player.emit('loadedmetadata');
30
- });
31
-
32
- this.media.addEventListener('play', () => {
33
- this.player.state.playing = true;
34
- this.player.state.paused = false;
35
- this.player.state.ended = false;
36
- this.player.emit('play');
37
-
38
- if (this.player.options.onPlay) {
39
- this.player.options.onPlay.call(this.player);
40
- }
41
-
42
- // Pause other players if enabled
43
- if (this.player.options.pauseOthersOnPlay) {
44
- this.pauseOtherPlayers();
45
- }
46
- });
47
-
48
- this.media.addEventListener('pause', () => {
49
- this.player.state.playing = false;
50
- this.player.state.paused = true;
51
- this.player.emit('pause');
52
-
53
- if (this.player.options.onPause) {
54
- this.player.options.onPause.call(this.player);
55
- }
56
- });
57
-
58
- this.media.addEventListener('ended', () => {
59
- this.player.state.playing = false;
60
- this.player.state.paused = true;
61
- this.player.state.ended = true;
62
- this.player.emit('ended');
63
-
64
- if (this.player.options.onEnded) {
65
- this.player.options.onEnded.call(this.player);
66
- }
67
-
68
- // Handle loop
69
- if (this.player.options.loop) {
70
- this.player.seek(0);
71
- this.player.play();
72
- }
73
- });
74
-
75
- this.media.addEventListener('timeupdate', () => {
76
- this.player.state.currentTime = this.media.currentTime;
77
- this.player.emit('timeupdate', this.media.currentTime);
78
-
79
- if (this.player.options.onTimeUpdate) {
80
- this.player.options.onTimeUpdate.call(this.player, this.media.currentTime);
81
- }
82
- });
83
-
84
- this.media.addEventListener('volumechange', () => {
85
- this.player.state.volume = this.media.volume;
86
- this.player.state.muted = this.media.muted;
87
- this.player.emit('volumechange', this.media.volume);
88
-
89
- if (this.player.options.onVolumeChange) {
90
- this.player.options.onVolumeChange.call(this.player, this.media.volume);
91
- }
92
- });
93
-
94
- this.media.addEventListener('seeking', () => {
95
- this.player.state.seeking = true;
96
- this.player.emit('seeking');
97
- });
98
-
99
- this.media.addEventListener('seeked', () => {
100
- this.player.state.seeking = false;
101
- this.player.emit('seeked');
102
- });
103
-
104
- this.media.addEventListener('waiting', () => {
105
- this.player.state.buffering = true;
106
- this.player.emit('waiting');
107
- });
108
-
109
- this.media.addEventListener('canplay', () => {
110
- this.player.state.buffering = false;
111
- this.player.emit('canplay');
112
- });
113
-
114
- this.media.addEventListener('progress', () => {
115
- if (this.media.buffered.length > 0) {
116
- const buffered = this.media.buffered.end(this.media.buffered.length - 1);
117
- this.player.emit('progress', buffered);
118
- }
119
- });
120
-
121
- this.media.addEventListener('error', (e) => {
122
- this.player.handleError(this.media.error);
123
- });
124
-
125
- this.media.addEventListener('ratechange', () => {
126
- this.player.state.playbackSpeed = this.media.playbackRate;
127
- this.player.emit('ratechange', this.media.playbackRate);
128
- });
129
- }
130
-
131
- pauseOtherPlayers() {
132
- // Pause other VidPly instances
133
- const allPlayers = document.querySelectorAll('.vidply-player');
134
- allPlayers.forEach(playerEl => {
135
- if (playerEl !== this.player.container) {
136
- const video = playerEl.querySelector('video, audio');
137
- if (video && !video.paused) {
138
- video.pause();
139
- }
140
- }
141
- });
142
- }
143
-
144
- play() {
145
- const promise = this.media.play();
146
-
147
- if (promise !== undefined) {
148
- promise.catch(error => {
149
- this.player.log('Play failed:', error, 'warn');
150
-
151
- // If autoplay failed, try muted autoplay
152
- if (this.player.options.autoplay && !this.player.state.muted) {
153
- this.player.log('Retrying play with muted audio', 'info');
154
- this.media.muted = true;
155
- this.media.play().catch(err => {
156
- this.player.handleError(err);
157
- });
158
- }
159
- });
160
- }
161
- }
162
-
163
- pause() {
164
- this.media.pause();
165
- }
166
-
167
- seek(time) {
168
- this.media.currentTime = time;
169
- }
170
-
171
- setVolume(volume) {
172
- this.media.volume = volume;
173
- }
174
-
175
- setMuted(muted) {
176
- this.media.muted = muted;
177
- }
178
-
179
- setPlaybackSpeed(speed) {
180
- this.media.playbackRate = speed;
181
- }
182
-
183
- /**
184
- * Get available quality levels from source elements
185
- * @returns {Array} Array of quality objects with index, height, width, and src
186
- */
187
- getQualities() {
188
- const sources = Array.from(this.media.querySelectorAll('source'));
189
-
190
- if (sources.length <= 1) {
191
- return [];
192
- }
193
-
194
- return sources.map((source, index) => {
195
- // Try to extract quality from data attributes or label
196
- const label = source.getAttribute('data-quality') || source.getAttribute('label') || '';
197
- const height = source.getAttribute('data-height') || this.extractHeightFromLabel(label);
198
- const width = source.getAttribute('data-width') || '';
199
-
200
- return {
201
- index,
202
- height: height ? parseInt(height) : 0,
203
- width: width ? parseInt(width) : 0,
204
- src: source.src,
205
- type: source.type,
206
- name: label || (height ? `${height}p` : `Quality ${index + 1}`)
207
- };
208
- }).filter(q => q.height > 0); // Only return qualities with valid height
209
- }
210
-
211
- /**
212
- * Extract height from quality label (e.g., "1080p" -> 1080)
213
- * @param {string} label
214
- * @returns {number}
215
- */
216
- extractHeightFromLabel(label) {
217
- const match = label.match(/(\d+)p/i);
218
- return match ? parseInt(match[1]) : 0;
219
- }
220
-
221
- /**
222
- * Switch to a specific quality level
223
- * @param {number} qualityIndex - Index of the quality level (-1 for auto, not applicable for HTML5)
224
- */
225
- switchQuality(qualityIndex) {
226
- const qualities = this.getQualities();
227
-
228
- if (qualityIndex < 0 || qualityIndex >= qualities.length) {
229
- this.player.log('Invalid quality index', 'warn');
230
- return;
231
- }
232
-
233
- const quality = qualities[qualityIndex];
234
- const currentTime = this.media.currentTime;
235
- const wasPlaying = !this.media.paused;
236
-
237
- // Store the current source for comparison
238
- const currentSrc = this.media.currentSrc;
239
-
240
- // Don't switch if already at this quality
241
- if (currentSrc === quality.src) {
242
- this.player.log('Already at this quality level', 'info');
243
- return;
244
- }
245
-
246
- this.player.log(`Switching to quality: ${quality.name}`, 'info');
247
-
248
- // Update the src
249
- this.media.src = quality.src;
250
-
251
- // Wait for the new source to load, then restore playback state
252
- const onLoadedMetadata = () => {
253
- this.media.removeEventListener('loadedmetadata', onLoadedMetadata);
254
-
255
- // Restore playback position
256
- this.media.currentTime = currentTime;
257
-
258
- // Resume playback if it was playing
259
- if (wasPlaying) {
260
- this.media.play().catch(err => {
261
- this.player.log('Failed to resume playback after quality switch', 'warn');
262
- });
263
- }
264
-
265
- // Emit quality change event
266
- this.player.emit('qualitychange', { quality: quality.name, index: qualityIndex });
267
- };
268
-
269
- this.media.addEventListener('loadedmetadata', onLoadedMetadata);
270
- this.media.load();
271
- }
272
-
273
- /**
274
- * Get current quality index
275
- * @returns {number}
276
- */
277
- getCurrentQuality() {
278
- const qualities = this.getQualities();
279
- const currentSrc = this.media.currentSrc;
280
-
281
- for (let i = 0; i < qualities.length; i++) {
282
- if (qualities[i].src === currentSrc) {
283
- return i;
284
- }
285
- }
286
-
287
- return 0; // Default to first quality if not found
288
- }
289
-
290
- destroy() {
291
- // Remove event listeners
292
- this.media.removeEventListener('loadedmetadata', () => {});
293
- this.media.removeEventListener('play', () => {});
294
- this.media.removeEventListener('pause', () => {});
295
- // ... (other listeners would be removed in a real implementation)
296
- }
297
- }
298
-
1
+ /**
2
+ * HTML5 Media Renderer
3
+ */
4
+
5
+ export class HTML5Renderer {
6
+ constructor(player) {
7
+ this.player = player;
8
+ this.media = player.element;
9
+ }
10
+
11
+ async init() {
12
+ // Hide native controls
13
+ this.media.controls = false;
14
+ this.media.removeAttribute('controls');
15
+
16
+ this.attachEvents();
17
+
18
+ // Set preload
19
+ this.media.preload = this.player.options.preload;
20
+
21
+ // Load media
22
+ this.media.load();
23
+ }
24
+
25
+ attachEvents() {
26
+ // Playback events
27
+ this.media.addEventListener('loadedmetadata', () => {
28
+ this.player.state.duration = this.media.duration;
29
+ this.player.emit('loadedmetadata');
30
+ });
31
+
32
+ this.media.addEventListener('play', () => {
33
+ this.player.state.playing = true;
34
+ this.player.state.paused = false;
35
+ this.player.state.ended = false;
36
+ this.player.emit('play');
37
+
38
+ if (this.player.options.onPlay) {
39
+ this.player.options.onPlay.call(this.player);
40
+ }
41
+
42
+ // Pause other players if enabled
43
+ if (this.player.options.pauseOthersOnPlay) {
44
+ this.pauseOtherPlayers();
45
+ }
46
+ });
47
+
48
+ this.media.addEventListener('pause', () => {
49
+ this.player.state.playing = false;
50
+ this.player.state.paused = true;
51
+ this.player.emit('pause');
52
+
53
+ if (this.player.options.onPause) {
54
+ this.player.options.onPause.call(this.player);
55
+ }
56
+ });
57
+
58
+ this.media.addEventListener('ended', () => {
59
+ this.player.state.playing = false;
60
+ this.player.state.paused = true;
61
+ this.player.state.ended = true;
62
+ this.player.emit('ended');
63
+
64
+ if (this.player.options.onEnded) {
65
+ this.player.options.onEnded.call(this.player);
66
+ }
67
+
68
+ // Handle loop
69
+ if (this.player.options.loop) {
70
+ this.player.seek(0);
71
+ this.player.play();
72
+ }
73
+ });
74
+
75
+ this.media.addEventListener('timeupdate', () => {
76
+ this.player.state.currentTime = this.media.currentTime;
77
+ this.player.emit('timeupdate', this.media.currentTime);
78
+
79
+ if (this.player.options.onTimeUpdate) {
80
+ this.player.options.onTimeUpdate.call(this.player, this.media.currentTime);
81
+ }
82
+ });
83
+
84
+ this.media.addEventListener('volumechange', () => {
85
+ this.player.state.volume = this.media.volume;
86
+ this.player.state.muted = this.media.muted;
87
+ this.player.emit('volumechange', this.media.volume);
88
+
89
+ if (this.player.options.onVolumeChange) {
90
+ this.player.options.onVolumeChange.call(this.player, this.media.volume);
91
+ }
92
+ });
93
+
94
+ this.media.addEventListener('seeking', () => {
95
+ this.player.state.seeking = true;
96
+ this.player.emit('seeking');
97
+ });
98
+
99
+ this.media.addEventListener('seeked', () => {
100
+ this.player.state.seeking = false;
101
+ this.player.emit('seeked');
102
+ });
103
+
104
+ this.media.addEventListener('waiting', () => {
105
+ this.player.state.buffering = true;
106
+ this.player.emit('waiting');
107
+ });
108
+
109
+ this.media.addEventListener('canplay', () => {
110
+ this.player.state.buffering = false;
111
+ this.player.emit('canplay');
112
+ });
113
+
114
+ this.media.addEventListener('progress', () => {
115
+ if (this.media.buffered.length > 0) {
116
+ const buffered = this.media.buffered.end(this.media.buffered.length - 1);
117
+ this.player.emit('progress', buffered);
118
+ }
119
+ });
120
+
121
+ this.media.addEventListener('error', (e) => {
122
+ this.player.handleError(this.media.error);
123
+ });
124
+
125
+ this.media.addEventListener('ratechange', () => {
126
+ this.player.state.playbackSpeed = this.media.playbackRate;
127
+ this.player.emit('ratechange', this.media.playbackRate);
128
+ });
129
+ }
130
+
131
+ pauseOtherPlayers() {
132
+ // Pause other VidPly instances
133
+ const allPlayers = document.querySelectorAll('.vidply-player');
134
+ allPlayers.forEach(playerEl => {
135
+ if (playerEl !== this.player.container) {
136
+ const video = playerEl.querySelector('video, audio');
137
+ if (video && !video.paused) {
138
+ video.pause();
139
+ }
140
+ }
141
+ });
142
+ }
143
+
144
+ play() {
145
+ const promise = this.media.play();
146
+
147
+ if (promise !== undefined) {
148
+ promise.catch(error => {
149
+ this.player.log('Play failed:', error, 'warn');
150
+
151
+ // If autoplay failed, try muted autoplay
152
+ if (this.player.options.autoplay && !this.player.state.muted) {
153
+ this.player.log('Retrying play with muted audio', 'info');
154
+ this.media.muted = true;
155
+ this.media.play().catch(err => {
156
+ this.player.handleError(err);
157
+ });
158
+ }
159
+ });
160
+ }
161
+ }
162
+
163
+ pause() {
164
+ this.media.pause();
165
+ }
166
+
167
+ seek(time) {
168
+ this.media.currentTime = time;
169
+ }
170
+
171
+ setVolume(volume) {
172
+ this.media.volume = volume;
173
+ }
174
+
175
+ setMuted(muted) {
176
+ this.media.muted = muted;
177
+ }
178
+
179
+ setPlaybackSpeed(speed) {
180
+ this.media.playbackRate = speed;
181
+ }
182
+
183
+ /**
184
+ * Get available quality levels from source elements
185
+ * @returns {Array} Array of quality objects with index, height, width, and src
186
+ */
187
+ getQualities() {
188
+ const sources = Array.from(this.media.querySelectorAll('source'));
189
+
190
+ if (sources.length <= 1) {
191
+ return [];
192
+ }
193
+
194
+ return sources.map((source, index) => {
195
+ // Try to extract quality from data attributes or label
196
+ const label = source.getAttribute('data-quality') || source.getAttribute('label') || '';
197
+ const height = source.getAttribute('data-height') || this.extractHeightFromLabel(label);
198
+ const width = source.getAttribute('data-width') || '';
199
+
200
+ return {
201
+ index,
202
+ height: height ? parseInt(height) : 0,
203
+ width: width ? parseInt(width) : 0,
204
+ src: source.src,
205
+ type: source.type,
206
+ name: label || (height ? `${height}p` : `Quality ${index + 1}`)
207
+ };
208
+ }).filter(q => q.height > 0); // Only return qualities with valid height
209
+ }
210
+
211
+ /**
212
+ * Extract height from quality label (e.g., "1080p" -> 1080)
213
+ * @param {string} label
214
+ * @returns {number}
215
+ */
216
+ extractHeightFromLabel(label) {
217
+ const match = label.match(/(\d+)p/i);
218
+ return match ? parseInt(match[1]) : 0;
219
+ }
220
+
221
+ /**
222
+ * Switch to a specific quality level
223
+ * @param {number} qualityIndex - Index of the quality level (-1 for auto, not applicable for HTML5)
224
+ */
225
+ switchQuality(qualityIndex) {
226
+ const qualities = this.getQualities();
227
+
228
+ if (qualityIndex < 0 || qualityIndex >= qualities.length) {
229
+ this.player.log('Invalid quality index', 'warn');
230
+ return;
231
+ }
232
+
233
+ const quality = qualities[qualityIndex];
234
+ const currentTime = this.media.currentTime;
235
+ const wasPlaying = !this.media.paused;
236
+
237
+ // Store the current source for comparison
238
+ const currentSrc = this.media.currentSrc;
239
+
240
+ // Don't switch if already at this quality
241
+ if (currentSrc === quality.src) {
242
+ this.player.log('Already at this quality level', 'info');
243
+ return;
244
+ }
245
+
246
+ this.player.log(`Switching to quality: ${quality.name}`, 'info');
247
+
248
+ // Update the src
249
+ this.media.src = quality.src;
250
+
251
+ // Wait for the new source to load, then restore playback state
252
+ const onLoadedMetadata = () => {
253
+ this.media.removeEventListener('loadedmetadata', onLoadedMetadata);
254
+
255
+ // Restore playback position
256
+ this.media.currentTime = currentTime;
257
+
258
+ // Resume playback if it was playing
259
+ if (wasPlaying) {
260
+ this.media.play().catch(err => {
261
+ this.player.log('Failed to resume playback after quality switch', 'warn');
262
+ });
263
+ }
264
+
265
+ // Emit quality change event
266
+ this.player.emit('qualitychange', { quality: quality.name, index: qualityIndex });
267
+ };
268
+
269
+ this.media.addEventListener('loadedmetadata', onLoadedMetadata);
270
+ this.media.load();
271
+ }
272
+
273
+ /**
274
+ * Get current quality index
275
+ * @returns {number}
276
+ */
277
+ getCurrentQuality() {
278
+ const qualities = this.getQualities();
279
+ const currentSrc = this.media.currentSrc;
280
+
281
+ for (let i = 0; i < qualities.length; i++) {
282
+ if (qualities[i].src === currentSrc) {
283
+ return i;
284
+ }
285
+ }
286
+
287
+ return 0; // Default to first quality if not found
288
+ }
289
+
290
+ destroy() {
291
+ // Remove event listeners
292
+ this.media.removeEventListener('loadedmetadata', () => {});
293
+ this.media.removeEventListener('play', () => {});
294
+ this.media.removeEventListener('pause', () => {});
295
+ // ... (other listeners would be removed in a real implementation)
296
+ }
297
+ }
298
+