myetv-player 1.2.0 → 1.4.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/css/myetv-player.css +242 -168
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +638 -203
- package/dist/myetv-player.min.js +548 -170
- package/package.json +35 -16
- package/plugins/twitch/myetv-player-twitch-plugin.js +125 -11
- package/plugins/vimeo/myetv-player-vimeo.js +80 -49
- package/plugins/youtube/README.md +5 -2
- package/plugins/youtube/myetv-player-youtube-plugin.js +766 -6
- package/.github/workflows/codeql.yml +0 -100
- package/.github/workflows/npm-publish.yml +0 -30
- package/SECURITY.md +0 -50
- package/build.js +0 -195
- package/scss/README.md +0 -161
- package/scss/_audio-player.scss +0 -21
- package/scss/_base.scss +0 -116
- package/scss/_controls.scss +0 -204
- package/scss/_loading.scss +0 -111
- package/scss/_menus.scss +0 -432
- package/scss/_mixins.scss +0 -112
- package/scss/_poster.scss +0 -8
- package/scss/_progress-bar.scss +0 -319
- package/scss/_resolution.scss +0 -68
- package/scss/_responsive.scss +0 -1368
- package/scss/_themes.scss +0 -30
- package/scss/_title-overlay.scss +0 -60
- package/scss/_tooltips.scss +0 -7
- package/scss/_variables.scss +0 -49
- package/scss/_video.scss +0 -221
- package/scss/_volume.scss +0 -122
- package/scss/_watermark.scss +0 -128
- package/scss/myetv-player.scss +0 -51
- package/scss/package.json +0 -16
- package/src/README.md +0 -560
- package/src/chapters.js +0 -521
- package/src/controls.js +0 -1242
- package/src/core.js +0 -1922
- package/src/events.js +0 -537
- package/src/fullscreen.js +0 -82
- package/src/i18n.js +0 -374
- package/src/playlist.js +0 -177
- package/src/plugins.js +0 -384
- package/src/quality.js +0 -963
- package/src/streaming.js +0 -346
- package/src/subtitles.js +0 -524
- package/src/utils.js +0 -65
- package/src/watermark.js +0 -246
package/src/controls.js
DELETED
|
@@ -1,1242 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
/* Controls Module for MYETV Video Player
|
|
3
|
-
* Conservative modularization - original code preserved exactly
|
|
4
|
-
* Created by https://www.myetv.tv https://oskarcosimo.com
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/* AUTO-HIDE SYSTEM */
|
|
8
|
-
initAutoHide() {
|
|
9
|
-
if (!this.options.autoHide) {
|
|
10
|
-
if (this.options.debug) console.log('Auto-hide disabled in options');
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (this.autoHideInitialized) {
|
|
15
|
-
if (this.options.debug) console.log('Auto-hide already initialized');
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (this.options.debug) console.log('Initializing auto-hide system');
|
|
20
|
-
|
|
21
|
-
// CHECK DOM ELEMENTS EXISTENCE
|
|
22
|
-
if (!this.container) {
|
|
23
|
-
if (this.options.debug) console.error('Container not found! Auto-hide cannot work');
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (!this.controls) {
|
|
28
|
-
if (this.options.debug) console.error('Controls not found! Auto-hide cannot work');
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (this.options.debug) console.log('DOM elements verified:', {
|
|
33
|
-
container: !!this.container,
|
|
34
|
-
controls: !!this.controls,
|
|
35
|
-
video: !!this.video
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Show controls initially
|
|
39
|
-
this.showControlsNow();
|
|
40
|
-
|
|
41
|
-
// Event listener for mousemove
|
|
42
|
-
this.container.addEventListener('mousemove', (e) => {
|
|
43
|
-
if (this.autoHideDebug) {
|
|
44
|
-
if (this.options.debug) console.log('Mouse movement in container - reset timer');
|
|
45
|
-
}
|
|
46
|
-
this.onMouseMoveInPlayer(e);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (this.options.debug) console.log('📡 Event listener mousemove added to container');
|
|
50
|
-
|
|
51
|
-
// Event listener for mouseenter/mouseleave
|
|
52
|
-
this.controls.addEventListener('mouseenter', (e) => {
|
|
53
|
-
if (this.autoHideDebug) {
|
|
54
|
-
if (this.options.debug) console.log('Mouse ENTERS controls - cancel timer');
|
|
55
|
-
}
|
|
56
|
-
this.onMouseEnterControls(e);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
this.controls.addEventListener('mouseleave', (e) => {
|
|
60
|
-
if (this.autoHideDebug) {
|
|
61
|
-
if (this.options.debug) console.log('Mouse EXITS controls - restart timer');
|
|
62
|
-
|
|
63
|
-
// Touch events for mobile devices
|
|
64
|
-
this.container.addEventListener('touchstart', () => {
|
|
65
|
-
this.showControlsNow();
|
|
66
|
-
this.resetAutoHideTimer();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
this.container.addEventListener('touchend', () => {
|
|
70
|
-
this.resetAutoHideTimer();
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
this.onMouseLeaveControls(e);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
if (this.options.debug) console.log('Event listener mouseenter/mouseleave added to controls');
|
|
77
|
-
|
|
78
|
-
this.autoHideInitialized = true;
|
|
79
|
-
if (this.options.debug) console.log('Auto-hide system fully initialized');
|
|
80
|
-
|
|
81
|
-
// Test
|
|
82
|
-
this.resetAutoHideTimer();
|
|
83
|
-
if (this.options.debug) console.log('Initial timer started');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
onMouseMoveInPlayer(e) {
|
|
87
|
-
this.showControlsNow();
|
|
88
|
-
this.showCursor();
|
|
89
|
-
this.resetAutoHideTimer();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
onMouseEnterControls(e) {
|
|
93
|
-
this.mouseOverControls = true;
|
|
94
|
-
this.showControlsNow();
|
|
95
|
-
|
|
96
|
-
if (this.autoHideTimer) {
|
|
97
|
-
clearTimeout(this.autoHideTimer);
|
|
98
|
-
this.autoHideTimer = null;
|
|
99
|
-
if (this.autoHideDebug) {
|
|
100
|
-
if (this.options.debug) console.log('Auto-hide timer cancelled');
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
onMouseLeaveControls(e) {
|
|
106
|
-
this.mouseOverControls = false;
|
|
107
|
-
this.resetAutoHideTimer();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
resetAutoHideTimer() {
|
|
111
|
-
if (this.autoHideTimer) {
|
|
112
|
-
clearTimeout(this.autoHideTimer);
|
|
113
|
-
this.autoHideTimer = null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
117
|
-
if (this.mouseOverControls && !isTouchDevice) {
|
|
118
|
-
if (this.autoHideDebug) {
|
|
119
|
-
if (this.options.debug) console.log('Not starting timer - mouse on controls');
|
|
120
|
-
}
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (this.video && this.video.paused) {
|
|
125
|
-
if (this.autoHideDebug) {
|
|
126
|
-
if (this.options.debug) console.log('Not starting timer - video paused');
|
|
127
|
-
}
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
this.autoHideTimer = setTimeout(() => {
|
|
132
|
-
if (this.autoHideDebug) {
|
|
133
|
-
if (this.options.debug) console.log(`Timer expired after ${this.options.autoHideDelay}ms - nascondo controlli`);
|
|
134
|
-
}
|
|
135
|
-
this.hideControlsNow();
|
|
136
|
-
}, this.options.autoHideDelay);
|
|
137
|
-
|
|
138
|
-
if (this.autoHideDebug) {
|
|
139
|
-
if (this.options.debug) console.log(`Auto-hide timer started: ${this.options.autoHideDelay}ms`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
showControlsNow() {
|
|
144
|
-
if (this.controls) {
|
|
145
|
-
this.controls.classList.add('show');
|
|
146
|
-
|
|
147
|
-
// Add has-controls class to container (for watermark visibility)
|
|
148
|
-
if (this.container) {
|
|
149
|
-
this.container.classList.add('has-controls');
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
this.updateControlbarHeight();
|
|
153
|
-
|
|
154
|
-
// Update watermark position
|
|
155
|
-
if (this.updateWatermarkPosition) {
|
|
156
|
-
this.updateWatermarkPosition();
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Show title overlay with controls (if not persistent)
|
|
160
|
-
if (this.options.showTitleOverlay && !this.options.persistentTitle && this.options.videoTitle) {
|
|
161
|
-
this.showTitleOverlay();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// *show cursor when controls are shown*
|
|
165
|
-
this.showCursor();
|
|
166
|
-
|
|
167
|
-
if (this.autoHideDebug && this.options.debug) console.log('✅ Controls shown');
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
hideControlsNow() {
|
|
172
|
-
// Dont hide if mouse is still over controls (allow hiding on touch devices)
|
|
173
|
-
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
174
|
-
if (this.mouseOverControls && !isTouchDevice) {
|
|
175
|
-
if (this.autoHideDebug && this.options.debug) console.log('❌ Not hiding - mouse still over controls');
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Dont hide if video is paused
|
|
180
|
-
if (this.video && this.video.paused) {
|
|
181
|
-
if (this.autoHideDebug && this.options.debug) console.log('❌ Not hiding - video is paused');
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (this.controls) {
|
|
186
|
-
this.controls.classList.remove('show');
|
|
187
|
-
|
|
188
|
-
// Remove has-controls class from container (for watermark visibility)
|
|
189
|
-
if (this.container) {
|
|
190
|
-
this.container.classList.remove('has-controls');
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
this.updateControlbarHeight();
|
|
194
|
-
|
|
195
|
-
// Update watermark position
|
|
196
|
-
if (this.updateWatermarkPosition) {
|
|
197
|
-
this.updateWatermarkPosition();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Hide title overlay with controls (if not persistent)
|
|
201
|
-
if (this.options.showTitleOverlay && !this.options.persistentTitle) {
|
|
202
|
-
this.hideTitleOverlay();
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// *hide cursor after controls are hidden*
|
|
206
|
-
this.hideCursor();
|
|
207
|
-
|
|
208
|
-
if (this.autoHideDebug && this.options.debug) console.log('✅ Controls hidden');
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
showControls() {
|
|
213
|
-
this.showControlsNow();
|
|
214
|
-
this.resetAutoHideTimer();
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
hideControls() {
|
|
218
|
-
this.hideControlsNow();
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
hideControlsWithDelay() {
|
|
222
|
-
this.resetAutoHideTimer();
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
clearControlsTimeout() {
|
|
226
|
-
if (this.autoHideTimer) {
|
|
227
|
-
clearTimeout(this.autoHideTimer);
|
|
228
|
-
this.autoHideTimer = null;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Default controlbar styles injection
|
|
233
|
-
injectDefaultControlbarStyles() {
|
|
234
|
-
if (document.getElementById('default-controlbar-styles')) {
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const controlBarOpacity = Math.max(0, Math.min(1, this.options.controlBarOpacity));
|
|
239
|
-
const titleOverlayOpacity = Math.max(0, Math.min(1, this.options.titleOverlayOpacity));
|
|
240
|
-
|
|
241
|
-
const style = document.createElement('style');
|
|
242
|
-
style.id = 'default-controlbar-styles';
|
|
243
|
-
style.textContent = `
|
|
244
|
-
.video-wrapper:not(.youtube-active):not(.vimeo-active):not(.facebook-active) .controls {
|
|
245
|
-
background: linear-gradient(
|
|
246
|
-
to top,
|
|
247
|
-
rgba(0, 0, 0, ${controlBarOpacity}) 0%,
|
|
248
|
-
rgba(0, 0, 0, ${controlBarOpacity * 0.89}) 20%,
|
|
249
|
-
rgba(0, 0, 0, ${controlBarOpacity * 0.74}) 40%,
|
|
250
|
-
rgba(0, 0, 0, ${controlBarOpacity * 0.53}) 60%,
|
|
251
|
-
rgba(0, 0, 0, ${controlBarOpacity * 0.32}) 80%,
|
|
252
|
-
rgba(0, 0, 0, ${controlBarOpacity * 0.21}) 100%
|
|
253
|
-
);
|
|
254
|
-
backdrop-filter: blur(3px);
|
|
255
|
-
min-height: 60px;
|
|
256
|
-
padding-bottom: 10px;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
.video-wrapper:not(.youtube-active):not(.vimeo-active):not(.facebook-active) .title-overlay {
|
|
260
|
-
background: linear-gradient(
|
|
261
|
-
to bottom,
|
|
262
|
-
rgba(0, 0, 0, ${titleOverlayOpacity}) 0%,
|
|
263
|
-
rgba(0, 0, 0, ${titleOverlayOpacity * 0.89}) 20%,
|
|
264
|
-
rgba(0, 0, 0, ${titleOverlayOpacity * 0.74}) 40%,
|
|
265
|
-
rgba(0, 0, 0, ${titleOverlayOpacity * 0.53}) 60%,
|
|
266
|
-
rgba(0, 0, 0, ${titleOverlayOpacity * 0.32}) 80%,
|
|
267
|
-
rgba(0, 0, 0, ${titleOverlayOpacity * 0.21}) 100%
|
|
268
|
-
);
|
|
269
|
-
backdrop-filter: blur(3px);
|
|
270
|
-
min-height: 80px;
|
|
271
|
-
padding-top: 20px;
|
|
272
|
-
}
|
|
273
|
-
`;
|
|
274
|
-
|
|
275
|
-
document.head.appendChild(style);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Debug methods
|
|
279
|
-
enableAutoHideDebug() {
|
|
280
|
-
this.autoHideDebug = true;
|
|
281
|
-
if (this.options.debug) console.log('AUTO-HIDE DEBUG ENABLED');
|
|
282
|
-
if (this.options.debug) console.log('Stato attuale:', {
|
|
283
|
-
initialized: this.autoHideInitialized,
|
|
284
|
-
autoHide: this.options.autoHide,
|
|
285
|
-
delay: this.options.autoHideDelay,
|
|
286
|
-
mouseOverControls: this.mouseOverControls,
|
|
287
|
-
timerActive: !!this.autoHideTimer,
|
|
288
|
-
container: !!this.container,
|
|
289
|
-
controls: !!this.controls,
|
|
290
|
-
video: !!this.video,
|
|
291
|
-
videoPaused: this.video ? this.video.paused : 'N/A'
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
if (!this.autoHideInitialized) {
|
|
295
|
-
if (this.options.debug) console.log('Auto-hide NOT yet initialized! Initializing now...');
|
|
296
|
-
this.initAutoHide();
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
disableAutoHideDebug() {
|
|
301
|
-
this.autoHideDebug = false;
|
|
302
|
-
if (this.options.debug) console.log('Auto-hide debug disabled');
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
testAutoHide() {
|
|
306
|
-
if (this.options.debug) console.log('TEST AUTO-HIDE COMPLETED:');
|
|
307
|
-
if (this.options.debug) console.log('System status:', {
|
|
308
|
-
initialized: this.autoHideInitialized,
|
|
309
|
-
autoHide: this.options.autoHide,
|
|
310
|
-
delay: this.options.autoHideDelay,
|
|
311
|
-
mouseOverControls: this.mouseOverControls,
|
|
312
|
-
timerActive: !!this.autoHideTimer
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
if (this.options.debug) console.log('Elementi DOM:', {
|
|
316
|
-
container: !!this.container,
|
|
317
|
-
controls: !!this.controls,
|
|
318
|
-
video: !!this.video
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
if (this.options.debug) console.log('Stato video:', {
|
|
322
|
-
paused: this.video ? this.video.paused : 'N/A',
|
|
323
|
-
currentTime: this.video ? this.video.currentTime : 'N/A',
|
|
324
|
-
duration: this.video ? this.video.duration : 'N/A'
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
if (!this.autoHideInitialized) {
|
|
328
|
-
if (this.options.debug) console.log('PROBLEM: Auto-hide not initialized!');
|
|
329
|
-
if (this.options.debug) console.log('Forcing initialization...');
|
|
330
|
-
this.initAutoHide();
|
|
331
|
-
} else {
|
|
332
|
-
if (this.options.debug) console.log('Auto-hide initialized correctly');
|
|
333
|
-
if (this.options.debug) console.log('Forcing timer reset for test...');
|
|
334
|
-
this.resetAutoHideTimer();
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/* SUBTITLES UI MANAGEMENT */
|
|
339
|
-
updateSubtitlesUI() {
|
|
340
|
-
const subtitlesControl = this.controls?.querySelector('.subtitles-control');
|
|
341
|
-
|
|
342
|
-
if (this.textTracks.length > 0 && this.options.showSubtitles) {
|
|
343
|
-
if (subtitlesControl) {
|
|
344
|
-
subtitlesControl.style.display = 'block';
|
|
345
|
-
}
|
|
346
|
-
this.populateSubtitlesMenu();
|
|
347
|
-
} else {
|
|
348
|
-
if (subtitlesControl) {
|
|
349
|
-
subtitlesControl.style.display = 'none';
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
populateSubtitlesMenu() {
|
|
355
|
-
const subtitlesMenu = this.controls?.querySelector('.subtitles-menu');
|
|
356
|
-
if (!subtitlesMenu) return;
|
|
357
|
-
|
|
358
|
-
let menuHTML = `<div class="subtitles-option ${!this.subtitlesEnabled ? 'active' : ''}" data-track="off">${this.t('subtitlesoff') || 'Off'}</div>`;
|
|
359
|
-
|
|
360
|
-
this.textTracks.forEach((trackData, index) => {
|
|
361
|
-
const isActive = this.currentSubtitleTrack === trackData.track;
|
|
362
|
-
menuHTML += `<div class="subtitles-option ${isActive ? 'active' : ''}" data-track="${index}">${trackData.label}</div>`;
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
subtitlesMenu.innerHTML = menuHTML;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
toggleSubtitles() {
|
|
369
|
-
if (this.textTracks.length === 0) return;
|
|
370
|
-
|
|
371
|
-
if (this.subtitlesEnabled) {
|
|
372
|
-
this.disableSubtitles();
|
|
373
|
-
} else {
|
|
374
|
-
this.enableSubtitleTrack(0);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
updateSubtitlesButton() {
|
|
379
|
-
const subtitlesBtn = this.controls?.querySelector('.subtitles-btn');
|
|
380
|
-
if (!subtitlesBtn) return;
|
|
381
|
-
|
|
382
|
-
if (this.subtitlesEnabled) {
|
|
383
|
-
subtitlesBtn.classList.add('active');
|
|
384
|
-
subtitlesBtn.title = this.t('subtitlesdisable') || 'Disable subtitles';
|
|
385
|
-
} else {
|
|
386
|
-
subtitlesBtn.classList.remove('active');
|
|
387
|
-
subtitlesBtn.title = this.t('subtitlesenable') || 'Enable subtitles';
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
handleSubtitlesMenuClick(e) {
|
|
392
|
-
if (!e.target.classList.contains('subtitles-option')) return;
|
|
393
|
-
|
|
394
|
-
const trackData = e.target.getAttribute('data-track');
|
|
395
|
-
|
|
396
|
-
if (trackData === 'off') {
|
|
397
|
-
this.disableSubtitles();
|
|
398
|
-
} else {
|
|
399
|
-
const trackIndex = parseInt(trackData);
|
|
400
|
-
this.enableSubtitleTrack(trackIndex);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/* PLAYER CONTROLS SETUP */
|
|
405
|
-
hideNativePlayer() {
|
|
406
|
-
this.video.controls = false;
|
|
407
|
-
this.video.setAttribute('controls', 'false');
|
|
408
|
-
this.video.removeAttribute('controls');
|
|
409
|
-
this.video.style.visibility = 'hidden';
|
|
410
|
-
this.video.style.opacity = '0';
|
|
411
|
-
this.video.style.pointerEvents = 'none';
|
|
412
|
-
this.video.classList.add('video-player');
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
createControls() {
|
|
416
|
-
const controlsId = `videoControls-${this.getUniqueId()}`;
|
|
417
|
-
|
|
418
|
-
const controlsHTML = `
|
|
419
|
-
<div class="controls" id="${controlsId}">
|
|
420
|
-
<div class="progress-container">
|
|
421
|
-
<div class="progress-bar">
|
|
422
|
-
<div class="progress-buffer"></div>
|
|
423
|
-
<div class="progress-filled"></div>
|
|
424
|
-
</div>
|
|
425
|
-
<div class="progress-handle progress-handle-${this.options.seekHandleShape}"></div>
|
|
426
|
-
${this.options.showSeekTooltip ? '<div class="seek-tooltip">0:00</div>' : ''}
|
|
427
|
-
</div>
|
|
428
|
-
|
|
429
|
-
<div class="controls-main">
|
|
430
|
-
<div class="controls-left">
|
|
431
|
-
<button class="control-btn play-pause-btn" data-tooltip="play_pause">
|
|
432
|
-
<span class="icon play-icon"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M8 5v14l11-7z"/></svg></span>
|
|
433
|
-
<span class="icon pause-icon hidden"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M6 4h4v16H6zm8 0h4v16h-4z"/></svg></span>
|
|
434
|
-
</button>
|
|
435
|
-
|
|
436
|
-
<button class="control-btn mute-btn" data-tooltip="mute_unmute">
|
|
437
|
-
<span class="icon volume-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303z"/><path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89z"/><path d="M10.025 8a4.486 4.486 0 0 1-1.318 3.182L8 10.475A3.489 3.489 0 0 0 9.025 8c0-.966-.392-1.841-1.025-2.475l.707-.707A4.486 4.486 0 0 1 10.025 8M7 4a.5.5 0 0 0-.812-.39L3.825 5.5H1.5A.5.5 0 0 0 1 6v4a.5.5 0 0 0 .5.5h2.325l2.363 1.89A.5.5 0 0 0 7 12z"/></svg></span>
|
|
438
|
-
<span class="icon mute-icon hidden"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06m7.137 2.096a.5.5 0 0 1 0 .708L12.207 8l1.647 1.646a.5.5 0 0 1-.708.708L11.5 8.707l-1.646 1.647a.5.5 0 0 1-.708-.708L10.793 8 9.146 6.354a.5.5 0 1 1 .708-.708L11.5 7.293l1.646-1.647a.5.5 0 0 1 .708 0"/></svg></span>
|
|
439
|
-
</button>
|
|
440
|
-
|
|
441
|
-
<div class="volume-container" data-mobile-slider="${this.options.volumeSlider}">
|
|
442
|
-
<input type="range" class="volume-slider" min="0" max="100" value="100" data-tooltip="volume">
|
|
443
|
-
</div>
|
|
444
|
-
|
|
445
|
-
<div class="time-display">
|
|
446
|
-
<span class="current-time">0:00</span>
|
|
447
|
-
<span>/</span>
|
|
448
|
-
<span class="duration">0:00</span>
|
|
449
|
-
</div>
|
|
450
|
-
</div>
|
|
451
|
-
|
|
452
|
-
<div class="controls-right">
|
|
453
|
-
<button class="control-btn playlist-prev-btn" data-tooltip="prevvideo" style="display: none;">
|
|
454
|
-
<span class="icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M3.5 12V4l7 4zm8-8v8l-7-4z"/></svg></span>
|
|
455
|
-
</button>
|
|
456
|
-
<button class="control-btn playlist-next-btn" data-tooltip="nextvideo" style="display: none;">
|
|
457
|
-
<span class="icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M12.5 4v8l-7-4zm-8 0v8l7-4z"/></svg></span>
|
|
458
|
-
</button>
|
|
459
|
-
|
|
460
|
-
${this.options.showSubtitles ? `
|
|
461
|
-
<div class="subtitles-control" style="display: none;">
|
|
462
|
-
<button class="control-btn subtitles-btn" data-tooltip="subtitles">
|
|
463
|
-
<span class="icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1z"/><path d="M6.096 4.972c.234 0 .44.05.617.152.177.1.312.235.405.403.093.169.14.36.14.577 0 .216-.047.406-.14.572a1.03 1.03 0 0 1-.405.403 1.2 1.2 0 0 1-.617.152 1.2 1.2 0 0 1-.615-.152 1.03 1.03 0 0 1-.406-.403 1.28 1.28 0 0 1-.14-.572c0-.216.046-.408.14-.577.093-.168.228-.303.406-.403.177-.101.383-.152.615-.152m4.915 0c.234 0 .44.05.617.152.177.1.312.235.405.403.093.169.14.36.14.577 0 .216-.047.406-.14.572a1.03 1.03 0 0 1-.405.403 1.2 1.2 0 0 1-.617.152 1.2 1.2 0 0 1-.615-.152 1.03 1.03 0 0 1-.406-.403 1.28 1.28 0 0 1-.14-.572c0-.216.046-.408.14-.577.093-.168.228-.303.406-.403.177-.101.383-.152.615-.152M6.096 9.972c.234 0 .44.05.617.152.177.1.312.235.405.403.093.169.14.36.14.577 0 .216-.047.406-.14.572a1.03 1.03 0 0 1-.405.403 1.2 1.2 0 0 1-.617.152 1.2 1.2 0 0 1-.615-.152 1.03 1.03 0 0 1-.406-.403 1.28 1.28 0 0 1-.14-.572c0-.216.046-.408.14-.577.093-.168.228-.303.406-.403.177-.101.383-.152.615-.152m4.915 0c.234 0 .44.05.617.152.177.1.312.235.405.403.093.169.14.36.14.577 0 .216-.047.406-.14.572a1.03 1.03 0 0 1-.405.403 1.2 1.2 0 0 1-.617.152 1.2 1.2 0 0 1-.615-.152 1.03 1.03 0 0 1-.406-.403 1.28 1.28 0 0 1-.14-.572c0-.216.046-.408.14-.577.093-.168.228-.303.406-.403.177-.101.383-.152.615-.152"/></svg></span>
|
|
464
|
-
</button>
|
|
465
|
-
<div class="subtitles-menu">
|
|
466
|
-
<div class="subtitles-option active" data-track="off">Off</div>
|
|
467
|
-
</div>
|
|
468
|
-
</div>
|
|
469
|
-
` : ''}
|
|
470
|
-
|
|
471
|
-
${this.options.showSpeedControl ? `
|
|
472
|
-
<div class="speed-control">
|
|
473
|
-
<button class="control-btn speed-btn" data-tooltip="playback_speed">1x</button>
|
|
474
|
-
<div class="speed-menu">
|
|
475
|
-
<div class="speed-option" data-speed="0.5">0.5x</div>
|
|
476
|
-
<div class="speed-option" data-speed="0.75">0.75x</div>
|
|
477
|
-
<div class="speed-option active" data-speed="1">1x</div>
|
|
478
|
-
<div class="speed-option" data-speed="1.25">1.25x</div>
|
|
479
|
-
<div class="speed-option" data-speed="1.5">1.5x</div>
|
|
480
|
-
<div class="speed-option" data-speed="2">2x</div>
|
|
481
|
-
</div>
|
|
482
|
-
</div>
|
|
483
|
-
` : ''}
|
|
484
|
-
|
|
485
|
-
${this.options.showQualitySelector && this.originalSources && this.originalSources.length > 1 ? `
|
|
486
|
-
<div class="quality-control">
|
|
487
|
-
<button class="control-btn quality-btn" data-tooltip="video_quality">
|
|
488
|
-
<div class="quality-btn-text">
|
|
489
|
-
<div class="selected-quality">${this.t('auto')}</div>
|
|
490
|
-
<div class="current-quality"></div>
|
|
491
|
-
</div>
|
|
492
|
-
</button>
|
|
493
|
-
<div class="quality-menu">
|
|
494
|
-
<div class="quality-option selected" data-quality="auto">${this.t('auto')}</div>
|
|
495
|
-
${this.originalSources.map(s =>
|
|
496
|
-
`<div class="quality-option" data-quality="${s.quality}">${s.quality}</div>`
|
|
497
|
-
).join('')}
|
|
498
|
-
</div>
|
|
499
|
-
</div>
|
|
500
|
-
` : ''}
|
|
501
|
-
|
|
502
|
-
${this.options.showPictureInPicture && this.isPiPSupported ? `
|
|
503
|
-
<button class="control-btn pip-btn" data-tooltip="picture_in_picture">
|
|
504
|
-
<span class="icon pip-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M0 3.5A1.5 1.5 0 0 1 1.5 2h13A1.5 1.5 0 0 1 16 3.5v9a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 12.5zM1.5 3a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h13a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5z"/><path d="M8 8.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5v3a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5z"/></svg></span>
|
|
505
|
-
<span class="icon pip-exit-icon hidden"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M0 3.5A1.5 1.5 0 0 1 1.5 2h13A1.5 1.5 0 0 1 16 3.5v9a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 12.5zM1.5 3a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h13a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5z"/></svg></span>
|
|
506
|
-
</button>
|
|
507
|
-
` : ''}
|
|
508
|
-
|
|
509
|
-
<div class="settings-control">
|
|
510
|
-
<button class="control-btn settings-btn" data-tooltip="settings_menu">
|
|
511
|
-
<span class="icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492M5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0"/><path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52z"/></svg></span>
|
|
512
|
-
</button>
|
|
513
|
-
<div class="settings-menu"></div>
|
|
514
|
-
</div>
|
|
515
|
-
|
|
516
|
-
${this.options.showFullscreen ? `
|
|
517
|
-
<button class="control-btn fullscreen-btn" data-tooltip="fullscreen">
|
|
518
|
-
<span class="icon fullscreen-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M1.5 1a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4A1.5 1.5 0 0 1 1.5 0h4a.5.5 0 0 1 0 1zM10 .5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 16 1.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5M.5 10a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 0 14.5v-4a.5.5 0 0 1 .5-.5m15 0a.5.5 0 0 1 .5.5v4a1.5 1.5 0 0 1-1.5 1.5h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5"/></svg></span>
|
|
519
|
-
<span class="icon exit-fullscreen-icon hidden"><svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M5.5 0a.5.5 0 0 1 .5.5v4A1.5 1.5 0 0 1 4.5 6h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5m5 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 10 4.5v-4a.5.5 0 0 1 .5-.5M0 10.5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 6 11.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5m10 1a1.5 1.5 0 0 1 1.5-1.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0z"/></svg></span>
|
|
520
|
-
</button>
|
|
521
|
-
` : ''}
|
|
522
|
-
</div>
|
|
523
|
-
</div>
|
|
524
|
-
</div>
|
|
525
|
-
`;
|
|
526
|
-
|
|
527
|
-
this.container.insertAdjacentHTML('beforeend', controlsHTML);
|
|
528
|
-
this.controls = document.getElementById(controlsId);
|
|
529
|
-
|
|
530
|
-
// NEW: Initialize responsive settings menu
|
|
531
|
-
setTimeout(() => {
|
|
532
|
-
this.initializeResponsiveMenu();
|
|
533
|
-
this.updateControlbarHeight();
|
|
534
|
-
}, 100);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/* Initialize responsive menu with dynamic width calculation */
|
|
538
|
-
initializeResponsiveMenu() {
|
|
539
|
-
if (!this.controls) return;
|
|
540
|
-
|
|
541
|
-
// Track screen size
|
|
542
|
-
this.isSmallScreen = false;
|
|
543
|
-
|
|
544
|
-
// Check initial size
|
|
545
|
-
this.checkScreenSize();
|
|
546
|
-
|
|
547
|
-
// Bind resize handler with updateControlbarHeight
|
|
548
|
-
const resizeHandler = () => {
|
|
549
|
-
this.checkScreenSize();
|
|
550
|
-
this.updateControlbarHeight();
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
// Bind del context
|
|
554
|
-
this.resizeHandler = resizeHandler.bind(this);
|
|
555
|
-
window.addEventListener('resize', this.resizeHandler);
|
|
556
|
-
|
|
557
|
-
// Bind events for settings menu
|
|
558
|
-
this.bindSettingsMenuEvents();
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// Dynamic controlbar height tracking for watermark positioning
|
|
562
|
-
updateControlbarHeight() {
|
|
563
|
-
if (!this.controls) return;
|
|
564
|
-
|
|
565
|
-
const height = this.controls.offsetHeight;
|
|
566
|
-
if (this.container) {
|
|
567
|
-
|
|
568
|
-
this.container.style.setProperty('--player-controls-height', `${height}px`);
|
|
569
|
-
|
|
570
|
-
const watermark = this.container.querySelector('.video-watermark.watermark-bottomleft, .video-watermark.watermark-bottomright');
|
|
571
|
-
if (watermark) {
|
|
572
|
-
const hasControls = this.container.classList.contains('has-controls');
|
|
573
|
-
const isHideOnAutoHide = watermark.classList.contains('hide-on-autohide');
|
|
574
|
-
|
|
575
|
-
if (hasControls || !isHideOnAutoHide) {
|
|
576
|
-
watermark.style.bottom = `${height + 15}px`;
|
|
577
|
-
} else {
|
|
578
|
-
watermark.style.bottom = '15px';
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
if (this.options.debug) {
|
|
584
|
-
console.log(`Controlbar height updated: ${height}px`);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
/* Dynamic width calculation based on logo presence */
|
|
589
|
-
getResponsiveThreshold() {
|
|
590
|
-
// Check if brand logo is enabled and present
|
|
591
|
-
const hasLogo = this.options.brandLogoEnabled && this.options.brandLogoUrl;
|
|
592
|
-
|
|
593
|
-
// If logo is present, use higher threshold (650px), otherwise 550px
|
|
594
|
-
return hasLogo ? 650 : 550;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
/* Check if screen is under dynamic threshold */
|
|
598
|
-
checkScreenSize() {
|
|
599
|
-
const threshold = this.getResponsiveThreshold();
|
|
600
|
-
const newIsSmallScreen = window.innerWidth <= threshold;
|
|
601
|
-
|
|
602
|
-
if (newIsSmallScreen !== this.isSmallScreen) {
|
|
603
|
-
this.isSmallScreen = newIsSmallScreen;
|
|
604
|
-
this.updateSettingsMenuVisibility();
|
|
605
|
-
|
|
606
|
-
if (this.options.debug) {
|
|
607
|
-
console.log(`Screen check: ${window.innerWidth}px vs ${threshold}px (threshold), logo: ${this.options.brandLogoEnabled}, small: ${this.isSmallScreen}`);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/* Update settings menu visibility based on screen size */
|
|
613
|
-
updateSettingsMenuVisibility() {
|
|
614
|
-
const settingsControl = this.controls?.querySelector('.settings-control');
|
|
615
|
-
if (!settingsControl) return;
|
|
616
|
-
|
|
617
|
-
if (this.isSmallScreen) {
|
|
618
|
-
// Show settings menu and hide individual controls
|
|
619
|
-
settingsControl.style.display = 'block';
|
|
620
|
-
|
|
621
|
-
// Hide controls that will be moved to settings menu
|
|
622
|
-
const pipBtn = this.controls.querySelector('.pip-btn');
|
|
623
|
-
const speedControl = this.controls.querySelector('.speed-control');
|
|
624
|
-
const subtitlesControl = this.controls.querySelector('.subtitles-control');
|
|
625
|
-
|
|
626
|
-
if (pipBtn) pipBtn.style.display = 'none';
|
|
627
|
-
if (speedControl) speedControl.style.display = 'none';
|
|
628
|
-
if (subtitlesControl) subtitlesControl.style.display = 'none';
|
|
629
|
-
|
|
630
|
-
this.populateSettingsMenu();
|
|
631
|
-
} else {
|
|
632
|
-
// Hide settings menu and show individual controls
|
|
633
|
-
settingsControl.style.display = 'none';
|
|
634
|
-
|
|
635
|
-
// Show original controls
|
|
636
|
-
const pipBtn = this.controls.querySelector('.pip-btn');
|
|
637
|
-
const speedControl = this.controls.querySelector('.speed-control');
|
|
638
|
-
const subtitlesControl = this.controls.querySelector('.subtitles-control');
|
|
639
|
-
|
|
640
|
-
if (pipBtn && this.options.showPictureInPicture && this.isPiPSupported) {
|
|
641
|
-
pipBtn.style.display = 'flex';
|
|
642
|
-
}
|
|
643
|
-
if (speedControl && this.options.showSpeedControl) {
|
|
644
|
-
speedControl.style.display = 'block';
|
|
645
|
-
}
|
|
646
|
-
if (subtitlesControl && this.options.showSubtitles && this.textTracks.length > 0) {
|
|
647
|
-
subtitlesControl.style.display = 'block';
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
* Populate settings menu with controls
|
|
654
|
-
*/
|
|
655
|
-
populateSettingsMenu() {
|
|
656
|
-
const settingsMenu = this.controls?.querySelector('.settings-menu');
|
|
657
|
-
if (!settingsMenu) return;
|
|
658
|
-
|
|
659
|
-
let menuHTML = '';
|
|
660
|
-
|
|
661
|
-
// Picture-in-Picture option
|
|
662
|
-
if (this.options.showPictureInPicture && this.isPiPSupported) {
|
|
663
|
-
const pipLabel = this.t('picture_in_picture') || 'Picture-in-Picture';
|
|
664
|
-
menuHTML += `<div class="settings-option" data-action="pip">
|
|
665
|
-
<span class="settings-option-label">${pipLabel}</span>
|
|
666
|
-
</div>`;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// Speed Control - expandable
|
|
670
|
-
if (this.options.showSpeedControl) {
|
|
671
|
-
const speedLabel = this.t('playback_speed') || 'Playback Speed';
|
|
672
|
-
const currentSpeed = this.video ? this.video.playbackRate : 1;
|
|
673
|
-
|
|
674
|
-
menuHTML += `
|
|
675
|
-
<div class="settings-expandable-wrapper">
|
|
676
|
-
<div class="settings-option expandable-trigger" data-action="speed-expand">
|
|
677
|
-
<span class="settings-option-label">${speedLabel}: ${currentSpeed}x</span>
|
|
678
|
-
<span class="expand-arrow">▼</span>
|
|
679
|
-
</div>
|
|
680
|
-
<div class="settings-expandable-content" style="display: none;">`;
|
|
681
|
-
|
|
682
|
-
const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
|
683
|
-
speeds.forEach(speed => {
|
|
684
|
-
const isActive = Math.abs(speed - currentSpeed) < 0.01;
|
|
685
|
-
menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-speed="${speed}">${speed}x</div>`;
|
|
686
|
-
});
|
|
687
|
-
|
|
688
|
-
menuHTML += `</div></div>`;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// Subtitles - expandable
|
|
692
|
-
if (this.options.showSubtitles && this.textTracks && this.textTracks.length > 0) {
|
|
693
|
-
const subtitlesLabel = this.t('subtitles') || 'Subtitles';
|
|
694
|
-
const currentTrack = this.currentSubtitleTrack;
|
|
695
|
-
const currentLabel = this.subtitlesEnabled && currentTrack ? currentTrack.label : (this.t('subtitlesoff') || 'Off');
|
|
696
|
-
|
|
697
|
-
menuHTML += `
|
|
698
|
-
<div class="settings-expandable-wrapper">
|
|
699
|
-
<div class="settings-option expandable-trigger" data-action="subtitles-expand">
|
|
700
|
-
<span class="settings-option-label">${subtitlesLabel}: ${currentLabel}</span>
|
|
701
|
-
<span class="expand-arrow">▼</span>
|
|
702
|
-
</div>
|
|
703
|
-
<div class="settings-expandable-content" style="display: none;">`;
|
|
704
|
-
|
|
705
|
-
// Off option
|
|
706
|
-
menuHTML += `<div class="settings-suboption ${!this.subtitlesEnabled ? 'active' : ''}" data-track="off">${this.t('subtitlesoff') || 'Off'}</div>`;
|
|
707
|
-
|
|
708
|
-
// Subtitle tracks
|
|
709
|
-
this.textTracks.forEach((trackData, index) => {
|
|
710
|
-
const isActive = this.currentSubtitleTrack === trackData.track;
|
|
711
|
-
menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-track="${index}">${trackData.label}</div>`;
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
menuHTML += `</div></div>`;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
settingsMenu.innerHTML = menuHTML;
|
|
718
|
-
|
|
719
|
-
// Add scrollbar if needed
|
|
720
|
-
this.addSettingsMenuScrollbar();
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
/**
|
|
724
|
-
* Add scrollbar to settings menu on mobile
|
|
725
|
-
*/
|
|
726
|
-
addSettingsMenuScrollbar() {
|
|
727
|
-
const settingsMenu = this.controls?.querySelector('.settings-menu');
|
|
728
|
-
if (!settingsMenu) return;
|
|
729
|
-
|
|
730
|
-
const playerHeight = this.container.offsetHeight;
|
|
731
|
-
const maxMenuHeight = playerHeight - 100;
|
|
732
|
-
|
|
733
|
-
settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
|
|
734
|
-
settingsMenu.style.overflowY = 'auto';
|
|
735
|
-
settingsMenu.style.overflowX = 'hidden';
|
|
736
|
-
|
|
737
|
-
// Add scrollbar styling
|
|
738
|
-
if (!document.getElementById('player-settings-scrollbar-style')) {
|
|
739
|
-
const scrollbarStyle = document.createElement('style');
|
|
740
|
-
scrollbarStyle.id = 'player-settings-scrollbar-style';
|
|
741
|
-
scrollbarStyle.textContent = `
|
|
742
|
-
.settings-menu::-webkit-scrollbar {
|
|
743
|
-
width: 6px;
|
|
744
|
-
}
|
|
745
|
-
.settings-menu::-webkit-scrollbar-track {
|
|
746
|
-
background: rgba(255,255,255,0.05);
|
|
747
|
-
border-radius: 3px;
|
|
748
|
-
}
|
|
749
|
-
.settings-menu::-webkit-scrollbar-thumb {
|
|
750
|
-
background: rgba(255,255,255,0.3);
|
|
751
|
-
border-radius: 3px;
|
|
752
|
-
}
|
|
753
|
-
.settings-menu::-webkit-scrollbar-thumb:hover {
|
|
754
|
-
background: rgba(255,255,255,0.5);
|
|
755
|
-
}
|
|
756
|
-
`;
|
|
757
|
-
document.head.appendChild(scrollbarStyle);
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
settingsMenu.style.scrollbarWidth = 'thin';
|
|
761
|
-
settingsMenu.style.scrollbarColor = 'rgba(255,255,255,0.3) transparent';
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
* Bind settings menu events
|
|
766
|
-
*/
|
|
767
|
-
bindSettingsMenuEvents() {
|
|
768
|
-
const settingsMenu = this.controls?.querySelector('.settings-menu');
|
|
769
|
-
if (!settingsMenu) return;
|
|
770
|
-
|
|
771
|
-
settingsMenu.addEventListener('click', (e) => {
|
|
772
|
-
e.stopPropagation();
|
|
773
|
-
|
|
774
|
-
// Handle expandable triggers
|
|
775
|
-
if (e.target.classList.contains('expandable-trigger') || e.target.closest('.expandable-trigger')) {
|
|
776
|
-
const trigger = e.target.classList.contains('expandable-trigger') ? e.target : e.target.closest('.expandable-trigger');
|
|
777
|
-
const wrapper = trigger.closest('.settings-expandable-wrapper');
|
|
778
|
-
const content = wrapper.querySelector('.settings-expandable-content');
|
|
779
|
-
const arrow = trigger.querySelector('.expand-arrow');
|
|
780
|
-
|
|
781
|
-
const isExpanded = content.style.display !== 'none';
|
|
782
|
-
|
|
783
|
-
if (isExpanded) {
|
|
784
|
-
content.style.display = 'none';
|
|
785
|
-
arrow.style.transform = 'rotate(0deg)';
|
|
786
|
-
} else {
|
|
787
|
-
content.style.display = 'block';
|
|
788
|
-
arrow.style.transform = 'rotate(180deg)';
|
|
789
|
-
}
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
// Handle direct actions (like PiP)
|
|
794
|
-
if (e.target.classList.contains('settings-option') || e.target.closest('.settings-option')) {
|
|
795
|
-
const option = e.target.classList.contains('settings-option') ? e.target : e.target.closest('.settings-option');
|
|
796
|
-
const action = option.getAttribute('data-action');
|
|
797
|
-
|
|
798
|
-
if (action === 'pip') {
|
|
799
|
-
this.togglePictureInPicture();
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// Handle submenu actions
|
|
805
|
-
if (e.target.classList.contains('settings-suboption')) {
|
|
806
|
-
const wrapper = e.target.closest('.settings-expandable-wrapper');
|
|
807
|
-
const trigger = wrapper.querySelector('.expandable-trigger');
|
|
808
|
-
const action = trigger.getAttribute('data-action');
|
|
809
|
-
|
|
810
|
-
if (action === 'speed-expand') {
|
|
811
|
-
const speed = parseFloat(e.target.getAttribute('data-speed'));
|
|
812
|
-
if (speed && speed > 0 && this.video && !this.isChangingQuality) {
|
|
813
|
-
this.video.playbackRate = speed;
|
|
814
|
-
|
|
815
|
-
// Update active states
|
|
816
|
-
wrapper.querySelectorAll('.settings-suboption').forEach(opt => opt.classList.remove('active'));
|
|
817
|
-
e.target.classList.add('active');
|
|
818
|
-
|
|
819
|
-
// Update trigger text
|
|
820
|
-
const label = trigger.querySelector('.settings-option-label');
|
|
821
|
-
if (label) {
|
|
822
|
-
const speedLabel = this.t('playback_speed') || 'Playback Speed';
|
|
823
|
-
label.textContent = `${speedLabel}: ${speed}x`;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// Trigger event
|
|
827
|
-
this.triggerEvent('speedchange', { speed, previousSpeed: this.video.playbackRate });
|
|
828
|
-
}
|
|
829
|
-
} else if (action === 'subtitles-expand') {
|
|
830
|
-
const trackData = e.target.getAttribute('data-track');
|
|
831
|
-
if (trackData === 'off') {
|
|
832
|
-
this.disableSubtitles();
|
|
833
|
-
} else {
|
|
834
|
-
const trackIndex = parseInt(trackData);
|
|
835
|
-
this.enableSubtitleTrack(trackIndex);
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// Update active states
|
|
839
|
-
wrapper.querySelectorAll('.settings-suboption').forEach(opt => opt.classList.remove('active'));
|
|
840
|
-
e.target.classList.add('active');
|
|
841
|
-
|
|
842
|
-
// Update trigger text
|
|
843
|
-
const label = trigger.querySelector('.settings-option-label');
|
|
844
|
-
if (label) {
|
|
845
|
-
const subtitlesLabel = this.t('subtitles') || 'Subtitles';
|
|
846
|
-
label.textContent = `${subtitlesLabel}: ${e.target.textContent}`;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
/* TITLE OVERLAY MANAGEMENT */
|
|
854
|
-
showTitleOverlay() {
|
|
855
|
-
if (this.titleOverlay && this.options.videoTitle) {
|
|
856
|
-
this.titleOverlay.classList.add('show');
|
|
857
|
-
|
|
858
|
-
if (this.options.persistentTitle) {
|
|
859
|
-
this.titleOverlay.classList.add('persistent');
|
|
860
|
-
} else {
|
|
861
|
-
this.titleOverlay.classList.remove('persistent');
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
return this;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
hideTitleOverlay() {
|
|
868
|
-
if (this.titleOverlay) {
|
|
869
|
-
this.titleOverlay.classList.remove('show');
|
|
870
|
-
this.titleOverlay.classList.remove('persistent');
|
|
871
|
-
}
|
|
872
|
-
return this;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
toggleTitleOverlay(show = null) {
|
|
876
|
-
if (show === null) {
|
|
877
|
-
return this.titleOverlay && this.titleOverlay.classList.contains('show')
|
|
878
|
-
? this.hideTitleOverlay()
|
|
879
|
-
: this.showTitleOverlay();
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
return show ? this.showTitleOverlay() : this.hideTitleOverlay();
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
/* KEYBOARD CONTROLS */
|
|
886
|
-
setupKeyboardControls() {
|
|
887
|
-
document.addEventListener('keydown', (e) => {
|
|
888
|
-
// Ignore if user is typing in an input field
|
|
889
|
-
if (document.activeElement && document.activeElement.tagName === 'INPUT') return;
|
|
890
|
-
|
|
891
|
-
// On keyboard input, treat as mouse movement for auto-hide
|
|
892
|
-
if (this.options.autoHide && this.autoHideInitialized) {
|
|
893
|
-
this.showControlsNow();
|
|
894
|
-
this.resetAutoHideTimer();
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
switch (e.code) {
|
|
898
|
-
case 'Space':
|
|
899
|
-
e.preventDefault();
|
|
900
|
-
this.togglePlayPause();
|
|
901
|
-
break;
|
|
902
|
-
case 'KeyM':
|
|
903
|
-
this.toggleMute();
|
|
904
|
-
break;
|
|
905
|
-
case 'KeyF':
|
|
906
|
-
if (this.options.showFullscreen) {
|
|
907
|
-
this.toggleFullscreen();
|
|
908
|
-
}
|
|
909
|
-
break;
|
|
910
|
-
case 'KeyP':
|
|
911
|
-
if (this.options.showPictureInPicture && this.isPiPSupported) {
|
|
912
|
-
this.togglePictureInPicture();
|
|
913
|
-
}
|
|
914
|
-
break;
|
|
915
|
-
case 'KeyT':
|
|
916
|
-
if (this.options.showTitleOverlay) {
|
|
917
|
-
this.toggleTitleOverlay();
|
|
918
|
-
}
|
|
919
|
-
break;
|
|
920
|
-
case 'KeyS':
|
|
921
|
-
if (this.options.showSubtitles) {
|
|
922
|
-
this.toggleSubtitles();
|
|
923
|
-
}
|
|
924
|
-
break;
|
|
925
|
-
case 'KeyD':
|
|
926
|
-
this.debugQuality ? this.disableQualityDebug() : this.enableQualityDebug();
|
|
927
|
-
break;
|
|
928
|
-
case 'ArrowLeft':
|
|
929
|
-
e.preventDefault();
|
|
930
|
-
this.skipTime(-10);
|
|
931
|
-
break;
|
|
932
|
-
case 'ArrowRight':
|
|
933
|
-
e.preventDefault();
|
|
934
|
-
this.skipTime(10);
|
|
935
|
-
break;
|
|
936
|
-
case 'ArrowUp':
|
|
937
|
-
e.preventDefault();
|
|
938
|
-
this.changeVolume(0.1);
|
|
939
|
-
break;
|
|
940
|
-
case 'ArrowDown':
|
|
941
|
-
e.preventDefault();
|
|
942
|
-
this.changeVolume(-0.1);
|
|
943
|
-
break;
|
|
944
|
-
}
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
/* CONTROL ACTIONS */
|
|
949
|
-
togglePlayPause() {
|
|
950
|
-
if (!this.video || this.isChangingQuality) return;
|
|
951
|
-
|
|
952
|
-
if (this.video.paused) {
|
|
953
|
-
this.play();
|
|
954
|
-
} else {
|
|
955
|
-
this.pause();
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
toggleMute() {
|
|
960
|
-
if (!this.video) return;
|
|
961
|
-
|
|
962
|
-
const wasMuted = this.video.muted;
|
|
963
|
-
this.video.muted = !this.video.muted;
|
|
964
|
-
|
|
965
|
-
this.updateMuteButton();
|
|
966
|
-
this.updateVolumeSliderVisual();
|
|
967
|
-
this.initVolumeTooltip();
|
|
968
|
-
|
|
969
|
-
// Triggers volumechange event
|
|
970
|
-
this.triggerEvent('volumechange', {
|
|
971
|
-
volume: this.getVolume(),
|
|
972
|
-
muted: this.isMuted(),
|
|
973
|
-
previousMuted: wasMuted
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
updateMuteButton() {
|
|
978
|
-
if (!this.video || !this.volumeIcon || !this.muteIcon) return;
|
|
979
|
-
|
|
980
|
-
if (this.video.muted || this.video.volume === 0) {
|
|
981
|
-
this.volumeIcon.classList.add('hidden');
|
|
982
|
-
this.muteIcon.classList.remove('hidden');
|
|
983
|
-
} else {
|
|
984
|
-
this.volumeIcon.classList.remove('hidden');
|
|
985
|
-
this.muteIcon.classList.add('hidden');
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
/* LOADING STATES */
|
|
990
|
-
showLoading() {
|
|
991
|
-
if (this.loadingOverlay) {
|
|
992
|
-
this.loadingOverlay.classList.add('active');
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
hideLoading() {
|
|
997
|
-
if (this.loadingOverlay) {
|
|
998
|
-
this.loadingOverlay.classList.remove('active');
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
/* FULLSCREEN CONTROLS */
|
|
1003
|
-
toggleFullscreen() {
|
|
1004
|
-
if (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement) {
|
|
1005
|
-
this.exitFullscreen();
|
|
1006
|
-
} else {
|
|
1007
|
-
this.enterFullscreen();
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
updateFullscreenButton() {
|
|
1012
|
-
if (!this.fullscreenIcon || !this.exitFullscreenIcon) return;
|
|
1013
|
-
|
|
1014
|
-
const isFullscreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement;
|
|
1015
|
-
|
|
1016
|
-
if (isFullscreen) {
|
|
1017
|
-
this.fullscreenIcon.classList.add('hidden');
|
|
1018
|
-
this.exitFullscreenIcon.classList.remove('hidden');
|
|
1019
|
-
} else {
|
|
1020
|
-
this.fullscreenIcon.classList.remove('hidden');
|
|
1021
|
-
this.exitFullscreenIcon.classList.add('hidden');
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
// Triggers fullscreenchange event
|
|
1025
|
-
this.triggerEvent('fullscreenchange', {
|
|
1026
|
-
active: !!isFullscreen,
|
|
1027
|
-
mode: isFullscreen ? 'enter' : 'exit'
|
|
1028
|
-
});
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
/* PICTURE IN PICTURE CONTROLS */
|
|
1032
|
-
togglePictureInPicture() {
|
|
1033
|
-
if (!this.isPiPSupported || !this.video) return;
|
|
1034
|
-
|
|
1035
|
-
if (document.pictureInPictureElement) {
|
|
1036
|
-
this.exitPictureInPicture();
|
|
1037
|
-
} else {
|
|
1038
|
-
this.enterPictureInPicture();
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
/* SEEK TOOLTIP MANAGEMENT */
|
|
1043
|
-
toggleSeekTooltip(show = null) {
|
|
1044
|
-
if (show === null) {
|
|
1045
|
-
this.options.showSeekTooltip = !this.options.showSeekTooltip;
|
|
1046
|
-
} else {
|
|
1047
|
-
this.options.showSeekTooltip = show;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
if (this.seekTooltip) {
|
|
1051
|
-
if (this.options.showSeekTooltip) {
|
|
1052
|
-
this.setupSeekTooltip();
|
|
1053
|
-
} else {
|
|
1054
|
-
this.seekTooltip.classList.remove('visible');
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
/* AUTO-HIDE CONFIGURATION */
|
|
1060
|
-
setAutoHideDelay(delay) {
|
|
1061
|
-
if (typeof delay === 'number' && delay >= 0) {
|
|
1062
|
-
this.options.autoHideDelay = delay;
|
|
1063
|
-
if (this.options.debug) console.log(`Auto-hide delay set to ${delay}ms`);
|
|
1064
|
-
}
|
|
1065
|
-
return this;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
getAutoHideDelay() {
|
|
1069
|
-
return this.options.autoHideDelay;
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
enableAutoHide() {
|
|
1073
|
-
if (!this.options.autoHide) {
|
|
1074
|
-
this.options.autoHide = true;
|
|
1075
|
-
if (!this.autoHideInitialized) {
|
|
1076
|
-
this.initAutoHide();
|
|
1077
|
-
}
|
|
1078
|
-
if (this.options.debug) console.log('Auto-hide enabled');
|
|
1079
|
-
}
|
|
1080
|
-
return this;
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
disableAutoHide() {
|
|
1084
|
-
if (this.options.autoHide) {
|
|
1085
|
-
this.options.autoHide = false;
|
|
1086
|
-
if (this.autoHideTimer) {
|
|
1087
|
-
clearTimeout(this.autoHideTimer);
|
|
1088
|
-
this.autoHideTimer = null;
|
|
1089
|
-
}
|
|
1090
|
-
this.showControlsNow();
|
|
1091
|
-
if (this.options.debug) console.log('Auto-hide disabled');
|
|
1092
|
-
}
|
|
1093
|
-
return this;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
forceShowControls() {
|
|
1097
|
-
this.showControlsNow();
|
|
1098
|
-
if (this.autoHideInitialized) {
|
|
1099
|
-
this.resetAutoHideTimer();
|
|
1100
|
-
}
|
|
1101
|
-
return this;
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
forceHideControls() {
|
|
1105
|
-
if (!this.mouseOverControls && this.video && !this.video.paused) {
|
|
1106
|
-
this.hideControlsNow();
|
|
1107
|
-
}
|
|
1108
|
-
return this;
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
isAutoHideEnabled() {
|
|
1112
|
-
return this.options.autoHide;
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
isAutoHideInitialized() {
|
|
1116
|
-
return this.autoHideInitialized;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
/**
|
|
1120
|
-
* Hide mouse cursor in player container
|
|
1121
|
-
* Only hides cursor in main container, not in plugin iframes
|
|
1122
|
-
*/
|
|
1123
|
-
hideCursor() {
|
|
1124
|
-
if (!this.options.hideCursor) {
|
|
1125
|
-
return; // Do not hide cursor if option is disabled
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
if (this.container) {
|
|
1129
|
-
this.container.classList.add('hide-cursor');
|
|
1130
|
-
if (this.options.debug) console.log('🖱️ Cursor hidden');
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
/**
|
|
1135
|
-
* Show mouse cursor in player container
|
|
1136
|
-
*/
|
|
1137
|
-
showCursor() {
|
|
1138
|
-
if (this.container) {
|
|
1139
|
-
this.container.classList.remove('hide-cursor');
|
|
1140
|
-
if (this.options.debug) console.log('🖱️ Cursor shown');
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
/**
|
|
1145
|
-
* Enable cursor hiding when controlbar is hidden
|
|
1146
|
-
* @returns {Object} this
|
|
1147
|
-
*/
|
|
1148
|
-
enableCursorHiding() {
|
|
1149
|
-
this.options.hideCursor = true;
|
|
1150
|
-
if (this.options.debug) console.log('Cursor hiding enabled');
|
|
1151
|
-
return this;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
/**
|
|
1155
|
-
* Disable cursor hiding - cursor will always be visible
|
|
1156
|
-
* @returns {Object} this
|
|
1157
|
-
*/
|
|
1158
|
-
disableCursorHiding() {
|
|
1159
|
-
this.options.hideCursor = false;
|
|
1160
|
-
this.showCursor(); // Ensure cursor is shown immediately
|
|
1161
|
-
if (this.options.debug) console.log('Cursor hiding disabled');
|
|
1162
|
-
return this;
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
/**
|
|
1166
|
-
* Check if cursor hiding is enabled
|
|
1167
|
-
* @returns {Boolean} True if cursor hiding is enabled
|
|
1168
|
-
*/
|
|
1169
|
-
isCursorHidingEnabled() {
|
|
1170
|
-
return this.options.hideCursor;
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
/* PLAYLIST CONTROLS */
|
|
1174
|
-
showPlaylistControls() {
|
|
1175
|
-
if (!this.playlistPrevBtn || !this.playlistNextBtn) return;
|
|
1176
|
-
|
|
1177
|
-
this.playlistPrevBtn.style.display = 'flex';
|
|
1178
|
-
this.playlistNextBtn.style.display = 'flex';
|
|
1179
|
-
this.updatePlaylistButtons();
|
|
1180
|
-
|
|
1181
|
-
if (this.options.debug) console.log('Playlist controls shown');
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
hidePlaylistControls() {
|
|
1185
|
-
if (!this.playlistPrevBtn || !this.playlistNextBtn) return;
|
|
1186
|
-
|
|
1187
|
-
this.playlistPrevBtn.style.display = 'none';
|
|
1188
|
-
this.playlistNextBtn.style.display = 'none';
|
|
1189
|
-
|
|
1190
|
-
if (this.options.debug) console.log('Playlist controls hidden');
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
updatePlaylistButtons() {
|
|
1194
|
-
if (!this.playlistPrevBtn || !this.playlistNextBtn || !this.isPlaylistActive) return;
|
|
1195
|
-
|
|
1196
|
-
const canGoPrev = this.currentPlaylistIndex > 0 || this.options.playlistLoop;
|
|
1197
|
-
const canGoNext = this.currentPlaylistIndex < this.playlist.length - 1 || this.options.playlistLoop;
|
|
1198
|
-
|
|
1199
|
-
this.playlistPrevBtn.disabled = !canGoPrev;
|
|
1200
|
-
this.playlistNextBtn.disabled = !canGoNext;
|
|
1201
|
-
|
|
1202
|
-
// Update visual state
|
|
1203
|
-
if (canGoPrev) {
|
|
1204
|
-
this.playlistPrevBtn.style.opacity = '1';
|
|
1205
|
-
this.playlistPrevBtn.style.cursor = 'pointer';
|
|
1206
|
-
} else {
|
|
1207
|
-
this.playlistPrevBtn.style.opacity = '0.4';
|
|
1208
|
-
this.playlistPrevBtn.style.cursor = 'not-allowed';
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
if (canGoNext) {
|
|
1212
|
-
this.playlistNextBtn.style.opacity = '1';
|
|
1213
|
-
this.playlistNextBtn.style.cursor = 'pointer';
|
|
1214
|
-
} else {
|
|
1215
|
-
this.playlistNextBtn.style.opacity = '0.4';
|
|
1216
|
-
this.playlistNextBtn.style.cursor = 'not-allowed';
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
/* RESPONSIVE OPTIMIZATION */
|
|
1221
|
-
optimizeButtonsForSmallHeight() {
|
|
1222
|
-
const currentHeight = window.innerHeight;
|
|
1223
|
-
const controlsRect = this.controls.getBoundingClientRect();
|
|
1224
|
-
|
|
1225
|
-
// If controlbar is taller than 40% of viewport, optimize
|
|
1226
|
-
if (controlsRect.height > currentHeight * 0.4) {
|
|
1227
|
-
this.controls.classList.add('ultra-compact');
|
|
1228
|
-
if (this.options.debug) console.log('Applied ultra-compact mode for height:', currentHeight);
|
|
1229
|
-
} else {
|
|
1230
|
-
this.controls.classList.remove('ultra-compact');
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
// Hide non-essential buttons on very small heights
|
|
1234
|
-
const nonEssentialButtons = this.controls.querySelectorAll('.pip-btn, .speed-control');
|
|
1235
|
-
if (currentHeight < 180) {
|
|
1236
|
-
nonEssentialButtons.forEach(btn => btn.style.display = 'none');
|
|
1237
|
-
} else {
|
|
1238
|
-
nonEssentialButtons.forEach(btn => btn.style.display = '');
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
/* Controls methods for main class - All original functionality preserved exactly */
|