myetv-player 1.0.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.
Files changed (50) hide show
  1. package/.github/workflows/npm-publish.yml +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +866 -0
  4. package/build.js +189 -0
  5. package/css/README.md +1 -0
  6. package/css/myetv-player.css +13702 -0
  7. package/css/myetv-player.min.css +1 -0
  8. package/dist/README.md +1 -0
  9. package/dist/myetv-player.js +6408 -0
  10. package/dist/myetv-player.min.js +6183 -0
  11. package/package.json +27 -0
  12. package/plugins/README.md +1 -0
  13. package/plugins/google-analytics/README.md +1 -0
  14. package/plugins/google-analytics/myetv-player-g-analytics-plugin.js +548 -0
  15. package/plugins/youtube/README.md +1 -0
  16. package/plugins/youtube/myetv-player-youtube-plugin.js +418 -0
  17. package/scss/README.md +1 -0
  18. package/scss/_audio-player.scss +21 -0
  19. package/scss/_base.scss +131 -0
  20. package/scss/_controls.scss +30 -0
  21. package/scss/_loading.scss +111 -0
  22. package/scss/_menus.scss +4070 -0
  23. package/scss/_mixins.scss +112 -0
  24. package/scss/_poster.scss +8 -0
  25. package/scss/_progress-bar.scss +2203 -0
  26. package/scss/_resolution.scss +68 -0
  27. package/scss/_responsive.scss +1532 -0
  28. package/scss/_themes.scss +30 -0
  29. package/scss/_title-overlay.scss +2262 -0
  30. package/scss/_tooltips.scss +7 -0
  31. package/scss/_variables.scss +49 -0
  32. package/scss/_video.scss +2401 -0
  33. package/scss/_volume.scss +1981 -0
  34. package/scss/_watermark.scss +8 -0
  35. package/scss/myetv-player.scss +51 -0
  36. package/scss/package.json +16 -0
  37. package/src/README.md +1 -0
  38. package/src/chapters.js +521 -0
  39. package/src/controls.js +1005 -0
  40. package/src/core.js +1650 -0
  41. package/src/events.js +330 -0
  42. package/src/fullscreen.js +82 -0
  43. package/src/i18n.js +348 -0
  44. package/src/playlist.js +177 -0
  45. package/src/plugins.js +384 -0
  46. package/src/quality.js +921 -0
  47. package/src/streaming.js +346 -0
  48. package/src/subtitles.js +426 -0
  49. package/src/utils.js +51 -0
  50. package/src/watermark.js +195 -0
@@ -0,0 +1,426 @@
1
+ /* Subtitles Module for MYETV Video Player
2
+ * Conservative modularization - original code preserved exactly
3
+ * Created by https://www.myetv.tv https://oskarcosimo.com
4
+ */
5
+
6
+ initializeSubtitles() {
7
+ this.detectTextTracks();
8
+ this.updateSubtitlesUI();
9
+ this.bindSubtitleEvents();
10
+ this.initializeCustomSubtitles();
11
+
12
+ if (this.options.debug) console.log('📝 Detected ' + this.textTracks.length + ' subtitles traces');
13
+ }
14
+
15
+ initializeCustomSubtitles() {
16
+ // Initialize player variables
17
+ this.customSubtitles = [];
18
+ this.currentCustomSubtitles = [];
19
+ this.customSubtitlesEnabled = false;
20
+ this.customOverlayElement = null;
21
+ this.customUpdateInterval = null;
22
+ this.currentCustomTrackIndex = -1;
23
+
24
+ this.createCustomSubtitleOverlay();
25
+ this.loadCustomSubtitleTracks();
26
+
27
+ }
28
+
29
+ createCustomSubtitleOverlay() {
30
+ var existing = document.querySelector('.custom-subtitle-overlay');
31
+ if (existing && existing.parentNode) {
32
+ existing.parentNode.removeChild(existing);
33
+ }
34
+
35
+ this.customOverlayElement = document.createElement('div');
36
+ this.customOverlayElement.className = 'custom-subtitle-overlay';
37
+
38
+ // ENHANCED styles with responsive defaults
39
+ this.customOverlayElement.style.cssText =
40
+ 'position: absolute;' +
41
+ 'bottom: 80px;' +
42
+ 'left: 50%;' +
43
+ 'transform: translateX(-50%);' +
44
+ 'z-index: 5;' +
45
+ 'color: white;' +
46
+ 'font-family: Arial, sans-serif;' +
47
+ 'font-size: clamp(12px, 4vw, 18px);' + // RESPONSIVE font-size
48
+ 'font-weight: bold;' +
49
+ 'text-align: center;' +
50
+ 'text-shadow: 2px 2px 4px rgba(0, 0, 0, 1);' +
51
+ 'background-color: rgba(0, 0, 0, 0.6);' +
52
+ 'padding: 8px 16px;' +
53
+ 'border-radius: 6px;' +
54
+ 'max-width: 80%;' +
55
+ 'line-height: 1.3;' +
56
+ 'white-space: pre-line;' +
57
+ 'display: none;' +
58
+ 'pointer-events: none;' +
59
+ 'box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);';
60
+
61
+ var playerContainer = this.video.parentElement;
62
+ if (playerContainer) {
63
+ playerContainer.style.position = 'relative';
64
+ // ENSURE proper layer stacking
65
+ if (!playerContainer.style.zIndex) {
66
+ playerContainer.style.zIndex = '1';
67
+ }
68
+ playerContainer.appendChild(this.customOverlayElement);
69
+ }
70
+
71
+ if (this.options.debug) console.log('✅ Custom subtitle overlay created with responsive settings');
72
+ }
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
+ });
96
+
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
+ });
106
+
107
+ loadPromises.push(promise);
108
+ })(track, i);
109
+ }
110
+
111
+ Promise.all(loadPromises).then(function () {
112
+ if (self.options.debug && self.customSubtitles.length > 0) {
113
+ console.log('✅ All custom subtitle tracks loaded');
114
+ }
115
+
116
+ if (self.options.debug) {
117
+ console.log('📝 Subtitles loaded but NOT auto-enabled - user must activate manually');
118
+ }
119
+ });
120
+ }
121
+
122
+ parseCustomSRT(srtText) {
123
+ var subtitles = [];
124
+ var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
125
+ var blocks = normalizedText.trim().split('\n\n');
126
+
127
+ for (var i = 0; i < blocks.length; i++) {
128
+ var block = blocks[i];
129
+ var lines = block.trim().split('\n');
130
+
131
+ if (lines.length >= 3) {
132
+ var timeLine = lines[1].trim();
133
+ var timeMatch = timeLine.match(/(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})/);
134
+
135
+ if (timeMatch) {
136
+ var startTime = this.customTimeToSeconds(timeMatch[1]);
137
+ var endTime = this.customTimeToSeconds(timeMatch[2]);
138
+ var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
139
+
140
+ if (text.length > 0 && startTime < endTime) {
141
+ subtitles.push({
142
+ start: startTime,
143
+ end: endTime,
144
+ text: text
145
+ });
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ return subtitles;
152
+ }
153
+
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);
163
+
164
+ return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
165
+ }
166
+
167
+ enableCustomSubtitleTrack(trackIndex) {
168
+ if (trackIndex < 0 || trackIndex >= this.customSubtitles.length) return false;
169
+
170
+ this.disableCustomSubtitles();
171
+
172
+ this.customSubtitlesEnabled = true;
173
+ this.currentCustomTrackIndex = trackIndex;
174
+ this.currentCustomSubtitles = this.customSubtitles[trackIndex].subtitles;
175
+
176
+ var self = this;
177
+ this.customUpdateInterval = setInterval(function () {
178
+ if (self.customSubtitlesEnabled && self.currentCustomSubtitles.length > 0) {
179
+ self.updateCustomSubtitleDisplay();
180
+ }
181
+ }, 100);
182
+
183
+ if (this.options.debug) {
184
+ console.log('✅ Custom subtitles enabled: ' + this.customSubtitles[trackIndex].label);
185
+ }
186
+
187
+ return true;
188
+ }
189
+
190
+ updateCustomSubtitleDisplay() {
191
+ if (!this.customSubtitlesEnabled || this.currentCustomSubtitles.length === 0) return;
192
+
193
+ var currentTime = this.video.currentTime;
194
+ var currentSubtitle = null;
195
+
196
+ for (var i = 0; i < this.currentCustomSubtitles.length; i++) {
197
+ var sub = this.currentCustomSubtitles[i];
198
+ if (currentTime >= sub.start && currentTime <= sub.end) {
199
+ currentSubtitle = sub;
200
+ break;
201
+ }
202
+ }
203
+
204
+ if (currentSubtitle) {
205
+ this.customOverlayElement.textContent = currentSubtitle.text;
206
+ this.customOverlayElement.style.display = 'block';
207
+ } else {
208
+ this.customOverlayElement.style.display = 'none';
209
+ this.customOverlayElement.textContent = '';
210
+ }
211
+ }
212
+
213
+ disableCustomSubtitles() {
214
+ this.customSubtitlesEnabled = false;
215
+ this.currentCustomTrackIndex = -1;
216
+
217
+ if (this.customOverlayElement) {
218
+ this.customOverlayElement.style.display = 'none';
219
+ this.customOverlayElement.textContent = '';
220
+ }
221
+
222
+ if (this.customUpdateInterval) {
223
+ clearInterval(this.customUpdateInterval);
224
+ this.customUpdateInterval = null;
225
+ }
226
+
227
+ if (this.options.debug) console.log('❌ Custom subtitles disabled');
228
+ }
229
+
230
+ detectTextTracks() {
231
+ this.textTracks = [];
232
+
233
+ if (this.video.textTracks) {
234
+ if (this.options.debug) console.log('🔍 Detecting text tracks... Found: ' + this.video.textTracks.length);
235
+
236
+ for (var i = 0; i < this.video.textTracks.length; i++) {
237
+ var track = this.video.textTracks[i];
238
+
239
+ if (track.kind === 'subtitles' || track.kind === 'captions') {
240
+ this.textTracks.push({
241
+ track: track,
242
+ label: track.label || 'Track ' + (i + 1),
243
+ language: track.language || 'unknown',
244
+ kind: track.kind,
245
+ index: i
246
+ });
247
+ }
248
+ }
249
+
250
+ if (this.options.debug) console.log('📊 Total subtitle tracks detected: ' + this.textTracks.length);
251
+ }
252
+ }
253
+
254
+ enableSubtitleTrack(trackIndex) {
255
+ if (trackIndex < 0 || trackIndex >= this.textTracks.length) return;
256
+
257
+ this.disableAllTracks();
258
+
259
+ var success = this.enableCustomSubtitleTrack(trackIndex);
260
+
261
+ if (success) {
262
+ this.currentSubtitleTrack = this.textTracks[trackIndex].track;
263
+ this.subtitlesEnabled = true;
264
+
265
+ this.updateSubtitlesButton();
266
+ this.populateSubtitlesMenu();
267
+
268
+ if (this.options.debug) {
269
+ console.log('✅ Subtitles enabled: ' + this.textTracks[trackIndex].label);
270
+ }
271
+
272
+ // Trigger evento
273
+ this.triggerEvent('subtitlechange', {
274
+ enabled: true,
275
+ trackIndex: trackIndex,
276
+ trackLabel: this.textTracks[trackIndex].label,
277
+ trackLanguage: this.textTracks[trackIndex].language
278
+ });
279
+ }
280
+ }
281
+
282
+ disableSubtitles() {
283
+ this.disableCustomSubtitles();
284
+ this.disableAllTracks();
285
+
286
+ this.currentSubtitleTrack = null;
287
+ this.subtitlesEnabled = false;
288
+
289
+ this.updateSubtitlesButton();
290
+ this.populateSubtitlesMenu();
291
+
292
+ if (this.options.debug) console.log('📝 Subtitles disabled');
293
+
294
+ this.triggerEvent('subtitlechange', {
295
+ enabled: false,
296
+ trackIndex: -1
297
+ });
298
+ }
299
+
300
+ 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
+ }
305
+ }
306
+ }
307
+
308
+ getAvailableSubtitles() {
309
+ return this.textTracks.map(function (t) {
310
+ return {
311
+ label: t.label,
312
+ language: t.language,
313
+ kind: t.kind
314
+ };
315
+ });
316
+ }
317
+
318
+ setSubtitleTrack(trackIndex) {
319
+ if (trackIndex === -1) {
320
+ this.disableSubtitles();
321
+ } else {
322
+ this.enableSubtitleTrack(trackIndex);
323
+ }
324
+ return this;
325
+ }
326
+
327
+ getCurrentSubtitleTrack() {
328
+ if (!this.subtitlesEnabled || !this.currentSubtitleTrack) return -1;
329
+
330
+ for (var i = 0; i < this.textTracks.length; i++) {
331
+ if (this.textTracks[i].track === this.currentSubtitleTrack) {
332
+ return i;
333
+ }
334
+ }
335
+ return -1;
336
+ }
337
+
338
+ isSubtitlesEnabled() {
339
+ return this.subtitlesEnabled;
340
+ }
341
+
342
+ updateSubtitlesButton() {
343
+ var subtitlesBtn = this.controls && this.controls.querySelector('.subtitles-btn');
344
+ if (!subtitlesBtn) return;
345
+
346
+ if (this.subtitlesEnabled) {
347
+ subtitlesBtn.classList.add('active');
348
+ subtitlesBtn.title = this.t('subtitlesdisable');
349
+ } else {
350
+ subtitlesBtn.classList.remove('active');
351
+ subtitlesBtn.title = this.t('subtitlesenable');
352
+ }
353
+ }
354
+
355
+ populateSubtitlesMenu() {
356
+ var subtitlesMenu = this.controls && this.controls.querySelector('.subtitles-menu');
357
+ if (!subtitlesMenu) return;
358
+
359
+ var menuHTML = '<div class="subtitles-option ' + (!this.subtitlesEnabled ? 'active' : '') + '" data-track="off">Off</div>';
360
+
361
+ for (var i = 0; i < this.textTracks.length; i++) {
362
+ var trackData = this.textTracks[i];
363
+ var isActive = this.currentSubtitleTrack === trackData.track;
364
+ menuHTML += '<div class="subtitles-option ' + (isActive ? 'active' : '') + '" data-track="' + i + '">' + trackData.label + '</div>';
365
+ }
366
+
367
+ subtitlesMenu.innerHTML = menuHTML;
368
+ }
369
+
370
+ updateSubtitlesUI() {
371
+ var subtitlesControl = this.controls && this.controls.querySelector('.subtitles-control');
372
+
373
+ if (this.textTracks.length > 0 && this.options.showSubtitles) {
374
+ if (subtitlesControl) subtitlesControl.style.display = 'block';
375
+ this.populateSubtitlesMenu();
376
+ } else {
377
+ if (subtitlesControl) subtitlesControl.style.display = 'none';
378
+ }
379
+
380
+ this.updateSubtitlesButton();
381
+ }
382
+
383
+ bindSubtitleEvents() {
384
+ var self = this;
385
+
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();
391
+ });
392
+ }
393
+
394
+ var subtitlesMenu = this.controls && this.controls.querySelector('.subtitles-menu');
395
+ if (subtitlesMenu) {
396
+ subtitlesMenu.addEventListener('click', function (e) {
397
+ self.handleSubtitlesMenuClick(e);
398
+ });
399
+ }
400
+ }
401
+
402
+ handleSubtitlesMenuClick(e) {
403
+ if (!e.target.classList.contains('subtitles-option')) return;
404
+
405
+ var trackData = e.target.getAttribute('data-track');
406
+
407
+ if (trackData === 'off') {
408
+ this.disableSubtitles();
409
+ } else {
410
+ var trackIndex = parseInt(trackData, 10);
411
+ this.enableSubtitleTrack(trackIndex);
412
+ }
413
+
414
+ this.updateSubtitlesButton();
415
+ this.populateSubtitlesMenu();
416
+ }
417
+
418
+ toggleSubtitles() {
419
+ if (this.textTracks.length === 0) return;
420
+
421
+ if (this.subtitlesEnabled) {
422
+ this.disableSubtitles();
423
+ } else {
424
+ this.enableSubtitleTrack(0);
425
+ }
426
+ }
package/src/utils.js ADDED
@@ -0,0 +1,51 @@
1
+ // Utils Module for MYETV Video Player
2
+ // Conservative modularization - original code preserved exactly
3
+ // Created by https://www.myetv.tv https://oskarcosimo.com
4
+
5
+ getBufferedTime() {
6
+ if (!this.video || !this.video.buffered || this.video.buffered.length === 0) return 0;
7
+ try {
8
+ return this.video.buffered.end(this.video.buffered.length - 1);
9
+ } catch (error) {
10
+ return 0;
11
+ }
12
+ }
13
+
14
+ clearTitleTimeout() {
15
+ if (this.titleTimeout) {
16
+ clearTimeout(this.titleTimeout);
17
+ this.titleTimeout = null;
18
+ }
19
+ }
20
+
21
+ skipTime(seconds) {
22
+ if (!this.video || !this.video.duration || this.isChangingQuality) return;
23
+
24
+ this.video.currentTime = Math.max(0, Math.min(this.video.duration, this.video.currentTime + seconds));
25
+ }
26
+
27
+ updateTimeDisplay() {
28
+ if (this.currentTimeEl && this.video) {
29
+ this.currentTimeEl.textContent = this.formatTime(this.video.currentTime || 0);
30
+ }
31
+
32
+ if (this.durationEl && this.video && this.video.duration && !isNaN(this.video.duration)) {
33
+ this.durationEl.textContent = this.formatTime(this.video.duration);
34
+ }
35
+ }
36
+
37
+ formatTime(seconds) {
38
+ if (isNaN(seconds) || seconds < 0) return '0:00';
39
+
40
+ const hours = Math.floor(seconds / 3600);
41
+ const minutes = Math.floor((seconds % 3600) / 60);
42
+ const secs = Math.floor(seconds % 60);
43
+
44
+ if (hours > 0) {
45
+ return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
46
+ }
47
+ return `${minutes}:${secs.toString().padStart(2, '0')}`;
48
+ }
49
+
50
+ // Utils methods for main class
51
+ // All original functionality preserved exactly
@@ -0,0 +1,195 @@
1
+ // Watermark Module for MYETV Video Player
2
+ // Displays a logo overlay on the video with customizable position and link
3
+ // Created by https://www.myetv.tv https://oskarcosimo.com
4
+
5
+ /**
6
+ * Initialize watermark overlay
7
+ * Creates a watermark element overlaid on the video player
8
+ */
9
+ initializeWatermark() {
10
+ if (!this.options.watermarkUrl) {
11
+ if (this.options.debug) console.log('🏷️ Watermark disabled - no URL provided');
12
+ return;
13
+ }
14
+
15
+ if (this.options.debug) console.log('🏷️ Initializing watermark overlay');
16
+
17
+ // Create watermark container
18
+ const watermark = document.createElement('div');
19
+ watermark.className = 'video-watermark';
20
+
21
+ // Set position class - FIX: use template literal correctly
22
+ const position = this.options.watermarkPosition || 'bottomright';
23
+ watermark.classList.add(`watermark-${position}`); // ← FIX QUI
24
+
25
+ // Add hide-on-autohide class if option is enabled
26
+ if (this.options.hideWatermark) {
27
+ watermark.classList.add('hide-on-autohide');
28
+ }
29
+
30
+ // Create watermark image
31
+ const watermarkImg = document.createElement('img');
32
+ watermarkImg.src = this.options.watermarkUrl;
33
+ watermarkImg.alt = 'Watermark';
34
+
35
+ // Add title/tooltip if provided
36
+ if (this.options.watermarkTitle) {
37
+ watermarkImg.title = this.options.watermarkTitle;
38
+ }
39
+
40
+ // Handle image loading error
41
+ watermarkImg.onerror = () => {
42
+ if (this.options.debug) console.warn('🏷️ Watermark image failed to load:', this.options.watermarkUrl);
43
+ watermark.style.display = 'none';
44
+ };
45
+
46
+ watermarkImg.onload = () => {
47
+ if (this.options.debug) console.log('🏷️ Watermark image loaded successfully');
48
+ };
49
+
50
+ // Add click handler if link URL is provided
51
+ if (this.options.watermarkLink) {
52
+ watermark.style.cursor = 'pointer';
53
+ watermark.addEventListener('click', (e) => {
54
+ e.stopPropagation(); // Prevent video controls interference
55
+ window.open(this.options.watermarkLink, '_blank', 'noopener,noreferrer');
56
+ if (this.options.debug) console.log('🏷️ Watermark clicked, opening:', this.options.watermarkLink);
57
+ });
58
+ } else {
59
+ watermark.style.cursor = 'default';
60
+ }
61
+
62
+ // Append image to watermark container
63
+ watermark.appendChild(watermarkImg);
64
+
65
+ // Insert watermark before controls (above video, below controls)
66
+ if (this.controls) {
67
+ this.container.insertBefore(watermark, this.controls);
68
+ } else {
69
+ this.container.appendChild(watermark);
70
+ }
71
+
72
+ // Store reference to watermark element
73
+ this.watermarkElement = watermark;
74
+
75
+ if (this.options.debug) {
76
+ console.log('🏷️ Watermark created:', {
77
+ url: this.options.watermarkUrl,
78
+ link: this.options.watermarkLink || 'none',
79
+ position: position,
80
+ title: this.options.watermarkTitle || 'none',
81
+ hideWithControls: this.options.hideWatermark
82
+ });
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Set or update watermark configuration
88
+ * @param {string} url - URL of the watermark image
89
+ * @param {string} link - Optional URL to open when watermark is clicked
90
+ * @param {string} position - Position of watermark (topleft, topright, bottomleft, bottomright)
91
+ * @param {string} title - Optional tooltip title for the watermark
92
+ */
93
+ setWatermark(url, link = '', position = 'bottomright', title = '') {
94
+ // Update options
95
+ this.options.watermarkUrl = url;
96
+ this.options.watermarkLink = link;
97
+ this.options.watermarkPosition = position;
98
+ this.options.watermarkTitle = title;
99
+
100
+ // Remove existing watermark if present
101
+ if (this.watermarkElement) {
102
+ this.watermarkElement.remove();
103
+ this.watermarkElement = null;
104
+ }
105
+
106
+ // Recreate watermark if URL is provided
107
+ if (url) {
108
+ this.initializeWatermark();
109
+ }
110
+
111
+ return this;
112
+ }
113
+
114
+ /**
115
+ * Remove watermark from player
116
+ */
117
+ removeWatermark() {
118
+ if (this.watermarkElement) {
119
+ this.watermarkElement.remove();
120
+ this.watermarkElement = null;
121
+ }
122
+
123
+ this.options.watermarkUrl = '';
124
+ this.options.watermarkLink = '';
125
+ this.options.watermarkPosition = 'bottomright';
126
+ this.options.watermarkTitle = '';
127
+
128
+ if (this.options.debug) console.log('🏷️ Watermark removed');
129
+
130
+ return this;
131
+ }
132
+
133
+ /**
134
+ * Update watermark position
135
+ * @param {string} position - New position (topleft, topright, bottomleft, bottomright)
136
+ */
137
+ setWatermarkPosition(position) {
138
+ if (!['topleft', 'topright', 'bottomleft', 'bottomright'].includes(position)) {
139
+ if (this.options.debug) console.warn('🏷️ Invalid watermark position:', position);
140
+ return this;
141
+ }
142
+
143
+ this.options.watermarkPosition = position;
144
+
145
+ if (this.watermarkElement) {
146
+ // Remove all position classes
147
+ this.watermarkElement.classList.remove(
148
+ 'watermark-topleft',
149
+ 'watermark-topright',
150
+ 'watermark-bottomleft',
151
+ 'watermark-bottomright'
152
+ );
153
+
154
+ // Add new position class - FIX: use template literal correctly
155
+ this.watermarkElement.classList.add(`watermark-${position}`); // ← FIX QUI
156
+ }
157
+
158
+ if (this.options.debug) console.log('🏷️ Watermark position updated to:', position);
159
+
160
+ return this;
161
+ }
162
+
163
+ /**
164
+ * Set whether watermark should hide with controls
165
+ * @param {boolean} hide - True to hide watermark with controls, false to keep always visible
166
+ */
167
+ setWatermarkAutoHide(hide) {
168
+ this.options.hideWatermark = hide;
169
+
170
+ if (this.watermarkElement) {
171
+ if (hide) {
172
+ this.watermarkElement.classList.add('hide-on-autohide');
173
+ } else {
174
+ this.watermarkElement.classList.remove('hide-on-autohide');
175
+ }
176
+ }
177
+
178
+ if (this.options.debug) console.log('🏷️ Watermark auto-hide set to:', hide);
179
+
180
+ return this;
181
+ }
182
+
183
+ /**
184
+ * Get current watermark settings
185
+ * @returns {object} Current watermark configuration
186
+ */
187
+ getWatermarkSettings() {
188
+ return {
189
+ url: this.options.watermarkUrl || '',
190
+ link: this.options.watermarkLink || '',
191
+ position: this.options.watermarkPosition || 'bottomright',
192
+ title: this.options.watermarkTitle || '',
193
+ hideWithControls: this.options.hideWatermark
194
+ };
195
+ }