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.
@@ -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
- 'played': [],
511
- 'paused': [],
512
- 'subtitlechange': [],
513
- 'chapterchange': [],
514
- 'pipchange': [],
515
- 'fullscreenchange': [],
516
- 'speedchange': [],
517
- 'timeupdate': [],
518
- 'volumechange': [],
519
- 'qualitychange': [],
520
- 'playlistchange': [],
521
- 'ended': []
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 = this.t('brand_logo');
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(); // Prevent video controls interference
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
- if (this.options.brandLogoLinkUrl) {
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
- if (this.video) {
2391
- this.video.addEventListener('loadedmetadata', () => {
2392
- this.updateDuration();
2393
- setTimeout(() => {
2394
- this.initializeSubtitles();
2395
- }, 100);
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
- this.video.addEventListener('timeupdate', () => this.updateProgress());
2398
- this.video.addEventListener('progress', () => this.updateBuffer());
2399
- this.video.addEventListener('waiting', () => {
2400
- if (!this.isChangingQuality) {
2401
- this.showLoading();
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
- this.video.addEventListener('canplay', () => {
2405
- if (!this.isChangingQuality) {
2406
- this.hideLoading();
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
- this.video.addEventListener('ended', () => this.onVideoEnded());
2410
- this.video.addEventListener('loadstart', () => {
2411
- if (!this.isChangingQuality) {
2412
- this.showLoading();
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
- this.video.addEventListener('loadeddata', () => {
2416
- if (!this.isChangingQuality) {
2417
- this.hideLoading();
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
- updateTimeDisplay() {
6770
- if (this.currentTimeEl && this.video) {
6771
- this.currentTimeEl.textContent = this.formatTime(this.video.currentTime || 0);
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
- if (this.durationEl && this.video && this.video.duration && !isNaN(this.video.duration)) {
6775
- this.durationEl.textContent = this.formatTime(this.video.duration);
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';