myetv-player 1.1.1 → 1.1.3

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/src/controls.js CHANGED
@@ -368,13 +368,13 @@ createControls() {
368
368
  const controlsHTML = `
369
369
  <div class="controls" id="${controlsId}">
370
370
  <div class="progress-container">
371
- <div class="progress-bar">
372
- <div class="progress-buffer"></div>
373
- <div class="progress-filled"></div>
374
- <div class="progress-handle progress-handle-${this.options.seekHandleShape}"></div>
375
- </div>
376
- ${this.options.showSeekTooltip ? '<div class="seek-tooltip">0:00</div>' : ''}
377
- </div>
371
+ <div class="progress-bar">
372
+ <div class="progress-buffer"></div>
373
+ <div class="progress-filled"></div>
374
+ </div>
375
+ <div class="progress-handle progress-handle-${this.options.seekHandleShape}"></div> <!-- ✅ Fuori da progress-bar -->
376
+ ${this.options.showSeekTooltip ? '<div class="seek-tooltip">0:00</div>' : ''}
377
+ </div>
378
378
 
379
379
  <div class="controls-main">
380
380
  <div class="controls-left">
package/src/core.js CHANGED
@@ -70,7 +70,6 @@ constructor(videoElement, options = {}) {
70
70
  this.currentQualityIndex = 0;
71
71
  this.qualities = [];
72
72
  this.originalSources = [];
73
- this.setupMenuToggles(); // Initialize menu toggle system
74
73
  this.isPiPSupported = this.checkPiPSupport();
75
74
  this.seekTooltip = null;
76
75
  this.titleOverlay = null;
@@ -182,6 +181,7 @@ constructor(videoElement, options = {}) {
182
181
  this.interceptAutoLoading();
183
182
  this.createPlayerStructure();
184
183
  this.initializeElements();
184
+ this.setupMenuToggles(); // Initialize menu toggle system
185
185
  // audio player adaptation
186
186
  this.adaptToAudioFile = function () {
187
187
  if (this.options.audiofile) {
@@ -798,74 +798,84 @@ initializeElements() {
798
798
  this.speedMenu = this.controls?.querySelector('.speed-menu');
799
799
  this.qualityMenu = this.controls?.querySelector('.quality-menu');
800
800
  this.subtitlesMenu = this.controls?.querySelector('.subtitles-menu');
801
+ // Apply seek handle shape from options
802
+ if (this.progressHandle && this.options.seekHandleShape) {
803
+ this.setSeekHandleShape(this.options.seekHandleShape);
804
+ }
801
805
  }
802
806
 
803
807
  // Generic method to close all active menus (works with plugins too)
804
808
  closeAllMenus() {
805
- // Find all elements with class ending in '-menu' that have 'active' class
806
- const allMenus = this.controls?.querySelectorAll('[class*="-menu"].active');
807
- allMenus?.forEach(menu => {
808
- menu.classList.remove('active');
809
- });
809
+ if (!this.controls) return;
810
810
 
811
- // Remove active state from all control buttons
812
- const allButtons = this.controls?.querySelectorAll('.control-btn.active');
813
- allButtons?.forEach(btn => {
814
- btn.classList.remove('active');
815
- });
811
+ const menus = this.controls.querySelectorAll('.speed-menu, .quality-menu, .subtitles-menu, .settings-menu');
812
+ const buttons = this.controls.querySelectorAll('.control-btn');
813
+
814
+ menus.forEach(menu => menu.classList.remove('active'));
815
+ buttons.forEach(btn => btn.classList.remove('active'));
816
+
817
+ this.currentOpenMenu = null;
818
+
819
+ if (this.options.debug) {
820
+ console.log('All menus closed');
821
+ }
816
822
  }
817
823
 
818
824
  // Generic menu toggle setup (works with core menus and plugin menus)
819
825
  setupMenuToggles() {
820
- // Delegate click events to control bar for any button with associated menu
821
- if (this.controls) {
822
- this.controls.addEventListener('click', (e) => {
823
- // Find if clicked element is a control button or inside one
824
- const button = e.target.closest('.control-btn');
825
-
826
- if (!button) return;
827
-
828
- // Get button classes to find associated menu
829
- const buttonClasses = button.className.split(' ');
830
- let menuClass = null;
831
-
832
- // Find if this button has an associated menu (e.g., speed-btn -> speed-menu)
833
- for (const cls of buttonClasses) {
834
- if (cls.endsWith('-btn')) {
835
- const menuName = cls.replace('-btn', '-menu');
836
- const menu = this.controls.querySelector('.' + menuName);
837
- if (menu) {
838
- menuClass = menuName;
839
- break;
840
- }
841
- }
826
+ if (!this.controls) return;
827
+
828
+ this.currentOpenMenu = null;
829
+
830
+ this.controls.addEventListener('click', (e) => {
831
+ const button = e.target.closest('.control-btn');
832
+ if (!button) return;
833
+
834
+ const buttonClasses = Array.from(button.classList);
835
+ let menuElement = null;
836
+
837
+ for (const cls of buttonClasses) {
838
+ if (cls.endsWith('-btn')) {
839
+ const menuClass = cls.replace('-btn', '-menu');
840
+ menuElement = this.controls.querySelector(`.${menuClass}`);
841
+ if (menuElement) break;
842
842
  }
843
+ }
843
844
 
844
- if (!menuClass) return;
845
+ if (!menuElement) return;
845
846
 
846
- e.stopPropagation();
847
+ e.stopPropagation();
848
+ e.preventDefault();
847
849
 
848
- // Get the menu element
849
- const menu = this.controls.querySelector('.' + menuClass);
850
- const isOpen = menu.classList.contains('active');
850
+ const isOpen = menuElement.classList.contains('active');
851
851
 
852
- // Close all menus first
853
- this.closeAllMenus();
852
+ this.closeAllMenus();
854
853
 
855
- // If menu was closed, open it
856
- if (!isOpen) {
857
- menu.classList.add('active');
858
- button.classList.add('active');
854
+ if (!isOpen) {
855
+ menuElement.classList.add('active');
856
+ button.classList.add('active');
857
+ this.currentOpenMenu = menuElement;
858
+ if (this.options.debug) {
859
+ console.log('Menu opened:', menuElement.className);
859
860
  }
860
- });
861
- }
861
+ } else {
862
+ this.currentOpenMenu = null;
863
+ if (this.options.debug) {
864
+ console.log('Menu closed:', menuElement.className);
865
+ }
866
+ }
867
+ });
862
868
 
863
- // Close menus when clicking outside controls
864
869
  document.addEventListener('click', (e) => {
865
- if (!this.controls?.contains(e.target)) {
870
+ if (!this.controls) return;
871
+ if (!this.controls.contains(e.target)) {
866
872
  this.closeAllMenus();
867
873
  }
868
874
  });
875
+
876
+ if (this.options.debug) {
877
+ console.log('✅ Menu toggle system initialized (click-based, auto-close)');
878
+ }
869
879
  }
870
880
 
871
881
  updateVolumeSliderVisual() {
@@ -1176,9 +1186,11 @@ updateBuffer() {
1176
1186
  }
1177
1187
 
1178
1188
  startSeeking(e) {
1189
+ if (e.cancelable) e.preventDefault();
1179
1190
  if (this.isChangingQuality) return;
1180
1191
 
1181
1192
  this.isUserSeeking = true;
1193
+ this.progressContainer.classList.add('seeking');
1182
1194
  this.seek(e);
1183
1195
  e.preventDefault();
1184
1196
 
@@ -1190,6 +1202,7 @@ startSeeking(e) {
1190
1202
  }
1191
1203
 
1192
1204
  continueSeeking(e) {
1205
+ if (e.cancelable) e.preventDefault();
1193
1206
  if (this.isUserSeeking && !this.isChangingQuality) {
1194
1207
  this.seek(e);
1195
1208
  }
@@ -1197,9 +1210,13 @@ continueSeeking(e) {
1197
1210
 
1198
1211
  endSeeking() {
1199
1212
  this.isUserSeeking = false;
1213
+ this.progressContainer.classList.remove('seeking');
1200
1214
  }
1201
1215
 
1202
1216
  seek(e) {
1217
+ if (e.cancelable) {
1218
+ e.preventDefault();
1219
+ }
1203
1220
  if (!this.video || !this.progressContainer || !this.progressFilled || !this.progressHandle || this.isChangingQuality) return;
1204
1221
 
1205
1222
  const rect = this.progressContainer.getBoundingClientRect();
@@ -1620,7 +1637,7 @@ bindPosterEvents() {
1620
1637
  // Hide poster when video is loading/playing
1621
1638
  this.video.addEventListener('playing', () => {
1622
1639
  this.hidePoster();
1623
- });
1640
+ });
1624
1641
 
1625
1642
  // Show poster on load if not autoplay
1626
1643
  if (!this.options.autoplay) {
package/src/events.js CHANGED
@@ -170,6 +170,7 @@
170
170
  // Playback events
171
171
  this.video.addEventListener('playing', () => {
172
172
  this.hideLoading();
173
+ this.closeAllMenus();
173
174
  // Trigger playing event - video is now actually playing
174
175
  this.triggerEvent('playing', {
175
176
  currentTime: this.getCurrentTime(),
@@ -394,6 +395,10 @@
394
395
  // Mouse events (desktop)
395
396
  this.progressContainer.addEventListener('click', (e) => this.seek(e));
396
397
  this.progressContainer.addEventListener('mousedown', (e) => this.startSeeking(e));
398
+ if (this.progressHandle) {
399
+ this.progressHandle.addEventListener('mousedown', this.startSeeking.bind(this));
400
+ this.progressHandle.addEventListener('touchstart', this.startSeeking.bind(this), { passive: false });
401
+ }
397
402
 
398
403
  // Touch events (mobile)
399
404
  this.progressContainer.addEventListener('touchstart', (e) => {
package/src/subtitles.js CHANGED
@@ -41,10 +41,10 @@ createCustomSubtitleOverlay() {
41
41
  'bottom: 80px;' +
42
42
  'left: 50%;' +
43
43
  'transform: translateX(-50%);' +
44
- 'z-index: 5;' +
44
+ 'z-index: 999;' +
45
45
  'color: white;' +
46
46
  'font-family: Arial, sans-serif;' +
47
- 'font-size: clamp(12px, 4vw, 18px);' + // RESPONSIVE font-size
47
+ 'font-size: clamp(12px, 4vw, 18px);' +
48
48
  'font-weight: bold;' +
49
49
  'text-align: center;' +
50
50
  'text-shadow: 2px 2px 4px rgba(0, 0, 0, 1);' +
@@ -71,52 +71,29 @@ createCustomSubtitleOverlay() {
71
71
  if (this.options.debug) console.log('✅ Custom subtitle overlay created with responsive settings');
72
72
  }
73
73
 
74
- loadCustomSubtitleTracks() {
75
- var self = this;
76
- var trackElements = document.querySelectorAll('track[kind="subtitles"], track[kind="captions"]');
77
- var loadPromises = [];
78
-
79
- if (this.options.debug) console.log('📥 Loading ' + trackElements.length + ' subtitle files...');
80
-
81
- for (var i = 0; i < trackElements.length; i++) {
82
- var track = trackElements[i];
83
-
84
- (function (trackElement, index) {
85
- var promise = fetch(trackElement.src)
86
- .then(function (response) {
87
- return response.text();
88
- })
89
- .then(function (srtText) {
90
- var subtitles = self.parseCustomSRT(srtText);
91
- self.customSubtitles.push({
92
- label: trackElement.label || 'Track ' + (index + 1),
93
- language: trackElement.srclang || 'unknown',
94
- subtitles: subtitles
95
- });
74
+ customTimeToSeconds(timeString) {
75
+ if (!timeString) return 0;
96
76
 
97
- if (self.options.debug) {
98
- console.log('✅ Loaded: ' + trackElement.label + ' (' + subtitles.length + ' subtitles)');
99
- }
100
- })
101
- .catch(function (error) {
102
- if (self.options.debug) {
103
- console.error('❌ Error loading ' + trackElement.src + ':', error);
104
- }
105
- });
77
+ var parts = timeString.split(',');
78
+ if (parts.length !== 2) return 0;
106
79
 
107
- loadPromises.push(promise);
108
- })(track, i);
109
- }
80
+ var time = parts[0];
81
+ var millis = parts[1];
110
82
 
111
- Promise.all(loadPromises).then(function () {
112
- if (self.options.debug && self.customSubtitles.length > 0) {
113
- console.log('✅ All custom subtitle tracks loaded');
114
- }
83
+ var timeParts = time.split(':');
84
+ if (timeParts.length !== 3) return 0;
115
85
 
116
- if (self.options.debug) {
117
- console.log('📝 Subtitles loaded but NOT auto-enabled - user must activate manually');
118
- }
119
- });
86
+ var hours = parseInt(timeParts[0], 10);
87
+ var minutes = parseInt(timeParts[1], 10);
88
+ var seconds = parseInt(timeParts[2], 10);
89
+ var milliseconds = parseInt(millis, 10);
90
+
91
+ if (isNaN(hours) || isNaN(minutes) || isNaN(seconds) || isNaN(milliseconds)) {
92
+ console.error('❌ customTimeToSeconds failed for:', timeString);
93
+ return 0;
94
+ }
95
+
96
+ return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
120
97
  }
121
98
 
122
99
  parseCustomSRT(srtText) {
@@ -135,9 +112,9 @@ parseCustomSRT(srtText) {
135
112
  if (timeMatch) {
136
113
  var startTime = this.customTimeToSeconds(timeMatch[1]);
137
114
  var endTime = this.customTimeToSeconds(timeMatch[2]);
138
- var text = this.sanitizeSubtitleText(lines.slice(2).join('\n').trim());
115
+ var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
139
116
 
140
- if (text.length > 0 && startTime < endTime) {
117
+ if (text && text.length > 0 && startTime < endTime) {
141
118
  subtitles.push({
142
119
  start: startTime,
143
120
  end: endTime,
@@ -148,20 +125,97 @@ parseCustomSRT(srtText) {
148
125
  }
149
126
  }
150
127
 
128
+ if (this.options.debug) console.log('✅ Parsed ' + subtitles.length + ' subtitles');
151
129
  return subtitles;
152
130
  }
153
131
 
154
- customTimeToSeconds(timeString) {
155
- var parts = timeString.split(',');
156
- var time = parts[0];
157
- var millis = parts[1];
158
- var timeParts = time.split(':');
159
- var hours = parseInt(timeParts[0], 10);
160
- var minutes = parseInt(timeParts[1], 10);
161
- var seconds = parseInt(timeParts[2], 10);
162
- var milliseconds = parseInt(millis, 10);
132
+ loadCustomSubtitleTracks() {
133
+ var self = this;
134
+ var tracks = this.video.querySelectorAll('track[kind="subtitles"]');
135
+ if (tracks.length === 0) return;
136
+
137
+ tracks.forEach(function (track, index) {
138
+ var src = track.getAttribute('src');
139
+ var label = track.getAttribute('label') || 'Unknown';
140
+ var srclang = track.getAttribute('srclang') || '';
141
+
142
+ // CREA L'OGGETTO PRIMA E AGGIUNGILO SUBITO
143
+ var trackObj = {
144
+ label: label,
145
+ language: srclang,
146
+ subtitles: [],
147
+ trackIndex: index
148
+ };
149
+ self.customSubtitles.push(trackObj);
150
+
151
+ fetch(src)
152
+ .then(function (response) {
153
+ return response.text();
154
+ })
155
+ .then(function (srtText) {
156
+ var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
157
+ var blocks = normalizedText.trim().split('\n\n');
158
+
159
+ for (var i = 0; i < blocks.length; i++) {
160
+ var block = blocks[i].trim();
161
+ if (!block) continue;
162
+ var lines = block.split('\n');
163
+
164
+ if (lines.length >= 3) {
165
+ var timeLine = lines[1].trim();
166
+ var timeMatch = timeLine.match(/(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})/);
167
+
168
+ if (timeMatch) {
169
+ var startParts = timeMatch[1].split(',');
170
+ var startTimeParts = startParts[0].split(':');
171
+ var startTime = parseInt(startTimeParts[0], 10) * 3600 + parseInt(startTimeParts[1], 10) * 60 + parseInt(startTimeParts[2], 10) + parseInt(startParts[1], 10) / 1000;
172
+
173
+ var endParts = timeMatch[2].split(',');
174
+ var endTimeParts = endParts[0].split(':');
175
+ var endTime = parseInt(endTimeParts[0], 10) * 3600 + parseInt(endTimeParts[1], 10) * 60 + parseInt(endTimeParts[2], 10) + parseInt(endParts[1], 10) / 1000;
176
+
177
+ var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
178
+
179
+ if (text && text.length > 0 && !isNaN(startTime) && !isNaN(endTime) && startTime < endTime) {
180
+ trackObj.subtitles.push({
181
+ start: startTime,
182
+ end: endTime,
183
+ text: text
184
+ });
185
+ }
186
+ }
187
+ }
188
+ }
163
189
 
164
- return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
190
+ if (self.options.debug) {
191
+ console.log('✅ Loaded ' + trackObj.subtitles.length + ' subtitles for ' + label);
192
+ }
193
+ })
194
+ .catch(function (error) {
195
+ console.error('❌ Error loading ' + label + ':', error);
196
+ });
197
+ });
198
+ }
199
+
200
+ sanitizeSubtitleText(text) {
201
+ if (!text) return '';
202
+
203
+ // Remove HTML tags
204
+ var sanitized = text.replace(/<[^>]*>/g, '');
205
+
206
+ // Remove styling tags common in SRT files
207
+ sanitized = sanitized.replace(/{\\.*?}/g, '');
208
+ sanitized = sanitized.replace(/\\N/g, '\n');
209
+
210
+ // Clean up multiple spaces
211
+ sanitized = sanitized.replace(/\s+/g, ' ').trim();
212
+
213
+ // Decode HTML entities if present
214
+ var tempDiv = document.createElement('div');
215
+ tempDiv.innerHTML = sanitized;
216
+ sanitized = tempDiv.textContent || tempDiv.innerText || sanitized;
217
+
218
+ return sanitized;
165
219
  }
166
220
 
167
221
  enableCustomSubtitleTrack(trackIndex) {
@@ -254,28 +308,39 @@ detectTextTracks() {
254
308
  enableSubtitleTrack(trackIndex) {
255
309
  if (trackIndex < 0 || trackIndex >= this.textTracks.length) return;
256
310
 
311
+ // Disable all tracks first
257
312
  this.disableAllTracks();
258
313
 
314
+ // Enable ONLY the custom subtitle system (not native browser)
259
315
  var success = this.enableCustomSubtitleTrack(trackIndex);
260
316
 
261
317
  if (success) {
262
318
  this.currentSubtitleTrack = this.textTracks[trackIndex].track;
263
319
  this.subtitlesEnabled = true;
264
320
 
321
+ // Make sure native tracks stay DISABLED
322
+ if (this.video.textTracks && this.video.textTracks[trackIndex]) {
323
+ this.video.textTracks[trackIndex].mode = 'disabled'; // Keep native disabled
324
+ }
325
+
265
326
  this.updateSubtitlesButton();
266
327
  this.populateSubtitlesMenu();
267
328
 
268
329
  if (this.options.debug) {
269
- console.log('✅ Subtitles enabled: ' + this.textTracks[trackIndex].label);
330
+ console.log('✅ Custom subtitles enabled:', this.textTracks[trackIndex].label);
270
331
  }
271
332
 
272
- // Trigger evento
333
+ // Trigger subtitle change event
273
334
  this.triggerEvent('subtitlechange', {
274
335
  enabled: true,
275
336
  trackIndex: trackIndex,
276
337
  trackLabel: this.textTracks[trackIndex].label,
277
338
  trackLanguage: this.textTracks[trackIndex].language
278
339
  });
340
+ } else {
341
+ if (this.options.debug) {
342
+ console.error('❌ Failed to enable custom subtitles for track', trackIndex);
343
+ }
279
344
  }
280
345
  }
281
346
 
@@ -298,11 +363,15 @@ disableSubtitles() {
298
363
  }
299
364
 
300
365
  disableAllTracks() {
301
- if (this.video.textTracks) {
302
- for (var i = 0; i < this.video.textTracks.length; i++) {
303
- this.video.textTracks[i].mode = 'hidden';
304
- }
366
+ if (!this.video || !this.video.textTracks) return;
367
+
368
+ // Disable all native tracks
369
+ for (var i = 0; i < this.video.textTracks.length; i++) {
370
+ this.video.textTracks[i].mode = 'hidden';
305
371
  }
372
+
373
+ // Also disable custom subtitles
374
+ this.disableCustomSubtitles();
306
375
  }
307
376
 
308
377
  getAvailableSubtitles() {
@@ -343,11 +412,11 @@ updateSubtitlesButton() {
343
412
  var subtitlesBtn = this.controls && this.controls.querySelector('.subtitles-btn');
344
413
  if (!subtitlesBtn) return;
345
414
 
415
+ subtitlesBtn.classList.remove('active');
416
+
346
417
  if (this.subtitlesEnabled) {
347
- subtitlesBtn.classList.add('active');
348
418
  subtitlesBtn.title = this.t('subtitlesdisable');
349
419
  } else {
350
- subtitlesBtn.classList.remove('active');
351
420
  subtitlesBtn.title = this.t('subtitlesenable');
352
421
  }
353
422
  }
@@ -383,32 +452,61 @@ updateSubtitlesUI() {
383
452
  bindSubtitleEvents() {
384
453
  var self = this;
385
454
 
386
- var subtitlesBtn = this.controls && this.controls.querySelector('.subtitles-btn');
387
- if (subtitlesBtn) {
388
- subtitlesBtn.addEventListener('click', function (e) {
389
- e.stopPropagation();
390
- self.toggleSubtitles();
455
+ if (this.video.textTracks) {
456
+ this.isChangingSubtitles = false; // flag to prevent loops
457
+
458
+ this.video.textTracks.addEventListener('change', function () {
459
+ // ignore changes initiated by the player itself
460
+ if (self.isChangingSubtitles) {
461
+ return;
462
+ }
463
+
464
+ // only update ui
465
+ self.updateSubtitlesUI();
391
466
  });
392
467
  }
393
468
 
469
+ // Add timeupdate listener for custom subtitle display
470
+ this.video.addEventListener('timeupdate', () => {
471
+ if (this.customSubtitlesEnabled) {
472
+ this.updateCustomSubtitleDisplay();
473
+ }
474
+ });
475
+
476
+ // Menu click events
394
477
  var subtitlesMenu = this.controls && this.controls.querySelector('.subtitles-menu');
395
478
  if (subtitlesMenu) {
396
479
  subtitlesMenu.addEventListener('click', function (e) {
397
- self.handleSubtitlesMenuClick(e);
480
+ var option = e.target.closest('.subtitles-option');
481
+ if (!option) return;
482
+
483
+ self.isChangingSubtitles = true; // active flag
484
+
485
+ var trackIndex = option.getAttribute('data-track');
486
+ if (trackIndex === 'off') {
487
+ self.disableSubtitles();
488
+ } else {
489
+ self.enableSubtitleTrack(parseInt(trackIndex));
490
+ }
491
+
492
+ setTimeout(function () {
493
+ self.isChangingSubtitles = false; // disable flag
494
+ }, 100);
398
495
  });
399
496
  }
400
497
  }
401
498
 
402
499
  handleSubtitlesMenuClick(e) {
403
- if (!e.target.classList.contains('subtitles-option')) return;
500
+ var option = e.target.closest('.subtitles-option');
501
+ if (!option) return; // This prevents button clicks from toggling
404
502
 
405
- var trackData = e.target.getAttribute('data-track');
503
+ var trackIndex = option.getAttribute('data-track');
406
504
 
407
- if (trackData === 'off') {
505
+ if (trackIndex === 'off') {
408
506
  this.disableSubtitles();
409
507
  } else {
410
- var trackIndex = parseInt(trackData, 10);
411
- this.enableSubtitleTrack(trackIndex);
508
+ // Don't check for 'toggle' - just enable the track
509
+ this.enableSubtitleTrack(parseInt(trackIndex));
412
510
  }
413
511
 
414
512
  this.updateSubtitlesButton();