myetv-player 1.0.10 → 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 +145 -2
- package/css/myetv-player.css +69 -0
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +227 -52
- package/dist/myetv-player.min.js +225 -50
- package/package.json +3 -1
- package/plugins/cloudflare/README.md +26 -4
- package/plugins/cloudflare/myetv-player-cloudflare-stream-plugin.js +1273 -217
- package/plugins/facebook/myetv-player-facebook-plugin.js +1340 -164
- package/plugins/twitch/myetv-player-twitch-plugin.js +428 -167
- package/plugins/vimeo/README.md +1 -1
- package/plugins/vimeo/myetv-player-vimeo.js +560 -247
- package/plugins/youtube/README.md +5 -2
- package/plugins/youtube/myetv-player-youtube-plugin.js +572 -116
- package/scss/_controls.scss +53 -0
- package/scss/_title-overlay.scss +27 -0
- package/src/core.js +89 -21
- package/src/events.js +118 -25
- package/src/utils.js +20 -6
package/dist/myetv-player.js
CHANGED
|
@@ -422,6 +422,7 @@ constructor(videoElement, options = {}) {
|
|
|
422
422
|
showSeekTooltip: true,
|
|
423
423
|
showTitleOverlay: false,
|
|
424
424
|
videoTitle: '',
|
|
425
|
+
videoSubtitle: '',
|
|
425
426
|
persistentTitle: false,
|
|
426
427
|
debug: false, // Enable/disable debug logging
|
|
427
428
|
autoplay: false, // if video should autoplay at start
|
|
@@ -432,6 +433,7 @@ constructor(videoElement, options = {}) {
|
|
|
432
433
|
brandLogoEnabled: false, // Enable/disable brand logo
|
|
433
434
|
brandLogoUrl: '', // URL for brand logo image
|
|
434
435
|
brandLogoLinkUrl: '', // Optional URL to open when clicking the logo
|
|
436
|
+
brandLogoTooltipText: '', // Tooltip text for brand logo
|
|
435
437
|
playlistEnabled: true, // Enable/disable playlist detection
|
|
436
438
|
playlistAutoPlay: true, // Auto-play next video when current ends
|
|
437
439
|
playlistLoop: false, // Loop playlist when reaching the end
|
|
@@ -507,18 +509,42 @@ constructor(videoElement, options = {}) {
|
|
|
507
509
|
|
|
508
510
|
// Custom event system
|
|
509
511
|
this.eventCallbacks = {
|
|
510
|
-
|
|
511
|
-
'
|
|
512
|
-
'
|
|
513
|
-
'
|
|
514
|
-
'
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
'
|
|
518
|
-
'
|
|
519
|
-
'
|
|
520
|
-
'
|
|
521
|
-
|
|
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
|
|
522
548
|
};
|
|
523
549
|
|
|
524
550
|
// Playlist management
|
|
@@ -880,6 +906,14 @@ markPlayerReady() {
|
|
|
880
906
|
this.container.classList.add('player-initialized');
|
|
881
907
|
}
|
|
882
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
|
+
|
|
883
917
|
if (this.video) {
|
|
884
918
|
this.video.style.visibility = '';
|
|
885
919
|
this.video.style.opacity = '';
|
|
@@ -978,9 +1012,16 @@ createTitleOverlay() {
|
|
|
978
1012
|
const titleText = document.createElement('h2');
|
|
979
1013
|
titleText.className = 'title-text';
|
|
980
1014
|
titleText.textContent = this.options.videoTitle || '';
|
|
981
|
-
|
|
982
1015
|
overlay.appendChild(titleText);
|
|
983
1016
|
|
|
1017
|
+
// add subtitles
|
|
1018
|
+
if (this.options.videoSubtitle) {
|
|
1019
|
+
const subtitleText = document.createElement('p');
|
|
1020
|
+
subtitleText.className = 'subtitle-text';
|
|
1021
|
+
subtitleText.textContent = this.options.videoSubtitle;
|
|
1022
|
+
overlay.appendChild(subtitleText);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
984
1025
|
if (this.controls) {
|
|
985
1026
|
this.container.insertBefore(overlay, this.controls);
|
|
986
1027
|
} else {
|
|
@@ -1054,6 +1095,31 @@ getVideoTitle() {
|
|
|
1054
1095
|
return this.options.videoTitle;
|
|
1055
1096
|
}
|
|
1056
1097
|
|
|
1098
|
+
setVideoSubtitle(subtitle) {
|
|
1099
|
+
this.options.videoSubtitle = subtitle || '';
|
|
1100
|
+
|
|
1101
|
+
if (this.titleOverlay) {
|
|
1102
|
+
let subtitleElement = this.titleOverlay.querySelector('.subtitle-text');
|
|
1103
|
+
|
|
1104
|
+
if (subtitle) {
|
|
1105
|
+
if (!subtitleElement) {
|
|
1106
|
+
subtitleElement = document.createElement('p');
|
|
1107
|
+
subtitleElement.className = 'subtitle-text';
|
|
1108
|
+
this.titleOverlay.appendChild(subtitleElement);
|
|
1109
|
+
}
|
|
1110
|
+
subtitleElement.textContent = subtitle;
|
|
1111
|
+
} else if (subtitleElement) {
|
|
1112
|
+
subtitleElement.remove();
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
return this;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
getVideoSubtitle() {
|
|
1120
|
+
return this.options.videoSubtitle;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1057
1123
|
setPersistentTitle(persistent) {
|
|
1058
1124
|
this.options.persistentTitle = persistent;
|
|
1059
1125
|
|
|
@@ -1709,7 +1775,14 @@ createBrandLogo() {
|
|
|
1709
1775
|
const logo = document.createElement('img');
|
|
1710
1776
|
logo.className = 'brand-logo';
|
|
1711
1777
|
logo.src = this.options.brandLogoUrl;
|
|
1712
|
-
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
|
+
}
|
|
1713
1786
|
|
|
1714
1787
|
// Handle loading error
|
|
1715
1788
|
logo.onerror = () => {
|
|
@@ -1725,7 +1798,7 @@ createBrandLogo() {
|
|
|
1725
1798
|
if (this.options.brandLogoLinkUrl) {
|
|
1726
1799
|
logo.style.cursor = 'pointer';
|
|
1727
1800
|
logo.addEventListener('click', (e) => {
|
|
1728
|
-
e.stopPropagation();
|
|
1801
|
+
e.stopPropagation();
|
|
1729
1802
|
window.open(this.options.brandLogoLinkUrl, '_blank', 'noopener,noreferrer');
|
|
1730
1803
|
if (this.options.debug) console.log('Brand logo clicked, opening:', this.options.brandLogoLinkUrl);
|
|
1731
1804
|
});
|
|
@@ -1733,15 +1806,10 @@ createBrandLogo() {
|
|
|
1733
1806
|
logo.style.cursor = 'default';
|
|
1734
1807
|
}
|
|
1735
1808
|
|
|
1736
|
-
// Position the brand logo at the right of the controlbar (at the left of the buttons)
|
|
1737
1809
|
controlsRight.insertBefore(logo, controlsRight.firstChild);
|
|
1738
1810
|
|
|
1739
1811
|
if (this.options.debug) {
|
|
1740
|
-
|
|
1741
|
-
console.log('Brand logo with click handler created for:', this.options.brandLogoLinkUrl);
|
|
1742
|
-
} else {
|
|
1743
|
-
console.log('Brand logo created (no link)');
|
|
1744
|
-
}
|
|
1812
|
+
console.log('Brand logo created with tooltip:', logo.title || 'no tooltip');
|
|
1745
1813
|
}
|
|
1746
1814
|
}
|
|
1747
1815
|
|
|
@@ -2387,36 +2455,129 @@ addEventListener(eventType, callback) {
|
|
|
2387
2455
|
}
|
|
2388
2456
|
|
|
2389
2457
|
bindEvents() {
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
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()
|
|
2396
2467
|
});
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
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
|
|
2403
2485
|
});
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2486
|
+
});
|
|
2487
|
+
|
|
2488
|
+
this.video.addEventListener('seeked', () => {
|
|
2489
|
+
// Trigger seeked event - seek operation completed
|
|
2490
|
+
this.triggerEvent('seeked', {
|
|
2491
|
+
currentTime: this.getCurrentTime()
|
|
2408
2492
|
});
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
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
|
|
2414
2512
|
});
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
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()
|
|
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()
|
|
2419
2573
|
});
|
|
2574
|
+
});
|
|
2575
|
+
|
|
2576
|
+
|
|
2577
|
+
this.video.addEventListener('timeupdate', () => this.updateProgress());
|
|
2578
|
+
|
|
2579
|
+
this.video.addEventListener('ended', () => this.onVideoEnded());
|
|
2580
|
+
|
|
2420
2581
|
// Complete video click logic with doubleTapPause support (DESKTOP)
|
|
2421
2582
|
this.video.addEventListener('click', () => {
|
|
2422
2583
|
if (!this.options.pauseClick) return;
|
|
@@ -6766,15 +6927,29 @@ getBufferedTime() {
|
|
|
6766
6927
|
this.video.currentTime = Math.max(0, Math.min(this.video.duration, this.video.currentTime + seconds));
|
|
6767
6928
|
}
|
|
6768
6929
|
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6930
|
+
updateTimeDisplay() {
|
|
6931
|
+
// update current time
|
|
6932
|
+
if (this.currentTimeEl && this.video) {
|
|
6933
|
+
this.currentTimeEl.textContent = this.formatTime(this.video.currentTime || 0);
|
|
6934
|
+
}
|
|
6935
|
+
|
|
6936
|
+
// update duration or show badge if encoding
|
|
6937
|
+
if (this.durationEl && this.video) {
|
|
6938
|
+
const duration = this.video.duration;
|
|
6773
6939
|
|
|
6774
|
-
|
|
6775
|
-
|
|
6940
|
+
// check if duration is valid
|
|
6941
|
+
if (!duration || isNaN(duration) || !isFinite(duration)) {
|
|
6942
|
+
// Video in encoding - show badge instead of duration
|
|
6943
|
+
this.durationEl.innerHTML = '<span class="encoding-badge">Encoding in progress</span>';
|
|
6944
|
+
this.durationEl.classList.add('encoding-state');
|
|
6945
|
+
} else {
|
|
6946
|
+
// valid duration - show normal
|
|
6947
|
+
this.durationEl.textContent = this.formatTime(duration);
|
|
6948
|
+
this.durationEl.classList.remove('encoding-state');
|
|
6776
6949
|
}
|
|
6777
6950
|
}
|
|
6951
|
+
}
|
|
6952
|
+
|
|
6778
6953
|
|
|
6779
6954
|
formatTime(seconds) {
|
|
6780
6955
|
if (isNaN(seconds) || seconds < 0) return '0:00';
|