vidply 1.0.5 → 1.0.6

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,302 +1,302 @@
1
- /**
2
- * HLS Streaming Renderer
3
- * Uses hls.js for browsers that don't natively support HLS
4
- */
5
-
6
- export class HLSRenderer {
7
- constructor(player) {
8
- this.player = player;
9
- this.media = player.element;
10
- this.hls = null;
11
- }
12
-
13
- async init() {
14
- // Check if browser natively supports HLS (Safari)
15
- if (this.canPlayNatively()) {
16
- this.player.log('Using native HLS support');
17
- await this.initNative();
18
- } else {
19
- this.player.log('Using hls.js for HLS support');
20
- await this.initHlsJs();
21
- }
22
- }
23
-
24
- canPlayNatively() {
25
- const video = document.createElement('video');
26
- return video.canPlayType('application/vnd.apple.mpegurl') !== '';
27
- }
28
-
29
- async initNative() {
30
- // Use HTML5 renderer for native HLS support
31
- const HTML5Renderer = (await import('./HTML5Renderer.js')).HTML5Renderer;
32
- const renderer = new HTML5Renderer(this.player);
33
- await renderer.init();
34
-
35
- // Copy methods
36
- Object.getOwnPropertyNames(Object.getPrototypeOf(renderer)).forEach(method => {
37
- if (method !== 'constructor' && typeof renderer[method] === 'function') {
38
- this[method] = renderer[method].bind(renderer);
39
- }
40
- });
41
- }
42
-
43
- async initHlsJs() {
44
- // Hide native controls
45
- this.media.controls = false;
46
- this.media.removeAttribute('controls');
47
-
48
- // Load hls.js if not already loaded
49
- if (!window.Hls) {
50
- await this.loadHlsJs();
51
- }
52
-
53
- if (!window.Hls.isSupported()) {
54
- throw new Error('HLS is not supported in this browser');
55
- }
56
-
57
- // Create hls.js instance with better error recovery
58
- this.hls = new window.Hls({
59
- debug: this.player.options.debug,
60
- enableWorker: true,
61
- lowLatencyMode: false,
62
- backBufferLength: 90,
63
- maxBufferLength: 30,
64
- maxMaxBufferLength: 600,
65
- maxBufferSize: 60 * 1000 * 1000,
66
- maxBufferHole: 0.5,
67
- // Network retry settings
68
- manifestLoadingTimeOut: 10000,
69
- manifestLoadingMaxRetry: 4,
70
- manifestLoadingRetryDelay: 1000,
71
- manifestLoadingMaxRetryTimeout: 64000,
72
- levelLoadingTimeOut: 10000,
73
- levelLoadingMaxRetry: 4,
74
- levelLoadingRetryDelay: 1000,
75
- levelLoadingMaxRetryTimeout: 64000,
76
- fragLoadingTimeOut: 20000,
77
- fragLoadingMaxRetry: 6,
78
- fragLoadingRetryDelay: 1000,
79
- fragLoadingMaxRetryTimeout: 64000
80
- });
81
-
82
- // Attach media element
83
- this.hls.attachMedia(this.media);
84
-
85
- // Load source - Get from attribute to avoid blob URL conversion
86
- let src;
87
- const sourceElement = this.player.element.querySelector('source');
88
- if (sourceElement) {
89
- // Use getAttribute to get the original URL, not the blob-converted one
90
- src = sourceElement.getAttribute('src');
91
- } else {
92
- // Fallback to element's src attribute
93
- src = this.player.element.getAttribute('src') || this.player.element.src;
94
- }
95
-
96
- this.player.log(`Loading HLS source: ${src}`, 'log');
97
-
98
- if (!src) {
99
- throw new Error('No HLS source found');
100
- }
101
-
102
- this.hls.loadSource(src);
103
-
104
- // Attach events
105
- this.attachHlsEvents();
106
- this.attachMediaEvents();
107
- }
108
-
109
- async loadHlsJs() {
110
- return new Promise((resolve, reject) => {
111
- const script = document.createElement('script');
112
- script.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest';
113
- script.onload = () => resolve();
114
- script.onerror = () => reject(new Error('Failed to load hls.js'));
115
- document.head.appendChild(script);
116
- });
117
- }
118
-
119
- attachHlsEvents() {
120
- this.hls.on(window.Hls.Events.MANIFEST_PARSED, (event, data) => {
121
- this.player.log('HLS manifest loaded, found ' + data.levels.length + ' quality levels');
122
- this.player.emit('hlsmanifestparsed', data);
123
- });
124
-
125
- this.hls.on(window.Hls.Events.LEVEL_SWITCHED, (event, data) => {
126
- this.player.log('HLS level switched to ' + data.level);
127
- this.player.emit('hlslevelswitched', data);
128
- });
129
-
130
- this.hls.on(window.Hls.Events.ERROR, (event, data) => {
131
- this.handleHlsError(data);
132
- });
133
-
134
- this.hls.on(window.Hls.Events.FRAG_BUFFERED, () => {
135
- this.player.state.buffering = false;
136
- });
137
- }
138
-
139
- attachMediaEvents() {
140
- // Use same events as HTML5 renderer
141
- this.media.addEventListener('loadedmetadata', () => {
142
- this.player.state.duration = this.media.duration;
143
- this.player.emit('loadedmetadata');
144
- });
145
-
146
- this.media.addEventListener('play', () => {
147
- this.player.state.playing = true;
148
- this.player.state.paused = false;
149
- this.player.state.ended = false;
150
- this.player.emit('play');
151
-
152
- if (this.player.options.onPlay) {
153
- this.player.options.onPlay.call(this.player);
154
- }
155
- });
156
-
157
- this.media.addEventListener('pause', () => {
158
- this.player.state.playing = false;
159
- this.player.state.paused = true;
160
- this.player.emit('pause');
161
-
162
- if (this.player.options.onPause) {
163
- this.player.options.onPause.call(this.player);
164
- }
165
- });
166
-
167
- this.media.addEventListener('ended', () => {
168
- this.player.state.playing = false;
169
- this.player.state.paused = true;
170
- this.player.state.ended = true;
171
- this.player.emit('ended');
172
-
173
- if (this.player.options.onEnded) {
174
- this.player.options.onEnded.call(this.player);
175
- }
176
-
177
- if (this.player.options.loop) {
178
- this.player.seek(0);
179
- this.player.play();
180
- }
181
- });
182
-
183
- this.media.addEventListener('timeupdate', () => {
184
- this.player.state.currentTime = this.media.currentTime;
185
- this.player.emit('timeupdate', this.media.currentTime);
186
-
187
- if (this.player.options.onTimeUpdate) {
188
- this.player.options.onTimeUpdate.call(this.player, this.media.currentTime);
189
- }
190
- });
191
-
192
- this.media.addEventListener('volumechange', () => {
193
- this.player.state.volume = this.media.volume;
194
- this.player.state.muted = this.media.muted;
195
- this.player.emit('volumechange', this.media.volume);
196
- });
197
-
198
- this.media.addEventListener('waiting', () => {
199
- this.player.state.buffering = true;
200
- this.player.emit('waiting');
201
- });
202
-
203
- this.media.addEventListener('canplay', () => {
204
- this.player.state.buffering = false;
205
- this.player.emit('canplay');
206
- });
207
-
208
- this.media.addEventListener('error', () => {
209
- this.player.handleError(this.media.error);
210
- });
211
- }
212
-
213
- handleHlsError(data) {
214
- // Log detailed error info
215
- this.player.log(`HLS Error - Type: ${data.type}, Details: ${data.details}, Fatal: ${data.fatal}`, 'warn');
216
- if (data.response) {
217
- this.player.log(`Response code: ${data.response.code}, URL: ${data.response.url}`, 'warn');
218
- }
219
-
220
- if (data.fatal) {
221
- switch (data.type) {
222
- case window.Hls.ErrorTypes.NETWORK_ERROR:
223
- this.player.log('Fatal network error, trying to recover...', 'error');
224
- this.player.log(`Network error details: ${data.details}`, 'error');
225
- setTimeout(() => {
226
- this.hls.startLoad();
227
- }, 1000);
228
- break;
229
-
230
- case window.Hls.ErrorTypes.MEDIA_ERROR:
231
- this.player.log('Fatal media error, trying to recover...', 'error');
232
- this.hls.recoverMediaError();
233
- break;
234
-
235
- default:
236
- this.player.log('Fatal error, cannot recover', 'error');
237
- this.player.handleError(new Error(`HLS Error: ${data.type} - ${data.details}`));
238
- this.hls.destroy();
239
- break;
240
- }
241
- } else {
242
- this.player.log('Non-fatal HLS error: ' + data.details, 'warn');
243
- }
244
- }
245
-
246
- play() {
247
- const promise = this.media.play();
248
-
249
- if (promise !== undefined) {
250
- promise.catch(error => {
251
- this.player.log('Play failed:', error, 'warn');
252
- });
253
- }
254
- }
255
-
256
- pause() {
257
- this.media.pause();
258
- }
259
-
260
- seek(time) {
261
- this.media.currentTime = time;
262
- }
263
-
264
- setVolume(volume) {
265
- this.media.volume = volume;
266
- }
267
-
268
- setMuted(muted) {
269
- this.media.muted = muted;
270
- }
271
-
272
- setPlaybackSpeed(speed) {
273
- this.media.playbackRate = speed;
274
- }
275
-
276
- switchQuality(levelIndex) {
277
- if (this.hls) {
278
- this.hls.currentLevel = levelIndex;
279
- }
280
- }
281
-
282
- getQualities() {
283
- if (this.hls && this.hls.levels) {
284
- return this.hls.levels.map((level, index) => ({
285
- index,
286
- height: level.height,
287
- width: level.width,
288
- bitrate: level.bitrate,
289
- name: `${level.height}p`
290
- }));
291
- }
292
- return [];
293
- }
294
-
295
- destroy() {
296
- if (this.hls) {
297
- this.hls.destroy();
298
- this.hls = null;
299
- }
300
- }
301
- }
302
-
1
+ /**
2
+ * HLS Streaming Renderer
3
+ * Uses hls.js for browsers that don't natively support HLS
4
+ */
5
+
6
+ export class HLSRenderer {
7
+ constructor(player) {
8
+ this.player = player;
9
+ this.media = player.element;
10
+ this.hls = null;
11
+ }
12
+
13
+ async init() {
14
+ // Check if browser natively supports HLS (Safari)
15
+ if (this.canPlayNatively()) {
16
+ this.player.log('Using native HLS support');
17
+ await this.initNative();
18
+ } else {
19
+ this.player.log('Using hls.js for HLS support');
20
+ await this.initHlsJs();
21
+ }
22
+ }
23
+
24
+ canPlayNatively() {
25
+ const video = document.createElement('video');
26
+ return video.canPlayType('application/vnd.apple.mpegurl') !== '';
27
+ }
28
+
29
+ async initNative() {
30
+ // Use HTML5 renderer for native HLS support
31
+ const HTML5Renderer = (await import('./HTML5Renderer.js')).HTML5Renderer;
32
+ const renderer = new HTML5Renderer(this.player);
33
+ await renderer.init();
34
+
35
+ // Copy methods
36
+ Object.getOwnPropertyNames(Object.getPrototypeOf(renderer)).forEach(method => {
37
+ if (method !== 'constructor' && typeof renderer[method] === 'function') {
38
+ this[method] = renderer[method].bind(renderer);
39
+ }
40
+ });
41
+ }
42
+
43
+ async initHlsJs() {
44
+ // Hide native controls
45
+ this.media.controls = false;
46
+ this.media.removeAttribute('controls');
47
+
48
+ // Load hls.js if not already loaded
49
+ if (!window.Hls) {
50
+ await this.loadHlsJs();
51
+ }
52
+
53
+ if (!window.Hls.isSupported()) {
54
+ throw new Error('HLS is not supported in this browser');
55
+ }
56
+
57
+ // Create hls.js instance with better error recovery
58
+ this.hls = new window.Hls({
59
+ debug: this.player.options.debug,
60
+ enableWorker: true,
61
+ lowLatencyMode: false,
62
+ backBufferLength: 90,
63
+ maxBufferLength: 30,
64
+ maxMaxBufferLength: 600,
65
+ maxBufferSize: 60 * 1000 * 1000,
66
+ maxBufferHole: 0.5,
67
+ // Network retry settings
68
+ manifestLoadingTimeOut: 10000,
69
+ manifestLoadingMaxRetry: 4,
70
+ manifestLoadingRetryDelay: 1000,
71
+ manifestLoadingMaxRetryTimeout: 64000,
72
+ levelLoadingTimeOut: 10000,
73
+ levelLoadingMaxRetry: 4,
74
+ levelLoadingRetryDelay: 1000,
75
+ levelLoadingMaxRetryTimeout: 64000,
76
+ fragLoadingTimeOut: 20000,
77
+ fragLoadingMaxRetry: 6,
78
+ fragLoadingRetryDelay: 1000,
79
+ fragLoadingMaxRetryTimeout: 64000
80
+ });
81
+
82
+ // Attach media element
83
+ this.hls.attachMedia(this.media);
84
+
85
+ // Load source - Get from attribute to avoid blob URL conversion
86
+ let src;
87
+ const sourceElement = this.player.element.querySelector('source');
88
+ if (sourceElement) {
89
+ // Use getAttribute to get the original URL, not the blob-converted one
90
+ src = sourceElement.getAttribute('src');
91
+ } else {
92
+ // Fallback to element's src attribute
93
+ src = this.player.element.getAttribute('src') || this.player.element.src;
94
+ }
95
+
96
+ this.player.log(`Loading HLS source: ${src}`, 'log');
97
+
98
+ if (!src) {
99
+ throw new Error('No HLS source found');
100
+ }
101
+
102
+ this.hls.loadSource(src);
103
+
104
+ // Attach events
105
+ this.attachHlsEvents();
106
+ this.attachMediaEvents();
107
+ }
108
+
109
+ async loadHlsJs() {
110
+ return new Promise((resolve, reject) => {
111
+ const script = document.createElement('script');
112
+ script.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest';
113
+ script.onload = () => resolve();
114
+ script.onerror = () => reject(new Error('Failed to load hls.js'));
115
+ document.head.appendChild(script);
116
+ });
117
+ }
118
+
119
+ attachHlsEvents() {
120
+ this.hls.on(window.Hls.Events.MANIFEST_PARSED, (event, data) => {
121
+ this.player.log('HLS manifest loaded, found ' + data.levels.length + ' quality levels');
122
+ this.player.emit('hlsmanifestparsed', data);
123
+ });
124
+
125
+ this.hls.on(window.Hls.Events.LEVEL_SWITCHED, (event, data) => {
126
+ this.player.log('HLS level switched to ' + data.level);
127
+ this.player.emit('hlslevelswitched', data);
128
+ });
129
+
130
+ this.hls.on(window.Hls.Events.ERROR, (event, data) => {
131
+ this.handleHlsError(data);
132
+ });
133
+
134
+ this.hls.on(window.Hls.Events.FRAG_BUFFERED, () => {
135
+ this.player.state.buffering = false;
136
+ });
137
+ }
138
+
139
+ attachMediaEvents() {
140
+ // Use same events as HTML5 renderer
141
+ this.media.addEventListener('loadedmetadata', () => {
142
+ this.player.state.duration = this.media.duration;
143
+ this.player.emit('loadedmetadata');
144
+ });
145
+
146
+ this.media.addEventListener('play', () => {
147
+ this.player.state.playing = true;
148
+ this.player.state.paused = false;
149
+ this.player.state.ended = false;
150
+ this.player.emit('play');
151
+
152
+ if (this.player.options.onPlay) {
153
+ this.player.options.onPlay.call(this.player);
154
+ }
155
+ });
156
+
157
+ this.media.addEventListener('pause', () => {
158
+ this.player.state.playing = false;
159
+ this.player.state.paused = true;
160
+ this.player.emit('pause');
161
+
162
+ if (this.player.options.onPause) {
163
+ this.player.options.onPause.call(this.player);
164
+ }
165
+ });
166
+
167
+ this.media.addEventListener('ended', () => {
168
+ this.player.state.playing = false;
169
+ this.player.state.paused = true;
170
+ this.player.state.ended = true;
171
+ this.player.emit('ended');
172
+
173
+ if (this.player.options.onEnded) {
174
+ this.player.options.onEnded.call(this.player);
175
+ }
176
+
177
+ if (this.player.options.loop) {
178
+ this.player.seek(0);
179
+ this.player.play();
180
+ }
181
+ });
182
+
183
+ this.media.addEventListener('timeupdate', () => {
184
+ this.player.state.currentTime = this.media.currentTime;
185
+ this.player.emit('timeupdate', this.media.currentTime);
186
+
187
+ if (this.player.options.onTimeUpdate) {
188
+ this.player.options.onTimeUpdate.call(this.player, this.media.currentTime);
189
+ }
190
+ });
191
+
192
+ this.media.addEventListener('volumechange', () => {
193
+ this.player.state.volume = this.media.volume;
194
+ this.player.state.muted = this.media.muted;
195
+ this.player.emit('volumechange', this.media.volume);
196
+ });
197
+
198
+ this.media.addEventListener('waiting', () => {
199
+ this.player.state.buffering = true;
200
+ this.player.emit('waiting');
201
+ });
202
+
203
+ this.media.addEventListener('canplay', () => {
204
+ this.player.state.buffering = false;
205
+ this.player.emit('canplay');
206
+ });
207
+
208
+ this.media.addEventListener('error', () => {
209
+ this.player.handleError(this.media.error);
210
+ });
211
+ }
212
+
213
+ handleHlsError(data) {
214
+ // Log detailed error info
215
+ this.player.log(`HLS Error - Type: ${data.type}, Details: ${data.details}, Fatal: ${data.fatal}`, 'warn');
216
+ if (data.response) {
217
+ this.player.log(`Response code: ${data.response.code}, URL: ${data.response.url}`, 'warn');
218
+ }
219
+
220
+ if (data.fatal) {
221
+ switch (data.type) {
222
+ case window.Hls.ErrorTypes.NETWORK_ERROR:
223
+ this.player.log('Fatal network error, trying to recover...', 'error');
224
+ this.player.log(`Network error details: ${data.details}`, 'error');
225
+ setTimeout(() => {
226
+ this.hls.startLoad();
227
+ }, 1000);
228
+ break;
229
+
230
+ case window.Hls.ErrorTypes.MEDIA_ERROR:
231
+ this.player.log('Fatal media error, trying to recover...', 'error');
232
+ this.hls.recoverMediaError();
233
+ break;
234
+
235
+ default:
236
+ this.player.log('Fatal error, cannot recover', 'error');
237
+ this.player.handleError(new Error(`HLS Error: ${data.type} - ${data.details}`));
238
+ this.hls.destroy();
239
+ break;
240
+ }
241
+ } else {
242
+ this.player.log('Non-fatal HLS error: ' + data.details, 'warn');
243
+ }
244
+ }
245
+
246
+ play() {
247
+ const promise = this.media.play();
248
+
249
+ if (promise !== undefined) {
250
+ promise.catch(error => {
251
+ this.player.log('Play failed:', error, 'warn');
252
+ });
253
+ }
254
+ }
255
+
256
+ pause() {
257
+ this.media.pause();
258
+ }
259
+
260
+ seek(time) {
261
+ this.media.currentTime = time;
262
+ }
263
+
264
+ setVolume(volume) {
265
+ this.media.volume = volume;
266
+ }
267
+
268
+ setMuted(muted) {
269
+ this.media.muted = muted;
270
+ }
271
+
272
+ setPlaybackSpeed(speed) {
273
+ this.media.playbackRate = speed;
274
+ }
275
+
276
+ switchQuality(levelIndex) {
277
+ if (this.hls) {
278
+ this.hls.currentLevel = levelIndex;
279
+ }
280
+ }
281
+
282
+ getQualities() {
283
+ if (this.hls && this.hls.levels) {
284
+ return this.hls.levels.map((level, index) => ({
285
+ index,
286
+ height: level.height,
287
+ width: level.width,
288
+ bitrate: level.bitrate,
289
+ name: `${level.height}p`
290
+ }));
291
+ }
292
+ return [];
293
+ }
294
+
295
+ destroy() {
296
+ if (this.hls) {
297
+ this.hls.destroy();
298
+ this.hls = null;
299
+ }
300
+ }
301
+ }
302
+