myetv-player 1.2.0 → 1.3.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 (47) hide show
  1. package/css/myetv-player.css +131 -0
  2. package/css/myetv-player.min.css +1 -1
  3. package/dist/myetv-player.js +547 -102
  4. package/dist/myetv-player.min.js +486 -93
  5. package/package.json +35 -17
  6. package/plugins/twitch/myetv-player-twitch-plugin.js +125 -11
  7. package/plugins/vimeo/myetv-player-vimeo.js +80 -49
  8. package/plugins/youtube/README.md +5 -2
  9. package/plugins/youtube/myetv-player-youtube-plugin.js +766 -6
  10. package/.github/workflows/codeql.yml +0 -100
  11. package/.github/workflows/npm-publish.yml +0 -30
  12. package/SECURITY.md +0 -50
  13. package/build.js +0 -195
  14. package/scss/README.md +0 -161
  15. package/scss/_audio-player.scss +0 -21
  16. package/scss/_base.scss +0 -116
  17. package/scss/_controls.scss +0 -204
  18. package/scss/_loading.scss +0 -111
  19. package/scss/_menus.scss +0 -432
  20. package/scss/_mixins.scss +0 -112
  21. package/scss/_poster.scss +0 -8
  22. package/scss/_progress-bar.scss +0 -319
  23. package/scss/_resolution.scss +0 -68
  24. package/scss/_responsive.scss +0 -1368
  25. package/scss/_themes.scss +0 -30
  26. package/scss/_title-overlay.scss +0 -60
  27. package/scss/_tooltips.scss +0 -7
  28. package/scss/_variables.scss +0 -49
  29. package/scss/_video.scss +0 -221
  30. package/scss/_volume.scss +0 -122
  31. package/scss/_watermark.scss +0 -128
  32. package/scss/myetv-player.scss +0 -51
  33. package/scss/package.json +0 -16
  34. package/src/README.md +0 -560
  35. package/src/chapters.js +0 -521
  36. package/src/controls.js +0 -1242
  37. package/src/core.js +0 -1922
  38. package/src/events.js +0 -537
  39. package/src/fullscreen.js +0 -82
  40. package/src/i18n.js +0 -374
  41. package/src/playlist.js +0 -177
  42. package/src/plugins.js +0 -384
  43. package/src/quality.js +0 -963
  44. package/src/streaming.js +0 -346
  45. package/src/subtitles.js +0 -524
  46. package/src/utils.js +0 -65
  47. package/src/watermark.js +0 -246
package/src/chapters.js DELETED
@@ -1,521 +0,0 @@
1
- /* Chapters Module for MYETV Video Player
2
- * Chapter markers with tooltips and thumbnails on timeline
3
- * Created by https://www.myetv.tv https://oskarcosimo.com
4
- */
5
-
6
- /**
7
- * Initialize chapter markers system
8
- * Chapters can be defined in initialization options as:
9
- * - JSON array: chapters: [{time: 0, title: "Intro", image: "url"}, ...]
10
- * - String format: chapters: "0:00:00|Intro|image.jpg,0:02:30|Chapter 2|image2.jpg"
11
- */
12
- initializeChapters() {
13
- if (!this.options.chapters || !Array.isArray(this.options.chapters) && typeof this.options.chapters !== 'string') {
14
- if (this.options.debug) console.log('📚 No chapters defined');
15
- return;
16
- }
17
-
18
- // Parse chapters from different formats
19
- this.chapters = this.parseChapters(this.options.chapters);
20
-
21
- if (this.chapters.length === 0) {
22
- if (this.options.debug) console.warn('📚 Chapters defined but empty after parsing');
23
- return;
24
- }
25
-
26
- // Sort chapters by time
27
- this.chapters.sort((a, b) => a.time - b.time);
28
-
29
- if (this.options.debug) console.log('📚 Chapters initialized:', this.chapters);
30
-
31
- // Create chapter markers on the progress bar
32
- this.createChapterMarkers();
33
-
34
- // Create chapter tooltip
35
- this.createChapterTooltip();
36
-
37
- // Bind chapter events
38
- this.bindChapterEvents();
39
- }
40
-
41
- /**
42
- * Parse chapters from various input formats
43
- * @param {Array|String} chaptersInput - Chapters data
44
- * @returns {Array} Normalized chapters array
45
- */
46
- parseChapters(chaptersInput) {
47
- // If already array of objects, validate and return
48
- if (Array.isArray(chaptersInput)) {
49
- return chaptersInput.map(chapter => this.normalizeChapter(chapter)).filter(c => c !== null);
50
- }
51
-
52
- // If string format, parse it
53
- // Format: "time|title|image,time|title|image,..."
54
- // Example: "0:00:00|Introduction|intro.jpg,0:02:30|Chapter 2|chapter2.jpg"
55
- if (typeof chaptersInput === 'string') {
56
- const chapterStrings = chaptersInput.split(',').map(s => s.trim());
57
- const parsedChapters = [];
58
-
59
- for (const chapterStr of chapterStrings) {
60
- const parts = chapterStr.split('|').map(p => p.trim());
61
- if (parts.length < 2) {
62
- if (this.options.debug) console.warn('📚 Invalid chapter format:', chapterStr);
63
- continue;
64
- }
65
-
66
- const chapter = {
67
- time: this.parseTimeToSeconds(parts[0]),
68
- title: parts[1],
69
- image: parts[2] || null
70
- };
71
-
72
- const normalized = this.normalizeChapter(chapter);
73
- if (normalized) {
74
- parsedChapters.push(normalized);
75
- }
76
- }
77
-
78
- return parsedChapters;
79
- }
80
-
81
- if (this.options.debug) console.warn('📚 Invalid chapters format');
82
- return [];
83
- }
84
-
85
- /**
86
- * Normalize and validate a single chapter object
87
- * @param {Object} chapter - Chapter object
88
- * @returns {Object|null} Normalized chapter or null if invalid
89
- */
90
- normalizeChapter(chapter) {
91
- if (!chapter || typeof chapter !== 'object') {
92
- return null;
93
- }
94
-
95
- // Ensure required fields
96
- if (!chapter.hasOwnProperty('time') || !chapter.hasOwnProperty('title')) {
97
- if (this.options.debug) console.warn('📚 Chapter missing required fields:', chapter);
98
- return null;
99
- }
100
-
101
- // Convert time to seconds if string
102
- let timeInSeconds = chapter.time;
103
- if (typeof timeInSeconds === 'string') {
104
- timeInSeconds = this.parseTimeToSeconds(timeInSeconds);
105
- }
106
-
107
- if (typeof timeInSeconds !== 'number' || timeInSeconds < 0) {
108
- if (this.options.debug) console.warn('📚 Invalid chapter time:', chapter.time);
109
- return null;
110
- }
111
-
112
- return {
113
- time: timeInSeconds,
114
- title: String(chapter.title),
115
- image: chapter.image || null,
116
- color: chapter.color || null // Optional custom color
117
- };
118
- }
119
-
120
- /**
121
- * Parse time string to seconds
122
- * Supports formats: "HH:MM:SS", "MM:SS", "SS"
123
- * @param {String} timeStr - Time string
124
- * @returns {Number} Time in seconds
125
- */
126
- parseTimeToSeconds(timeStr) {
127
- if (typeof timeStr === 'number') {
128
- return timeStr;
129
- }
130
-
131
- const parts = String(timeStr).split(':').map(p => parseInt(p.trim(), 10));
132
-
133
- if (parts.length === 3) {
134
- // HH:MM:SS
135
- return parts[0] * 3600 + parts[1] * 60 + parts[2];
136
- } else if (parts.length === 2) {
137
- // MM:SS
138
- return parts[0] * 60 + parts[1];
139
- } else if (parts.length === 1) {
140
- // SS
141
- return parts[0];
142
- }
143
-
144
- return 0;
145
- }
146
-
147
- /**
148
- * Create visual chapter markers on the progress bar
149
- */
150
- createChapterMarkers() {
151
- if (!this.progressContainer || !this.video || !this.chapters) {
152
- return;
153
- }
154
-
155
- // Create container for chapter markers
156
- const markersContainer = document.createElement('div');
157
- markersContainer.className = 'chapter-markers-container';
158
-
159
- this.chapters.forEach((chapter, index) => {
160
- const marker = document.createElement('div');
161
- marker.className = 'chapter-marker';
162
- marker.setAttribute('data-chapter-index', index);
163
- marker.setAttribute('data-chapter-time', chapter.time);
164
- marker.setAttribute('data-chapter-title', chapter.title);
165
-
166
- // Set custom color if provided
167
- if (chapter.color) {
168
- marker.style.backgroundColor = chapter.color;
169
- }
170
-
171
- markersContainer.appendChild(marker);
172
- });
173
-
174
- // Insert markers container into progress container
175
- this.progressContainer.appendChild(markersContainer);
176
- this.chapterMarkersContainer = markersContainer;
177
-
178
- // Update marker positions when video duration is known
179
- if (this.video.duration && !isNaN(this.video.duration)) {
180
- this.updateChapterMarkerPositions();
181
- } else {
182
- // Wait for metadata to be loaded
183
- const loadedMetadataHandler = () => {
184
- this.updateChapterMarkerPositions();
185
- this.video.removeEventListener('loadedmetadata', loadedMetadataHandler);
186
- };
187
- this.video.addEventListener('loadedmetadata', loadedMetadataHandler);
188
- }
189
-
190
- if (this.options.debug) console.log('📚 Chapter markers created on timeline');
191
- }
192
-
193
- /**
194
- * Update chapter marker positions based on video duration
195
- */
196
- updateChapterMarkerPositions() {
197
- if (!this.video || !this.video.duration || !this.chapterMarkersContainer) {
198
- return;
199
- }
200
-
201
- const markers = this.chapterMarkersContainer.querySelectorAll('.chapter-marker');
202
- const duration = this.video.duration;
203
-
204
- markers.forEach((marker, index) => {
205
- if (this.chapters[index]) {
206
- const percentage = (this.chapters[index].time / duration) * 100;
207
- marker.style.left = percentage + '%';
208
- }
209
- });
210
-
211
- if (this.options.debug) console.log('📚 Chapter marker positions updated');
212
- }
213
-
214
- /**
215
- * Create chapter tooltip
216
- */
217
- createChapterTooltip() {
218
- if (!this.progressContainer) {
219
- return;
220
- }
221
-
222
- const tooltip = document.createElement('div');
223
- tooltip.className = 'chapter-tooltip';
224
- tooltip.style.opacity = '0';
225
- tooltip.style.visibility = 'hidden';
226
-
227
- // Tooltip content structure
228
- tooltip.innerHTML = `
229
- <div class="chapter-tooltip-image"></div>
230
- <div class="chapter-tooltip-title"></div>
231
- <div class="chapter-tooltip-time"></div>
232
- `;
233
-
234
- this.progressContainer.appendChild(tooltip);
235
- this.chapterTooltip = tooltip;
236
-
237
- if (this.options.debug) console.log('📚 Chapter tooltip created');
238
- }
239
-
240
- /**
241
- * Bind chapter-related events
242
- */
243
- bindChapterEvents() {
244
- if (!this.chapterMarkersContainer || !this.chapterTooltip) {
245
- return;
246
- }
247
-
248
- // Hover on chapter markers
249
- const markers = this.chapterMarkersContainer.querySelectorAll('.chapter-marker');
250
-
251
- markers.forEach((marker, index) => {
252
- marker.addEventListener('mouseenter', (e) => {
253
- this.showChapterTooltip(index, e);
254
- });
255
-
256
- marker.addEventListener('mousemove', (e) => {
257
- this.updateChapterTooltipPosition(e);
258
- });
259
-
260
- marker.addEventListener('mouseleave', () => {
261
- this.hideChapterTooltip();
262
- });
263
-
264
- // Click to jump to chapter
265
- marker.addEventListener('click', (e) => {
266
- e.stopPropagation();
267
- this.jumpToChapter(index);
268
- });
269
- });
270
-
271
- // Update active chapter during playback
272
- if (this.video) {
273
- this.video.addEventListener('timeupdate', () => {
274
- this.updateActiveChapter();
275
- });
276
- }
277
-
278
- if (this.options.debug) console.log('📚 Chapter events bound');
279
- }
280
-
281
- /**
282
- * Show chapter tooltip
283
- * @param {Number} chapterIndex - Index of the chapter
284
- * @param {MouseEvent} e - Mouse event
285
- */
286
- showChapterTooltip(chapterIndex, e) {
287
- if (!this.chapterTooltip || !this.chapters[chapterIndex]) {
288
- return;
289
- }
290
-
291
- const chapter = this.chapters[chapterIndex];
292
-
293
- // Update tooltip content
294
- const imageEl = this.chapterTooltip.querySelector('.chapter-tooltip-image');
295
- const titleEl = this.chapterTooltip.querySelector('.chapter-tooltip-title');
296
- const timeEl = this.chapterTooltip.querySelector('.chapter-tooltip-time');
297
-
298
- // Set image
299
- if (chapter.image) {
300
- imageEl.style.display = 'block';
301
- imageEl.style.backgroundImage = `url(${chapter.image})`;
302
- } else {
303
- imageEl.style.display = 'none';
304
- }
305
-
306
- // Set title
307
- titleEl.textContent = chapter.title;
308
-
309
- // Set time
310
- timeEl.textContent = this.formatTime(chapter.time);
311
-
312
- // Show tooltip
313
- this.chapterTooltip.style.opacity = '1';
314
- this.chapterTooltip.style.visibility = 'visible';
315
-
316
- // Position tooltip
317
- this.updateChapterTooltipPosition(e);
318
- }
319
-
320
- /**
321
- * Update chapter tooltip position
322
- * @param {MouseEvent} e - Mouse event
323
- */
324
- updateChapterTooltipPosition(e) {
325
- if (!this.chapterTooltip || !this.progressContainer) {
326
- return;
327
- }
328
-
329
- const rect = this.progressContainer.getBoundingClientRect();
330
- const tooltipRect = this.chapterTooltip.getBoundingClientRect();
331
- const mouseX = e.clientX - rect.left;
332
-
333
- // Calculate position
334
- let leftPosition = mouseX;
335
- const tooltipWidth = tooltipRect.width || 200;
336
- const containerWidth = rect.width;
337
-
338
- // Keep tooltip within bounds
339
- leftPosition = Math.max(tooltipWidth / 2, Math.min(containerWidth - tooltipWidth / 2, mouseX));
340
-
341
- this.chapterTooltip.style.left = leftPosition + 'px';
342
- }
343
-
344
- /**
345
- * Hide chapter tooltip
346
- */
347
- hideChapterTooltip() {
348
- if (!this.chapterTooltip) {
349
- return;
350
- }
351
-
352
- this.chapterTooltip.style.opacity = '0';
353
- this.chapterTooltip.style.visibility = 'hidden';
354
- }
355
-
356
- /**
357
- * Jump to specific chapter
358
- * @param {Number} chapterIndex - Index of the chapter
359
- */
360
- jumpToChapter(chapterIndex) {
361
- if (!this.video || !this.chapters[chapterIndex]) {
362
- return;
363
- }
364
-
365
- const chapter = this.chapters[chapterIndex];
366
- this.video.currentTime = chapter.time;
367
-
368
- if (this.options.debug) console.log(`📚 Jumped to chapter: ${chapter.title} at ${chapter.time}s`);
369
-
370
- // Trigger custom event
371
- this.triggerEvent('chapterchange', {
372
- chapterIndex: chapterIndex,
373
- chapter: chapter,
374
- currentTime: this.video.currentTime
375
- });
376
- }
377
-
378
- /**
379
- * Update active chapter marker during playback
380
- */
381
- updateActiveChapter() {
382
- if (!this.video || !this.chapterMarkersContainer || !this.chapters) {
383
- return;
384
- }
385
-
386
- const currentTime = this.video.currentTime;
387
- const markers = this.chapterMarkersContainer.querySelectorAll('.chapter-marker');
388
-
389
- // Find current chapter
390
- let currentChapterIndex = -1;
391
- for (let i = this.chapters.length - 1; i >= 0; i--) {
392
- if (currentTime >= this.chapters[i].time) {
393
- currentChapterIndex = i;
394
- break;
395
- }
396
- }
397
-
398
- // Update active state
399
- markers.forEach((marker, index) => {
400
- if (index === currentChapterIndex) {
401
- marker.classList.add('active');
402
- } else {
403
- marker.classList.remove('active');
404
- }
405
- });
406
- }
407
-
408
- /**
409
- * Get current chapter info
410
- * @returns {Object|null} Current chapter object or null
411
- */
412
- getCurrentChapter() {
413
- if (!this.video || !this.chapters || this.chapters.length === 0) {
414
- return null;
415
- }
416
-
417
- const currentTime = this.video.currentTime;
418
-
419
- for (let i = this.chapters.length - 1; i >= 0; i--) {
420
- if (currentTime >= this.chapters[i].time) {
421
- return {
422
- index: i,
423
- chapter: this.chapters[i]
424
- };
425
- }
426
- }
427
-
428
- return null;
429
- }
430
-
431
- /**
432
- * Get all chapters
433
- * @returns {Array} Array of chapter objects
434
- */
435
- getChapters() {
436
- return this.chapters || [];
437
- }
438
-
439
- /**
440
- * Get chapter by index
441
- * @param {Number} index - Chapter index
442
- * @returns {Object|null} Chapter object or null
443
- */
444
- getChapterByIndex(index) {
445
- if (!this.chapters || index < 0 || index >= this.chapters.length) {
446
- return null;
447
- }
448
- return this.chapters[index];
449
- }
450
-
451
- /**
452
- * Navigate to next chapter
453
- * @returns {Boolean} True if navigated successfully
454
- */
455
- nextChapter() {
456
- const current = this.getCurrentChapter();
457
- if (!current || current.index >= this.chapters.length - 1) {
458
- return false;
459
- }
460
-
461
- this.jumpToChapter(current.index + 1);
462
- return true;
463
- }
464
-
465
- /**
466
- * Navigate to previous chapter
467
- * @returns {Boolean} True if navigated successfully
468
- */
469
- previousChapter() {
470
- const current = this.getCurrentChapter();
471
- if (!current) {
472
- return false;
473
- }
474
-
475
- // If we're more than 3 seconds into current chapter, go to its start
476
- if (this.video && this.video.currentTime - current.chapter.time > 3) {
477
- this.jumpToChapter(current.index);
478
- return true;
479
- }
480
-
481
- // Otherwise go to previous chapter
482
- if (current.index > 0) {
483
- this.jumpToChapter(current.index - 1);
484
- return true;
485
- }
486
-
487
- return false;
488
- }
489
-
490
- /**
491
- * Set chapters dynamically
492
- * @param {Array|String} chapters - Chapters data
493
- */
494
- setChapters(chapters) {
495
- // Remove existing chapter markers
496
- if (this.chapterMarkersContainer) {
497
- this.chapterMarkersContainer.remove();
498
- this.chapterMarkersContainer = null;
499
- }
500
-
501
- if (this.chapterTooltip) {
502
- this.chapterTooltip.remove();
503
- this.chapterTooltip = null;
504
- }
505
-
506
- // Set new chapters
507
- this.options.chapters = chapters;
508
- this.chapters = [];
509
-
510
- // Re-initialize chapters
511
- this.initializeChapters();
512
-
513
- if (this.options.debug) console.log('📚 Chapters updated dynamically');
514
- }
515
-
516
- /**
517
- * Clear all chapters
518
- */
519
- clearChapters() {
520
- this.setChapters(null);
521
- }