myetv-player 1.1.0 → 1.1.1
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/README.md +69 -0
- package/dist/myetv-player.js +173 -45
- package/dist/myetv-player.min.js +171 -43
- package/package.json +2 -1
- package/plugins/youtube/myetv-player-youtube-plugin.js +183 -13
- package/src/core.js +55 -20
- package/src/events.js +118 -25
package/README.md
CHANGED
|
@@ -119,6 +119,7 @@ const player = new MYETVvideoplayer('my-video', {
|
|
|
119
119
|
| `brandLogoEnabled` | boolean | `false` | Show/hide the brand logo in the controlbar |
|
|
120
120
|
| `brandLogoUrl` | string | `''` | Brand logo url in the controlbar (png, jpg, gif) - image height 44px - image width 120px |
|
|
121
121
|
| `brandLogoLinkUrl` | string | `''` | Optional URL to open in a new page when clicking the brand logo in the controlbar
|
|
122
|
+
| `brandLogoTooltipText` | string | `''` | Optional Custom tooltip of the brand logo (the default is the url of the brand logo, if present)
|
|
122
123
|
| `watermarkUrl` | string | `''` | Optional URL of the image watermark over the video, reccomended dimension: width: 180px, height: 100px
|
|
123
124
|
| `watermarkLink` | string | `''` | Optional URL to open in a new page when clicking the watermark logo in the video
|
|
124
125
|
| `watermarkPosition` | string | `''` | Optional where to show the watermark logo in the video (values are: top-left, top-right, bottom-left, bottom-right)
|
|
@@ -283,8 +284,21 @@ console.log(player.getCurrentResolution()); // Get current resolution
|
|
|
283
284
|
```
|
|
284
285
|
## API Events
|
|
285
286
|
The MYETV Video Player includes a comprehensive custom event system that allows you to monitor all player state changes in real-time.
|
|
287
|
+
### on player ready
|
|
288
|
+
Description: Triggered when the video player is ready
|
|
289
|
+
|
|
290
|
+
When: Player is ready to receive other events
|
|
291
|
+
```
|
|
292
|
+
player.addEventListener('playerready', (event) => {
|
|
293
|
+
console.log('Player is ready!', event);
|
|
294
|
+
//now it's secure to call other apis method
|
|
295
|
+
player.setVolume(0.8);
|
|
296
|
+
player.play();
|
|
297
|
+
});
|
|
298
|
+
```
|
|
286
299
|
### on played
|
|
287
300
|
Description: Triggered when the video starts playing
|
|
301
|
+
|
|
288
302
|
When: User presses play or video starts automatically
|
|
289
303
|
```
|
|
290
304
|
player.addEventListener('played', (event) => {
|
|
@@ -294,24 +308,72 @@ player.addEventListener('played', (event) => {
|
|
|
294
308
|
});
|
|
295
309
|
});
|
|
296
310
|
```
|
|
311
|
+
### on playing
|
|
312
|
+
Description: Triggered when the video is playing
|
|
313
|
+
|
|
314
|
+
When: Video is effectively playing
|
|
315
|
+
```
|
|
316
|
+
player.addEventListener('playing', (event) => {
|
|
317
|
+
console.log('Video is playing at', event.currentTime);
|
|
318
|
+
});
|
|
319
|
+
```
|
|
297
320
|
### on paused
|
|
298
321
|
Description: Triggered when the video is pause
|
|
322
|
+
|
|
299
323
|
When: User presses pause or video stops
|
|
300
324
|
```
|
|
301
325
|
player.addEventListener('paused', (event) => {
|
|
302
326
|
console.log('Video paused at:', event.currentTime + 's');
|
|
303
327
|
});
|
|
304
328
|
```
|
|
329
|
+
### on waiting
|
|
330
|
+
Description: Triggered when the video is buffering
|
|
331
|
+
|
|
332
|
+
When: Video is buffering and is waiting
|
|
333
|
+
```
|
|
334
|
+
player.addEventListener('waiting', (event) => {
|
|
335
|
+
console.log('Video is buffering...');
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
### on seeking
|
|
339
|
+
Description: Triggered when the video is being seeking
|
|
340
|
+
|
|
341
|
+
When: The user is seeking over the video
|
|
342
|
+
```
|
|
343
|
+
player.addEventListener('seeking', (event) => {
|
|
344
|
+
console.log('User is seeking to', event.targetTime);
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
### on seeked
|
|
348
|
+
Description: Triggered when the video is finished seeked
|
|
349
|
+
|
|
350
|
+
When: The user have finished seeking and seeked the video
|
|
351
|
+
```
|
|
352
|
+
player.addEventListener('seeked', (event) => {
|
|
353
|
+
console.log('Seek completed at', event.currentTime);
|
|
354
|
+
});
|
|
355
|
+
```
|
|
305
356
|
### on ended
|
|
306
357
|
Description: Triggered when the video is ended
|
|
358
|
+
|
|
307
359
|
When: Video is ended
|
|
308
360
|
```
|
|
309
361
|
player.addEventListener('ended', (e) => {
|
|
310
362
|
console.log('Video terminato!', e.currentTime, e.duration, e.playlistInfo);
|
|
311
363
|
});
|
|
312
364
|
```
|
|
365
|
+
### on error
|
|
366
|
+
Description: Triggered when the video have some error
|
|
367
|
+
|
|
368
|
+
When: Video have some error on load
|
|
369
|
+
```
|
|
370
|
+
player.addEventListener('error', (event) => {
|
|
371
|
+
console.error('Playback error:', event.message);
|
|
372
|
+
});
|
|
373
|
+
```
|
|
313
374
|
### on subtitle change
|
|
314
375
|
Description: Triggered when subtitles are enabled/disabled or track changes
|
|
376
|
+
|
|
315
377
|
When: User toggles subtitles or switches subtitle tracks
|
|
316
378
|
```
|
|
317
379
|
player.addEventListener('subtitlechange', (event) => {
|
|
@@ -324,6 +386,7 @@ player.addEventListener('subtitlechange', (event) => {
|
|
|
324
386
|
```
|
|
325
387
|
### on chapters change
|
|
326
388
|
Description: Triggered when chapters are changes
|
|
389
|
+
|
|
327
390
|
When: User switches chapters tracks
|
|
328
391
|
```
|
|
329
392
|
player.on('chapterchange', (data) => {
|
|
@@ -332,6 +395,7 @@ player.on('chapterchange', (data) => {
|
|
|
332
395
|
```
|
|
333
396
|
### on pip change
|
|
334
397
|
Description: Triggered when Picture-in-Picture mode changes
|
|
398
|
+
|
|
335
399
|
When: Video enters or exits PiP mode
|
|
336
400
|
```
|
|
337
401
|
player.addEventListener('pipchange', (event) => {
|
|
@@ -340,6 +404,7 @@ player.addEventListener('pipchange', (event) => {
|
|
|
340
404
|
```
|
|
341
405
|
### on fullscreen change
|
|
342
406
|
Description: Triggered when fullscreen mode changes
|
|
407
|
+
|
|
343
408
|
When: Player enters or exits fullscreen mode
|
|
344
409
|
```
|
|
345
410
|
player.addEventListener('fullscreenchange', (event) => {
|
|
@@ -348,6 +413,7 @@ player.addEventListener('fullscreenchange', (event) => {
|
|
|
348
413
|
```
|
|
349
414
|
### on speed change
|
|
350
415
|
Description: Triggered when playback speed changes
|
|
416
|
+
|
|
351
417
|
When: User modifies playback speed (0.5x, 1x, 1.5x, 2x, etc.)
|
|
352
418
|
```
|
|
353
419
|
player.addEventListener('speedchange', (event) => {
|
|
@@ -356,6 +422,7 @@ player.addEventListener('speedchange', (event) => {
|
|
|
356
422
|
```
|
|
357
423
|
### on time update
|
|
358
424
|
Description: Triggered during playback to update progress
|
|
425
|
+
|
|
359
426
|
When: Every 250ms during playback (throttled for performance)
|
|
360
427
|
```
|
|
361
428
|
player.addEventListener('timeupdate', (event) => {
|
|
@@ -366,6 +433,7 @@ player.addEventListener('timeupdate', (event) => {
|
|
|
366
433
|
```
|
|
367
434
|
### on volumechange
|
|
368
435
|
Description: Triggered when volume or mute state changes
|
|
436
|
+
|
|
369
437
|
When: User modifies volume or toggles mute
|
|
370
438
|
```
|
|
371
439
|
player.addEventListener('volumechange', (event) => {
|
|
@@ -384,6 +452,7 @@ player.addEventListener('playlistchange', (e) => {
|
|
|
384
452
|
```
|
|
385
453
|
### Main APIs
|
|
386
454
|
getEventData()
|
|
455
|
+
|
|
387
456
|
Returns all requested state data in a single object:
|
|
388
457
|
```
|
|
389
458
|
const state = player.getEventData();
|
package/dist/myetv-player.js
CHANGED
|
@@ -433,6 +433,7 @@ constructor(videoElement, options = {}) {
|
|
|
433
433
|
brandLogoEnabled: false, // Enable/disable brand logo
|
|
434
434
|
brandLogoUrl: '', // URL for brand logo image
|
|
435
435
|
brandLogoLinkUrl: '', // Optional URL to open when clicking the logo
|
|
436
|
+
brandLogoTooltipText: '', // Tooltip text for brand logo
|
|
436
437
|
playlistEnabled: true, // Enable/disable playlist detection
|
|
437
438
|
playlistAutoPlay: true, // Auto-play next video when current ends
|
|
438
439
|
playlistLoop: false, // Loop playlist when reaching the end
|
|
@@ -508,18 +509,42 @@ constructor(videoElement, options = {}) {
|
|
|
508
509
|
|
|
509
510
|
// Custom event system
|
|
510
511
|
this.eventCallbacks = {
|
|
511
|
-
|
|
512
|
-
'
|
|
513
|
-
'
|
|
514
|
-
'
|
|
515
|
-
'
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
'
|
|
519
|
-
'
|
|
520
|
-
'
|
|
521
|
-
'
|
|
522
|
-
|
|
512
|
+
// Core lifecycle events
|
|
513
|
+
'playerready': [], // Fired when player is fully initialized and ready
|
|
514
|
+
'played': [], // Fired when video starts playing
|
|
515
|
+
'paused': [], // Fired when video is paused
|
|
516
|
+
'ended': [], // Fired when video playback ends
|
|
517
|
+
|
|
518
|
+
// Playback state events
|
|
519
|
+
'playing': [], // Fired when video is actually playing (after buffering)
|
|
520
|
+
'waiting': [], // Fired when video is waiting for data (buffering)
|
|
521
|
+
'seeking': [], // Fired when seek operation starts
|
|
522
|
+
'seeked': [], // Fired when seek operation completes
|
|
523
|
+
|
|
524
|
+
// Loading events
|
|
525
|
+
'loadstart': [], // Fired when browser starts looking for media
|
|
526
|
+
'loadedmetadata': [], // Fired when metadata (duration, dimensions) is loaded
|
|
527
|
+
'loadeddata': [], // Fired when data for current frame is loaded
|
|
528
|
+
'canplay': [], // Fired when browser can start playing video
|
|
529
|
+
'progress': [], // Fired periodically while downloading media
|
|
530
|
+
'durationchange': [], // Fired when duration attribute changes
|
|
531
|
+
|
|
532
|
+
// Error events
|
|
533
|
+
'error': [], // Fired when media loading or playback error occurs
|
|
534
|
+
'stalled': [], // Fired when browser is trying to get data but it's not available
|
|
535
|
+
|
|
536
|
+
// Control events
|
|
537
|
+
'timeupdate': [], // Fired when current playback position changes
|
|
538
|
+
'volumechange': [], // Fired when volume or muted state changes
|
|
539
|
+
'speedchange': [], // Fired when playback speed changes
|
|
540
|
+
'qualitychange': [], // Fired when video quality changes
|
|
541
|
+
|
|
542
|
+
// Feature events
|
|
543
|
+
'subtitlechange': [], // Fired when subtitle track changes
|
|
544
|
+
'chapterchange': [], // Fired when video chapter changes
|
|
545
|
+
'pipchange': [], // Fired when picture-in-picture mode changes
|
|
546
|
+
'fullscreenchange': [], // Fired when fullscreen mode changes
|
|
547
|
+
'playlistchange': [] // Fired when playlist item changes
|
|
523
548
|
};
|
|
524
549
|
|
|
525
550
|
// Playlist management
|
|
@@ -881,6 +906,14 @@ markPlayerReady() {
|
|
|
881
906
|
this.container.classList.add('player-initialized');
|
|
882
907
|
}
|
|
883
908
|
|
|
909
|
+
this.triggerEvent('playerready', {
|
|
910
|
+
playerState: this.getPlayerState(),
|
|
911
|
+
qualities: this.qualities,
|
|
912
|
+
subtitles: this.textTracks,
|
|
913
|
+
chapters: this.chapters,
|
|
914
|
+
playlist: this.getPlaylistInfo()
|
|
915
|
+
});
|
|
916
|
+
|
|
884
917
|
if (this.video) {
|
|
885
918
|
this.video.style.visibility = '';
|
|
886
919
|
this.video.style.opacity = '';
|
|
@@ -1742,7 +1775,14 @@ createBrandLogo() {
|
|
|
1742
1775
|
const logo = document.createElement('img');
|
|
1743
1776
|
logo.className = 'brand-logo';
|
|
1744
1777
|
logo.src = this.options.brandLogoUrl;
|
|
1745
|
-
logo.alt =
|
|
1778
|
+
logo.alt = 'Brand logo';
|
|
1779
|
+
|
|
1780
|
+
// Add tooltip ONLY if link URL is present
|
|
1781
|
+
if (this.options.brandLogoLinkUrl) {
|
|
1782
|
+
// Use custom tooltip text if provided, otherwise fallback to URL
|
|
1783
|
+
logo.title = this.options.brandLogoTooltipText || this.options.brandLogoLinkUrl;
|
|
1784
|
+
// NON usare data-tooltip per evitare che venga sovrascritto da updateTooltips()
|
|
1785
|
+
}
|
|
1746
1786
|
|
|
1747
1787
|
// Handle loading error
|
|
1748
1788
|
logo.onerror = () => {
|
|
@@ -1758,7 +1798,7 @@ createBrandLogo() {
|
|
|
1758
1798
|
if (this.options.brandLogoLinkUrl) {
|
|
1759
1799
|
logo.style.cursor = 'pointer';
|
|
1760
1800
|
logo.addEventListener('click', (e) => {
|
|
1761
|
-
e.stopPropagation();
|
|
1801
|
+
e.stopPropagation();
|
|
1762
1802
|
window.open(this.options.brandLogoLinkUrl, '_blank', 'noopener,noreferrer');
|
|
1763
1803
|
if (this.options.debug) console.log('Brand logo clicked, opening:', this.options.brandLogoLinkUrl);
|
|
1764
1804
|
});
|
|
@@ -1766,15 +1806,10 @@ createBrandLogo() {
|
|
|
1766
1806
|
logo.style.cursor = 'default';
|
|
1767
1807
|
}
|
|
1768
1808
|
|
|
1769
|
-
// Position the brand logo at the right of the controlbar (at the left of the buttons)
|
|
1770
1809
|
controlsRight.insertBefore(logo, controlsRight.firstChild);
|
|
1771
1810
|
|
|
1772
1811
|
if (this.options.debug) {
|
|
1773
|
-
|
|
1774
|
-
console.log('Brand logo with click handler created for:', this.options.brandLogoLinkUrl);
|
|
1775
|
-
} else {
|
|
1776
|
-
console.log('Brand logo created (no link)');
|
|
1777
|
-
}
|
|
1812
|
+
console.log('Brand logo created with tooltip:', logo.title || 'no tooltip');
|
|
1778
1813
|
}
|
|
1779
1814
|
}
|
|
1780
1815
|
|
|
@@ -2420,36 +2455,129 @@ addEventListener(eventType, callback) {
|
|
|
2420
2455
|
}
|
|
2421
2456
|
|
|
2422
2457
|
bindEvents() {
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2458
|
+
if (this.video) {
|
|
2459
|
+
|
|
2460
|
+
// Playback events
|
|
2461
|
+
this.video.addEventListener('playing', () => {
|
|
2462
|
+
this.hideLoading();
|
|
2463
|
+
// Trigger playing event - video is now actually playing
|
|
2464
|
+
this.triggerEvent('playing', {
|
|
2465
|
+
currentTime: this.getCurrentTime(),
|
|
2466
|
+
duration: this.getDuration()
|
|
2429
2467
|
});
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2468
|
+
});
|
|
2469
|
+
|
|
2470
|
+
this.video.addEventListener('waiting', () => {
|
|
2471
|
+
if (!this.isChangingQuality) {
|
|
2472
|
+
this.showLoading();
|
|
2473
|
+
// Trigger waiting event - video is buffering
|
|
2474
|
+
this.triggerEvent('waiting', {
|
|
2475
|
+
currentTime: this.getCurrentTime()
|
|
2476
|
+
});
|
|
2477
|
+
}
|
|
2478
|
+
});
|
|
2479
|
+
|
|
2480
|
+
this.video.addEventListener('seeking', () => {
|
|
2481
|
+
// Trigger seeking event - seek operation started
|
|
2482
|
+
this.triggerEvent('seeking', {
|
|
2483
|
+
currentTime: this.getCurrentTime(),
|
|
2484
|
+
targetTime: this.video.currentTime
|
|
2436
2485
|
});
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2486
|
+
});
|
|
2487
|
+
|
|
2488
|
+
this.video.addEventListener('seeked', () => {
|
|
2489
|
+
// Trigger seeked event - seek operation completed
|
|
2490
|
+
this.triggerEvent('seeked', {
|
|
2491
|
+
currentTime: this.getCurrentTime()
|
|
2441
2492
|
});
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2493
|
+
});
|
|
2494
|
+
|
|
2495
|
+
// Loading events
|
|
2496
|
+
this.video.addEventListener('loadstart', () => {
|
|
2497
|
+
if (!this.isChangingQuality) {
|
|
2498
|
+
this.showLoading();
|
|
2499
|
+
}
|
|
2500
|
+
// Trigger loadstart event - browser started loading media
|
|
2501
|
+
this.triggerEvent('loadstart');
|
|
2502
|
+
});
|
|
2503
|
+
|
|
2504
|
+
this.video.addEventListener('loadedmetadata', () => {
|
|
2505
|
+
this.updateDuration();
|
|
2506
|
+
|
|
2507
|
+
// Trigger loadedmetadata event - video metadata loaded
|
|
2508
|
+
this.triggerEvent('loadedmetadata', {
|
|
2509
|
+
duration: this.getDuration(),
|
|
2510
|
+
videoWidth: this.video.videoWidth,
|
|
2511
|
+
videoHeight: this.video.videoHeight
|
|
2447
2512
|
});
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2513
|
+
|
|
2514
|
+
// Initialize subtitles after metadata is loaded
|
|
2515
|
+
setTimeout(() => {
|
|
2516
|
+
this.initializeSubtitles();
|
|
2517
|
+
}, 100);
|
|
2518
|
+
});
|
|
2519
|
+
|
|
2520
|
+
this.video.addEventListener('loadeddata', () => {
|
|
2521
|
+
if (!this.isChangingQuality) {
|
|
2522
|
+
this.hideLoading();
|
|
2523
|
+
}
|
|
2524
|
+
// Trigger loadeddata event - current frame data loaded
|
|
2525
|
+
this.triggerEvent('loadeddata', {
|
|
2526
|
+
currentTime: this.getCurrentTime()
|
|
2527
|
+
});
|
|
2528
|
+
});
|
|
2529
|
+
|
|
2530
|
+
this.video.addEventListener('canplay', () => {
|
|
2531
|
+
if (!this.isChangingQuality) {
|
|
2532
|
+
this.hideLoading();
|
|
2533
|
+
}
|
|
2534
|
+
// Trigger canplay event - video can start playing
|
|
2535
|
+
this.triggerEvent('canplay', {
|
|
2536
|
+
currentTime: this.getCurrentTime(),
|
|
2537
|
+
duration: this.getDuration()
|
|
2452
2538
|
});
|
|
2539
|
+
});
|
|
2540
|
+
|
|
2541
|
+
this.video.addEventListener('progress', () => {
|
|
2542
|
+
this.updateBuffer();
|
|
2543
|
+
// Trigger progress event - browser is downloading media
|
|
2544
|
+
this.triggerEvent('progress', {
|
|
2545
|
+
buffered: this.getBufferedTime(),
|
|
2546
|
+
duration: this.getDuration()
|
|
2547
|
+
});
|
|
2548
|
+
});
|
|
2549
|
+
|
|
2550
|
+
this.video.addEventListener('durationchange', () => {
|
|
2551
|
+
this.updateDuration();
|
|
2552
|
+
// Trigger durationchange event - video duration changed
|
|
2553
|
+
this.triggerEvent('durationchange', {
|
|
2554
|
+
duration: this.getDuration()
|
|
2555
|
+
});
|
|
2556
|
+
});
|
|
2557
|
+
|
|
2558
|
+
// Error events
|
|
2559
|
+
this.video.addEventListener('error', (e) => {
|
|
2560
|
+
this.onVideoError(e);
|
|
2561
|
+
// Trigger error event - media loading/playback error occurred
|
|
2562
|
+
this.triggerEvent('error', {
|
|
2563
|
+
code: this.video.error?.code,
|
|
2564
|
+
message: this.video.error?.message,
|
|
2565
|
+
src: this.video.currentSrc || this.video.src
|
|
2566
|
+
});
|
|
2567
|
+
});
|
|
2568
|
+
|
|
2569
|
+
this.video.addEventListener('stalled', () => {
|
|
2570
|
+
// Trigger stalled event - browser is trying to fetch data but it's not available
|
|
2571
|
+
this.triggerEvent('stalled', {
|
|
2572
|
+
currentTime: this.getCurrentTime()
|
|
2573
|
+
});
|
|
2574
|
+
});
|
|
2575
|
+
|
|
2576
|
+
|
|
2577
|
+
this.video.addEventListener('timeupdate', () => this.updateProgress());
|
|
2578
|
+
|
|
2579
|
+
this.video.addEventListener('ended', () => this.onVideoEnded());
|
|
2580
|
+
|
|
2453
2581
|
// Complete video click logic with doubleTapPause support (DESKTOP)
|
|
2454
2582
|
this.video.addEventListener('click', () => {
|
|
2455
2583
|
if (!this.options.pauseClick) return;
|
package/dist/myetv-player.min.js
CHANGED
|
@@ -419,6 +419,7 @@ constructor(videoElement, options = {}) {
|
|
|
419
419
|
brandLogoEnabled: false,
|
|
420
420
|
brandLogoUrl: '',
|
|
421
421
|
brandLogoLinkUrl: '',
|
|
422
|
+
brandLogoTooltipText: '',
|
|
422
423
|
playlistEnabled: true,
|
|
423
424
|
playlistAutoPlay: true,
|
|
424
425
|
playlistLoop: false,
|
|
@@ -494,18 +495,42 @@ constructor(videoElement, options = {}) {
|
|
|
494
495
|
|
|
495
496
|
|
|
496
497
|
this.eventCallbacks = {
|
|
497
|
-
|
|
498
|
-
'
|
|
499
|
-
'
|
|
500
|
-
'
|
|
501
|
-
'
|
|
498
|
+
|
|
499
|
+
'playerready': [],
|
|
500
|
+
'played': [],
|
|
501
|
+
'paused': [],
|
|
502
|
+
'ended': [],
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
'playing': [],
|
|
506
|
+
'waiting': [],
|
|
507
|
+
'seeking': [],
|
|
508
|
+
'seeked': [],
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
'loadstart': [],
|
|
512
|
+
'loadedmetadata': [],
|
|
513
|
+
'loadeddata': [],
|
|
514
|
+
'canplay': [],
|
|
515
|
+
'progress': [],
|
|
516
|
+
'durationchange': [],
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
'error': [],
|
|
520
|
+
'stalled': [],
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
'timeupdate': [],
|
|
524
|
+
'volumechange': [],
|
|
525
|
+
'speedchange': [],
|
|
526
|
+
'qualitychange': [],
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
'subtitlechange': [],
|
|
530
|
+
'chapterchange': [],
|
|
531
|
+
'pipchange': [],
|
|
502
532
|
'fullscreenchange': [],
|
|
503
|
-
'
|
|
504
|
-
'timeupdate': [],
|
|
505
|
-
'volumechange': [],
|
|
506
|
-
'qualitychange': [],
|
|
507
|
-
'playlistchange': [],
|
|
508
|
-
'ended': []
|
|
533
|
+
'playlistchange': []
|
|
509
534
|
};
|
|
510
535
|
|
|
511
536
|
|
|
@@ -867,6 +892,14 @@ markPlayerReady() {
|
|
|
867
892
|
this.container.classList.add('player-initialized');
|
|
868
893
|
}
|
|
869
894
|
|
|
895
|
+
this.triggerEvent('playerready', {
|
|
896
|
+
playerState: this.getPlayerState(),
|
|
897
|
+
qualities: this.qualities,
|
|
898
|
+
subtitles: this.textTracks,
|
|
899
|
+
chapters: this.chapters,
|
|
900
|
+
playlist: this.getPlaylistInfo()
|
|
901
|
+
});
|
|
902
|
+
|
|
870
903
|
if (this.video) {
|
|
871
904
|
this.video.style.visibility = '';
|
|
872
905
|
this.video.style.opacity = '';
|
|
@@ -1716,7 +1749,14 @@ createBrandLogo() {
|
|
|
1716
1749
|
const logo = document.createElement('img');
|
|
1717
1750
|
logo.className = 'brand-logo';
|
|
1718
1751
|
logo.src = this.options.brandLogoUrl;
|
|
1719
|
-
logo.alt =
|
|
1752
|
+
logo.alt = 'Brand logo';
|
|
1753
|
+
|
|
1754
|
+
|
|
1755
|
+
if (this.options.brandLogoLinkUrl) {
|
|
1756
|
+
|
|
1757
|
+
logo.title = this.options.brandLogoTooltipText || this.options.brandLogoLinkUrl;
|
|
1758
|
+
|
|
1759
|
+
}
|
|
1720
1760
|
|
|
1721
1761
|
|
|
1722
1762
|
logo.onerror = () => {
|
|
@@ -1740,15 +1780,10 @@ createBrandLogo() {
|
|
|
1740
1780
|
logo.style.cursor = 'default';
|
|
1741
1781
|
}
|
|
1742
1782
|
|
|
1743
|
-
|
|
1744
1783
|
controlsRight.insertBefore(logo, controlsRight.firstChild);
|
|
1745
1784
|
|
|
1746
1785
|
if (this.options.debug) {
|
|
1747
|
-
|
|
1748
|
-
console.log('Brand logo with click handler created for:', this.options.brandLogoLinkUrl);
|
|
1749
|
-
} else {
|
|
1750
|
-
console.log('Brand logo created (no link)');
|
|
1751
|
-
}
|
|
1786
|
+
console.log('Brand logo created with tooltip:', logo.title || 'no tooltip');
|
|
1752
1787
|
}
|
|
1753
1788
|
}
|
|
1754
1789
|
|
|
@@ -2335,36 +2370,129 @@ addEventListener(eventType, callback) {
|
|
|
2335
2370
|
}
|
|
2336
2371
|
|
|
2337
2372
|
bindEvents() {
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2373
|
+
if (this.video) {
|
|
2374
|
+
|
|
2375
|
+
|
|
2376
|
+
this.video.addEventListener('playing', () => {
|
|
2377
|
+
this.hideLoading();
|
|
2378
|
+
|
|
2379
|
+
this.triggerEvent('playing', {
|
|
2380
|
+
currentTime: this.getCurrentTime(),
|
|
2381
|
+
duration: this.getDuration()
|
|
2344
2382
|
});
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2383
|
+
});
|
|
2384
|
+
|
|
2385
|
+
this.video.addEventListener('waiting', () => {
|
|
2386
|
+
if (!this.isChangingQuality) {
|
|
2387
|
+
this.showLoading();
|
|
2388
|
+
|
|
2389
|
+
this.triggerEvent('waiting', {
|
|
2390
|
+
currentTime: this.getCurrentTime()
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
});
|
|
2394
|
+
|
|
2395
|
+
this.video.addEventListener('seeking', () => {
|
|
2396
|
+
|
|
2397
|
+
this.triggerEvent('seeking', {
|
|
2398
|
+
currentTime: this.getCurrentTime(),
|
|
2399
|
+
targetTime: this.video.currentTime
|
|
2351
2400
|
});
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2401
|
+
});
|
|
2402
|
+
|
|
2403
|
+
this.video.addEventListener('seeked', () => {
|
|
2404
|
+
|
|
2405
|
+
this.triggerEvent('seeked', {
|
|
2406
|
+
currentTime: this.getCurrentTime()
|
|
2356
2407
|
});
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2408
|
+
});
|
|
2409
|
+
|
|
2410
|
+
|
|
2411
|
+
this.video.addEventListener('loadstart', () => {
|
|
2412
|
+
if (!this.isChangingQuality) {
|
|
2413
|
+
this.showLoading();
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
this.triggerEvent('loadstart');
|
|
2417
|
+
});
|
|
2418
|
+
|
|
2419
|
+
this.video.addEventListener('loadedmetadata', () => {
|
|
2420
|
+
this.updateDuration();
|
|
2421
|
+
|
|
2422
|
+
|
|
2423
|
+
this.triggerEvent('loadedmetadata', {
|
|
2424
|
+
duration: this.getDuration(),
|
|
2425
|
+
videoWidth: this.video.videoWidth,
|
|
2426
|
+
videoHeight: this.video.videoHeight
|
|
2362
2427
|
});
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2428
|
+
|
|
2429
|
+
|
|
2430
|
+
setTimeout(() => {
|
|
2431
|
+
this.initializeSubtitles();
|
|
2432
|
+
}, 100);
|
|
2433
|
+
});
|
|
2434
|
+
|
|
2435
|
+
this.video.addEventListener('loadeddata', () => {
|
|
2436
|
+
if (!this.isChangingQuality) {
|
|
2437
|
+
this.hideLoading();
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
this.triggerEvent('loadeddata', {
|
|
2441
|
+
currentTime: this.getCurrentTime()
|
|
2367
2442
|
});
|
|
2443
|
+
});
|
|
2444
|
+
|
|
2445
|
+
this.video.addEventListener('canplay', () => {
|
|
2446
|
+
if (!this.isChangingQuality) {
|
|
2447
|
+
this.hideLoading();
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
this.triggerEvent('canplay', {
|
|
2451
|
+
currentTime: this.getCurrentTime(),
|
|
2452
|
+
duration: this.getDuration()
|
|
2453
|
+
});
|
|
2454
|
+
});
|
|
2455
|
+
|
|
2456
|
+
this.video.addEventListener('progress', () => {
|
|
2457
|
+
this.updateBuffer();
|
|
2458
|
+
|
|
2459
|
+
this.triggerEvent('progress', {
|
|
2460
|
+
buffered: this.getBufferedTime(),
|
|
2461
|
+
duration: this.getDuration()
|
|
2462
|
+
});
|
|
2463
|
+
});
|
|
2464
|
+
|
|
2465
|
+
this.video.addEventListener('durationchange', () => {
|
|
2466
|
+
this.updateDuration();
|
|
2467
|
+
|
|
2468
|
+
this.triggerEvent('durationchange', {
|
|
2469
|
+
duration: this.getDuration()
|
|
2470
|
+
});
|
|
2471
|
+
});
|
|
2472
|
+
|
|
2473
|
+
|
|
2474
|
+
this.video.addEventListener('error', (e) => {
|
|
2475
|
+
this.onVideoError(e);
|
|
2476
|
+
|
|
2477
|
+
this.triggerEvent('error', {
|
|
2478
|
+
code: this.video.error?.code,
|
|
2479
|
+
message: this.video.error?.message,
|
|
2480
|
+
src: this.video.currentSrc || this.video.src
|
|
2481
|
+
});
|
|
2482
|
+
});
|
|
2483
|
+
|
|
2484
|
+
this.video.addEventListener('stalled', () => {
|
|
2485
|
+
|
|
2486
|
+
this.triggerEvent('stalled', {
|
|
2487
|
+
currentTime: this.getCurrentTime()
|
|
2488
|
+
});
|
|
2489
|
+
});
|
|
2490
|
+
|
|
2491
|
+
|
|
2492
|
+
this.video.addEventListener('timeupdate', () => this.updateProgress());
|
|
2493
|
+
|
|
2494
|
+
this.video.addEventListener('ended', () => this.onVideoEnded());
|
|
2495
|
+
|
|
2368
2496
|
|
|
2369
2497
|
this.video.addEventListener('click', () => {
|
|
2370
2498
|
if (!this.options.pauseClick) return;
|
package/package.json
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
autoplay: options.autoplay !== undefined ? options.autoplay : false,
|
|
15
15
|
showYouTubeUI: options.showYouTubeUI !== undefined ? options.showYouTubeUI : false,
|
|
16
16
|
autoLoadFromData: options.autoLoadFromData !== undefined ? options.autoLoadFromData : true,
|
|
17
|
-
quality: options.quality || '
|
|
17
|
+
quality: options.quality || 'default',
|
|
18
|
+
|
|
18
19
|
enableQualityControl: options.enableQualityControl !== undefined ? options.enableQualityControl : true,
|
|
19
20
|
enableCaptions: options.enableCaptions !== undefined ? options.enableCaptions : true,
|
|
20
21
|
|
|
@@ -31,6 +32,13 @@
|
|
|
31
32
|
...options
|
|
32
33
|
};
|
|
33
34
|
|
|
35
|
+
// Normalize 'auto' to 'default' for YouTube API compatibility
|
|
36
|
+
if (this.options.quality === 'auto') {
|
|
37
|
+
this.options.quality = 'default';
|
|
38
|
+
}
|
|
39
|
+
// Track original user choice for UI display
|
|
40
|
+
this.userQualityChoice = options.quality || 'default';
|
|
41
|
+
|
|
34
42
|
this.ytPlayer = null;
|
|
35
43
|
this.isYouTubeReady = false;
|
|
36
44
|
this.videoId = this.options.videoId;
|
|
@@ -716,6 +724,131 @@ width: fit-content;
|
|
|
716
724
|
this.api.triggerEvent('youtubeplugin:videoloaded', { videoId });
|
|
717
725
|
}
|
|
718
726
|
|
|
727
|
+
setAdaptiveQuality() {
|
|
728
|
+
if (!this.ytPlayer) return;
|
|
729
|
+
|
|
730
|
+
try {
|
|
731
|
+
// Check network connection speed if available
|
|
732
|
+
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
733
|
+
let suggestedQuality = 'default';
|
|
734
|
+
|
|
735
|
+
if (connection) {
|
|
736
|
+
const effectiveType = connection.effectiveType; // '4g', '3g', '2g', 'slow-2g'
|
|
737
|
+
const downlink = connection.downlink; // Mbps
|
|
738
|
+
|
|
739
|
+
if (this.api.player.options.debug) {
|
|
740
|
+
console.log('[YT Plugin] Connection:', effectiveType, 'Downlink:', downlink, 'Mbps');
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Set quality based on connection speed
|
|
744
|
+
if (effectiveType === 'slow-2g' || downlink < 0.5) {
|
|
745
|
+
suggestedQuality = 'small'; // 240p
|
|
746
|
+
} else if (effectiveType === '2g' || downlink < 1) {
|
|
747
|
+
suggestedQuality = 'medium'; // 360p
|
|
748
|
+
} else if (effectiveType === '3g' || downlink < 2.5) {
|
|
749
|
+
suggestedQuality = 'large'; // 480p
|
|
750
|
+
} else if (downlink < 5) {
|
|
751
|
+
suggestedQuality = 'hd720'; // 720p
|
|
752
|
+
} else if (downlink < 10) {
|
|
753
|
+
suggestedQuality = 'hd1080'; // 1080p
|
|
754
|
+
} else if (downlink < 20) {
|
|
755
|
+
suggestedQuality = 'hd1440'; // 1440p (2K)
|
|
756
|
+
} else if (downlink < 35) {
|
|
757
|
+
suggestedQuality = 'hd2160'; // 2160p (4K)
|
|
758
|
+
} else {
|
|
759
|
+
suggestedQuality = 'highres'; // 8K o migliore disponibile
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (this.api.player.options.debug) {
|
|
763
|
+
console.log('[YT Plugin] Setting suggested quality:', suggestedQuality);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
this.ytPlayer.setPlaybackQuality(suggestedQuality);
|
|
767
|
+
} else {
|
|
768
|
+
// Fallback: start with medium quality on unknown devices
|
|
769
|
+
if (this.api.player.options.debug) {
|
|
770
|
+
console.log('[YT Plugin] Connection API not available, using large (480p) as safe default');
|
|
771
|
+
}
|
|
772
|
+
this.ytPlayer.setPlaybackQuality('large'); // 480p come default sicuro
|
|
773
|
+
}
|
|
774
|
+
} catch (error) {
|
|
775
|
+
if (this.api.player.options.debug) {
|
|
776
|
+
console.error('[YT Plugin] Error setting adaptive quality:', error);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
startBufferMonitoring() {
|
|
782
|
+
if (this.bufferMonitorInterval) {
|
|
783
|
+
clearInterval(this.bufferMonitorInterval);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
let bufferingCount = 0;
|
|
787
|
+
let lastState = null;
|
|
788
|
+
|
|
789
|
+
this.bufferMonitorInterval = setInterval(() => {
|
|
790
|
+
if (!this.ytPlayer) return;
|
|
791
|
+
|
|
792
|
+
try {
|
|
793
|
+
const state = this.ytPlayer.getPlayerState();
|
|
794
|
+
|
|
795
|
+
// Detect buffering (state 3)
|
|
796
|
+
if (state === YT.PlayerState.BUFFERING) {
|
|
797
|
+
bufferingCount++;
|
|
798
|
+
|
|
799
|
+
if (this.api.player.options.debug) {
|
|
800
|
+
console.log('[YT Plugin] Buffering detected, count:', bufferingCount);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// If buffering happens too often, reduce quality
|
|
804
|
+
if (bufferingCount >= 3) {
|
|
805
|
+
const currentQuality = this.ytPlayer.getPlaybackQuality();
|
|
806
|
+
const availableQualities = this.ytPlayer.getAvailableQualityLevels();
|
|
807
|
+
|
|
808
|
+
if (this.api.player.options.debug) {
|
|
809
|
+
console.log('[YT Plugin] Too much buffering, current quality:', currentQuality);
|
|
810
|
+
console.log('[YT Plugin] Available qualities:', availableQualities);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Quality hierarchy (highest to lowest)
|
|
814
|
+
const qualityLevels = ['highres', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny'];
|
|
815
|
+
const currentIndex = qualityLevels.indexOf(currentQuality);
|
|
816
|
+
|
|
817
|
+
// Try to go to next lower quality
|
|
818
|
+
if (currentIndex < qualityLevels.length - 1) {
|
|
819
|
+
const lowerQuality = qualityLevels[currentIndex + 1];
|
|
820
|
+
|
|
821
|
+
// Check if lower quality is available
|
|
822
|
+
if (availableQualities.includes(lowerQuality)) {
|
|
823
|
+
if (this.api.player.options.debug) {
|
|
824
|
+
console.log('[YT Plugin] Reducing quality to:', lowerQuality);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
this.ytPlayer.setPlaybackQuality(lowerQuality);
|
|
828
|
+
bufferingCount = 0; // Reset counter
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
} else if (state === YT.PlayerState.PLAYING) {
|
|
833
|
+
// Reset buffering count when playing smoothly
|
|
834
|
+
if (lastState === YT.PlayerState.BUFFERING) {
|
|
835
|
+
setTimeout(() => {
|
|
836
|
+
if (this.ytPlayer.getPlayerState() === YT.PlayerState.PLAYING) {
|
|
837
|
+
bufferingCount = Math.max(0, bufferingCount - 1);
|
|
838
|
+
}
|
|
839
|
+
}, 5000); // Wait 5 seconds of smooth playback
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
lastState = state;
|
|
844
|
+
} catch (error) {
|
|
845
|
+
if (this.api.player.options.debug) {
|
|
846
|
+
console.error('[YT Plugin] Error in buffer monitoring:', error);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}, 1000); // Check every second
|
|
850
|
+
}
|
|
851
|
+
|
|
719
852
|
createMouseMoveOverlay() {
|
|
720
853
|
if (this.mouseMoveOverlay) return;
|
|
721
854
|
|
|
@@ -1010,6 +1143,8 @@ width: fit-content;
|
|
|
1010
1143
|
this.injectYouTubeCSSOverride();
|
|
1011
1144
|
|
|
1012
1145
|
this.syncControls();
|
|
1146
|
+
// Start buffer monitoring for auto quality adjustment
|
|
1147
|
+
this.startBufferMonitoring();
|
|
1013
1148
|
|
|
1014
1149
|
// Hide custom controls when YouTube native UI is enabled
|
|
1015
1150
|
if (this.options.showYouTubeUI) {
|
|
@@ -1051,6 +1186,8 @@ width: fit-content;
|
|
|
1051
1186
|
// Check if this is a live stream
|
|
1052
1187
|
setTimeout(() => this.checkIfLiveStream(), 2000);
|
|
1053
1188
|
setTimeout(() => this.checkIfLiveStream(), 5000);
|
|
1189
|
+
// Set adaptive quality based on connection
|
|
1190
|
+
setTimeout(() => this.setAdaptiveQuality(), 1000);
|
|
1054
1191
|
|
|
1055
1192
|
|
|
1056
1193
|
// Listen for window resize
|
|
@@ -1700,7 +1837,7 @@ width: fit-content;
|
|
|
1700
1837
|
const actualQuality = this.ytPlayer.getPlaybackQuality();
|
|
1701
1838
|
|
|
1702
1839
|
// Only update UI if in AUTO mode, otherwise respect manual selection
|
|
1703
|
-
if (this.
|
|
1840
|
+
if (this.userQualityChoice === 'default' || this.userQualityChoice === 'auto') {
|
|
1704
1841
|
if (actualQuality !== this.currentPlayingQuality) {
|
|
1705
1842
|
this.currentPlayingQuality = actualQuality;
|
|
1706
1843
|
if (this.api.player.options.debug) {
|
|
@@ -1880,6 +2017,8 @@ width: fit-content;
|
|
|
1880
2017
|
if (!this.ytPlayer || !this.ytPlayer.setPlaybackQuality) return false;
|
|
1881
2018
|
|
|
1882
2019
|
try {
|
|
2020
|
+
// Track user's quality choice for display
|
|
2021
|
+
this.userQualityChoice = quality;
|
|
1883
2022
|
if (this.api.player.options.debug) {
|
|
1884
2023
|
console.log('[YT Plugin] Setting quality to:', quality);
|
|
1885
2024
|
console.log('[YT Plugin] Current quality:', this.ytPlayer.getPlaybackQuality());
|
|
@@ -2651,18 +2790,39 @@ width: fit-content;
|
|
|
2651
2790
|
}
|
|
2652
2791
|
|
|
2653
2792
|
onPlayerError(event) {
|
|
2654
|
-
const
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
const
|
|
2662
|
-
|
|
2793
|
+
const errorCode = event.data;
|
|
2794
|
+
|
|
2795
|
+
if (this.api.player.options.debug) {
|
|
2796
|
+
console.error('[YT Plugin] Player error:', errorCode);
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
// Error codes che indicano video non disponibile
|
|
2800
|
+
const unavailableErrors = [
|
|
2801
|
+
2, // Invalid video ID
|
|
2802
|
+
5, // HTML5 player error
|
|
2803
|
+
100, // Video not found / removed
|
|
2804
|
+
101, // Video not allowed to be played in embedded players (private/restricted)
|
|
2805
|
+
150 // Same as 101
|
|
2806
|
+
];
|
|
2807
|
+
|
|
2808
|
+
if (unavailableErrors.includes(errorCode)) {
|
|
2809
|
+
if (this.api.player.options.debug) {
|
|
2810
|
+
console.log('[YT Plugin] Video unavailable, triggering ended event');
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
// Trigger the ended event from your player API
|
|
2814
|
+
this.api.triggerEvent('ended', {
|
|
2815
|
+
reason: 'video_unavailable',
|
|
2816
|
+
errorCode: errorCode
|
|
2817
|
+
});
|
|
2818
|
+
|
|
2819
|
+
// Optional: show poster overlay again
|
|
2820
|
+
this.showPosterOverlay();
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2663
2823
|
this.api.triggerEvent('youtubeplugin:error', {
|
|
2664
|
-
errorCode:
|
|
2665
|
-
|
|
2824
|
+
errorCode: errorCode,
|
|
2825
|
+
videoId: this.videoId
|
|
2666
2826
|
});
|
|
2667
2827
|
}
|
|
2668
2828
|
|
|
@@ -3151,6 +3311,16 @@ width: fit-content;
|
|
|
3151
3311
|
this.ytPlayerContainer = null;
|
|
3152
3312
|
}
|
|
3153
3313
|
|
|
3314
|
+
if (this.bufferMonitorInterval) {
|
|
3315
|
+
clearInterval(this.bufferMonitorInterval);
|
|
3316
|
+
this.bufferMonitorInterval = null;
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
if (this.qualityMonitorInterval) {
|
|
3320
|
+
clearInterval(this.qualityMonitorInterval);
|
|
3321
|
+
this.qualityMonitorInterval = null;
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3154
3324
|
this.removeMouseMoveOverlay();
|
|
3155
3325
|
|
|
3156
3326
|
this.api.container.classList.remove('youtube-active');
|
package/src/core.js
CHANGED
|
@@ -37,6 +37,7 @@ constructor(videoElement, options = {}) {
|
|
|
37
37
|
brandLogoEnabled: false, // Enable/disable brand logo
|
|
38
38
|
brandLogoUrl: '', // URL for brand logo image
|
|
39
39
|
brandLogoLinkUrl: '', // Optional URL to open when clicking the logo
|
|
40
|
+
brandLogoTooltipText: '', // Tooltip text for brand logo
|
|
40
41
|
playlistEnabled: true, // Enable/disable playlist detection
|
|
41
42
|
playlistAutoPlay: true, // Auto-play next video when current ends
|
|
42
43
|
playlistLoop: false, // Loop playlist when reaching the end
|
|
@@ -112,18 +113,42 @@ constructor(videoElement, options = {}) {
|
|
|
112
113
|
|
|
113
114
|
// Custom event system
|
|
114
115
|
this.eventCallbacks = {
|
|
115
|
-
|
|
116
|
-
'
|
|
117
|
-
'
|
|
118
|
-
'
|
|
119
|
-
'
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
'
|
|
123
|
-
'
|
|
124
|
-
'
|
|
125
|
-
'
|
|
126
|
-
|
|
116
|
+
// Core lifecycle events
|
|
117
|
+
'playerready': [], // Fired when player is fully initialized and ready
|
|
118
|
+
'played': [], // Fired when video starts playing
|
|
119
|
+
'paused': [], // Fired when video is paused
|
|
120
|
+
'ended': [], // Fired when video playback ends
|
|
121
|
+
|
|
122
|
+
// Playback state events
|
|
123
|
+
'playing': [], // Fired when video is actually playing (after buffering)
|
|
124
|
+
'waiting': [], // Fired when video is waiting for data (buffering)
|
|
125
|
+
'seeking': [], // Fired when seek operation starts
|
|
126
|
+
'seeked': [], // Fired when seek operation completes
|
|
127
|
+
|
|
128
|
+
// Loading events
|
|
129
|
+
'loadstart': [], // Fired when browser starts looking for media
|
|
130
|
+
'loadedmetadata': [], // Fired when metadata (duration, dimensions) is loaded
|
|
131
|
+
'loadeddata': [], // Fired when data for current frame is loaded
|
|
132
|
+
'canplay': [], // Fired when browser can start playing video
|
|
133
|
+
'progress': [], // Fired periodically while downloading media
|
|
134
|
+
'durationchange': [], // Fired when duration attribute changes
|
|
135
|
+
|
|
136
|
+
// Error events
|
|
137
|
+
'error': [], // Fired when media loading or playback error occurs
|
|
138
|
+
'stalled': [], // Fired when browser is trying to get data but it's not available
|
|
139
|
+
|
|
140
|
+
// Control events
|
|
141
|
+
'timeupdate': [], // Fired when current playback position changes
|
|
142
|
+
'volumechange': [], // Fired when volume or muted state changes
|
|
143
|
+
'speedchange': [], // Fired when playback speed changes
|
|
144
|
+
'qualitychange': [], // Fired when video quality changes
|
|
145
|
+
|
|
146
|
+
// Feature events
|
|
147
|
+
'subtitlechange': [], // Fired when subtitle track changes
|
|
148
|
+
'chapterchange': [], // Fired when video chapter changes
|
|
149
|
+
'pipchange': [], // Fired when picture-in-picture mode changes
|
|
150
|
+
'fullscreenchange': [], // Fired when fullscreen mode changes
|
|
151
|
+
'playlistchange': [] // Fired when playlist item changes
|
|
127
152
|
};
|
|
128
153
|
|
|
129
154
|
// Playlist management
|
|
@@ -485,6 +510,14 @@ markPlayerReady() {
|
|
|
485
510
|
this.container.classList.add('player-initialized');
|
|
486
511
|
}
|
|
487
512
|
|
|
513
|
+
this.triggerEvent('playerready', {
|
|
514
|
+
playerState: this.getPlayerState(),
|
|
515
|
+
qualities: this.qualities,
|
|
516
|
+
subtitles: this.textTracks,
|
|
517
|
+
chapters: this.chapters,
|
|
518
|
+
playlist: this.getPlaylistInfo()
|
|
519
|
+
});
|
|
520
|
+
|
|
488
521
|
if (this.video) {
|
|
489
522
|
this.video.style.visibility = '';
|
|
490
523
|
this.video.style.opacity = '';
|
|
@@ -1346,7 +1379,14 @@ createBrandLogo() {
|
|
|
1346
1379
|
const logo = document.createElement('img');
|
|
1347
1380
|
logo.className = 'brand-logo';
|
|
1348
1381
|
logo.src = this.options.brandLogoUrl;
|
|
1349
|
-
logo.alt =
|
|
1382
|
+
logo.alt = 'Brand logo';
|
|
1383
|
+
|
|
1384
|
+
// Add tooltip ONLY if link URL is present
|
|
1385
|
+
if (this.options.brandLogoLinkUrl) {
|
|
1386
|
+
// Use custom tooltip text if provided, otherwise fallback to URL
|
|
1387
|
+
logo.title = this.options.brandLogoTooltipText || this.options.brandLogoLinkUrl;
|
|
1388
|
+
// NON usare data-tooltip per evitare che venga sovrascritto da updateTooltips()
|
|
1389
|
+
}
|
|
1350
1390
|
|
|
1351
1391
|
// Handle loading error
|
|
1352
1392
|
logo.onerror = () => {
|
|
@@ -1362,7 +1402,7 @@ createBrandLogo() {
|
|
|
1362
1402
|
if (this.options.brandLogoLinkUrl) {
|
|
1363
1403
|
logo.style.cursor = 'pointer';
|
|
1364
1404
|
logo.addEventListener('click', (e) => {
|
|
1365
|
-
e.stopPropagation();
|
|
1405
|
+
e.stopPropagation();
|
|
1366
1406
|
window.open(this.options.brandLogoLinkUrl, '_blank', 'noopener,noreferrer');
|
|
1367
1407
|
if (this.options.debug) console.log('Brand logo clicked, opening:', this.options.brandLogoLinkUrl);
|
|
1368
1408
|
});
|
|
@@ -1370,15 +1410,10 @@ createBrandLogo() {
|
|
|
1370
1410
|
logo.style.cursor = 'default';
|
|
1371
1411
|
}
|
|
1372
1412
|
|
|
1373
|
-
// Position the brand logo at the right of the controlbar (at the left of the buttons)
|
|
1374
1413
|
controlsRight.insertBefore(logo, controlsRight.firstChild);
|
|
1375
1414
|
|
|
1376
1415
|
if (this.options.debug) {
|
|
1377
|
-
|
|
1378
|
-
console.log('Brand logo with click handler created for:', this.options.brandLogoLinkUrl);
|
|
1379
|
-
} else {
|
|
1380
|
-
console.log('Brand logo created (no link)');
|
|
1381
|
-
}
|
|
1416
|
+
console.log('Brand logo created with tooltip:', logo.title || 'no tooltip');
|
|
1382
1417
|
}
|
|
1383
1418
|
}
|
|
1384
1419
|
|
package/src/events.js
CHANGED
|
@@ -165,36 +165,129 @@
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
bindEvents() {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
168
|
+
if (this.video) {
|
|
169
|
+
|
|
170
|
+
// Playback events
|
|
171
|
+
this.video.addEventListener('playing', () => {
|
|
172
|
+
this.hideLoading();
|
|
173
|
+
// Trigger playing event - video is now actually playing
|
|
174
|
+
this.triggerEvent('playing', {
|
|
175
|
+
currentTime: this.getCurrentTime(),
|
|
176
|
+
duration: this.getDuration()
|
|
174
177
|
});
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
this.video.addEventListener('waiting', () => {
|
|
181
|
+
if (!this.isChangingQuality) {
|
|
182
|
+
this.showLoading();
|
|
183
|
+
// Trigger waiting event - video is buffering
|
|
184
|
+
this.triggerEvent('waiting', {
|
|
185
|
+
currentTime: this.getCurrentTime()
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
this.video.addEventListener('seeking', () => {
|
|
191
|
+
// Trigger seeking event - seek operation started
|
|
192
|
+
this.triggerEvent('seeking', {
|
|
193
|
+
currentTime: this.getCurrentTime(),
|
|
194
|
+
targetTime: this.video.currentTime
|
|
181
195
|
});
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
this.video.addEventListener('seeked', () => {
|
|
199
|
+
// Trigger seeked event - seek operation completed
|
|
200
|
+
this.triggerEvent('seeked', {
|
|
201
|
+
currentTime: this.getCurrentTime()
|
|
186
202
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Loading events
|
|
206
|
+
this.video.addEventListener('loadstart', () => {
|
|
207
|
+
if (!this.isChangingQuality) {
|
|
208
|
+
this.showLoading();
|
|
209
|
+
}
|
|
210
|
+
// Trigger loadstart event - browser started loading media
|
|
211
|
+
this.triggerEvent('loadstart');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
this.video.addEventListener('loadedmetadata', () => {
|
|
215
|
+
this.updateDuration();
|
|
216
|
+
|
|
217
|
+
// Trigger loadedmetadata event - video metadata loaded
|
|
218
|
+
this.triggerEvent('loadedmetadata', {
|
|
219
|
+
duration: this.getDuration(),
|
|
220
|
+
videoWidth: this.video.videoWidth,
|
|
221
|
+
videoHeight: this.video.videoHeight
|
|
192
222
|
});
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
223
|
+
|
|
224
|
+
// Initialize subtitles after metadata is loaded
|
|
225
|
+
setTimeout(() => {
|
|
226
|
+
this.initializeSubtitles();
|
|
227
|
+
}, 100);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
this.video.addEventListener('loadeddata', () => {
|
|
231
|
+
if (!this.isChangingQuality) {
|
|
232
|
+
this.hideLoading();
|
|
233
|
+
}
|
|
234
|
+
// Trigger loadeddata event - current frame data loaded
|
|
235
|
+
this.triggerEvent('loadeddata', {
|
|
236
|
+
currentTime: this.getCurrentTime()
|
|
197
237
|
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
this.video.addEventListener('canplay', () => {
|
|
241
|
+
if (!this.isChangingQuality) {
|
|
242
|
+
this.hideLoading();
|
|
243
|
+
}
|
|
244
|
+
// Trigger canplay event - video can start playing
|
|
245
|
+
this.triggerEvent('canplay', {
|
|
246
|
+
currentTime: this.getCurrentTime(),
|
|
247
|
+
duration: this.getDuration()
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
this.video.addEventListener('progress', () => {
|
|
252
|
+
this.updateBuffer();
|
|
253
|
+
// Trigger progress event - browser is downloading media
|
|
254
|
+
this.triggerEvent('progress', {
|
|
255
|
+
buffered: this.getBufferedTime(),
|
|
256
|
+
duration: this.getDuration()
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
this.video.addEventListener('durationchange', () => {
|
|
261
|
+
this.updateDuration();
|
|
262
|
+
// Trigger durationchange event - video duration changed
|
|
263
|
+
this.triggerEvent('durationchange', {
|
|
264
|
+
duration: this.getDuration()
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Error events
|
|
269
|
+
this.video.addEventListener('error', (e) => {
|
|
270
|
+
this.onVideoError(e);
|
|
271
|
+
// Trigger error event - media loading/playback error occurred
|
|
272
|
+
this.triggerEvent('error', {
|
|
273
|
+
code: this.video.error?.code,
|
|
274
|
+
message: this.video.error?.message,
|
|
275
|
+
src: this.video.currentSrc || this.video.src
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
this.video.addEventListener('stalled', () => {
|
|
280
|
+
// Trigger stalled event - browser is trying to fetch data but it's not available
|
|
281
|
+
this.triggerEvent('stalled', {
|
|
282
|
+
currentTime: this.getCurrentTime()
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
this.video.addEventListener('timeupdate', () => this.updateProgress());
|
|
288
|
+
|
|
289
|
+
this.video.addEventListener('ended', () => this.onVideoEnded());
|
|
290
|
+
|
|
198
291
|
// Complete video click logic with doubleTapPause support (DESKTOP)
|
|
199
292
|
this.video.addEventListener('click', () => {
|
|
200
293
|
if (!this.options.pauseClick) return;
|