@universal-web-video-player/player-web-component 0.5.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/dist/index.cjs ADDED
@@ -0,0 +1,1226 @@
1
+ "use strict";var g=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var k=(r,l)=>{for(var t in l)g(r,t,{get:l[t],enumerable:!0})},w=(r,l,t,e)=>{if(l&&typeof l=="object"||typeof l=="function")for(let s of x(l))!E.call(r,s)&&s!==t&&g(r,s,{get:()=>l[s],enumerable:!(e=y(l,s))||e.enumerable});return r};var S=r=>w(g({},"__esModule",{value:!0}),r);var B={};k(B,{UvpPlayerElement:()=>h,defineUvpPlayerElement:()=>v});module.exports=S(B);var d=require("@universal-web-video-player/player-core"),c=require("feather-icons"),L="uvp-player",M=["source-url","source-type","source-qualities","autoplay","muted","poster","controls","auto-next","loop","playback-rate","volume","custom-controls","playlist","scale-mode","hide-settings","hide-fullscreen","hide-playlist","hide-volume"],h=class extends HTMLElement{constructor(){super();this.cleanupCallbacks=[];this.isCustomControlsMode=!1;this.showControls=!0;this.isUserSeeking=!1;this.controlsHideTimer=null;this.isFullscreen=!1;this.isInteractionActive=!0;this.isLocked=!1;this.progressRafId=null;this.lastStableDuration=0;this.isLive=!1;this.handleDocumentClick=t=>{let e=t.target;this.contains(e)||(this.closeSettingsMenu(),this.playlistPanel.classList.remove("open"))};this.handleVideoProgress=()=>{let t=this.videoEl.duration;if(this.isLive||t===1/0){this.isLive=!0,this.liveBadgeEl.classList.remove("hidden"),this.liveBadgeEl.classList.add("is-live"),this.currentTimeEl.classList.add("hidden"),this.durationEl.classList.add("hidden"),this.progressInput.value="100",this.updateProgressVisual(100);return}this.currentTimeEl.classList.remove("hidden"),this.durationEl.classList.remove("hidden"),Number.isFinite(t)&&t>0?t>this.lastStableDuration?this.lastStableDuration=t:t=this.lastStableDuration:t=this.lastStableDuration||t||0;let e=this.videoEl.currentTime||0,s=t>0?e/t*100:0;!this.isUserSeeking&&t>0&&(this.progressInput.value=String(s)),this.updateProgressVisual(s),this.progressBufferedEl.style.width=`${this.getBufferedPercent(t,e)}%`,this.currentTimeEl.textContent=this.formatTime(e),this.durationEl.textContent=this.formatTime(t),this.durationEl.style.color="",this.durationEl.style.fontWeight=""};this.handleQualityUiRefresh=()=>{this.refreshQualityUi()};this.handlePlayStateChange=()=>{let t=this.videoEl.paused;this.centerPlayPauseButton.querySelectorAll(".play-icon").forEach(e=>e.classList.toggle("hidden",!t)),this.centerPlayPauseButton.querySelectorAll(".pause-icon").forEach(e=>e.classList.toggle("hidden",t)),this.controlPlayPauseButton.querySelectorAll(".play-icon").forEach(e=>e.classList.toggle("hidden",!t)),this.controlPlayPauseButton.querySelectorAll(".pause-icon").forEach(e=>e.classList.toggle("hidden",t)),this.setButtonIcon(this.centerPlayPauseButton,t?"play":"pause"),this.setButtonIcon(this.controlPlayPauseButton,t?"play":"pause","20"),this.controlPlayPauseButton.setAttribute("aria-label",t?"Play":"Pause"),t?this.stopProgressLoop():this.startProgressLoop(),this.markInteraction(),this.updateNavigationButtons()};this.handleVolumeChange=()=>{let t=this.videoEl.muted||this.videoEl.volume===0;this.muteButton.querySelectorAll(".volume-icon").forEach(i=>i.classList.toggle("hidden",t)),this.muteButton.querySelectorAll(".mute-icon").forEach(i=>i.classList.toggle("hidden",!t)),this.setButtonIcon(this.muteButton,t?"volume-x":"volume-2","20"),this.muteButton.setAttribute("aria-label",t?"Unmute":"Mute");let e=t?0:this.videoEl.volume??0;this.volumeInput.value=String(e);let s=e*100;this.volumeInput.style.background=`linear-gradient(to right, var(--uvp-accent) 0%, var(--uvp-accent) ${s}%, rgba(255, 255, 255, 0.2) ${s}%, rgba(255, 255, 255, 0.2) 100%)`};this.handleFullscreenChange=()=>{let t=document.fullscreenElement||document.webkitFullscreenElement||document.mozFullScreenElement||document.msFullscreenElement;this.isFullscreen=!!t,this.fullscreenButton.querySelectorAll(".fullscreen-icon").forEach(e=>e.classList.toggle("hidden",this.isFullscreen)),this.fullscreenButton.querySelectorAll(".exit-fullscreen-icon").forEach(e=>e.classList.toggle("hidden",!this.isFullscreen)),this.setButtonIcon(this.fullscreenButton,this.isFullscreen?"minimize":"maximize","20"),this.fullscreenButton.setAttribute("aria-label",this.isFullscreen?"Exit fullscreen":"Enter fullscreen"),this.dispatchStateChange()};this.isProcessingFullscreen=!1;this.handleKeyDown=t=>{let e=t.target;if(!(e.tagName==="INPUT"||e.tagName==="TEXTAREA"||e.isContentEditable))switch(t.code){case"Space":t.preventDefault(),this.videoEl.paused?this.play():this.pause();break;case"ArrowRight":t.preventDefault(),this.seek(this.videoEl.currentTime+10);break;case"ArrowLeft":t.preventDefault(),this.seek(this.videoEl.currentTime-10);break;case"ArrowUp":t.preventDefault(),this.setVolume(this.videoEl.volume+.1),this.videoEl.muted&&this.core.setMuted(!1),this.handleVolumeChange(),this.markInteraction();break;case"ArrowDown":t.preventDefault(),this.setVolume(this.videoEl.volume-.1),this.handleVolumeChange(),this.markInteraction();break;case"KeyF":t.preventDefault(),this.toggleFullscreen();break;case"KeyM":t.preventDefault(),this.core.setMuted(!this.videoEl.muted),this.handleVolumeChange();break;case"Escape":this.isFullscreen&&(t.preventDefault(),this.toggleFullscreen());break}};this.markInteraction=()=>{this.isCustomControlsMode||!this.showControls||(this.shellEl.classList.remove("ui-hidden"),this.clearHideTimer(),!this.videoEl.paused&&(this.controlsHideTimer=window.setTimeout(()=>{this.shellEl.classList.add("ui-hidden"),this.closeSettingsMenu()},2200)))};this.shadowRootRef=this.attachShadow({mode:"open"}),this.shadowRootRef.innerHTML=`
2
+ <style>
3
+ * {
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ :host {
8
+ display: block;
9
+ width: 100%;
10
+ height: 100%;
11
+ min-width: 100%;
12
+ min-height: 100%;
13
+ --uvp-bg: #2A2F35;
14
+ --uvp-control-bg: rgba(96, 21, 107, 0.85);
15
+ --uvp-text: #FFFFFF;
16
+ --uvp-accent: #A400BC;
17
+ --uvp-accent-2: #4664E1;
18
+ --uvp-accent-3: #00D17F;
19
+ --uvp-border: rgba(255, 255, 255, 0.15);
20
+ --uvp-radius: 14px;
21
+ --uvp-control-spacing: 12px;
22
+ --uvp-control-padding: 16px;
23
+ --uvp-button-radius: 10px;
24
+ --uvp-font-family: 'Outfit', Inter, system-ui, sans-serif;
25
+ }
26
+
27
+ .live-badge {
28
+ display: inline-flex;
29
+ align-items: center;
30
+ gap: 8px;
31
+ background: rgba(0, 0, 0, 0.6);
32
+ color: #fff;
33
+ padding: 6px 12px;
34
+ border-radius: 6px;
35
+ font-size: 12px;
36
+ font-weight: 800;
37
+ letter-spacing: 0.8px;
38
+ text-transform: uppercase;
39
+ cursor: pointer;
40
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
41
+ border: 1px solid rgba(255, 255, 255, 0.15);
42
+ position: absolute;
43
+ top: 16px;
44
+ left: 16px;
45
+ z-index: 10;
46
+ backdrop-filter: blur(4px);
47
+ }
48
+
49
+ .live-badge:hover {
50
+ background: rgba(255, 255, 255, 0.1);
51
+ border-color: rgba(255, 255, 255, 0.3);
52
+ transform: translateY(-1px);
53
+ }
54
+
55
+ .live-badge.is-live::before {
56
+ content: "";
57
+ display: block;
58
+ width: 8px;
59
+ height: 8px;
60
+ background: #ff3b30;
61
+ border-radius: 50%;
62
+ box-shadow: 0 0 10px #ff3b30;
63
+ animation: pulse-red 2s infinite;
64
+ }
65
+
66
+ @keyframes pulse-red {
67
+ 0% { transform: scale(0.9); box-shadow: 0 0 0 0 rgba(255, 59, 48, 0.8); }
68
+ 50% { transform: scale(1.1); box-shadow: 0 0 0 8px rgba(255, 59, 48, 0); }
69
+ 100% { transform: scale(0.9); box-shadow: 0 0 0 0 rgba(255, 59, 48, 0); }
70
+ }
71
+
72
+ .live-badge.hidden {
73
+ display: none !important;
74
+ }
75
+
76
+ .live-badge:hover {
77
+ background: rgba(255, 255, 255, 0.1);
78
+ }
79
+
80
+ .shell {
81
+ position: relative;
82
+ width: 100%;
83
+ height: 100%;
84
+ background: #000;
85
+ overflow: hidden;
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ border-radius: 12px;
90
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
91
+ border: 1px solid rgba(255, 255, 255, 0.1);
92
+ }
93
+
94
+ :host(:fullscreen) .shell,
95
+ :host(:-webkit-full-screen) .shell,
96
+ :host(:-moz-full-screen) .shell,
97
+ .shell:fullscreen,
98
+ .shell:-webkit-full-screen,
99
+ .shell:-moz-full-screen {
100
+ width: 100vw;
101
+ height: 100vh;
102
+ border-radius: 0;
103
+ border: none;
104
+ max-width: none;
105
+ max-height: none;
106
+ }
107
+
108
+ .settings-wrap {
109
+ position: relative;
110
+ display: flex;
111
+ align-items: center;
112
+ }
113
+
114
+ .settings-menu {
115
+ position: absolute;
116
+ bottom: calc(100% + 12px);
117
+ right: -20px;
118
+ background: rgba(15, 15, 15, 0.98);
119
+ border: 1px solid rgba(255, 255, 255, 0.1);
120
+ border-radius: 12px;
121
+ width: 220px;
122
+ max-height: 350px;
123
+ overflow-y: auto;
124
+ padding: 6px;
125
+ display: flex;
126
+ flex-direction: column;
127
+ z-index: 10;
128
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6);
129
+ transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
130
+ transform-origin: bottom right;
131
+ }
132
+
133
+ .settings-menu.hidden {
134
+ display: none;
135
+ opacity: 0;
136
+ transform: scale(0.95);
137
+ }
138
+
139
+ .menu-screen {
140
+ display: flex;
141
+ flex-direction: column;
142
+ gap: 2px;
143
+ }
144
+
145
+ .menu-header {
146
+ display: flex;
147
+ align-items: center;
148
+ padding: 6px 8px;
149
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
150
+ margin-bottom: 4px;
151
+ }
152
+
153
+ .menu-back {
154
+ background: transparent !important;
155
+ border: none !important;
156
+ color: var(--uvp-text);
157
+ cursor: pointer;
158
+ padding: 0;
159
+ margin-right: 8px;
160
+ display: flex;
161
+ align-items: center;
162
+ justify-content: center;
163
+ border-radius: 50%;
164
+ width: 24px;
165
+ height: 24px;
166
+ box-shadow: none !important;
167
+ }
168
+
169
+ .menu-back:hover {
170
+ background: rgba(255, 255, 255, 0.1) !important;
171
+ }
172
+
173
+ .menu-title {
174
+ font-weight: 600;
175
+ font-size: 13px;
176
+ }
177
+
178
+ .settings-item {
179
+ border: none !important;
180
+ background: transparent !important;
181
+ color: var(--uvp-text);
182
+ border-radius: 6px;
183
+ min-height: 32px;
184
+ height: auto !important;
185
+ display: flex;
186
+ align-items: center;
187
+ justify-content: space-between;
188
+ padding: 0 8px;
189
+ font: 12px/1 'Outfit', Inter, system-ui, sans-serif;
190
+ transition: background 150ms ease;
191
+ cursor: pointer;
192
+ }
193
+
194
+ .settings-item:hover {
195
+ background: rgba(255, 255, 255, 0.1);
196
+ }
197
+
198
+ .settings-item .item-left {
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 10px;
202
+ }
203
+
204
+ .settings-item .item-right {
205
+ display: flex;
206
+ align-items: center;
207
+ gap: 6px;
208
+ color: rgba(255, 255, 255, 0.5);
209
+ font-size: 12px;
210
+ }
211
+
212
+ .settings-item svg {
213
+ opacity: 0.7;
214
+ }
215
+
216
+ /* Mobile Bottom Sheet */
217
+
218
+
219
+ /* Toggle Switch Styles */
220
+ .switch {
221
+ position: relative;
222
+ display: inline-block;
223
+ width: 34px;
224
+ height: 20px;
225
+ pointer-events: none;
226
+ }
227
+
228
+ .switch input {
229
+ opacity: 0;
230
+ width: 0;
231
+ height: 0;
232
+ }
233
+
234
+ .slider {
235
+ position: absolute;
236
+ cursor: pointer;
237
+ top: 0;
238
+ left: 0;
239
+ right: 0;
240
+ bottom: 0;
241
+ background-color: rgba(255, 255, 255, 0.2);
242
+ transition: .3s;
243
+ border-radius: 34px;
244
+ }
245
+
246
+ .slider:before {
247
+ position: absolute;
248
+ content: "";
249
+ height: 14px;
250
+ width: 14px;
251
+ left: 3px;
252
+ bottom: 3px;
253
+ background-color: white;
254
+ transition: .3s;
255
+ border-radius: 50%;
256
+ }
257
+
258
+ input:checked + .slider {
259
+ background-color: var(--uvp-accent);
260
+ }
261
+
262
+ input:checked + .slider:before {
263
+ transform: translateX(14px);
264
+ }
265
+
266
+ /* Playlist Panel */
267
+ .playlist-panel {
268
+ position: absolute;
269
+ top: 0;
270
+ right: 0;
271
+ bottom: 0;
272
+ width: 320px;
273
+ background: #1a1a1a;
274
+ border-left: 1px solid rgba(255, 255, 255, 0.1);
275
+ z-index: 100;
276
+ transform: translateX(100%);
277
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
278
+ display: flex;
279
+ flex-direction: column;
280
+ box-shadow: -10px 0 30px rgba(0, 0, 0, 0.5);
281
+ pointer-events: auto;
282
+ }
283
+
284
+ .playlist-panel.open {
285
+ transform: translateX(0);
286
+ }
287
+
288
+ .playlist-header {
289
+ padding: 20px;
290
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
291
+ display: flex;
292
+ align-items: center;
293
+ justify-content: space-between;
294
+ }
295
+
296
+ .playlist-header h3 {
297
+ margin: 0;
298
+ font-size: 16px;
299
+ font-weight: 600;
300
+ color: var(--uvp-text);
301
+ }
302
+
303
+ .playlist-close {
304
+ background: transparent;
305
+ border: none;
306
+ color: rgba(255, 255, 255, 0.5);
307
+ cursor: pointer;
308
+ padding: 4px;
309
+ transition: color 0.2s;
310
+ }
311
+
312
+ .playlist-close:hover {
313
+ color: var(--uvp-text);
314
+ }
315
+
316
+ .playlist-items {
317
+ flex: 1;
318
+ overflow-y: auto;
319
+ padding: 8px 0;
320
+ }
321
+
322
+ /* Custom Scrollbar for Playlist */
323
+ .playlist-items::-webkit-scrollbar {
324
+ width: 6px;
325
+ }
326
+ .playlist-items::-webkit-scrollbar-track {
327
+ background: transparent;
328
+ }
329
+ .playlist-items::-webkit-scrollbar-thumb {
330
+ background: rgba(255, 255, 255, 0.1);
331
+ border-radius: 3px;
332
+ }
333
+ .playlist-items::-webkit-scrollbar-thumb:hover {
334
+ background: rgba(255, 255, 255, 0.2);
335
+ }
336
+
337
+ .playlist-item {
338
+ display: flex;
339
+ align-items: center;
340
+ gap: 12px;
341
+ padding: 12px 20px;
342
+ cursor: pointer;
343
+ transition: background 0.2s;
344
+ border-left: 3px solid transparent;
345
+ }
346
+
347
+ .playlist-item:hover {
348
+ background: rgba(255, 255, 255, 0.05);
349
+ }
350
+
351
+ .playlist-item.active {
352
+ background: rgba(164, 0, 188, 0.15);
353
+ border-left-color: var(--uvp-accent);
354
+ }
355
+
356
+ .playlist-item-thumb {
357
+ width: 80px;
358
+ height: 45px;
359
+ background: #333;
360
+ border-radius: 4px;
361
+ overflow: hidden;
362
+ flex-shrink: 0;
363
+ position: relative;
364
+ display: flex;
365
+ align-items: center;
366
+ justify-content: center;
367
+ }
368
+
369
+ .playlist-item-thumb::after {
370
+ content: '';
371
+ display: block;
372
+ width: 20px;
373
+ height: 20px;
374
+ background: rgba(255, 255, 255, 0.1);
375
+ border-radius: 50%;
376
+ }
377
+
378
+ .playlist-item-thumb img {
379
+ position: absolute;
380
+ top: 0;
381
+ left: 0;
382
+ width: 100%;
383
+ height: 100%;
384
+ object-fit: cover;
385
+ z-index: 2;
386
+ }
387
+
388
+ .playlist-item-info {
389
+ flex: 1;
390
+ min-width: 0;
391
+ }
392
+
393
+ .playlist-item-title {
394
+ font-size: 14px;
395
+ font-weight: 500;
396
+ color: var(--uvp-text);
397
+ margin-bottom: 2px;
398
+ white-space: nowrap;
399
+ overflow: hidden;
400
+ text-overflow: ellipsis;
401
+ }
402
+
403
+ .playlist-item-meta {
404
+ font-size: 12px;
405
+ color: rgba(255, 255, 255, 0.5);
406
+ }
407
+
408
+ .playlist-item.active .playlist-item-title {
409
+ color: var(--uvp-accent);
410
+ }
411
+
412
+ video {
413
+ width: 100%;
414
+ height: 100%;
415
+ display: block;
416
+ background: #000;
417
+ transition: object-fit 0.3s ease;
418
+ }
419
+
420
+ .shell.scale-fit video {
421
+ object-fit: contain;
422
+ }
423
+ .shell.scale-fill video {
424
+ object-fit: fill;
425
+ }
426
+ .shell.scale-zoom video {
427
+ object-fit: cover;
428
+ }
429
+
430
+ .center-actions {
431
+ position: absolute;
432
+ left: 50%;
433
+ top: 50%;
434
+ transform: translate(-50%, -50%);
435
+ display: flex;
436
+ align-items: center;
437
+ gap: 16px;
438
+ transition: opacity 200ms ease;
439
+ }
440
+
441
+ .center-actions button {
442
+ width: 64px;
443
+ height: 64px;
444
+ border-radius: 999px;
445
+ border: none;
446
+ background: transparent;
447
+ color: #FFFFFF;
448
+ display: grid;
449
+ place-items: center;
450
+ cursor: pointer;
451
+ transition: transform 200ms ease, background 200ms ease, box-shadow 200ms ease;
452
+ }
453
+
454
+ .center-actions button:hover:not(:disabled) {
455
+ transform: scale(1.1);
456
+ background: rgba(255, 255, 255, 0.15);
457
+ }
458
+
459
+ .center-actions button:active:not(:disabled) {
460
+ transform: scale(0.95);
461
+ }
462
+
463
+ .center-actions .center-play-pause {
464
+ width: 80px;
465
+ height: 80px;
466
+ background: linear-gradient(135deg, #A400BC 0%, #60156B 100%);
467
+ border: 2px solid rgba(255, 255, 255, 0.4);
468
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
469
+ }
470
+
471
+ .center-actions .center-play-pause:hover:not(:disabled) {
472
+ background: linear-gradient(135deg, #D54DE8 0%, #A400BC 100%);
473
+ box-shadow: 0 6px 20px rgba(164, 0, 188, 0.4);
474
+ }
475
+
476
+ .center-actions button svg,
477
+ .icon-btn svg {
478
+ width: 24px;
479
+ height: 24px;
480
+ stroke: currentColor;
481
+ display: block;
482
+ margin: 0 auto;
483
+ }
484
+
485
+ .icon-btn {
486
+ width: 36px;
487
+ height: 36px;
488
+ display: flex;
489
+ align-items: center;
490
+ justify-content: center;
491
+ background: transparent;
492
+ border: none;
493
+ color: #fff;
494
+ cursor: pointer;
495
+ border-radius: var(--uvp-button-radius);
496
+ transition: background 0.2s;
497
+ }
498
+
499
+ .icon-btn:hover {
500
+ background: rgba(255, 255, 255, 0.1);
501
+ }
502
+
503
+ .icon-btn svg {
504
+ width: 18px;
505
+ height: 18px;
506
+ stroke-width: 1.8;
507
+ }
508
+
509
+ .controls {
510
+ position: absolute;
511
+ left: 0;
512
+ right: 0;
513
+ bottom: 0;
514
+ padding: 10px;
515
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.75) 0%, rgba(0, 0, 0, 0.3) 70%, transparent 100%);
516
+ display: flex;
517
+ flex-direction: column;
518
+ gap: 12px;
519
+ transition: opacity 250ms ease;
520
+ }
521
+
522
+ .top-overlay {
523
+ position: absolute;
524
+ top: 0;
525
+ left: 0;
526
+ right: 0;
527
+ height: 80px;
528
+ background: linear-gradient(to bottom, rgba(0, 0, 0, 0.75) 0%, rgba(0, 0, 0, 0.3) 70%, transparent 100%);
529
+ z-index: 6;
530
+ transition: opacity 250ms ease;
531
+ pointer-events: none;
532
+ }
533
+
534
+ .top-controls {
535
+ position: absolute;
536
+ top: 0;
537
+ right: 0;
538
+ padding: 12px 20px;
539
+ display: flex;
540
+ gap: 12px;
541
+ z-index: 7;
542
+ color: var(--uvp-text);
543
+ transition: opacity 250ms ease;
544
+ }
545
+
546
+ .shell.ui-hidden .top-overlay,
547
+ .shell.ui-hidden .top-controls {
548
+ opacity: 0;
549
+ pointer-events: none;
550
+ }
551
+
552
+ .shell.locked .controls,
553
+ .shell.locked .playlist-panel,
554
+ .shell.locked .settings-wrap,
555
+ .shell.locked .playlist-toggle,
556
+ .shell.locked .aspect-shortcut {
557
+ display: none !important;
558
+ }
559
+
560
+ .controls-row {
561
+ display: flex;
562
+ align-items: center;
563
+ gap: 12px;
564
+ color: var(--uvp-text);
565
+ font: 14px/1.2 'Outfit', Inter, system-ui, sans-serif;
566
+ }
567
+
568
+ .controls-row button,
569
+ .controls-row select,
570
+ .top-controls button {
571
+ border-radius: 10px;
572
+ border: 1px solid rgba(255, 255, 255, 0.2);
573
+ background: rgba(96, 21, 107, 0.4);
574
+ color: var(--uvp-text);
575
+ height: 36px;
576
+ padding: 0 12px;
577
+ cursor: pointer;
578
+ transition: all 200ms ease;
579
+ }
580
+
581
+ .icon-btn {
582
+ width: 40px;
583
+ min-width: 40px;
584
+ height: 40px;
585
+ border: none !important;
586
+ background: transparent !important;
587
+ box-shadow: none;
588
+ padding: 0;
589
+ display: inline-flex;
590
+ align-items: center;
591
+ justify-content: center;
592
+ border-radius: 999px !important;
593
+ }
594
+
595
+ .icon-btn:hover {
596
+ background: rgba(255, 255, 255, 0.15) !important;
597
+ transform: translateY(-1px);
598
+ }
599
+
600
+ .icon-btn:active {
601
+ transform: translateY(0);
602
+ }
603
+
604
+ .speed-wrap {
605
+ position: relative;
606
+ }
607
+
608
+ .speed-button {
609
+ border: none !important;
610
+ background: transparent !important;
611
+ color: var(--uvp-text);
612
+ font-weight: 700;
613
+ min-width: 40px;
614
+ height: 36px !important;
615
+ padding: 0 8px !important;
616
+ border-radius: 8px !important;
617
+ }
618
+
619
+ .speed-button:hover {
620
+ background: rgba(255, 255, 255, 0.15) !important;
621
+ }
622
+
623
+ .speed-list, .quality-list {
624
+ display: flex;
625
+ flex-direction: column;
626
+ gap: 2px;
627
+ padding: 4px;
628
+ }
629
+
630
+ .speed-item, .quality-item, .aspect-item {
631
+ border: none !important;
632
+ background: transparent !important;
633
+ color: var(--uvp-text);
634
+ border-radius: 6px;
635
+ min-height: 32px;
636
+ height: auto !important;
637
+ display: flex;
638
+ align-items: center;
639
+ justify-content: space-between;
640
+ padding: 0 8px;
641
+ font: 12px/1 'Outfit', Inter, system-ui, sans-serif;
642
+ transition: all 150ms ease;
643
+ cursor: pointer;
644
+ width: 100%;
645
+ text-align: left;
646
+ }
647
+
648
+ .speed-item:hover, .quality-item:hover, .aspect-item:hover {
649
+ background: rgba(255, 255, 255, 0.08) !important;
650
+ }
651
+
652
+ .speed-item.active, .quality-item.active, .aspect-item.active {
653
+ color: var(--uvp-accent);
654
+ font-weight: 600;
655
+ background: rgba(164, 0, 188, 0.1) !important;
656
+ }
657
+
658
+ .speed-item .check, .quality-item .check, .aspect-item .check {
659
+ width: 18px;
660
+ height: 18px;
661
+ display: inline-flex;
662
+ align-items: center;
663
+ justify-content: center;
664
+ opacity: 0;
665
+ flex-shrink: 0;
666
+ stroke: currentColor;
667
+ }
668
+
669
+ .speed-item.active .check, .quality-item.active .check, .aspect-item.active .check {
670
+ opacity: 1;
671
+ }
672
+
673
+ .spacer {
674
+ flex: 1;
675
+ }
676
+
677
+ .controls-row input[type="range"] {
678
+ accent-color: var(--uvp-accent);
679
+ }
680
+
681
+ .progress-wrap {
682
+ position: relative;
683
+ flex: 1;
684
+ height: 20px;
685
+ display: flex;
686
+ align-items: center;
687
+ overflow: visible;
688
+ margin: 0 12px;
689
+ }
690
+
691
+ .progress-track,
692
+ .progress-buffered,
693
+ .progress-played {
694
+ position: absolute;
695
+ left: 0;
696
+ right: 0;
697
+ height: 6px;
698
+ border-radius: 999px;
699
+ }
700
+
701
+ .progress-track {
702
+ background: rgba(255, 255, 255, 0.2);
703
+ z-index: 1;
704
+ }
705
+
706
+ .progress-buffered {
707
+ width: 0%;
708
+ right: auto;
709
+ background: rgba(255, 255, 255, 0.4);
710
+ transition: width 240ms linear;
711
+ z-index: 2;
712
+ }
713
+
714
+ .progress-played {
715
+ width: 0%;
716
+ right: auto;
717
+ background: linear-gradient(90deg, #A400BC 0%, #D54DE8 100%);
718
+ transition: width 90ms linear;
719
+ z-index: 3;
720
+ }
721
+
722
+
723
+ .progress-thumb {
724
+ position: absolute;
725
+ top: 50%;
726
+ left: 0%;
727
+ width: 16px;
728
+ height: 16px;
729
+ border-radius: 999px;
730
+ background: #FFFFFF;
731
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.3), 0 0 0 4px rgba(164, 0, 188, 0.3);
732
+ transform: translate(-50%, -50%);
733
+ transition: left 90ms linear, transform 150ms ease;
734
+ z-index: 4;
735
+ pointer-events: none;
736
+ }
737
+
738
+ /* Removed hover scaling from progress thumb */
739
+
740
+ .progress {
741
+ position: absolute;
742
+ inset: 0;
743
+ width: 100%;
744
+ margin: 0;
745
+ -webkit-appearance: none;
746
+ appearance: none;
747
+ background: transparent;
748
+ cursor: pointer;
749
+ z-index: 5;
750
+ }
751
+
752
+ .progress::-webkit-slider-runnable-track {
753
+ height: 20px;
754
+ background: transparent;
755
+ }
756
+
757
+ .progress::-webkit-slider-thumb {
758
+ -webkit-appearance: none;
759
+ appearance: none;
760
+ width: 20px;
761
+ height: 20px;
762
+ background: transparent;
763
+ }
764
+
765
+ .volume {
766
+ width: 70px;
767
+ height: 4px;
768
+ -webkit-appearance: none;
769
+ appearance: none;
770
+ background: rgba(255, 255, 255, 0.2);
771
+ border-radius: 999px;
772
+ outline: none;
773
+ }
774
+
775
+ .volume::-webkit-slider-thumb {
776
+ -webkit-appearance: none;
777
+ appearance: none;
778
+ width: 12px;
779
+ height: 12px;
780
+ background: var(--uvp-accent);
781
+ border-radius: 50%;
782
+ cursor: pointer;
783
+ border: 2px solid #fff;
784
+ box-shadow: 0 0 5px rgba(0,0,0,0.3);
785
+ transition: transform 150ms ease;
786
+ }
787
+
788
+ .volume::-moz-range-thumb {
789
+ width: 12px;
790
+ height: 12px;
791
+ background: var(--uvp-accent);
792
+ border-radius: 50%;
793
+ cursor: pointer;
794
+ border: 2px solid #fff;
795
+ box-shadow: 0 0 5px rgba(0,0,0,0.3);
796
+ }
797
+
798
+ .spacer {
799
+ flex: 1;
800
+ }
801
+
802
+ .time {
803
+ min-width: 50px;
804
+ text-align: center;
805
+ font-variant-numeric: tabular-nums;
806
+ font-weight: 500;
807
+ }
808
+
809
+ .hidden {
810
+ display: none !important;
811
+ }
812
+
813
+ .shell.ui-hidden .controls,
814
+ .shell.ui-hidden .center-actions {
815
+ opacity: 0;
816
+ pointer-events: none;
817
+ }
818
+
819
+ @media (max-width: 600px) {
820
+ .settings-menu {
821
+ position: fixed;
822
+ bottom: 0;
823
+ left: 0;
824
+ right: 0;
825
+ width: 100%;
826
+ max-height: 70vh;
827
+ border-radius: 24px 24px 0 0;
828
+ padding: 16px 8px 32px 8px;
829
+ transform-origin: bottom center;
830
+ }
831
+
832
+ .settings-menu.hidden {
833
+ transform: translateY(100%);
834
+ }
835
+
836
+ .settings-wrap {
837
+ position: static;
838
+ }
839
+
840
+ .settings-scrim {
841
+ position: fixed;
842
+ inset: 0;
843
+ background: rgba(0, 0, 0, 0.6);
844
+ backdrop-filter: blur(4px);
845
+ z-index: 9;
846
+ opacity: 0;
847
+ pointer-events: none;
848
+ transition: opacity 0.3s ease;
849
+ }
850
+
851
+ .settings-scrim.visible {
852
+ opacity: 1;
853
+ pointer-events: auto;
854
+ }
855
+
856
+ .mute, .volume {
857
+ display: none !important;
858
+ }
859
+
860
+ .time {
861
+ font-size: 11px;
862
+ min-width: 36px;
863
+ }
864
+
865
+ .progress-wrap {
866
+ margin: 0 12px;
867
+ }
868
+
869
+ .controls-row {
870
+ gap: 0px;
871
+ }
872
+
873
+ .controls {
874
+ gap: 4px;
875
+ padding: 12px;
876
+ }
877
+
878
+ /* Mobile Playlist Bottom Sheet */
879
+ .playlist-panel {
880
+ position: fixed;
881
+ bottom: 0;
882
+ left: 0;
883
+ right: 0;
884
+ top: auto;
885
+ width: 100%;
886
+ height: auto;
887
+ background: rgba(15, 15, 15, 0.98);
888
+ border-radius: 24px 24px 0 0;
889
+ transform: translateY(100%);
890
+ border-left: none;
891
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
892
+ z-index: 1000;
893
+ }
894
+
895
+ .playlist-panel.open {
896
+ transform: translateY(0);
897
+ }
898
+
899
+ .playlist-header {
900
+ padding: 12px 20px;
901
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
902
+ }
903
+
904
+ .playlist-header h3 {
905
+ font-size: 14px;
906
+ }
907
+
908
+ .playlist-items {
909
+ display: flex;
910
+ flex-direction: row;
911
+ overflow-x: auto;
912
+ padding: 16px 20px 24px 20px;
913
+ gap: 16px;
914
+ scroll-snap-type: x mandatory;
915
+ scroll-padding: 0 20px;
916
+ -webkit-overflow-scrolling: touch;
917
+ }
918
+
919
+ .playlist-items::-webkit-scrollbar {
920
+ display: none;
921
+ }
922
+
923
+ .playlist-item {
924
+ flex-direction: column;
925
+ width: 140px;
926
+ padding: 0;
927
+ background: transparent;
928
+ border-left: none;
929
+ border-bottom: 3px solid transparent;
930
+ align-items: flex-start;
931
+ gap: 8px;
932
+ flex-shrink: 0;
933
+ scroll-snap-align: start;
934
+ }
935
+
936
+ .playlist-item.active {
937
+ background: transparent;
938
+ border-left-color: transparent;
939
+ border-bottom-color: var(--uvp-accent);
940
+ }
941
+
942
+ .playlist-item-thumb {
943
+ width: 100%;
944
+ height: 78px;
945
+ border-radius: 8px;
946
+ }
947
+
948
+ .playlist-item-info {
949
+ padding: 2px 4px;
950
+ }
951
+
952
+ .playlist-item-title {
953
+ font-size: 12px;
954
+ white-space: normal;
955
+ display: -webkit-box;
956
+ -webkit-line-clamp: 2;
957
+ -webkit-box-orient: vertical;
958
+ overflow: hidden;
959
+ line-height: 1.3;
960
+ }
961
+
962
+ .playlist-item-meta {
963
+ font-size: 10px;
964
+ margin-top: 2px;
965
+ }
966
+
967
+ /* Mobile Responsive Controls */
968
+ .icon-btn {
969
+ width: 28px;
970
+ height: 28px;
971
+ }
972
+ .icon-btn svg {
973
+ width: 16px;
974
+ height: 16px;
975
+ stroke-width: 2.0;
976
+ }
977
+ .controls-row {
978
+ gap: 4px;
979
+ }
980
+ .center-actions button {
981
+ width: 48px;
982
+ height: 48px;
983
+ }
984
+ .center-actions .center-play-pause {
985
+ width: 64px;
986
+ height: 64px;
987
+ }
988
+ .live-badge {
989
+ position: absolute;
990
+ top: 12px;
991
+ left: 12px;
992
+ width: 24px;
993
+ height: 24px;
994
+ padding: 0;
995
+ display: flex;
996
+ align-items: center;
997
+ justify-content: center;
998
+ border-radius: 50%;
999
+ font-size: 0;
1000
+ margin: 0;
1001
+ z-index: 10;
1002
+ background: rgba(0, 0, 0, 0.4);
1003
+ border: 1px solid rgba(255, 255, 255, 0.1);
1004
+ }
1005
+ .live-badge.is-live::before {
1006
+ margin: 0;
1007
+ }
1008
+ .time {
1009
+ font-size: 11px;
1010
+ }
1011
+ }
1012
+ </style>
1013
+ <div class="shell">
1014
+ <video part="video"></video>
1015
+ <div class="live-badge hidden" id="live-badge">Live</div>
1016
+ <div class="top-overlay"></div>
1017
+ <div class="top-controls" part="top-controls">
1018
+ <div part="top-controls-left" style="display: flex; gap: 8px; align-items: center;">
1019
+ <slot name="top-bar-left"></slot>
1020
+ </div>
1021
+ <div part="top-controls-right" style="display: flex; gap: 8px; align-items: center;">
1022
+ <slot name="top-bar-right"></slot>
1023
+ <button class="aspect-shortcut icon-btn" part="aspect-shortcut" aria-label="Cycle Aspect Ratio"></button>
1024
+ <button class="lock-button icon-btn" part="lock-button" aria-label="Lock controls"></button>
1025
+ </div>
1026
+ </div>
1027
+ <div class="playlist-panel" part="playlist-panel">
1028
+ <div class="playlist-header">
1029
+ <h3>Playlist</h3>
1030
+ <button class="playlist-close" aria-label="Close playlist"></button>
1031
+ </div>
1032
+ <div class="playlist-items" part="playlist-items"></div>
1033
+ </div>
1034
+ <div class="center-actions hidden" part="center-actions" style="display: none !important;">
1035
+ <slot name="center-overlay"></slot>
1036
+ <button class="center-prev" part="center-prev" aria-label="Previous">
1037
+ <slot name="icon-prev"><span class="icon-placeholder"></span></slot>
1038
+ </button>
1039
+ <button class="center-play-pause" part="center-play-pause" aria-label="Play">
1040
+ <slot name="icon-play" class="play-icon"><span class="icon-placeholder-play"></span></slot>
1041
+ <slot name="icon-pause" class="pause-icon hidden"><span class="icon-placeholder-pause"></span></slot>
1042
+ </button>
1043
+ <button class="center-next" part="center-next" aria-label="Next">
1044
+ <slot name="icon-next"><span class="icon-placeholder"></span></slot>
1045
+ </button>
1046
+ </div>
1047
+ <div class="controls" part="controls">
1048
+ <div class="controls-row">
1049
+ <div class="progress-wrap" part="progress-wrap">
1050
+ <div class="progress-track"></div>
1051
+ <div class="progress-buffered"></div>
1052
+ <div class="progress-played"></div>
1053
+ <div class="progress-thumb"></div>
1054
+ <input class="progress" part="progress" type="range" min="0" max="100" step="0.1" value="0" />
1055
+ </div>
1056
+ </div>
1057
+ <div class="controls-row" part="controls-row-actions">
1058
+ <slot name="controls-start"></slot>
1059
+ <button class="control-prev icon-btn" part="control-prev" aria-label="Previous">
1060
+ <slot name="icon-prev-small"><span class="icon-placeholder"></span></slot>
1061
+ </button>
1062
+ <button class="control-play-pause icon-btn" part="control-play-pause" aria-label="Play">
1063
+ <slot name="icon-play-small" class="play-icon"><span class="icon-placeholder-play"></span></slot>
1064
+ <slot name="icon-pause-small" class="pause-icon hidden"><span class="icon-placeholder-pause"></span></slot>
1065
+ </button>
1066
+ <button class="control-next icon-btn" part="control-next" aria-label="Next">
1067
+ <slot name="icon-next-small"><span class="icon-placeholder"></span></slot>
1068
+ </button>
1069
+
1070
+ <span class="time current-time" part="current-time">0:00</span>
1071
+ <span class="time duration" part="duration">0:00</span>
1072
+
1073
+ <button part="mute" class="mute icon-btn" aria-label="Mute">
1074
+ <slot name="icon-volume" class="volume-icon"><span class="icon-placeholder-volume"></span></slot>
1075
+ <slot name="icon-mute" class="mute-icon hidden"><span class="icon-placeholder-mute"></span></slot>
1076
+ </button>
1077
+ <input class="volume" part="volume-input" type="range" min="0" max="1" step="0.05" value="1" />
1078
+ <span class="spacer" part="controls-spacer"></span>
1079
+ <div class="settings-scrim" part="settings-scrim"></div>
1080
+ <div class="settings-wrap" part="settings-wrap">
1081
+ <button part="settings-button" class="settings-button icon-btn" aria-label="Settings" aria-expanded="false">
1082
+ <slot name="icon-settings"><span class="icon-placeholder"></span></slot>
1083
+ </button>
1084
+ <div class="settings-menu hidden" part="settings-menu">
1085
+ <div class="menu-screen" data-screen="main" part="settings-screen-main">
1086
+ <div class="settings-item" data-action="quality-screen">
1087
+ <div class="item-left">
1088
+ <span class="icon-quality"></span>
1089
+ <span>Quality</span>
1090
+ </div>
1091
+ <div class="item-right">
1092
+ <span class="current-quality-value">Auto</span>
1093
+ <span class="icon-chevron-right"></span>
1094
+ </div>
1095
+ </div>
1096
+ <div class="settings-item" data-action="speed-screen">
1097
+ <div class="item-left">
1098
+ <span class="icon-speed"></span>
1099
+ <span>Playback Speed</span>
1100
+ </div>
1101
+ <div class="item-right">
1102
+ <span class="current-speed-value">1x</span>
1103
+ <span class="icon-chevron-right"></span>
1104
+ </div>
1105
+ </div>
1106
+ <div class="settings-item" data-action="aspect-screen">
1107
+ <div class="item-left">
1108
+ <span class="icon-aspect"></span>
1109
+ <span>Aspect Ratio</span>
1110
+ </div>
1111
+ <div class="item-right">
1112
+ <span class="current-aspect-value">Fit</span>
1113
+ <span class="icon-chevron-right"></span>
1114
+ </div>
1115
+ </div>
1116
+ <div class="settings-item" data-action="autoplay">
1117
+ <div class="item-left">
1118
+ <span class="icon-play"></span>
1119
+ <span>Auto Play</span>
1120
+ </div>
1121
+ <label class="switch">
1122
+ <input type="checkbox" class="autoplay-toggle">
1123
+ <span class="slider"></span>
1124
+ </label>
1125
+ </div>
1126
+ <div class="settings-item" data-action="loop">
1127
+ <div class="item-left">
1128
+ <span class="icon-repeat"></span>
1129
+ <span>Loop</span>
1130
+ </div>
1131
+ <label class="switch">
1132
+ <input type="checkbox" class="loop-toggle">
1133
+ <span class="slider"></span>
1134
+ </label>
1135
+ </div>
1136
+ <div class="settings-item" data-action="download">
1137
+ <div class="item-left">
1138
+ <span class="download-icon"></span>
1139
+ <span class="download-text">Download</span>
1140
+ </div>
1141
+ </div>
1142
+ </div>
1143
+
1144
+ <!-- Quality Screen -->
1145
+ <div class="menu-screen hidden" data-screen="quality">
1146
+ <div class="menu-header">
1147
+ <button class="menu-back" data-target="main">
1148
+ <span class="icon-arrow-left"></span>
1149
+ </button>
1150
+ <span class="menu-title">Quality</span>
1151
+ </div>
1152
+ <div class="quality-list"></div>
1153
+ </div>
1154
+
1155
+ <!-- Speed Screen -->
1156
+ <div class="menu-screen hidden" data-screen="speed">
1157
+ <div class="menu-header">
1158
+ <button class="menu-back" data-target="main">
1159
+ <span class="icon-arrow-left"></span>
1160
+ </button>
1161
+ <span class="menu-title">Playback Speed</span>
1162
+ </div>
1163
+ <div class="speed-list">
1164
+ <button class="speed-item" data-rate="0.5"><span>0.5x</span><span class="check"></span></button>
1165
+ <button class="speed-item active" data-rate="1"><span>1x</span><span class="check"></span></button>
1166
+ <button class="speed-item" data-rate="1.5"><span>1.5x</span><span class="check"></span></button>
1167
+ <button class="speed-item" data-rate="2"><span>2x</span><span class="check"></span></button>
1168
+ </div>
1169
+ </div>
1170
+
1171
+ <!-- Aspect Screen -->
1172
+ <div class="menu-screen hidden" data-screen="aspect">
1173
+ <div class="menu-header">
1174
+ <button class="menu-back" data-target="main">
1175
+ <span class="icon-arrow-left"></span>
1176
+ </button>
1177
+ <span class="menu-title">Aspect Ratio</span>
1178
+ </div>
1179
+ <div class="aspect-list">
1180
+ <button class="aspect-item" data-mode="fit"><span>Fit</span><span class="check"></span></button>
1181
+ <button class="aspect-item" data-mode="fill"><span>Stretch</span><span class="check"></span></button>
1182
+ <button class="aspect-item" data-mode="zoom"><span>Zoom</span><span class="check"></span></button>
1183
+ </div>
1184
+ </div>
1185
+ </div>
1186
+ </div>
1187
+ <button part="playlist-toggle" class="playlist-toggle icon-btn hidden" aria-label="Show playlist">
1188
+ <slot name="icon-playlist"><span class="icon-placeholder"></span></slot>
1189
+ </button>
1190
+ <button part="fullscreen-button" class="fullscreen icon-btn" aria-label="Enter fullscreen">
1191
+ <slot name="icon-fullscreen" class="fullscreen-icon"><span class="icon-placeholder-fullscreen"></span></slot>
1192
+ <slot name="icon-exit-fullscreen" class="exit-fullscreen-icon hidden"><span class="icon-placeholder-exit-fullscreen"></span></slot>
1193
+ </button>
1194
+ <slot name="controls-end"></slot>
1195
+ </div>
1196
+ </div>
1197
+ <slot name="controls" class="hidden"></slot>
1198
+ </div>
1199
+ `,this.shellEl=this.shadowRootRef.querySelector(".shell"),this.videoEl=this.shadowRootRef.querySelector("video"),this.centerActionsEl=this.shadowRootRef.querySelector(".center-actions"),this.centerPrevButton=this.shadowRootRef.querySelector(".center-prev"),this.centerPlayPauseButton=this.shadowRootRef.querySelector(".center-play-pause"),this.centerNextButton=this.shadowRootRef.querySelector(".center-next"),this.controlPrevButton=this.shadowRootRef.querySelector(".control-prev"),this.controlPlayPauseButton=this.shadowRootRef.querySelector(".control-play-pause"),this.controlNextButton=this.shadowRootRef.querySelector(".control-next"),this.progressPlayedEl=this.shadowRootRef.querySelector(".progress-played"),this.progressBufferedEl=this.shadowRootRef.querySelector(".progress-buffered"),this.progressThumbEl=this.shadowRootRef.querySelector(".progress-thumb"),this.progressInput=this.shadowRootRef.querySelector(".progress"),this.currentTimeEl=this.shadowRootRef.querySelector(".current-time"),this.durationEl=this.shadowRootRef.querySelector(".duration"),this.liveBadgeEl=this.shadowRootRef.querySelector("#live-badge"),this.controlsEl=this.shadowRootRef.querySelector(".controls"),this.muteButton=this.shadowRootRef.querySelector(".mute"),this.volumeInput=this.shadowRootRef.querySelector(".volume"),this.settingsScrim=this.shadowRootRef.querySelector(".settings-scrim"),this.settingsWrap=this.shadowRootRef.querySelector(".settings-wrap"),this.settingsButton=this.shadowRootRef.querySelector(".settings-button"),this.settingsMenu=this.shadowRootRef.querySelector(".settings-menu"),this.settingsMainScreen=this.shadowRootRef.querySelector('[data-screen="main"]'),this.settingsQualityScreen=this.shadowRootRef.querySelector('[data-screen="quality"]'),this.settingsAspectScreen=this.shadowRootRef.querySelector('[data-screen="aspect"]'),this.settingsDownloadScreen=this.shadowRootRef.querySelector('[data-screen="download"]'),this.fullscreenButton=this.shadowRootRef.querySelector(".fullscreen"),this.autoplayToggle=this.shadowRootRef.querySelector(".autoplay-toggle"),this.loopToggle=this.shadowRootRef.querySelector(".loop-toggle"),this.playlistButton=this.shadowRootRef.querySelector(".playlist-toggle"),this.playlistPanel=this.shadowRootRef.querySelector(".playlist-panel"),this.playlistCloseButton=this.shadowRootRef.querySelector(".playlist-close"),this.playlistItemsContainer=this.shadowRootRef.querySelector(".playlist-items"),this.lockButton=this.shadowRootRef.querySelector(".lock-button"),this.aspectShortcut=this.shadowRootRef.querySelector(".aspect-shortcut"),this.slotEl=this.shadowRootRef.querySelector('slot[name="controls"]'),this.isCustomControlsMode=this.readBooleanAttr("custom-controls",!1),this.showControls=this.readBooleanAttr("controls",!0),this.core=new d.UvpPlayerCore(this.readConfigFromAttributes()),this.core.attachMediaElement(this.videoEl),this.bindUiEvents(),this.updateUiVisibility(),this.refreshDownloadUi()}static get observedAttributes(){return[...M]}connectedCallback(){this.core.getMediaElement()||this.core.attachMediaElement(this.videoEl);let t=["ready","sourcechange","play","pause","timeupdate","ended","playlistend","qualitychange","downloadstatus","qualitiesdiscovered","error"];this.cleanupCallbacks=t.map(s=>this.core.on(s,i=>{s==="sourcechange"&&(this.lastStableDuration=0,this.isLive=!1,this.liveBadgeEl.classList.add("hidden"),this.handlePlayStateChange()),s==="ready"&&(this.isLive=i.isLive,this.liveBadgeEl.classList.toggle("hidden",!this.isLive),this.liveBadgeEl.classList.toggle("is-live",this.isLive),this.refreshQualityUi()),this.dispatchEvent(new CustomEvent(s,{detail:i,bubbles:!0,composed:!0})),this.dispatchStateChange()})),this.cleanupCallbacks.push(this.core.on("qualitiesdiscovered",()=>{this.refreshQualityUi()})),this.cleanupCallbacks.push(this.core.on("qualitychange",()=>{this.refreshQualityUi()})),this.cleanupCallbacks.push(this.core.on("scaleModeChange",()=>{this.refreshScaleModeUi()})),this.videoEl.addEventListener("timeupdate",this.handleVideoProgress),this.videoEl.addEventListener("loadedmetadata",this.handleVideoProgress),this.videoEl.addEventListener("progress",this.handleVideoProgress),this.videoEl.addEventListener("play",this.handlePlayStateChange),this.videoEl.addEventListener("pause",this.handlePlayStateChange),this.videoEl.addEventListener("volumechange",this.handleVolumeChange),this.videoEl.addEventListener("loadedmetadata",this.handleQualityUiRefresh),document.addEventListener("fullscreenchange",this.handleFullscreenChange),document.addEventListener("webkitfullscreenchange",this.handleFullscreenChange),document.addEventListener("mozfullscreenchange",this.handleFullscreenChange),document.addEventListener("MSFullscreenChange",this.handleFullscreenChange),this.handlePlayStateChange(),this.handleVideoProgress(),this.handleVolumeChange(),this.handleFullscreenChange();let e=this.core.getConfig();this.autoplayToggle.checked=!!(e.autoplay||e.autoNext),this.loopToggle.checked=!!e.loop,this.renderStaticIcons(),this.refreshPlaylistUi(),this.refreshQualityUi(),this.refreshScaleModeUi(),this.core.on("sourcechange",()=>{this.updateNavigationButtons(),this.refreshQualityUi(),this.refreshPlaylistUi(),this.updateUiVisibility(),this.refreshDownloadUi()}),this.markInteraction(),document.addEventListener("keydown",this.handleKeyDown),document.addEventListener("click",this.handleDocumentClick)}refreshPlaylistUi(){let t=this.core.getPlaylist(),e=this.core.getCurrentIndex();this.playlistButton.classList.toggle("hidden",t.length<=1),this.playlistItemsContainer.innerHTML="",t.forEach((s,i)=>{let o=document.createElement("div");o.className=`playlist-item ${i===e?"active":""}`,o.innerHTML=`
1200
+ <div class="playlist-item-thumb">
1201
+ ${s.poster?`<img src="${s.poster}" alt="">`:""}
1202
+ </div>
1203
+ <div class="playlist-item-info">
1204
+ <div class="playlist-item-title">${s.title||`Video ${i+1}`}</div>
1205
+ <div class="playlist-item-meta">${(s.type||"mp4").toUpperCase()}</div>
1206
+ </div>
1207
+ `,o.addEventListener("click",()=>{this.core.goTo(i),this.playlistPanel.classList.remove("open"),this.markInteraction()}),this.playlistItemsContainer.appendChild(o)})}disconnectedCallback(){document.removeEventListener("keydown",this.handleKeyDown),document.removeEventListener("click",this.handleDocumentClick),this.cleanupCallbacks.forEach(t=>t()),this.cleanupCallbacks=[],this.videoEl.removeEventListener("timeupdate",this.handleVideoProgress),this.videoEl.removeEventListener("loadedmetadata",this.handleVideoProgress),this.videoEl.removeEventListener("progress",this.handleVideoProgress),this.videoEl.removeEventListener("play",this.handlePlayStateChange),this.videoEl.removeEventListener("pause",this.handlePlayStateChange),this.videoEl.removeEventListener("volumechange",this.handleVolumeChange),this.videoEl.removeEventListener("loadedmetadata",this.handleQualityUiRefresh),document.removeEventListener("fullscreenchange",this.handleFullscreenChange),document.removeEventListener("webkitfullscreenchange",this.handleFullscreenChange),document.removeEventListener("mozfullscreenchange",this.handleFullscreenChange),document.removeEventListener("MSFullscreenChange",this.handleFullscreenChange),this.shellEl.removeEventListener("mousemove",this.markInteraction),this.shellEl.removeEventListener("click",this.markInteraction),this.shellEl.removeEventListener("touchstart",this.markInteraction),this.shellEl.removeEventListener("mouseenter",this.markInteraction),this.clearHideTimer(),this.stopProgressLoop(),this.core.detachMediaElement()}attributeChangedCallback(t,e,s){e!==s&&this.applyAttributeToCore(t,s)}play(){return this.core.play()}pause(){this.core.pause()}seek(t){this.core.seek(t)}next(){this.core.next()}previous(){this.core.previous()}seekToLive(){this.core.seekToLive()}getPlayerState(){let t=this.core.getConfig();return{playing:!this.videoEl.paused,currentTime:this.videoEl.currentTime,duration:this.videoEl.duration,volume:this.videoEl.volume,muted:this.videoEl.muted,playbackRate:this.videoEl.playbackRate,currentSource:this.core.getCurrentSource(),currentIndex:this.core.getCurrentIndex(),playlist:this.core.getPlaylist(),scaleMode:this.core.getScaleMode(),isLocked:this.isLocked,isFullscreen:this.isFullscreen}}dispatchStateChange(){this.dispatchEvent(new CustomEvent("uvp-state-change",{detail:this.getPlayerState(),bubbles:!0,composed:!0}))}setPlaybackRate(t){this.core.setPlaybackRate(t)}setVolume(t){this.core.setVolume(t)}getApi(){return this.core}get config(){return this.core.getConfig()}set config(t){this.core.updateConfig(t),this.syncAllFromCore()}get sourceUrl(){return this.getAttribute("source-url")}set sourceUrl(t){t?this.setAttribute("source-url",t):this.removeAttribute("source-url")}get sourceType(){return this.getAttribute("source-type")}set sourceType(t){t?this.setAttribute("source-type",t):this.removeAttribute("source-type")}get sourceQualities(){return this.core.getQualities()??[]}set sourceQualities(t){let e=[];if(typeof t=="string")try{e=JSON.parse(t)}catch(s){console.error("[UVP] Failed to parse qualities string:",s);return}else Array.isArray(t)&&(e=t);if(this.core.updateConfig({source:{...this.core.getCurrentSource(),qualities:e}}),this.refreshQualityUi(),e.length<50){let s=JSON.stringify(e);this.getAttribute("source-qualities")!==s&&this.setAttribute("source-qualities",s)}}get playlist(){return this.core.getPlaylist()}set playlist(t){let e=[];if(typeof t=="string")try{e=JSON.parse(t)}catch(s){console.error("[UVP] Failed to parse playlist string:",s);return}else Array.isArray(t)&&(e=t);if(this.core.setPlaylist(e),this.refreshPlaylistUi(),this.updateNavigationButtons(),e.length<100){let s=JSON.stringify(e);this.getAttribute("playlist")!==s&&this.setAttribute("playlist",s)}}get autoplay(){return this.hasAttribute("autoplay")}set autoplay(t){t?this.setAttribute("autoplay",""):this.removeAttribute("autoplay")}get muted(){return this.hasAttribute("muted")}set muted(t){t?this.setAttribute("muted",""):this.removeAttribute("muted")}get poster(){return this.getAttribute("poster")}set poster(t){t?this.setAttribute("poster",t):this.removeAttribute("poster")}get controls(){return this.showControls}set controls(t){this.setAttribute("controls",String(t))}get autoNext(){return this.readBooleanAttr("auto-next",!1)}set autoNext(t){t?this.setAttribute("auto-next","true"):this.removeAttribute("auto-next")}get loop(){return this.readBooleanAttr("loop",!1)}set loop(t){t?this.setAttribute("loop","true"):this.removeAttribute("loop")}get playbackRate(){return this.core.getConfig().playbackRate??1}set playbackRate(t){this.setAttribute("playback-rate",String(t))}get volume(){return this.core.getConfig().volume??1}set volume(t){this.setAttribute("volume",String(t))}get customControls(){return this.isCustomControlsMode}set customControls(t){t?this.setAttribute("custom-controls","true"):this.removeAttribute("custom-controls")}get scaleMode(){return this.core.getScaleMode()}set scaleMode(t){this.setAttribute("scale-mode",t)}syncAllFromCore(){let t=this.core.getConfig();this.autoplayToggle.checked=!!(t.autoplay||t.autoNext),this.loopToggle.checked=!!t.loop,this.refreshPlaylistUi(),this.refreshQualityUi(),this.refreshScaleModeUi(),this.updateNavigationButtons(),this.updateUiVisibility()}readConfigFromAttributes(){return{source:this.buildSourceFromAttributes(),autoplay:this.hasAttribute("autoplay"),muted:this.hasAttribute("muted"),poster:this.getAttribute("poster")??void 0,controls:!1,autoNext:this.readBooleanAttr("auto-next",!1),loop:this.readBooleanAttr("loop",!1),playbackRate:this.readNumberAttr("playback-rate",1),volume:this.readNumberAttr("volume",1),scaleMode:this.getAttribute("scale-mode")??"fit"}}buildSourceFromAttributes(){let t=this.getAttribute("source-url");if(!t)return;let e=this.getAttribute("source-type")??"mp4",s=this.parseSourceQualitiesAttr();return{id:"single",url:t,type:e,...s.length>0?{qualities:s}:{}}}parseSourceQualitiesAttr(){let t=this.getAttribute("source-qualities");if(!t?.trim())return[];try{let e=JSON.parse(t);if(!Array.isArray(e))return[];let s=[];for(let i of e){if(!i||typeof i!="object")continue;let o=i,n=typeof o.id=="string"?o.id:"";if(!n)continue;let a=typeof o.label=="string"?o.label:typeof o.height=="number"&&Number.isFinite(o.height)?`${o.height}p`:n,u={id:n,label:a};typeof o.height=="number"&&Number.isFinite(o.height)&&(u.height=o.height),typeof o.bitrateKbps=="number"&&Number.isFinite(o.bitrateKbps)&&(u.bitrateKbps=o.bitrateKbps),s.push(u)}return s}catch{return[]}}applyAttributeToCore(t,e){let s=this.core.getConfig();switch(t){case"source-url":case"source-type":case"source-qualities":{let i=this.buildSourceFromAttributes(),o=this.core.getCurrentSource();(i?.url!==o?.url||i?.type!==o?.type||JSON.stringify(i?.qualities)!==JSON.stringify(o?.qualities))&&(this.core.updateConfig({source:i}),this.updateNavigationButtons(),this.refreshQualityUi());break}case"playlist":{let i=this.parsePlaylistAttr(),o=this.core.getPlaylist();JSON.stringify(i)!==JSON.stringify(o)&&(this.core.updateConfig({playlist:i}),this.updateNavigationButtons(),this.refreshQualityUi(),this.refreshPlaylistUi());break}case"autoplay":{let i=this.hasAttribute("autoplay");s.autoplay!==i&&this.core.updateConfig({autoplay:i});break}case"muted":{let i=this.hasAttribute("muted");s.muted!==i&&this.core.updateConfig({muted:i});break}case"poster":{let i=e??"";s.poster!==i&&this.core.updateConfig({poster:i});break}case"controls":{let i=this.readBooleanAttr("controls",!0);this.showControls!==i&&(this.showControls=i,this.updateUiVisibility());break}case"auto-next":{let i=this.readBooleanAttr("auto-next",!1);s.autoNext!==i&&(this.core.updateConfig({autoNext:i}),this.updateNavigationButtons());break}case"loop":{let i=this.readBooleanAttr("loop",!1);s.loop!==i&&(this.core.updateConfig({loop:i}),this.updateNavigationButtons());break}case"playback-rate":{let i=this.readNumberAttr("playback-rate",1);s.playbackRate!==i&&(this.core.setPlaybackRate(i),this.updateSpeedUi(i));break}case"volume":{let i=this.readNumberAttr("volume",1);s.volume!==i&&this.core.setVolume(i);break}case"custom-controls":{let i=this.readBooleanAttr("custom-controls",!1);this.isCustomControlsMode!==i&&(this.isCustomControlsMode=i,this.updateUiVisibility());break}case"scale-mode":{let i=e??"fit";this.core.getScaleMode()!==i&&this.core.setScaleMode(i);break}case"hide-settings":case"hide-fullscreen":case"hide-playlist":case"hide-volume":{this.updateUiVisibility();break}default:break}}readBooleanAttr(t,e){if(!this.hasAttribute(t))return e;let s=this.getAttribute(t);return s===""||s==="true"?!0:s==="false"?!1:e}readNumberAttr(t,e){let s=this.getAttribute(t);if(s==null)return e;let i=Number(s);return Number.isFinite(i)?i:e}bindUiEvents(){this.centerPlayPauseButton.addEventListener("click",()=>{this.videoEl.paused?this.play():this.pause(),this.markInteraction()}),this.centerPrevButton.addEventListener("click",()=>{this.centerPrevButton.disabled||(this.previous(),this.updateNavigationButtons(),this.markInteraction())}),this.centerNextButton.addEventListener("click",()=>{this.centerNextButton.disabled||(this.next(),this.updateNavigationButtons(),this.markInteraction())}),this.controlPlayPauseButton.addEventListener("click",()=>{this.videoEl.paused?this.play():this.pause(),this.markInteraction()}),this.controlPrevButton.addEventListener("click",()=>{this.controlPrevButton.disabled||(this.previous(),this.updateNavigationButtons(),this.markInteraction())}),this.controlNextButton.addEventListener("click",()=>{this.controlNextButton.disabled||(this.next(),this.updateNavigationButtons(),this.markInteraction())}),this.progressInput.addEventListener("input",()=>{this.isUserSeeking=!0;let t=Number(this.progressInput.value),e=this.videoEl.duration||0;this.currentTimeEl.textContent=this.formatTime(t/100*e),this.updateProgressVisual(t),this.markInteraction()}),this.progressInput.addEventListener("change",()=>{let t=this.videoEl.duration||0;if(t>0){let e=Number(this.progressInput.value);this.seek(e/100*t)}this.isUserSeeking=!1,this.markInteraction()}),this.muteButton.addEventListener("click",()=>{this.core.setMuted(!this.videoEl.muted),this.handleVolumeChange(),this.markInteraction()}),this.volumeInput.addEventListener("input",()=>{let t=Number(this.volumeInput.value);this.setVolume(t),t>0&&this.videoEl.muted&&this.core.setMuted(!1),this.handleVolumeChange(),this.markInteraction()}),this.fullscreenButton.addEventListener("click",()=>{this.toggleFullscreen()}),this.lockButton.addEventListener("click",t=>{t.stopPropagation(),this.isLocked=!this.isLocked,this.shellEl.classList.toggle("locked",this.isLocked),this.refreshLockButtonIcon(),this.markInteraction(),this.dispatchStateChange()}),this.aspectShortcut.addEventListener("click",t=>{t.stopPropagation();let e=this.core.getScaleMode(),s=["fit","fill","zoom"],i=(s.indexOf(e)+1)%s.length;this.core.setScaleMode(s[i]),this.markInteraction()}),this.liveBadgeEl.addEventListener("click",t=>{t.stopPropagation(),this.isLive&&(this.core.seekToLive(),this.markInteraction())}),this.settingsButton.addEventListener("click",t=>{t.stopPropagation(),this.settingsMenu.classList.contains("hidden")?(this.openSettingsMenu(),this.playlistPanel.classList.remove("open")):this.closeSettingsMenu(),this.markInteraction()}),this.settingsScrim.addEventListener("click",()=>{this.closeSettingsMenu(),this.playlistPanel.classList.remove("open"),this.settingsScrim&&this.settingsScrim.classList.remove("visible")}),this.settingsMenu.querySelectorAll(".settings-item[data-action]").forEach(t=>{t.addEventListener("click",e=>{e.stopPropagation();let s=t.dataset.action;if(s==="quality-screen")this.switchMenuScreen("quality");else if(s==="speed-screen")this.switchMenuScreen("speed");else if(s==="aspect-screen")this.switchMenuScreen("aspect");else if(s==="autoplay"){let i=!this.autoplayToggle.checked;this.autoplayToggle.checked=i,this.core.updateConfig({autoplay:i,autoNext:i})}else if(s==="loop"){let i=!this.loopToggle.checked;this.loopToggle.checked=i,this.core.updateConfig({loop:i})}else s==="download"&&this.handleDownloadAction(t);this.markInteraction()})}),this.settingsMenu.querySelectorAll(".menu-back").forEach(t=>{t.addEventListener("click",e=>{e.stopPropagation();let s=t.dataset.target||"main";this.switchMenuScreen(s),this.markInteraction()})}),this.settingsMenu.querySelectorAll(".speed-item").forEach(t=>{t.addEventListener("click",e=>{e.stopPropagation();let s=Number(t.dataset.rate);this.setPlaybackRate(s),this.updateSpeedUi(s),this.closeSettingsMenu(),this.markInteraction()})}),this.settingsMenu.querySelectorAll(".aspect-item").forEach(t=>{t.addEventListener("click",e=>{e.stopPropagation();let s=t.dataset.mode;this.core.setScaleMode(s),this.closeSettingsMenu(),this.markInteraction()})}),this.playlistButton.addEventListener("click",t=>{t.stopPropagation();let e=this.playlistPanel.classList.toggle("open");e&&this.closeSettingsMenu(),this.settingsScrim.classList.toggle("visible",e),this.markInteraction()}),this.playlistCloseButton.addEventListener("click",t=>{t.stopPropagation(),this.playlistPanel.classList.remove("open"),this.settingsScrim.classList.remove("visible"),this.markInteraction()}),this.shadowRootRef.addEventListener("click",t=>{let e=t.target,s=this.settingsWrap.contains(e),i=this.playlistPanel.contains(e)||this.playlistButton.contains(e),o=this.settingsScrim.contains(e);(!s||o)&&this.closeSettingsMenu(),i||this.playlistPanel.classList.remove("open")}),this.videoEl.addEventListener("ratechange",()=>{this.updateSpeedUi(this.videoEl.playbackRate||1),this.markInteraction()}),this.fullscreenButton.addEventListener("click",()=>{this.toggleFullscreen()}),this.shellEl.addEventListener("mousemove",this.markInteraction),this.shellEl.addEventListener("click",this.markInteraction),this.shellEl.addEventListener("touchstart",this.markInteraction),this.shellEl.addEventListener("mouseenter",this.markInteraction),this.shadowRootRef.querySelectorAll(".settings-item").forEach(t=>{t.addEventListener("click",()=>{let e=t.dataset.action;e==="quality-screen"&&this.switchMenuScreen("quality"),e==="speed-screen"&&this.switchMenuScreen("speed"),e==="aspect-screen"&&this.switchMenuScreen("aspect"),e==="download-screen"&&this.switchMenuScreen("download")})}),this.updateNavigationButtons()}openSettingsMenu(){this.refreshQualityUi(),this.refreshScaleModeUi(),this.refreshDownloadUi(),this.switchMenuScreen("main"),this.settingsMenu.classList.remove("hidden"),this.settingsButton.setAttribute("aria-expanded","true"),this.settingsScrim.classList.add("visible")}closeSettingsMenu(){this.settingsMenu.classList.add("hidden"),this.settingsButton.setAttribute("aria-expanded","false"),this.settingsScrim.classList.remove("visible")}switchMenuScreen(t){this.settingsMenu.querySelectorAll(".menu-screen").forEach(e=>{let s=e.dataset.screen===t;e.classList.toggle("hidden",!s)})}handleDownloadAction(t){let e=this.core.getCurrentSource();if(!e)return;if(e.type!=="mp4"&&e.type!=="mov"&&e.type!=="hls"&&e.type!=="dash"){alert("Downloading is only supported for MP4, MOV, HLS, and DASH formats.");return}let s=t.dataset.status==="downloading",i=t.dataset.status==="downloaded";if(!s)if(i)(0,d.removeCachedVideo)(e.id).then(()=>{this.refreshDownloadUi()});else{t.dataset.status="downloading";let o=t.querySelector(".download-text");o&&(o.textContent="Downloading 0%"),(0,d.cacheVideo)(e.id,e.url,n=>{n.status==="in_progress"?o&&(o.textContent=`Downloading ${Math.round(n.progress||0)}%`):n.status==="completed"?(t.dataset.status="none",this.refreshDownloadUi()):n.status==="error"&&(o&&(o.textContent="Download failed"),setTimeout(()=>{t.dataset.status="none",this.refreshDownloadUi()},2e3))},e.type)}}refreshDownloadUi(){let t=this.core.getCurrentSource(),e=this.settingsMenu.querySelector('[data-action="download"]');if(!e)return;if(!t||t.type!=="mp4"&&t.type!=="mov"&&t.type!=="hls"&&t.type!=="dash"){e.style.opacity="0.5",e.style.pointerEvents="none";let o=e.querySelector(".download-text");o&&(o.textContent="Download (Unsupported)");return}e.style.opacity="1",e.style.pointerEvents="auto";let s=e.querySelector(".download-text"),i=e.querySelector(".download-icon");e.dataset.status!=="downloading"&&(0,d.isCached)(t.id).then(o=>{this.core.getCurrentSource()?.id===t.id&&(o?(e.dataset.status="downloaded",s&&(s.textContent="Remove Download"),i&&(i.innerHTML=c.icons.trash.toSvg({width:"16",height:"16","stroke-width":"2.5"}))):(e.dataset.status="none",s&&(s.textContent="Download"),i&&(i.innerHTML=c.icons.download.toSvg({width:"16",height:"16","stroke-width":"2.5"}))))})}updateUiVisibility(){let t=this.isCustomControlsMode,e=this.showControls;if(this.videoEl.controls=!1,this.controlsEl.classList.toggle("hidden",t||!e),this.centerActionsEl.classList.toggle("hidden",t||!e),this.slotEl.classList.toggle("hidden",!t||!e),t||!e?(this.shellEl.classList.remove("ui-hidden"),this.clearHideTimer()):this.markInteraction(),!t&&e){let s=this.hasAttribute("hide-settings"),i=this.hasAttribute("hide-fullscreen"),o=this.hasAttribute("hide-playlist"),n=this.hasAttribute("hide-volume");this.settingsWrap.classList.toggle("hidden",s),this.fullscreenButton.classList.toggle("hidden",i);let a=this.core.getPlaylist().length>1;this.playlistButton.classList.toggle("hidden",o||!a),this.muteButton.classList.toggle("hidden",n),this.volumeInput.classList.toggle("hidden",n)}}toggleFullscreen(){if(this.isProcessingFullscreen)return;this.isProcessingFullscreen=!0,setTimeout(()=>{this.isProcessingFullscreen=!1},1e3);let t=document,e=this.shellEl;if(this.isFullscreen)document.exitFullscreen?document.exitFullscreen():t.webkitExitFullscreen?t.webkitExitFullscreen():t.mozCancelFullScreen?t.mozCancelFullScreen():t.msExitFullscreen&&t.msExitFullscreen();else try{e.requestFullscreen?e.requestFullscreen():e.webkitRequestFullscreen?e.webkitRequestFullscreen():e.mozRequestFullScreen?e.mozRequestFullScreen():e.msRequestFullscreen&&e.msRequestFullscreen()}catch(s){console.error("Fullscreen request failed:",s)}this.markInteraction()}formatTime(t){if(!Number.isFinite(t)||t<=0)return"0:00";let e=Math.floor(t),s=Math.floor(e/60),i=e%60;return`${s}:${String(i).padStart(2,"0")}`}clearHideTimer(){this.controlsHideTimer!=null&&(window.clearTimeout(this.controlsHideTimer),this.controlsHideTimer=null)}updateProgressVisual(t){let e=Math.max(0,Math.min(100,t));this.progressPlayedEl.style.width=`${e}%`,this.progressThumbEl.style.left=`${e}%`}updateNavigationButtons(){let t=this.core.canGoPrevious(),e=this.core.canGoNext();this.controlPrevButton.disabled=!t,this.controlNextButton.disabled=!e}parsePlaylistAttr(){let t=this.getAttribute("playlist");if(console.log("[UVP] Raw playlist attribute:",t),!t)return[];try{let e=JSON.parse(t);return Array.isArray(e)?e.map(s=>({...s,id:s.id?String(s.id):Math.random().toString(36).substring(7)})):[]}catch{return[]}}refreshQualityUi(){let t=this.core.getQualities(),e=this.core.getCurrentQuality(),s=e?.label||"Auto",i=this.settingsMenu.querySelector(".current-quality-value");i&&(i.textContent=s);let o=this.settingsMenu.querySelector(".quality-list");if(!o)return;o.innerHTML="";let n=(a,u)=>{let p=document.createElement("button"),b=a===(e?.id||null);p.className=`quality-item ${b?"active":""}`,p.innerHTML=`<span>${u}</span><span class="check"></span>`;let m=p.querySelector(".check");return m&&(m.innerHTML=c.icons.check.toSvg({width:"16",height:"16","stroke-width":"2.5"})),p.addEventListener("click",f=>{f.stopPropagation(),this.core.setSelectedQuality(a),this.closeSettingsMenu(),this.markInteraction()}),p};o.appendChild(n(null,"Auto")),t.forEach(a=>{let u=a.label||(a.height?`${a.height}p`:a.id);o.appendChild(n(a.id,u))})}renderAspectMenu(){this.settingsAspectScreen.innerHTML=`
1208
+ <div class="settings-header">
1209
+ <button class="back-button">Back</button>
1210
+ <span>Aspect Ratio</span>
1211
+ </div>
1212
+ <div class="settings-options">
1213
+ <div class="settings-option" data-mode="fit">
1214
+ <span>Fit</span>
1215
+ <span class="check-mark"></span>
1216
+ </div>
1217
+ <div class="settings-option" data-mode="fill">
1218
+ <span>Stretch</span>
1219
+ <span class="check-mark"></span>
1220
+ </div>
1221
+ <div class="settings-option" data-mode="zoom">
1222
+ <span>Zoom</span>
1223
+ <span class="check-mark"></span>
1224
+ </div>
1225
+ </div>
1226
+ `,this.settingsAspectScreen.querySelector(".back-button")?.addEventListener("click",()=>this.switchMenuScreen("main"));let e=this.settingsAspectScreen.querySelectorAll(".settings-option"),s=this.core.getScaleMode();e.forEach(i=>{let o=i.dataset.mode;o===s&&i.classList.add("active"),i.addEventListener("click",()=>{this.core.setScaleMode(o),this.switchMenuScreen("main")})})}refreshScaleModeUi(){let t=this.core.getScaleMode();this.shellEl.classList.remove("scale-fit","scale-fill","scale-zoom"),this.shellEl.classList.add(`scale-${t}`),this.settingsMenu.querySelectorAll(".aspect-item").forEach(s=>{let i=s.dataset.mode;s.classList.toggle("active",i===t)});let e=this.shadowRootRef.querySelector(".current-aspect-value");if(e){let s={fit:"Fit",fill:"Stretch",zoom:"Zoom"};e.textContent=s[t]||"Fit"}}renderStaticIcons(){this.setButtonIcon(this.centerPrevButton,"skip-back"),this.setButtonIcon(this.centerNextButton,"skip-forward"),this.setButtonIcon(this.centerPlayPauseButton,"play"),this.setButtonIcon(this.controlPrevButton,"skip-back","20"),this.setButtonIcon(this.controlNextButton,"skip-forward","20"),this.setButtonIcon(this.muteButton,"volume-2","20"),this.setButtonIcon(this.settingsButton,"settings","20"),this.setButtonIcon(this.fullscreenButton,"maximize","20"),this.setButtonIcon(this.playlistButton,"list","20"),this.setButtonIcon(this.playlistCloseButton,"x","20"),this.setButtonIcon(this.aspectShortcut,"box","20"),this.refreshLockButtonIcon();let t=(s,i,o="18")=>{this.settingsMenu.querySelectorAll(s).forEach(n=>{n.innerHTML=c.icons[i].toSvg({width:o,height:o,"stroke-width":"2"})})};t(".icon-quality","monitor"),t(".icon-speed","fast-forward"),t(".icon-aspect","box"),t(".icon-play","play"),t(".icon-repeat","repeat"),t(".icon-arrow-left","arrow-left","20"),this.settingsMenu.querySelectorAll(".icon-chevron-right").forEach(s=>{s.innerHTML=c.icons["chevron-right"].toSvg({width:"16",height:"16","stroke-width":"2.5"})});let e=this.settingsMenu.querySelector(".download-icon");e&&(e.innerHTML=c.icons.download.toSvg({width:"16",height:"16","stroke-width":"2.5"})),this.settingsMenu.querySelectorAll(".check").forEach(s=>{s.innerHTML=c.icons.check.toSvg({width:"16",height:"16","stroke-width":"2.5"})})}setButtonIcon(t,e,s="26"){if(!t)return;let i=c.icons[e];if(!i)return;let o=t.querySelectorAll('[class^="icon-placeholder"]');o.length>0?o.forEach(n=>{n.innerHTML=i.toSvg({width:s,height:s,"stroke-width":"2.2"})}):t.innerHTML=i.toSvg({width:s,height:s,"stroke-width":"2.2"})}refreshLockButtonIcon(){let t=this.isLocked?"lock":"unlock";this.setButtonIcon(this.lockButton,t)}updateSpeedUi(t){let e=[.5,1,1.5,2].includes(t)?t:1,s=this.settingsMenu.querySelector(".current-speed-value");s&&(s.textContent=`${e}x`),this.settingsMenu.querySelectorAll(".speed-item").forEach(i=>{let o=Number(i.dataset.rate);i.classList.toggle("active",o===e)})}getBufferedPercent(t,e){if(!t||!Number.isFinite(t)||t<=0)return 0;let s=this.videoEl.buffered;if(!s||s.length===0)return 0;let i=0;for(let o=0;o<s.length;o+=1){let n=s.start(o),a=s.end(o);e>=n&&e<=a,i=Math.max(i,a)}return Math.max(0,Math.min(100,i/t*100))}startProgressLoop(){this.stopProgressLoop();let t=()=>{if(this.videoEl.paused||this.videoEl.ended){this.progressRafId=null;return}this.handleVideoProgress(),this.progressRafId=window.requestAnimationFrame(t)};this.progressRafId=window.requestAnimationFrame(t)}stopProgressLoop(){this.progressRafId!=null&&(window.cancelAnimationFrame(this.progressRafId),this.progressRafId=null)}};function v(r=L){customElements.get(r)||customElements.define(r,h)}typeof window<"u"&&v();0&&(module.exports={UvpPlayerElement,defineUvpPlayerElement});