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.
- package/README.md +76 -2
- package/css/myetv-player.css +321 -208
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +219 -37
- package/dist/myetv-player.min.js +204 -26
- package/package.json +3 -1
- package/plugins/cloudflare/README.md +26 -4
- package/plugins/cloudflare/myetv-player-cloudflare-stream-plugin.js +1273 -217
- package/plugins/facebook/myetv-player-facebook-plugin.js +1340 -164
- package/plugins/twitch/myetv-player-twitch-plugin.js +428 -167
- package/plugins/vimeo/README.md +1 -1
- package/plugins/vimeo/myetv-player-vimeo.js +560 -247
- package/plugins/youtube/README.md +18 -7
- package/plugins/youtube/myetv-player-youtube-plugin.js +1485 -190
- package/scss/_base.scss +0 -15
- package/scss/_controls.scss +182 -2
- package/scss/_menus.scss +51 -0
- package/scss/_responsive.scss +187 -321
- package/scss/_title-overlay.scss +27 -0
- package/scss/_video.scss +0 -75
- package/scss/_watermark.scss +120 -0
- package/scss/myetv-player.scss +7 -7
- package/src/controls.js +72 -21
- package/src/core.js +43 -5
- package/src/events.js +33 -5
- package/src/utils.js +20 -6
- package/src/watermark.js +51 -0
package/scss/_video.scss
CHANGED
|
@@ -2320,81 +2320,6 @@ video::-webkit-media-text-track-display {
|
|
|
2320
2320
|
right: 15px;
|
|
2321
2321
|
}
|
|
2322
2322
|
|
|
2323
|
-
/* Bottom positions - Increased spacing from controlbar (was 70px, now 90px) */
|
|
2324
|
-
.video-watermark.watermark-bottomleft {
|
|
2325
|
-
bottom: 90px; /* Increased spacing */
|
|
2326
|
-
left: 15px;
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
|
-
.video-watermark.watermark-bottomright {
|
|
2330
|
-
bottom: 90px; /* Increased spacing */
|
|
2331
|
-
right: 15px;
|
|
2332
|
-
}
|
|
2333
|
-
|
|
2334
|
-
/* DYNAMIC POSITIONING: Always visible watermark moves down when controls are hidden */
|
|
2335
|
-
/* This applies ONLY to watermarks without hide-on-autohide class (always visible) */
|
|
2336
|
-
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide) {
|
|
2337
|
-
bottom: 15px; /* Move to bottom corner when controls hidden */
|
|
2338
|
-
}
|
|
2339
|
-
|
|
2340
|
-
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide) {
|
|
2341
|
-
bottom: 15px; /* Move to bottom corner when controls hidden */
|
|
2342
|
-
}
|
|
2343
|
-
|
|
2344
|
-
/* When controls are shown, bottom watermark stays above controlbar */
|
|
2345
|
-
.video-wrapper.has-controls .video-watermark.watermark-bottomleft,
|
|
2346
|
-
.video-wrapper.has-controls .video-watermark.watermark-bottomright {
|
|
2347
|
-
bottom: 90px; /* Stay above controlbar */
|
|
2348
|
-
}
|
|
2349
|
-
|
|
2350
|
-
/* Responsive adjustments for mobile */
|
|
2351
|
-
@media (max-width: 768px) {
|
|
2352
|
-
.video-watermark img {
|
|
2353
|
-
max-width: 100px;
|
|
2354
|
-
max-height: 50px;
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
.video-watermark.watermark-topleft,
|
|
2358
|
-
.video-watermark.watermark-topright {
|
|
2359
|
-
top: 10px;
|
|
2360
|
-
}
|
|
2361
|
-
|
|
2362
|
-
.video-watermark.watermark-topleft,
|
|
2363
|
-
.video-watermark.watermark-bottomleft {
|
|
2364
|
-
left: 10px;
|
|
2365
|
-
}
|
|
2366
|
-
|
|
2367
|
-
.video-watermark.watermark-topright,
|
|
2368
|
-
.video-watermark.watermark-bottomright {
|
|
2369
|
-
right: 10px;
|
|
2370
|
-
}
|
|
2371
|
-
|
|
2372
|
-
/* Bottom spacing adjusted for mobile */
|
|
2373
|
-
.video-watermark.watermark-bottomleft,
|
|
2374
|
-
.video-watermark.watermark-bottomright {
|
|
2375
|
-
bottom: 75px; /* Above controlbar on mobile */
|
|
2376
|
-
}
|
|
2377
|
-
|
|
2378
|
-
/* Always visible watermark on mobile - moves down when controls hidden */
|
|
2379
|
-
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide),
|
|
2380
|
-
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide) {
|
|
2381
|
-
bottom: 10px; /* Move to corner on mobile */
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2384
|
-
|
|
2385
|
-
/* Extra small screens */
|
|
2386
|
-
@media (max-width: 480px) {
|
|
2387
|
-
.video-watermark.watermark-bottomleft,
|
|
2388
|
-
.video-watermark.watermark-bottomright {
|
|
2389
|
-
bottom: 65px;
|
|
2390
|
-
}
|
|
2391
|
-
|
|
2392
|
-
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide),
|
|
2393
|
-
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide) {
|
|
2394
|
-
bottom: 8px;
|
|
2395
|
-
}
|
|
2396
|
-
}
|
|
2397
|
-
|
|
2398
2323
|
/* Clickable watermark cursor */
|
|
2399
2324
|
.video-watermark[style*="cursor: pointer"] {
|
|
2400
2325
|
cursor: pointer !important;
|
package/scss/_watermark.scss
CHANGED
|
@@ -5,4 +5,124 @@
|
|
|
5
5
|
@use 'mixins' as *;
|
|
6
6
|
@use 'variables' as *;
|
|
7
7
|
|
|
8
|
+
.video-watermark {
|
|
9
|
+
position: absolute;
|
|
10
|
+
z-index: 15;
|
|
11
|
+
pointer-events: auto;
|
|
12
|
+
opacity: 0.7;
|
|
13
|
+
transition: opacity 0.3s ease, visibility 0.3s ease, bottom 0.3s ease;
|
|
14
|
+
}
|
|
8
15
|
|
|
16
|
+
.video-watermark {
|
|
17
|
+
visibility: visible;
|
|
18
|
+
opacity: 0.7;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.video-wrapper:not(.has-controls) .video-watermark.hide-on-autohide {
|
|
22
|
+
visibility: hidden;
|
|
23
|
+
opacity: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.video-wrapper.has-controls .video-watermark {
|
|
27
|
+
visibility: visible;
|
|
28
|
+
opacity: 0.7;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.video-watermark:hover {
|
|
32
|
+
opacity: 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.video-watermark img {
|
|
36
|
+
display: block;
|
|
37
|
+
max-width: 150px;
|
|
38
|
+
max-height: 80px;
|
|
39
|
+
width: auto;
|
|
40
|
+
height: auto;
|
|
41
|
+
object-fit: contain;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Top positions */
|
|
45
|
+
.video-watermark.watermark-topleft {
|
|
46
|
+
top: 15px;
|
|
47
|
+
left: 15px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.video-watermark.watermark-topright {
|
|
51
|
+
top: 15px;
|
|
52
|
+
right: 15px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Bottom positions - Dynamic with controlbar height + spacing */
|
|
56
|
+
.video-watermark.watermark-bottomleft {
|
|
57
|
+
bottom: calc(var(--player-controls-height, 70px) + 15px);
|
|
58
|
+
left: 15px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.video-watermark.watermark-bottomright {
|
|
62
|
+
bottom: calc(var(--player-controls-height, 70px) + 15px);
|
|
63
|
+
right: 15px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* When controls hidden, move to corner */
|
|
67
|
+
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide) {
|
|
68
|
+
bottom: 15px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide) {
|
|
72
|
+
bottom: 15px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* When controls shown, stay above controlbar */
|
|
76
|
+
.video-wrapper.has-controls .video-watermark.watermark-bottomleft,
|
|
77
|
+
.video-wrapper.has-controls .video-watermark.watermark-bottomright {
|
|
78
|
+
bottom: calc(var(--player-controls-height, 70px) + 15px);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Responsive */
|
|
82
|
+
@media (max-width: 768px) {
|
|
83
|
+
.video-watermark img {
|
|
84
|
+
max-width: 100px;
|
|
85
|
+
max-height: 50px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.video-watermark.watermark-topleft,
|
|
89
|
+
.video-watermark.watermark-topright {
|
|
90
|
+
top: 10px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.video-watermark.watermark-topleft,
|
|
94
|
+
.video-watermark.watermark-bottomleft {
|
|
95
|
+
left: 10px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.video-watermark.watermark-topright,
|
|
99
|
+
.video-watermark.watermark-bottomright {
|
|
100
|
+
right: 10px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.video-watermark.watermark-bottomleft,
|
|
104
|
+
.video-watermark.watermark-bottomright {
|
|
105
|
+
bottom: calc(var(--player-controls-height, 60px) + 10px);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide),
|
|
109
|
+
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide) {
|
|
110
|
+
bottom: 10px;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@media (max-width: 480px) {
|
|
115
|
+
.video-watermark.watermark-bottomleft,
|
|
116
|
+
.video-watermark.watermark-bottomright {
|
|
117
|
+
bottom: calc(var(--player-controls-height, 55px) + 10px);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide),
|
|
121
|
+
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide) {
|
|
122
|
+
bottom: 8px;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.video-watermark[style*="cursor: pointer"] {
|
|
127
|
+
cursor: pointer !important;
|
|
128
|
+
}
|
package/scss/myetv-player.scss
CHANGED
|
@@ -35,17 +35,17 @@
|
|
|
35
35
|
// 10. Poster Overlay
|
|
36
36
|
@use 'poster';
|
|
37
37
|
|
|
38
|
-
// 11.
|
|
39
|
-
@use 'watermark';
|
|
40
|
-
|
|
41
|
-
// 12. Tooltips
|
|
38
|
+
// 11. Tooltips
|
|
42
39
|
@use 'tooltips';
|
|
43
40
|
|
|
44
|
-
// 12
|
|
41
|
+
// 12 Audio Player (audiofile + audiowave)
|
|
45
42
|
@use 'audio-player';
|
|
46
43
|
|
|
47
44
|
// 13. Themes (Blue, Green, Red)
|
|
48
45
|
@use 'themes';
|
|
49
46
|
|
|
50
|
-
// 14.
|
|
51
|
-
@use '
|
|
47
|
+
// 14. Watermark
|
|
48
|
+
@use 'watermark';
|
|
49
|
+
|
|
50
|
+
// 15. Responsive (must be last)
|
|
51
|
+
@use 'responsive';
|
package/src/controls.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Created by https://www.myetv.tv https://oskarcosimo.com
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
/* AUTO-HIDE SYSTEM
|
|
7
|
+
/* AUTO-HIDE SYSTEM */
|
|
8
8
|
initAutoHide() {
|
|
9
9
|
if (!this.options.autoHide) {
|
|
10
10
|
if (this.options.debug) console.log('Auto-hide disabled in options');
|
|
@@ -144,12 +144,17 @@ showControlsNow() {
|
|
|
144
144
|
this.controls.classList.add('show');
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
//
|
|
147
|
+
// Add has-controls class to container for watermark visibility
|
|
148
148
|
if (this.container) {
|
|
149
149
|
this.container.classList.add('has-controls');
|
|
150
|
+
this.updateControlbarHeight();
|
|
151
|
+
// Update watermark position
|
|
152
|
+
if (this.updateWatermarkPosition) {
|
|
153
|
+
this.updateWatermarkPosition();
|
|
154
|
+
}
|
|
150
155
|
}
|
|
151
156
|
|
|
152
|
-
//
|
|
157
|
+
// Show title overlay with controls if not persistent
|
|
153
158
|
if (this.options.showTitleOverlay && !this.options.persistentTitle && this.options.videoTitle) {
|
|
154
159
|
this.showTitleOverlay();
|
|
155
160
|
}
|
|
@@ -160,32 +165,44 @@ showControlsNow() {
|
|
|
160
165
|
hideControlsNow() {
|
|
161
166
|
// Don't hide if mouse is still over controls (allow hiding on touch devices)
|
|
162
167
|
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
168
|
+
|
|
163
169
|
if (this.mouseOverControls && !isTouchDevice) {
|
|
164
|
-
if (this.autoHideDebug && this.options.debug)
|
|
170
|
+
if (this.autoHideDebug && this.options.debug) {
|
|
171
|
+
console.log('🚫 Not hiding - mouse still over controls');
|
|
172
|
+
}
|
|
165
173
|
return;
|
|
166
174
|
}
|
|
167
175
|
|
|
168
176
|
// Don't hide if video is paused
|
|
169
177
|
if (this.video && this.video.paused) {
|
|
170
|
-
if (this.autoHideDebug && this.options.debug)
|
|
178
|
+
if (this.autoHideDebug && this.options.debug) {
|
|
179
|
+
console.log('🚫 Not hiding - video is paused');
|
|
180
|
+
}
|
|
171
181
|
return;
|
|
172
182
|
}
|
|
173
183
|
|
|
174
184
|
if (this.controls) {
|
|
175
185
|
this.controls.classList.remove('show');
|
|
176
|
-
}
|
|
177
186
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
187
|
+
// Remove has-controls class from container for watermark visibility
|
|
188
|
+
if (this.container) {
|
|
189
|
+
this.container.classList.remove('has-controls');
|
|
190
|
+
this.updateControlbarHeight();
|
|
191
|
+
// Update watermark position
|
|
192
|
+
if (this.updateWatermarkPosition) {
|
|
193
|
+
this.updateWatermarkPosition();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
181
196
|
}
|
|
182
197
|
|
|
183
|
-
//
|
|
198
|
+
// Hide title overlay with controls (if not persistent)
|
|
184
199
|
if (this.options.showTitleOverlay && !this.options.persistentTitle) {
|
|
185
200
|
this.hideTitleOverlay();
|
|
186
201
|
}
|
|
187
202
|
|
|
188
|
-
if (this.autoHideDebug && this.options.debug)
|
|
203
|
+
if (this.autoHideDebug && this.options.debug) {
|
|
204
|
+
console.log('👁️ Controls hidden');
|
|
205
|
+
}
|
|
189
206
|
}
|
|
190
207
|
|
|
191
208
|
showControls() {
|
|
@@ -464,28 +481,62 @@ createControls() {
|
|
|
464
481
|
// NEW: Initialize responsive settings menu
|
|
465
482
|
setTimeout(() => {
|
|
466
483
|
this.initializeResponsiveMenu();
|
|
484
|
+
this.updateControlbarHeight();
|
|
467
485
|
}, 100);
|
|
468
486
|
}
|
|
469
487
|
|
|
470
|
-
/*
|
|
488
|
+
/* Initialize responsive menu with dynamic width calculation */
|
|
471
489
|
initializeResponsiveMenu() {
|
|
472
490
|
if (!this.controls) return;
|
|
473
491
|
|
|
474
492
|
// Track screen size
|
|
475
493
|
this.isSmallScreen = false;
|
|
476
494
|
|
|
477
|
-
// Check initial size
|
|
495
|
+
// Check initial size
|
|
478
496
|
this.checkScreenSize();
|
|
479
497
|
|
|
480
|
-
|
|
498
|
+
// Bind resize handler with updateControlbarHeight
|
|
499
|
+
const resizeHandler = () => {
|
|
481
500
|
this.checkScreenSize();
|
|
482
|
-
|
|
501
|
+
this.updateControlbarHeight();
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// Bind del context
|
|
505
|
+
this.resizeHandler = resizeHandler.bind(this);
|
|
506
|
+
window.addEventListener('resize', this.resizeHandler);
|
|
483
507
|
|
|
484
508
|
// Bind events for settings menu
|
|
485
509
|
this.bindSettingsMenuEvents();
|
|
486
510
|
}
|
|
487
511
|
|
|
488
|
-
|
|
512
|
+
// Dynamic controlbar height tracking for watermark positioning
|
|
513
|
+
updateControlbarHeight() {
|
|
514
|
+
if (!this.controls) return;
|
|
515
|
+
|
|
516
|
+
const height = this.controls.offsetHeight;
|
|
517
|
+
if (this.container) {
|
|
518
|
+
|
|
519
|
+
this.container.style.setProperty('--player-controls-height', `${height}px`);
|
|
520
|
+
|
|
521
|
+
const watermark = this.container.querySelector('.video-watermark.watermark-bottomleft, .video-watermark.watermark-bottomright');
|
|
522
|
+
if (watermark) {
|
|
523
|
+
const hasControls = this.container.classList.contains('has-controls');
|
|
524
|
+
const isHideOnAutoHide = watermark.classList.contains('hide-on-autohide');
|
|
525
|
+
|
|
526
|
+
if (hasControls || !isHideOnAutoHide) {
|
|
527
|
+
watermark.style.bottom = `${height + 15}px`;
|
|
528
|
+
} else {
|
|
529
|
+
watermark.style.bottom = '15px';
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (this.options.debug) {
|
|
535
|
+
console.log(`Controlbar height updated: ${height}px`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/* Dynamic width calculation based on logo presence */
|
|
489
540
|
getResponsiveThreshold() {
|
|
490
541
|
// Check if brand logo is enabled and present
|
|
491
542
|
const hasLogo = this.options.brandLogoEnabled && this.options.brandLogoUrl;
|
|
@@ -494,7 +545,7 @@ getResponsiveThreshold() {
|
|
|
494
545
|
return hasLogo ? 650 : 550;
|
|
495
546
|
}
|
|
496
547
|
|
|
497
|
-
/*
|
|
548
|
+
/* Check if screen is under dynamic threshold */
|
|
498
549
|
checkScreenSize() {
|
|
499
550
|
const threshold = this.getResponsiveThreshold();
|
|
500
551
|
const newIsSmallScreen = window.innerWidth <= threshold;
|
|
@@ -504,12 +555,12 @@ checkScreenSize() {
|
|
|
504
555
|
this.updateSettingsMenuVisibility();
|
|
505
556
|
|
|
506
557
|
if (this.options.debug) {
|
|
507
|
-
console.log(`Screen check: ${window.innerWidth}px vs ${threshold}px threshold
|
|
558
|
+
console.log(`Screen check: ${window.innerWidth}px vs ${threshold}px (threshold), logo: ${this.options.brandLogoEnabled}, small: ${this.isSmallScreen}`);
|
|
508
559
|
}
|
|
509
560
|
}
|
|
510
561
|
}
|
|
511
562
|
|
|
512
|
-
/*
|
|
563
|
+
/* Update settings menu visibility based on screen size */
|
|
513
564
|
updateSettingsMenuVisibility() {
|
|
514
565
|
const settingsControl = this.controls?.querySelector('.settings-control');
|
|
515
566
|
if (!settingsControl) return;
|
|
@@ -549,7 +600,7 @@ updateSettingsMenuVisibility() {
|
|
|
549
600
|
}
|
|
550
601
|
}
|
|
551
602
|
|
|
552
|
-
/*
|
|
603
|
+
/* Populate settings menu with controls */
|
|
553
604
|
populateSettingsMenu() {
|
|
554
605
|
const settingsMenu = this.controls?.querySelector('.settings-menu');
|
|
555
606
|
if (!settingsMenu) return;
|
|
@@ -611,7 +662,7 @@ populateSettingsMenu() {
|
|
|
611
662
|
settingsMenu.innerHTML = menuHTML;
|
|
612
663
|
}
|
|
613
664
|
|
|
614
|
-
/*
|
|
665
|
+
/* Bind settings menu events */
|
|
615
666
|
bindSettingsMenuEvents() {
|
|
616
667
|
const settingsMenu = this.controls?.querySelector('.settings-menu');
|
|
617
668
|
if (!settingsMenu) return;
|
package/src/core.js
CHANGED
|
@@ -26,6 +26,7 @@ constructor(videoElement, options = {}) {
|
|
|
26
26
|
showSeekTooltip: true,
|
|
27
27
|
showTitleOverlay: false,
|
|
28
28
|
videoTitle: '',
|
|
29
|
+
videoSubtitle: '',
|
|
29
30
|
persistentTitle: false,
|
|
30
31
|
debug: false, // Enable/disable debug logging
|
|
31
32
|
autoplay: false, // if video should autoplay at start
|
|
@@ -582,9 +583,16 @@ createTitleOverlay() {
|
|
|
582
583
|
const titleText = document.createElement('h2');
|
|
583
584
|
titleText.className = 'title-text';
|
|
584
585
|
titleText.textContent = this.options.videoTitle || '';
|
|
585
|
-
|
|
586
586
|
overlay.appendChild(titleText);
|
|
587
587
|
|
|
588
|
+
// add subtitles
|
|
589
|
+
if (this.options.videoSubtitle) {
|
|
590
|
+
const subtitleText = document.createElement('p');
|
|
591
|
+
subtitleText.className = 'subtitle-text';
|
|
592
|
+
subtitleText.textContent = this.options.videoSubtitle;
|
|
593
|
+
overlay.appendChild(subtitleText);
|
|
594
|
+
}
|
|
595
|
+
|
|
588
596
|
if (this.controls) {
|
|
589
597
|
this.container.insertBefore(overlay, this.controls);
|
|
590
598
|
} else {
|
|
@@ -658,6 +666,31 @@ getVideoTitle() {
|
|
|
658
666
|
return this.options.videoTitle;
|
|
659
667
|
}
|
|
660
668
|
|
|
669
|
+
setVideoSubtitle(subtitle) {
|
|
670
|
+
this.options.videoSubtitle = subtitle || '';
|
|
671
|
+
|
|
672
|
+
if (this.titleOverlay) {
|
|
673
|
+
let subtitleElement = this.titleOverlay.querySelector('.subtitle-text');
|
|
674
|
+
|
|
675
|
+
if (subtitle) {
|
|
676
|
+
if (!subtitleElement) {
|
|
677
|
+
subtitleElement = document.createElement('p');
|
|
678
|
+
subtitleElement.className = 'subtitle-text';
|
|
679
|
+
this.titleOverlay.appendChild(subtitleElement);
|
|
680
|
+
}
|
|
681
|
+
subtitleElement.textContent = subtitle;
|
|
682
|
+
} else if (subtitleElement) {
|
|
683
|
+
subtitleElement.remove();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return this;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
getVideoSubtitle() {
|
|
691
|
+
return this.options.videoSubtitle;
|
|
692
|
+
}
|
|
693
|
+
|
|
661
694
|
setPersistentTitle(persistent) {
|
|
662
695
|
this.options.persistentTitle = persistent;
|
|
663
696
|
|
|
@@ -1137,14 +1170,19 @@ seek(e) {
|
|
|
1137
1170
|
if (!this.video || !this.progressContainer || !this.progressFilled || !this.progressHandle || this.isChangingQuality) return;
|
|
1138
1171
|
|
|
1139
1172
|
const rect = this.progressContainer.getBoundingClientRect();
|
|
1140
|
-
|
|
1173
|
+
|
|
1174
|
+
// Support both mouse and touch events
|
|
1175
|
+
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));
|
|
1176
|
+
|
|
1177
|
+
const clickX = clientX - rect.left;
|
|
1141
1178
|
const percentage = Math.max(0, Math.min(1, clickX / rect.width));
|
|
1142
1179
|
|
|
1143
1180
|
if (this.video.duration && !isNaN(this.video.duration)) {
|
|
1144
1181
|
this.video.currentTime = percentage * this.video.duration;
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
this.
|
|
1182
|
+
|
|
1183
|
+
const progress = `${percentage * 100}%`;
|
|
1184
|
+
this.progressFilled.style.width = progress;
|
|
1185
|
+
this.progressHandle.style.left = progress;
|
|
1148
1186
|
}
|
|
1149
1187
|
}
|
|
1150
1188
|
|
package/src/events.js
CHANGED
|
@@ -297,12 +297,28 @@
|
|
|
297
297
|
});
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
300
|
+
if (this.progressContainer) {
|
|
301
|
+
// Mouse events (desktop)
|
|
302
|
+
this.progressContainer.addEventListener('click', (e) => this.seek(e));
|
|
303
|
+
this.progressContainer.addEventListener('mousedown', (e) => this.startSeeking(e));
|
|
304
|
+
|
|
305
|
+
// Touch events (mobile)
|
|
306
|
+
this.progressContainer.addEventListener('touchstart', (e) => {
|
|
307
|
+
e.preventDefault(); // Prevent scrolling when touching the seek bar
|
|
308
|
+
this.startSeeking(e);
|
|
309
|
+
}, { passive: false });
|
|
304
310
|
|
|
305
311
|
this.setupSeekTooltip();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Add touch events directly on the handle for better mobile dragging
|
|
315
|
+
if (this.progressHandle) {
|
|
316
|
+
this.progressHandle.addEventListener('touchstart', (e) => {
|
|
317
|
+
e.preventDefault(); // Prevent default touch behavior
|
|
318
|
+
e.stopPropagation(); // Stop event from bubbling to progressContainer
|
|
319
|
+
this.startSeeking(e);
|
|
320
|
+
}, { passive: false });
|
|
321
|
+
}
|
|
306
322
|
|
|
307
323
|
// NOTE: Auto-hide events are handled in initAutoHide() after everything is ready
|
|
308
324
|
|
|
@@ -323,7 +339,19 @@
|
|
|
323
339
|
document.addEventListener('mozfullscreenchange', () => this.updateFullscreenButton());
|
|
324
340
|
|
|
325
341
|
document.addEventListener('mousemove', (e) => this.continueSeeking(e));
|
|
326
|
-
|
|
342
|
+
document.addEventListener('mouseup', () => this.endSeeking());
|
|
343
|
+
|
|
344
|
+
// Touch events for seeking (mobile)
|
|
345
|
+
document.addEventListener('touchmove', (e) => {
|
|
346
|
+
if (this.isUserSeeking) {
|
|
347
|
+
e.preventDefault(); // Prevent scrolling while seeking
|
|
348
|
+
this.continueSeeking(e);
|
|
349
|
+
}
|
|
350
|
+
}, { passive: false });
|
|
351
|
+
|
|
352
|
+
document.addEventListener('touchend', () => this.endSeeking());
|
|
353
|
+
document.addEventListener('touchcancel', () => this.endSeeking());
|
|
354
|
+
|
|
327
355
|
}
|
|
328
356
|
|
|
329
357
|
// Events methods for main class
|
package/src/utils.js
CHANGED
|
@@ -24,15 +24,29 @@
|
|
|
24
24
|
this.video.currentTime = Math.max(0, Math.min(this.video.duration, this.video.currentTime + seconds));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
updateTimeDisplay() {
|
|
28
|
+
// update current time
|
|
29
|
+
if (this.currentTimeEl && this.video) {
|
|
30
|
+
this.currentTimeEl.textContent = this.formatTime(this.video.currentTime || 0);
|
|
31
|
+
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
// update duration or show badge if encoding
|
|
34
|
+
if (this.durationEl && this.video) {
|
|
35
|
+
const duration = this.video.duration;
|
|
36
|
+
|
|
37
|
+
// check if duration is valid
|
|
38
|
+
if (!duration || isNaN(duration) || !isFinite(duration)) {
|
|
39
|
+
// Video in encoding - show badge instead of duration
|
|
40
|
+
this.durationEl.innerHTML = '<span class="encoding-badge">Encoding in progress</span>';
|
|
41
|
+
this.durationEl.classList.add('encoding-state');
|
|
42
|
+
} else {
|
|
43
|
+
// valid duration - show normal
|
|
44
|
+
this.durationEl.textContent = this.formatTime(duration);
|
|
45
|
+
this.durationEl.classList.remove('encoding-state');
|
|
34
46
|
}
|
|
35
47
|
}
|
|
48
|
+
}
|
|
49
|
+
|
|
36
50
|
|
|
37
51
|
formatTime(seconds) {
|
|
38
52
|
if (isNaN(seconds) || seconds < 0) return '0:00';
|
package/src/watermark.js
CHANGED
|
@@ -69,9 +69,19 @@ initializeWatermark() {
|
|
|
69
69
|
this.container.appendChild(watermark);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// Store reference to watermark element
|
|
72
73
|
// Store reference to watermark element
|
|
73
74
|
this.watermarkElement = watermark;
|
|
74
75
|
|
|
76
|
+
// Set initial position
|
|
77
|
+
this.updateWatermarkPosition();
|
|
78
|
+
|
|
79
|
+
// Update position on window resize
|
|
80
|
+
this.watermarkResizeHandler = () => {
|
|
81
|
+
this.updateWatermarkPosition();
|
|
82
|
+
};
|
|
83
|
+
window.addEventListener('resize', this.watermarkResizeHandler);
|
|
84
|
+
|
|
75
85
|
if (this.options.debug) {
|
|
76
86
|
console.log('🏷️ Watermark created:', {
|
|
77
87
|
url: this.options.watermarkUrl,
|
|
@@ -90,6 +100,7 @@ initializeWatermark() {
|
|
|
90
100
|
* @param {string} position - Position of watermark (topleft, topright, bottomleft, bottomright)
|
|
91
101
|
* @param {string} title - Optional tooltip title for the watermark
|
|
92
102
|
*/
|
|
103
|
+
|
|
93
104
|
setWatermark(url, link = '', position = 'bottomright', title = '') {
|
|
94
105
|
// Update options
|
|
95
106
|
this.options.watermarkUrl = url;
|
|
@@ -120,6 +131,12 @@ removeWatermark() {
|
|
|
120
131
|
this.watermarkElement = null;
|
|
121
132
|
}
|
|
122
133
|
|
|
134
|
+
// Remove resize listener
|
|
135
|
+
if (this.watermarkResizeHandler) {
|
|
136
|
+
window.removeEventListener('resize', this.watermarkResizeHandler);
|
|
137
|
+
this.watermarkResizeHandler = null;
|
|
138
|
+
}
|
|
139
|
+
|
|
123
140
|
this.options.watermarkUrl = '';
|
|
124
141
|
this.options.watermarkLink = '';
|
|
125
142
|
this.options.watermarkPosition = 'bottomright';
|
|
@@ -134,6 +151,7 @@ removeWatermark() {
|
|
|
134
151
|
* Update watermark position
|
|
135
152
|
* @param {string} position - New position (topleft, topright, bottomleft, bottomright)
|
|
136
153
|
*/
|
|
154
|
+
|
|
137
155
|
setWatermarkPosition(position) {
|
|
138
156
|
if (!['topleft', 'topright', 'bottomleft', 'bottomright'].includes(position)) {
|
|
139
157
|
if (this.options.debug) console.warn('🏷️ Invalid watermark position:', position);
|
|
@@ -160,6 +178,39 @@ setWatermarkPosition(position) {
|
|
|
160
178
|
return this;
|
|
161
179
|
}
|
|
162
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Update watermark position based on current controlbar height
|
|
183
|
+
* Called during window resize to keep watermark above controlbar
|
|
184
|
+
*/
|
|
185
|
+
updateWatermarkPosition() {
|
|
186
|
+
if (!this.watermarkElement) return;
|
|
187
|
+
if (!this.controls) return;
|
|
188
|
+
|
|
189
|
+
const position = this.options.watermarkPosition || 'bottomright';
|
|
190
|
+
|
|
191
|
+
// Only update bottom positions (top positions don't need adjustment)
|
|
192
|
+
if (position === 'bottomleft' || position === 'bottomright') {
|
|
193
|
+
const controlsHeight = this.controls.offsetHeight;
|
|
194
|
+
const spacing = 15; // Same spacing used in CSS
|
|
195
|
+
const bottomValue = controlsHeight + spacing;
|
|
196
|
+
|
|
197
|
+
// Check if controls are visible
|
|
198
|
+
const hasControls = this.container.classList.contains('has-controls');
|
|
199
|
+
|
|
200
|
+
if (hasControls || !this.options.hideWatermark) {
|
|
201
|
+
// Position above controlbar
|
|
202
|
+
this.watermarkElement.style.bottom = `${bottomValue}px`;
|
|
203
|
+
} else {
|
|
204
|
+
// Position at bottom corner when controls hidden
|
|
205
|
+
this.watermarkElement.style.bottom = '15px';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (this.options.debug) {
|
|
209
|
+
console.log(`🏷️ Watermark position updated: bottom ${this.watermarkElement.style.bottom}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
163
214
|
/**
|
|
164
215
|
* Set whether watermark should hide with controls
|
|
165
216
|
* @param {boolean} hide - True to hide watermark with controls, false to keep always visible
|