myetv-player 1.0.8 → 1.1.0

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.
@@ -408,6 +408,7 @@ constructor(videoElement, options = {}) {
408
408
  showSeekTooltip: true,
409
409
  showTitleOverlay: false,
410
410
  videoTitle: '',
411
+ videoSubtitle: '',
411
412
  persistentTitle: false,
412
413
  debug: false,
413
414
  autoplay: false,
@@ -964,9 +965,16 @@ createTitleOverlay() {
964
965
  const titleText = document.createElement('h2');
965
966
  titleText.className = 'title-text';
966
967
  titleText.textContent = this.options.videoTitle || '';
967
-
968
968
  overlay.appendChild(titleText);
969
969
 
970
+
971
+ if (this.options.videoSubtitle) {
972
+ const subtitleText = document.createElement('p');
973
+ subtitleText.className = 'subtitle-text';
974
+ subtitleText.textContent = this.options.videoSubtitle;
975
+ overlay.appendChild(subtitleText);
976
+ }
977
+
970
978
  if (this.controls) {
971
979
  this.container.insertBefore(overlay, this.controls);
972
980
  } else {
@@ -1040,6 +1048,31 @@ getVideoTitle() {
1040
1048
  return this.options.videoTitle;
1041
1049
  }
1042
1050
 
1051
+ setVideoSubtitle(subtitle) {
1052
+ this.options.videoSubtitle = subtitle || '';
1053
+
1054
+ if (this.titleOverlay) {
1055
+ let subtitleElement = this.titleOverlay.querySelector('.subtitle-text');
1056
+
1057
+ if (subtitle) {
1058
+ if (!subtitleElement) {
1059
+ subtitleElement = document.createElement('p');
1060
+ subtitleElement.className = 'subtitle-text';
1061
+ this.titleOverlay.appendChild(subtitleElement);
1062
+ }
1063
+ subtitleElement.textContent = subtitle;
1064
+ } else if (subtitleElement) {
1065
+ subtitleElement.remove();
1066
+ }
1067
+ }
1068
+
1069
+ return this;
1070
+ }
1071
+
1072
+ getVideoSubtitle() {
1073
+ return this.options.videoSubtitle;
1074
+ }
1075
+
1043
1076
  setPersistentTitle(persistent) {
1044
1077
  this.options.persistentTitle = persistent;
1045
1078
 
@@ -1510,14 +1543,19 @@ seek(e) {
1510
1543
  if (!this.video || !this.progressContainer || !this.progressFilled || !this.progressHandle || this.isChangingQuality) return;
1511
1544
 
1512
1545
  const rect = this.progressContainer.getBoundingClientRect();
1513
- const clickX = e.clientX - rect.left;
1546
+
1547
+
1548
+ const clientX = e.clientX !== undefined ? e.clientX : (e.touches && e.touches[0] ? e.touches[0].clientX : (e.changedTouches && e.changedTouches[0] ? e.changedTouches[0].clientX : 0));
1549
+
1550
+ const clickX = clientX - rect.left;
1514
1551
  const percentage = Math.max(0, Math.min(1, clickX / rect.width));
1515
1552
 
1516
1553
  if (this.video.duration && !isNaN(this.video.duration)) {
1517
1554
  this.video.currentTime = percentage * this.video.duration;
1518
- const progress = percentage * 100;
1519
- this.progressFilled.style.width = progress + '%';
1520
- this.progressHandle.style.left = progress + '%';
1555
+
1556
+ const progress = `${percentage * 100}%`;
1557
+ this.progressFilled.style.width = progress;
1558
+ this.progressHandle.style.left = progress;
1521
1559
  }
1522
1560
  }
1523
1561
 
@@ -2429,12 +2467,28 @@ addEventListener(eventType, callback) {
2429
2467
  });
2430
2468
  }
2431
2469
 
2432
- if (this.progressContainer) {
2433
- this.progressContainer.addEventListener('click', (e) => this.seek(e));
2434
- this.progressContainer.addEventListener('mousedown', (e) => this.startSeeking(e));
2435
- }
2470
+ if (this.progressContainer) {
2471
+
2472
+ this.progressContainer.addEventListener('click', (e) => this.seek(e));
2473
+ this.progressContainer.addEventListener('mousedown', (e) => this.startSeeking(e));
2474
+
2475
+
2476
+ this.progressContainer.addEventListener('touchstart', (e) => {
2477
+ e.preventDefault();
2478
+ this.startSeeking(e);
2479
+ }, { passive: false });
2436
2480
 
2437
2481
  this.setupSeekTooltip();
2482
+ }
2483
+
2484
+
2485
+ if (this.progressHandle) {
2486
+ this.progressHandle.addEventListener('touchstart', (e) => {
2487
+ e.preventDefault();
2488
+ e.stopPropagation();
2489
+ this.startSeeking(e);
2490
+ }, { passive: false });
2491
+ }
2438
2492
 
2439
2493
 
2440
2494
 
@@ -2455,7 +2509,19 @@ addEventListener(eventType, callback) {
2455
2509
  document.addEventListener('mozfullscreenchange', () => this.updateFullscreenButton());
2456
2510
 
2457
2511
  document.addEventListener('mousemove', (e) => this.continueSeeking(e));
2458
- document.addEventListener('mouseup', () => this.endSeeking());
2512
+ document.addEventListener('mouseup', () => this.endSeeking());
2513
+
2514
+
2515
+ document.addEventListener('touchmove', (e) => {
2516
+ if (this.isUserSeeking) {
2517
+ e.preventDefault();
2518
+ this.continueSeeking(e);
2519
+ }
2520
+ }, { passive: false });
2521
+
2522
+ document.addEventListener('touchend', () => this.endSeeking());
2523
+ document.addEventListener('touchcancel', () => this.endSeeking());
2524
+
2459
2525
  }
2460
2526
 
2461
2527
 
@@ -2603,6 +2669,11 @@ showControlsNow() {
2603
2669
 
2604
2670
  if (this.container) {
2605
2671
  this.container.classList.add('has-controls');
2672
+ this.updateControlbarHeight();
2673
+
2674
+ if (this.updateWatermarkPosition) {
2675
+ this.updateWatermarkPosition();
2676
+ }
2606
2677
  }
2607
2678
 
2608
2679
 
@@ -2616,24 +2687,34 @@ showControlsNow() {
2616
2687
  hideControlsNow() {
2617
2688
 
2618
2689
  const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
2690
+
2619
2691
  if (this.mouseOverControls && !isTouchDevice) {
2620
- if (this.autoHideDebug && this.options.debug) console.log('⏸️ Not hiding - mouse still over controls');
2692
+ if (this.autoHideDebug && this.options.debug) {
2693
+ console.log('🚫 Not hiding - mouse still over controls');
2694
+ }
2621
2695
  return;
2622
2696
  }
2623
2697
 
2624
2698
 
2625
2699
  if (this.video && this.video.paused) {
2626
- if (this.autoHideDebug && this.options.debug) console.log('⏸️ Not hiding - video is paused');
2700
+ if (this.autoHideDebug && this.options.debug) {
2701
+ console.log('🚫 Not hiding - video is paused');
2702
+ }
2627
2703
  return;
2628
2704
  }
2629
2705
 
2630
2706
  if (this.controls) {
2631
2707
  this.controls.classList.remove('show');
2632
- }
2633
2708
 
2634
-
2635
- if (this.container) {
2636
- this.container.classList.remove('has-controls');
2709
+
2710
+ if (this.container) {
2711
+ this.container.classList.remove('has-controls');
2712
+ this.updateControlbarHeight();
2713
+
2714
+ if (this.updateWatermarkPosition) {
2715
+ this.updateWatermarkPosition();
2716
+ }
2717
+ }
2637
2718
  }
2638
2719
 
2639
2720
 
@@ -2641,7 +2722,9 @@ hideControlsNow() {
2641
2722
  this.hideTitleOverlay();
2642
2723
  }
2643
2724
 
2644
- if (this.autoHideDebug && this.options.debug) console.log('❌ Controls hidden');
2725
+ if (this.autoHideDebug && this.options.debug) {
2726
+ console.log('👁️ Controls hidden');
2727
+ }
2645
2728
  }
2646
2729
 
2647
2730
  showControls() {
@@ -2919,6 +3002,7 @@ createControls() {
2919
3002
 
2920
3003
  setTimeout(() => {
2921
3004
  this.initializeResponsiveMenu();
3005
+ this.updateControlbarHeight();
2922
3006
  }, 100);
2923
3007
  }
2924
3008
 
@@ -2932,14 +3016,46 @@ initializeResponsiveMenu() {
2932
3016
 
2933
3017
  this.checkScreenSize();
2934
3018
 
2935
- window.addEventListener('resize', () => {
3019
+
3020
+ const resizeHandler = () => {
2936
3021
  this.checkScreenSize();
2937
- });
3022
+ this.updateControlbarHeight();
3023
+ };
3024
+
3025
+
3026
+ this.resizeHandler = resizeHandler.bind(this);
3027
+ window.addEventListener('resize', this.resizeHandler);
2938
3028
 
2939
3029
 
2940
3030
  this.bindSettingsMenuEvents();
2941
3031
  }
2942
3032
 
3033
+ updateControlbarHeight() {
3034
+ if (!this.controls) return;
3035
+
3036
+ const height = this.controls.offsetHeight;
3037
+ if (this.container) {
3038
+
3039
+ this.container.style.setProperty('--player-controls-height', `${height}px`);
3040
+
3041
+ const watermark = this.container.querySelector('.video-watermark.watermark-bottomleft, .video-watermark.watermark-bottomright');
3042
+ if (watermark) {
3043
+ const hasControls = this.container.classList.contains('has-controls');
3044
+ const isHideOnAutoHide = watermark.classList.contains('hide-on-autohide');
3045
+
3046
+ if (hasControls || !isHideOnAutoHide) {
3047
+ watermark.style.bottom = `${height + 15}px`;
3048
+ } else {
3049
+ watermark.style.bottom = '15px';
3050
+ }
3051
+ }
3052
+ }
3053
+
3054
+ if (this.options.debug) {
3055
+ console.log(`Controlbar height updated: ${height}px`);
3056
+ }
3057
+ }
3058
+
2943
3059
 
2944
3060
  getResponsiveThreshold() {
2945
3061
 
@@ -2959,7 +3075,7 @@ checkScreenSize() {
2959
3075
  this.updateSettingsMenuVisibility();
2960
3076
 
2961
3077
  if (this.options.debug) {
2962
- console.log(`Screen check: ${window.innerWidth}px vs ${threshold}px threshold (logo: ${this.options.brandLogoEnabled}), small: ${this.isSmallScreen}`);
3078
+ console.log(`Screen check: ${window.innerWidth}px vs ${threshold}px (threshold), logo: ${this.options.brandLogoEnabled}, small: ${this.isSmallScreen}`);
2963
3079
  }
2964
3080
  }
2965
3081
  }
@@ -5621,8 +5737,18 @@ initializeWatermark() {
5621
5737
  }
5622
5738
 
5623
5739
 
5740
+
5624
5741
  this.watermarkElement = watermark;
5625
5742
 
5743
+
5744
+ this.updateWatermarkPosition();
5745
+
5746
+
5747
+ this.watermarkResizeHandler = () => {
5748
+ this.updateWatermarkPosition();
5749
+ };
5750
+ window.addEventListener('resize', this.watermarkResizeHandler);
5751
+
5626
5752
  if (this.options.debug) {
5627
5753
  console.log('🏷️ Watermark created:', {
5628
5754
  url: this.options.watermarkUrl,
@@ -5635,6 +5761,7 @@ initializeWatermark() {
5635
5761
  }
5636
5762
 
5637
5763
 
5764
+
5638
5765
  setWatermark(url, link = '', position = 'bottomright', title = '') {
5639
5766
 
5640
5767
  this.options.watermarkUrl = url;
@@ -5663,6 +5790,12 @@ removeWatermark() {
5663
5790
  this.watermarkElement = null;
5664
5791
  }
5665
5792
 
5793
+
5794
+ if (this.watermarkResizeHandler) {
5795
+ window.removeEventListener('resize', this.watermarkResizeHandler);
5796
+ this.watermarkResizeHandler = null;
5797
+ }
5798
+
5666
5799
  this.options.watermarkUrl = '';
5667
5800
  this.options.watermarkLink = '';
5668
5801
  this.options.watermarkPosition = 'bottomright';
@@ -5674,6 +5807,7 @@ removeWatermark() {
5674
5807
  }
5675
5808
 
5676
5809
 
5810
+
5677
5811
  setWatermarkPosition(position) {
5678
5812
  if (!['topleft', 'topright', 'bottomleft', 'bottomright'].includes(position)) {
5679
5813
  if (this.options.debug) console.warn('🏷️ Invalid watermark position:', position);
@@ -5701,6 +5835,36 @@ setWatermarkPosition(position) {
5701
5835
  }
5702
5836
 
5703
5837
 
5838
+ updateWatermarkPosition() {
5839
+ if (!this.watermarkElement) return;
5840
+ if (!this.controls) return;
5841
+
5842
+ const position = this.options.watermarkPosition || 'bottomright';
5843
+
5844
+
5845
+ if (position === 'bottomleft' || position === 'bottomright') {
5846
+ const controlsHeight = this.controls.offsetHeight;
5847
+ const spacing = 15;
5848
+ const bottomValue = controlsHeight + spacing;
5849
+
5850
+
5851
+ const hasControls = this.container.classList.contains('has-controls');
5852
+
5853
+ if (hasControls || !this.options.hideWatermark) {
5854
+
5855
+ this.watermarkElement.style.bottom = `${bottomValue}px`;
5856
+ } else {
5857
+
5858
+ this.watermarkElement.style.bottom = '15px';
5859
+ }
5860
+
5861
+ if (this.options.debug) {
5862
+ console.log(`🏷️ Watermark position updated: bottom ${this.watermarkElement.style.bottom}`);
5863
+ }
5864
+ }
5865
+ }
5866
+
5867
+
5704
5868
  setWatermarkAutoHide(hide) {
5705
5869
  this.options.hideWatermark = hide;
5706
5870
 
@@ -6386,15 +6550,29 @@ getBufferedTime() {
6386
6550
  this.video.currentTime = Math.max(0, Math.min(this.video.duration, this.video.currentTime + seconds));
6387
6551
  }
6388
6552
 
6389
- updateTimeDisplay() {
6390
- if (this.currentTimeEl && this.video) {
6391
- this.currentTimeEl.textContent = this.formatTime(this.video.currentTime || 0);
6392
- }
6553
+ updateTimeDisplay() {
6554
+
6555
+ if (this.currentTimeEl && this.video) {
6556
+ this.currentTimeEl.textContent = this.formatTime(this.video.currentTime || 0);
6557
+ }
6558
+
6559
+
6560
+ if (this.durationEl && this.video) {
6561
+ const duration = this.video.duration;
6393
6562
 
6394
- if (this.durationEl && this.video && this.video.duration && !isNaN(this.video.duration)) {
6395
- this.durationEl.textContent = this.formatTime(this.video.duration);
6563
+
6564
+ if (!duration || isNaN(duration) || !isFinite(duration)) {
6565
+
6566
+ this.durationEl.innerHTML = '<span class="encoding-badge">Encoding in progress</span>';
6567
+ this.durationEl.classList.add('encoding-state');
6568
+ } else {
6569
+
6570
+ this.durationEl.textContent = this.formatTime(duration);
6571
+ this.durationEl.classList.remove('encoding-state');
6396
6572
  }
6397
6573
  }
6574
+ }
6575
+
6398
6576
 
6399
6577
  formatTime(seconds) {
6400
6578
  if (isNaN(seconds) || seconds < 0) return '0:00';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myetv-player",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "MYETV Video Player - Modular JavaScript and SCSS Build System",
5
5
  "main": "dist/myetv-player.js",
6
6
  "scripts": {
@@ -31,3 +31,5 @@
31
31
 
32
32
 
33
33
 
34
+
35
+
@@ -23,10 +23,12 @@ Official Cloudflare Stream integration plugin for MYETV Video Player. Embed vide
23
23
  ## Features
24
24
 
25
25
  - **Cloudflare Stream Integration**: Full support for Cloudflare Stream videos
26
+ - **DASH/HLS** adaptive streaming ready with full support and libraries hosted on cdnjs.com
26
27
  - **Private Videos**: Support for signed URLs and private video access
27
28
  - **Live Streaming**: Real-time live stream playback
28
29
  - **Player Customization**: Custom colors, poster images, and branding
29
30
  - **Auto-Detection**: Automatically detects Cloudflare Stream URLs
31
+ - **Auto-Quality**: Automatically detect video quality (also for hls/dash)
30
32
  - **Complete API**: Full control over playback, volume, seeking
31
33
  - **Analytics Ready**: Works with Cloudflare Analytics
32
34
  - **Ad Support**: VAST ad tag integration
@@ -122,6 +124,7 @@ const player = new MYETVPlayer('myVideo', {
122
124
  preload: 'metadata', // 'none', 'metadata', 'auto'
123
125
  controls: true, // Show player controls
124
126
  startTime: 0, // Start position in seconds
127
+ defaultQuality: 'auto', // the default quality of the video (auto, 720p, 480p, 360p ecc.)
125
128
 
126
129
  // ========== Player Customization ==========
127
130
  poster: 'https://example.com/poster.jpg', // Custom poster image
@@ -272,15 +275,34 @@ plugins: {
272
275
  });
273
276
  </script>
274
277
  ```
275
-
276
278
  **Supported URL Formats:**
277
- - `https://iframe.videodelivery.net/VIDEO_ID`
278
- - `https://customer-CODE.cloudflarestream.com/VIDEO_ID/iframe`
279
+ - `https://iframe.videodelivery.net/VIDEO_ID` (iframe)
280
+ - `https://customer-[code].cloudflarestream.com/VIDEO_ID/iframe` (iframe)
279
281
  - `https://videodelivery.net/VIDEO_ID`
280
282
 
283
+ ### Method 4: HLS / DASH Adaptive Streaming
284
+
285
+ ```html
286
+ <video id="myVideo" class="video-player"></video>
287
+
288
+ <script>
289
+ const player = new MYETVPlayer('myVideo', {
290
+ plugins: {
291
+ cloudflare: {
292
+ manifestUrl: 'https://customer-[code].cloudflarestream.com/VIDEO_ID/manifest/video.m3u8'
293
+ }
294
+ }
295
+ });
296
+ </script>
297
+ ```
298
+
299
+ **Supported URL Formats:**
300
+ - `https://customer-[code].cloudflarestream.com/VIDEO_ID/manifest/video.m3u8` (hls)
301
+ - `https://customer-[code].cloudflarestream.com/VIDEO_ID/manifest/video.mpd` (dash)
302
+
281
303
  ---
282
304
 
283
- ### Method 4: Load Dynamically
305
+ ### Method 5: Load Dynamically
284
306
 
285
307
  ```html
286
308
  <video id="myVideo" class="video-player"></video>