@ngxsplayer/ngx-smart-player 0.0.1-next.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/fesm2022/ngxsplayer-ngx-smart-player.mjs +2982 -675
- package/fesm2022/ngxsplayer-ngx-smart-player.mjs.map +1 -1
- package/index.d.ts +488 -26
- package/package.json +10 -7
|
@@ -1,9 +1,1861 @@
|
|
|
1
|
-
import { PlayerFacade, createSsaiManifestRewritePlugin } from '@ngxsp/core';
|
|
1
|
+
import { PlayerFacade, createSsaiManifestRewritePlugin, describeSubtitleFile, subtitleTextToVtt, decodeSubtitleBytes } from '@ngxsp/core';
|
|
2
2
|
export * from '@ngxsp/core';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { EventEmitter, HostListener, ViewChild,
|
|
4
|
+
import { Input, ChangeDetectionStrategy, Component, EventEmitter, Output, HostListener, ViewChild, input, output, computed, inject, ChangeDetectorRef, ElementRef, InjectionToken, makeEnvironmentProviders } from '@angular/core';
|
|
5
5
|
import { CommonModule } from '@angular/common';
|
|
6
6
|
import { Subscription } from 'rxjs';
|
|
7
|
+
import { NgxspPlayButton, NgxspTimeSlider, NgxspVolumeControl, NgxspLabeledControl, NgxspLiveBadge } from '@ngxsplayer/ngx-smart-player-ui-core';
|
|
8
|
+
|
|
9
|
+
class SmartPlayerIconComponent {
|
|
10
|
+
name = 'play';
|
|
11
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: SmartPlayerIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
12
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: SmartPlayerIconComponent, isStandalone: true, selector: "ngxsp-smart-icon", inputs: { name: "name" }, ngImport: i0, template: `
|
|
13
|
+
@switch (name) {
|
|
14
|
+
@case ('play') {
|
|
15
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8.25 6.82c0-1.3 1.43-2.1 2.54-1.43l8.12 4.93a1.96 1.96 0 0 1 0 3.36l-8.12 4.93a1.67 1.67 0 0 1-2.54-1.43V6.82Z"/></svg>
|
|
16
|
+
}
|
|
17
|
+
@case ('pause') {
|
|
18
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M7.5 5.25A1.25 1.25 0 0 1 8.75 6.5v11A1.25 1.25 0 0 1 6.25 17.5v-11A1.25 1.25 0 0 1 7.5 5.25Zm8 0a1.25 1.25 0 0 1 1.25 1.25v11a1.25 1.25 0 1 1-2.5 0v-11A1.25 1.25 0 0 1 15.5 5.25Z"/></svg>
|
|
19
|
+
}
|
|
20
|
+
@case ('prev') {
|
|
21
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M6 5.25a1 1 0 0 1 1 1v11.5a1 1 0 1 1-2 0V6.25a1 1 0 0 1 1-1Z"/><path d="M19 6.55v10.9a1.25 1.25 0 0 1-1.93 1.05l-8.2-5.45a1.25 1.25 0 0 1 0-2.1l8.2-5.45A1.25 1.25 0 0 1 19 6.55Z"/></svg>
|
|
22
|
+
}
|
|
23
|
+
@case ('next') {
|
|
24
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18 5.25a1 1 0 0 1 1 1v11.5a1 1 0 1 1-2 0V6.25a1 1 0 0 1 1-1Z"/><path d="M5 6.55v10.9a1.25 1.25 0 0 0 1.93 1.05l8.2-5.45a1.25 1.25 0 0 0 0-2.1l-8.2-5.45A1.25 1.25 0 0 0 5 6.55Z"/></svg>
|
|
25
|
+
}
|
|
26
|
+
@case ('seek-back') {
|
|
27
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5.5 13a6.5 6.5 0 1 0 6.5-6.5H8M10 4.5 8 6.5l2 2"/><text x="12" y="15.8" font-size="7.5" font-weight="700" text-anchor="middle" fill="currentColor" stroke="none" font-family="system-ui, sans-serif">10</text></svg>
|
|
28
|
+
}
|
|
29
|
+
@case ('seek-forward') {
|
|
30
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18.5 13a6.5 6.5 0 1 1-6.5-6.5H16M14 4.5 16 6.5l-2 2"/><text x="12" y="15.8" font-size="7.5" font-weight="700" text-anchor="middle" fill="currentColor" stroke="none" font-family="system-ui, sans-serif">10</text></svg>
|
|
31
|
+
}
|
|
32
|
+
@case ('volume-mute') {
|
|
33
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11 5 6.5 9H3.75v6h2.75L11 19V5Zm3.5 5.5 5 5m0-5-5 5"/></svg>
|
|
34
|
+
}
|
|
35
|
+
@case ('volume-low') {
|
|
36
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11 5 6.5 9H3.75v6h2.75L11 19V5Zm4.5 4.75a3.5 3.5 0 0 1 0 4.95"/></svg>
|
|
37
|
+
}
|
|
38
|
+
@case ('volume-high') {
|
|
39
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11 5 6.5 9H3.75v6h2.75L11 19V5Zm4.5 4.75a3.5 3.5 0 0 1 0 4.95m2.75-7.7a7.5 7.5 0 0 1 0 10.45"/></svg>
|
|
40
|
+
}
|
|
41
|
+
@case ('shuffle') {
|
|
42
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 6h3v3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m20 6-4.5 4.5"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 7h3.2c1 0 1.95.4 2.66 1.1l6.28 6.3A3.75 3.75 0 0 0 18.82 15H20"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 15h3v3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m20 18-4.56-4.56"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 17h3.2c1 0 1.95-.4 2.66-1.1l1.54-1.54"/></svg>
|
|
43
|
+
}
|
|
44
|
+
@case ('repeat') {
|
|
45
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3.5 20 6.5 17 9.5"/><path d="M4 11.5v-1a4 4 0 0 1 4-4h12"/><path d="M7 20.5 4 17.5 7 14.5"/><path d="M20 12.5v1a4 4 0 0 1-4 4H4"/></svg>
|
|
46
|
+
}
|
|
47
|
+
@case ('repeat-one') {
|
|
48
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3.5 20 6.5 17 9.5"/><path d="M4 11.5v-1a4 4 0 0 1 4-4h12"/><path d="M7 20.5 4 17.5 7 14.5"/><path d="M20 12.5v1a4 4 0 0 1-4 4H4"/><path d="M11 10.9 12.4 10v4.1"/></svg>
|
|
49
|
+
}
|
|
50
|
+
@case ('tracks') {
|
|
51
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 6.75h16M4 12h16M4 17.25h16"/></svg>
|
|
52
|
+
}
|
|
53
|
+
@case ('captions') {
|
|
54
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 11.25h2.5m2 0H14m2 0h.5M8 14.75h3.5m2 0H16"/></svg>
|
|
55
|
+
}
|
|
56
|
+
@case ('captions-off') {
|
|
57
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 11.25h2.5m2 0H14m2 0h.5M8 14.75h3.5m2 0H16"/><path stroke-linecap="round" stroke-width="1.8" d="M6 6l12 12"/></svg>
|
|
58
|
+
}
|
|
59
|
+
@case ('speed') {
|
|
60
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M5.25 5.82c0-1.3 1.43-2.1 2.54-1.43l5.71 3.47a1.96 1.96 0 0 1 0 3.36L7.79 14.7a1.67 1.67 0 0 1-2.54-1.43V5.82Zm8.5 0c0-1.3 1.43-2.1 2.54-1.43L22 7.86a1.96 1.96 0 0 1 0 3.36l-5.71 3.47a1.67 1.67 0 0 1-2.54-1.43V5.82Z"/></svg>
|
|
61
|
+
}
|
|
62
|
+
@case ('pip') {
|
|
63
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4.5" width="18" height="15" rx="2.5"/><rect x="12" y="11" width="7" height="6" rx="1" fill="currentColor" stroke="none"/></svg>
|
|
64
|
+
}
|
|
65
|
+
@case ('pip-active') {
|
|
66
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4.5" width="18" height="15" rx="2.5" fill="currentColor" fill-opacity=".22"/><rect x="12" y="11" width="7" height="6" rx="1" fill="currentColor" stroke="none"/></svg>
|
|
67
|
+
}
|
|
68
|
+
@case ('airplay') {
|
|
69
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M6 16.5H4.75A1.75 1.75 0 0 1 3 14.75v-8A1.75 1.75 0 0 1 4.75 5h14.5A1.75 1.75 0 0 1 21 6.75v8a1.75 1.75 0 0 1-1.75 1.75H18"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m12 14 4.5 6h-9L12 14Z"/></svg>
|
|
70
|
+
}
|
|
71
|
+
@case ('airplay-active') {
|
|
72
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M6 16.5H4.75A1.75 1.75 0 0 1 3 14.75v-8A1.75 1.75 0 0 1 4.75 5h14.5A1.75 1.75 0 0 1 21 6.75v8a1.75 1.75 0 0 1-1.75 1.75H18"/><path fill="currentColor" stroke="none" d="m12 13.4 5.2 6.9H6.8L12 13.4Z"/></svg>
|
|
73
|
+
}
|
|
74
|
+
@case ('cast') {
|
|
75
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 18.25a3.5 3.5 0 0 1 3.5 3.5"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 13.75A8 8 0 0 1 12.75 21.75"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 9.25A12.5 12.5 0 0 1 17.25 21.75"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M5.5 5.5h13a1.75 1.75 0 0 1 1.75 1.75v4.5"/></svg>
|
|
76
|
+
}
|
|
77
|
+
@case ('cast-active') {
|
|
78
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 18.25a3.5 3.5 0 0 1 3.5 3.5"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 13.75A8 8 0 0 1 12.75 21.75"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 9.25A12.5 12.5 0 0 1 17.25 21.75"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M5.5 5.5h13a1.75 1.75 0 0 1 1.75 1.75v4.5"/><circle cx="7" cy="19" r="1.5" fill="currentColor" stroke="none"/></svg>
|
|
79
|
+
}
|
|
80
|
+
@case ('devices') {
|
|
81
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2.75" y="4.75" width="12.5" height="9.5" rx="1.75"/><path d="M6.5 18.75h5M9 14.25v4.5"/><rect x="16.25" y="8.25" width="5" height="12" rx="1.5"/><circle cx="18.75" cy="16.25" r="1.4"/><path d="M18.75 11.25h.01"/></svg>
|
|
82
|
+
}
|
|
83
|
+
@case ('devices-active') {
|
|
84
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2.75" y="4.75" width="12.5" height="9.5" rx="1.75"/><path d="M6.5 18.75h5M9 14.25v4.5"/><rect x="16.25" y="8.25" width="5" height="12" rx="1.5"/><circle cx="18.75" cy="16.25" r="1.6" fill="currentColor" stroke="none"/><path d="M18.75 11.25h.01"/></svg>
|
|
85
|
+
}
|
|
86
|
+
@case ('radio') {
|
|
87
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7.5 8 16.5 3.75"/><rect x="3" y="8" width="18" height="12.5" rx="2.5"/><circle cx="15.25" cy="14.25" r="2.75"/><path d="M6.75 12.75h3.5M6.75 15.75h3.5"/></svg>
|
|
88
|
+
}
|
|
89
|
+
@case ('server') {
|
|
90
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="3.25" width="16" height="11" rx="2"/><path d="M7.5 6.75h.01M7.5 10.75h.01M10.5 6.75h6M10.5 10.75h6"/><path d="M12 14.25v4.5M3.5 20.5h6.75M13.75 20.5h6.75"/><circle cx="12" cy="20.5" r="1.6"/></svg>
|
|
91
|
+
}
|
|
92
|
+
@case ('window-mini') {
|
|
93
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4.5" width="18" height="15" rx="2.5"/><path d="M10 12.5 15 9.5M11 9.5h4v4"/><rect x="5.5" y="13" width="6" height="4.5" rx="1" fill="currentColor" stroke="none"/></svg>
|
|
94
|
+
}
|
|
95
|
+
@case ('window-bar') {
|
|
96
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4.5" width="18" height="15" rx="2.5"/><rect x="5" y="15" width="14" height="2.8" rx="1" fill="currentColor" stroke="none"/></svg>
|
|
97
|
+
}
|
|
98
|
+
@case ('cinema') {
|
|
99
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="7" width="20" height="10" rx="2"/><path d="M6 12h12M6 12l2-2M6 12l2 2M18 12l-2-2M18 12l-2 2"/></svg>
|
|
100
|
+
}
|
|
101
|
+
@case ('cinema-active') {
|
|
102
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="7" width="20" height="10" rx="2" fill="currentColor" fill-opacity=".22"/><path d="M6 12h12M6 12l2-2M6 12l2 2M18 12l-2-2M18 12l-2 2"/></svg>
|
|
103
|
+
}
|
|
104
|
+
@case ('info') {
|
|
105
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 11v5.5M12 7.5h.01"/></svg>
|
|
106
|
+
}
|
|
107
|
+
@case ('lyrics') {
|
|
108
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 5.5h16v10H10l-4.5 4v-4H4z"/><path d="M8 9.5h8M8 12.5h5"/></svg>
|
|
109
|
+
}
|
|
110
|
+
@case ('window-restore') {
|
|
111
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4.5" width="18" height="15" rx="2.5"/><path d="M10 14.5 15 9.5M11 9.5h4v4"/></svg>
|
|
112
|
+
}
|
|
113
|
+
@case ('bell') {
|
|
114
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M6.75 10.4a5.25 5.25 0 0 1 10.5 0c0 4.65 1.75 5.6 1.75 5.6H5s1.75-.95 1.75-5.6Z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M10 19a2.15 2.15 0 0 0 4 0"/></svg>
|
|
115
|
+
}
|
|
116
|
+
@case ('thumb-up') {
|
|
117
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7.25 10.5v9.25H4.7a1.7 1.7 0 0 1-1.7-1.7V12.2a1.7 1.7 0 0 1 1.7-1.7h2.55Zm0 0 4.85-7.05a1.75 1.75 0 0 1 3.18 1.16l-.44 3.2h3.88a2.1 2.1 0 0 1 2.05 2.55l-1.35 6.45a3.7 3.7 0 0 1-3.62 2.94H7.25"/></svg>
|
|
118
|
+
}
|
|
119
|
+
@case ('thumb-down') {
|
|
120
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7.25 13.5V4.25H4.7A1.7 1.7 0 0 0 3 5.95v5.85a1.7 1.7 0 0 0 1.7 1.7h2.55Zm0 0 4.85 7.05a1.75 1.75 0 0 0 3.18-1.16l-.44-3.2h3.88a2.1 2.1 0 0 0 2.05-2.55L19.42 7.2a3.7 3.7 0 0 0-3.62-2.94H7.25"/></svg>
|
|
121
|
+
}
|
|
122
|
+
@case ('share') {
|
|
123
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M18.25 8.25 21 5.5l-2.75-2.75"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20.4 5.5h-5.2A8.2 8.2 0 0 0 7 13.7v.8"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 11.5v6.75A1.75 1.75 0 0 0 5.75 20h12.5A1.75 1.75 0 0 0 20 18.25V14"/></svg>
|
|
124
|
+
}
|
|
125
|
+
@case ('playlist-plus') {
|
|
126
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="M4 6.5h11M4 11.5h11M4 16.5h8"/><path stroke-linecap="round" stroke-width="1.8" d="M18 13.5v6m-3-3h6"/></svg>
|
|
127
|
+
}
|
|
128
|
+
@case ('clock') {
|
|
129
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="12" cy="12" r="8.25" stroke-width="1.8"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 7.75v4.65l3.45 2.05"/></svg>
|
|
130
|
+
}
|
|
131
|
+
@case ('home') {
|
|
132
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m4 11.5 8-6.75 8 6.75"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M6.5 10.5v8.25h11v-8.25"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M10 18.75v-5h4v5"/></svg>
|
|
133
|
+
}
|
|
134
|
+
@case ('layers') {
|
|
135
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linejoin="round" stroke-width="1.8" d="M12 3.75 3.75 8 12 12.25 20.25 8 12 3.75Z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m5 12 7 3.6 7-3.6M5 16l7 3.6 7-3.6"/></svg>
|
|
136
|
+
}
|
|
137
|
+
@case ('video') {
|
|
138
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="4" y="5" width="16" height="14" rx="2.25" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 5v14M16 5v14M4 9h4M4 15h4M16 9h4M16 15h4"/></svg>
|
|
139
|
+
}
|
|
140
|
+
@case ('music') {
|
|
141
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M9 18.25a2.75 2.75 0 1 1-1.6-2.5V6.5l10-2v11.25a2.75 2.75 0 1 1-1.6-2.5V8.2l-8.4 1.68"/></svg>
|
|
142
|
+
}
|
|
143
|
+
@case ('broadcast') {
|
|
144
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="12" cy="12" r="2" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8.7 8.7a4.65 4.65 0 0 0 0 6.6m6.6 0a4.65 4.65 0 0 0 0-6.6M6.1 6.1a8.35 8.35 0 0 0 0 11.8m11.8 0a8.35 8.35 0 0 0 0-11.8"/></svg>
|
|
145
|
+
}
|
|
146
|
+
@case ('heart') {
|
|
147
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20.2 6.2c-1.8-1.9-4.8-1.8-6.5.2L12 8.3l-1.7-1.9c-1.7-2-4.7-2.1-6.5-.2-1.9 2-1.7 5.1.4 6.9L12 20l7.8-6.9c2.1-1.8 2.3-4.9.4-6.9Z"/></svg>
|
|
148
|
+
}
|
|
149
|
+
@case ('download') {
|
|
150
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 4v10m0 0 4-4m-4 4-4-4M5 19h14"/></svg>
|
|
151
|
+
}
|
|
152
|
+
@case ('trash') {
|
|
153
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.5 6.75h15M9.75 6.75V5.25A1.5 1.5 0 0 1 11.25 3.75h1.5a1.5 1.5 0 0 1 1.5 1.5v1.5M6.75 6.75l.7 12a1.5 1.5 0 0 0 1.5 1.5h6.1a1.5 1.5 0 0 0 1.5-1.5l.7-12M10.5 10.5v6M13.5 10.5v6"/></svg>
|
|
154
|
+
}
|
|
155
|
+
@case ('folder') {
|
|
156
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M3.75 7.5A2.25 2.25 0 0 1 6 5.25h4.15l2.1 2.25H18a2.25 2.25 0 0 1 2.25 2.25v6.75A2.25 2.25 0 0 1 18 18.75H6a2.25 2.25 0 0 1-2.25-2.25v-9Z"/></svg>
|
|
157
|
+
}
|
|
158
|
+
@case ('settings') {
|
|
159
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 15.25A3.25 3.25 0 1 0 12 8.75a3.25 3.25 0 0 0 0 6.5Z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M19.4 13.5a7.8 7.8 0 0 0 .05-3l2-1.55-2-3.45-2.42.98a8 8 0 0 0-2.58-1.5L14.1 2.4h-4.2l-.35 2.58a8 8 0 0 0-2.58 1.5L4.55 5.5l-2 3.45 2 1.55a7.8 7.8 0 0 0 .05 3l-2.05 1.55 2 3.45 2.46-.98a8 8 0 0 0 2.54 1.47l.35 2.61h4.2l.35-2.6a8 8 0 0 0 2.54-1.48l2.46.98 2-3.45-2.05-1.55Z"/></svg>
|
|
160
|
+
}
|
|
161
|
+
@case ('search') {
|
|
162
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="10.75" cy="10.75" r="6.25" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="m15.4 15.4 4.85 4.85"/></svg>
|
|
163
|
+
}
|
|
164
|
+
@case ('grid') {
|
|
165
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="4" y="4" width="6" height="6" rx="1.4" stroke-width="1.8"/><rect x="14" y="4" width="6" height="6" rx="1.4" stroke-width="1.8"/><rect x="4" y="14" width="6" height="6" rx="1.4" stroke-width="1.8"/><rect x="14" y="14" width="6" height="6" rx="1.4" stroke-width="1.8"/></svg>
|
|
166
|
+
}
|
|
167
|
+
@case ('list') {
|
|
168
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="M9 6h11M9 12h11M9 18h11"/><circle cx="4.8" cy="6" r="1.2" fill="currentColor" stroke="none"/><circle cx="4.8" cy="12" r="1.2" fill="currentColor" stroke="none"/><circle cx="4.8" cy="18" r="1.2" fill="currentColor" stroke="none"/></svg>
|
|
169
|
+
}
|
|
170
|
+
@case ('more') {
|
|
171
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.7"/><circle cx="12" cy="12" r="1.7"/><circle cx="19" cy="12" r="1.7"/></svg>
|
|
172
|
+
}
|
|
173
|
+
@case ('waveform') {
|
|
174
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="M4 13v-2m4 5V8m4 11V5m4 10V9m4 4v-2"/></svg>
|
|
175
|
+
}
|
|
176
|
+
@case ('sliders') {
|
|
177
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="M4 7h5m6 0h5M4 17h9m6 0h1"/><circle cx="12" cy="7" r="2.25" stroke-width="1.8"/><circle cx="16" cy="17" r="2.25" stroke-width="1.8"/></svg>
|
|
178
|
+
}
|
|
179
|
+
@case ('network') {
|
|
180
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="2.3" stroke-width="1.8"/><circle cx="6" cy="18" r="2.3" stroke-width="1.8"/><circle cx="18" cy="18" r="2.3" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M11 7.1 7.1 15.9M13 7.1l3.9 8.8M8.3 18h7.4"/></svg>
|
|
181
|
+
}
|
|
182
|
+
@case ('cloud') {
|
|
183
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7.25 18.5h10.2a4.05 4.05 0 0 0 .5-8.07 6.25 6.25 0 0 0-12-1.75A4.95 4.95 0 0 0 7.25 18.5Z"/></svg>
|
|
184
|
+
}
|
|
185
|
+
@case ('spark') {
|
|
186
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 3.5 14.3 9l5.7 2.2-5.7 2.3L12 20l-2.3-6.5L4 11.2 9.7 9 12 3.5Z"/></svg>
|
|
187
|
+
}
|
|
188
|
+
@case ('disc') {
|
|
189
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="12" cy="12" r="8.25" stroke-width="1.8"/><circle cx="12" cy="12" r="2.25" stroke-width="1.8"/></svg>
|
|
190
|
+
}
|
|
191
|
+
@case ('user') {
|
|
192
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="12" cy="8.5" r="3.75" stroke-width="1.8"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 19.75a7.25 7.25 0 0 1 14.5 0"/></svg>
|
|
193
|
+
}
|
|
194
|
+
@case ('tag') {
|
|
195
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linejoin="round" stroke-width="1.8" d="M3.75 5.75v5.1c0 .6.24 1.17.66 1.6l7.4 7.4a2 2 0 0 0 2.83 0l5.1-5.1a2 2 0 0 0 0-2.83l-7.4-7.4a2.25 2.25 0 0 0-1.6-.66h-5a2 2 0 0 0-2 1.89Z"/><circle cx="8.25" cy="8.25" r="1.25" fill="currentColor" stroke="none"/></svg>
|
|
196
|
+
}
|
|
197
|
+
@case ('close') {
|
|
198
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="m6.5 6.5 11 11m0-11-11 11"/></svg>
|
|
199
|
+
}
|
|
200
|
+
@case ('plus') {
|
|
201
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="M12 5v14M5 12h14"/></svg>
|
|
202
|
+
}
|
|
203
|
+
@case ('check') {
|
|
204
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m5 12.5 4.5 4.5L19 7.5"/></svg>
|
|
205
|
+
}
|
|
206
|
+
@case ('menu') {
|
|
207
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="M4 7h16M4 12h16M4 17h16"/></svg>
|
|
208
|
+
}
|
|
209
|
+
@case ('panel-right') {
|
|
210
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="4.5" width="17" height="15" rx="2.5" stroke-width="1.8"/><path stroke-width="1.8" d="M14.5 4.5v15"/></svg>
|
|
211
|
+
}
|
|
212
|
+
@case ('chevron-left') {
|
|
213
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m14.5 6-6 6 6 6"/></svg>
|
|
214
|
+
}
|
|
215
|
+
@case ('chevron-right') {
|
|
216
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m9.5 6 6 6-6 6"/></svg>
|
|
217
|
+
}
|
|
218
|
+
@case ('chevron-up') {
|
|
219
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m6 14.5 6-6 6 6"/></svg>
|
|
220
|
+
}
|
|
221
|
+
@case ('chevron-down') {
|
|
222
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m6 9.5 6 6 6-6"/></svg>
|
|
223
|
+
}
|
|
224
|
+
@case ('fullscreen') {
|
|
225
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.5 9V5.5a1 1 0 0 1 1-1H9m6 0h3.5a1 1 0 0 1 1 1V9m0 6v3.5a1 1 0 0 1-1 1H15m-6 0H5.5a1 1 0 0 1-1-1V15"/></svg>
|
|
226
|
+
}
|
|
227
|
+
@case ('fullscreen-exit') {
|
|
228
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M9 4.5V8a1 1 0 0 1-1 1H4.5m15 0H16a1 1 0 0 1-1-1V4.5m0 15V16a1 1 0 0 1 1-1h3.5m-15 0H8a1 1 0 0 1 1 1v3.5"/></svg>
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
`, isInline: true, styles: [":host{display:block;width:1em;height:1em;line-height:0;flex:0 0 auto}svg{display:block;width:100%;height:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
232
|
+
}
|
|
233
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: SmartPlayerIconComponent, decorators: [{
|
|
234
|
+
type: Component,
|
|
235
|
+
args: [{ selector: 'ngxsp-smart-icon', standalone: true, template: `
|
|
236
|
+
@switch (name) {
|
|
237
|
+
@case ('play') {
|
|
238
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8.25 6.82c0-1.3 1.43-2.1 2.54-1.43l8.12 4.93a1.96 1.96 0 0 1 0 3.36l-8.12 4.93a1.67 1.67 0 0 1-2.54-1.43V6.82Z"/></svg>
|
|
239
|
+
}
|
|
240
|
+
@case ('pause') {
|
|
241
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M7.5 5.25A1.25 1.25 0 0 1 8.75 6.5v11A1.25 1.25 0 0 1 6.25 17.5v-11A1.25 1.25 0 0 1 7.5 5.25Zm8 0a1.25 1.25 0 0 1 1.25 1.25v11a1.25 1.25 0 1 1-2.5 0v-11A1.25 1.25 0 0 1 15.5 5.25Z"/></svg>
|
|
242
|
+
}
|
|
243
|
+
@case ('prev') {
|
|
244
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M6 5.25a1 1 0 0 1 1 1v11.5a1 1 0 1 1-2 0V6.25a1 1 0 0 1 1-1Z"/><path d="M19 6.55v10.9a1.25 1.25 0 0 1-1.93 1.05l-8.2-5.45a1.25 1.25 0 0 1 0-2.1l8.2-5.45A1.25 1.25 0 0 1 19 6.55Z"/></svg>
|
|
245
|
+
}
|
|
246
|
+
@case ('next') {
|
|
247
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18 5.25a1 1 0 0 1 1 1v11.5a1 1 0 1 1-2 0V6.25a1 1 0 0 1 1-1Z"/><path d="M5 6.55v10.9a1.25 1.25 0 0 0 1.93 1.05l8.2-5.45a1.25 1.25 0 0 0 0-2.1l-8.2-5.45A1.25 1.25 0 0 0 5 6.55Z"/></svg>
|
|
248
|
+
}
|
|
249
|
+
@case ('seek-back') {
|
|
250
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5.5 13a6.5 6.5 0 1 0 6.5-6.5H8M10 4.5 8 6.5l2 2"/><text x="12" y="15.8" font-size="7.5" font-weight="700" text-anchor="middle" fill="currentColor" stroke="none" font-family="system-ui, sans-serif">10</text></svg>
|
|
251
|
+
}
|
|
252
|
+
@case ('seek-forward') {
|
|
253
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18.5 13a6.5 6.5 0 1 1-6.5-6.5H16M14 4.5 16 6.5l-2 2"/><text x="12" y="15.8" font-size="7.5" font-weight="700" text-anchor="middle" fill="currentColor" stroke="none" font-family="system-ui, sans-serif">10</text></svg>
|
|
254
|
+
}
|
|
255
|
+
@case ('volume-mute') {
|
|
256
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11 5 6.5 9H3.75v6h2.75L11 19V5Zm3.5 5.5 5 5m0-5-5 5"/></svg>
|
|
257
|
+
}
|
|
258
|
+
@case ('volume-low') {
|
|
259
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11 5 6.5 9H3.75v6h2.75L11 19V5Zm4.5 4.75a3.5 3.5 0 0 1 0 4.95"/></svg>
|
|
260
|
+
}
|
|
261
|
+
@case ('volume-high') {
|
|
262
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11 5 6.5 9H3.75v6h2.75L11 19V5Zm4.5 4.75a3.5 3.5 0 0 1 0 4.95m2.75-7.7a7.5 7.5 0 0 1 0 10.45"/></svg>
|
|
263
|
+
}
|
|
264
|
+
@case ('shuffle') {
|
|
265
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 6h3v3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m20 6-4.5 4.5"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 7h3.2c1 0 1.95.4 2.66 1.1l6.28 6.3A3.75 3.75 0 0 0 18.82 15H20"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 15h3v3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m20 18-4.56-4.56"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 17h3.2c1 0 1.95-.4 2.66-1.1l1.54-1.54"/></svg>
|
|
266
|
+
}
|
|
267
|
+
@case ('repeat') {
|
|
268
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3.5 20 6.5 17 9.5"/><path d="M4 11.5v-1a4 4 0 0 1 4-4h12"/><path d="M7 20.5 4 17.5 7 14.5"/><path d="M20 12.5v1a4 4 0 0 1-4 4H4"/></svg>
|
|
269
|
+
}
|
|
270
|
+
@case ('repeat-one') {
|
|
271
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3.5 20 6.5 17 9.5"/><path d="M4 11.5v-1a4 4 0 0 1 4-4h12"/><path d="M7 20.5 4 17.5 7 14.5"/><path d="M20 12.5v1a4 4 0 0 1-4 4H4"/><path d="M11 10.9 12.4 10v4.1"/></svg>
|
|
272
|
+
}
|
|
273
|
+
@case ('tracks') {
|
|
274
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 6.75h16M4 12h16M4 17.25h16"/></svg>
|
|
275
|
+
}
|
|
276
|
+
@case ('captions') {
|
|
277
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 11.25h2.5m2 0H14m2 0h.5M8 14.75h3.5m2 0H16"/></svg>
|
|
278
|
+
}
|
|
279
|
+
@case ('captions-off') {
|
|
280
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 11.25h2.5m2 0H14m2 0h.5M8 14.75h3.5m2 0H16"/><path stroke-linecap="round" stroke-width="1.8" d="M6 6l12 12"/></svg>
|
|
281
|
+
}
|
|
282
|
+
@case ('speed') {
|
|
283
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M5.25 5.82c0-1.3 1.43-2.1 2.54-1.43l5.71 3.47a1.96 1.96 0 0 1 0 3.36L7.79 14.7a1.67 1.67 0 0 1-2.54-1.43V5.82Zm8.5 0c0-1.3 1.43-2.1 2.54-1.43L22 7.86a1.96 1.96 0 0 1 0 3.36l-5.71 3.47a1.67 1.67 0 0 1-2.54-1.43V5.82Z"/></svg>
|
|
284
|
+
}
|
|
285
|
+
@case ('pip') {
|
|
286
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4.5" width="18" height="15" rx="2.5"/><rect x="12" y="11" width="7" height="6" rx="1" fill="currentColor" stroke="none"/></svg>
|
|
287
|
+
}
|
|
288
|
+
@case ('pip-active') {
|
|
289
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4.5" width="18" height="15" rx="2.5" fill="currentColor" fill-opacity=".22"/><rect x="12" y="11" width="7" height="6" rx="1" fill="currentColor" stroke="none"/></svg>
|
|
290
|
+
}
|
|
291
|
+
@case ('airplay') {
|
|
292
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M6 16.5H4.75A1.75 1.75 0 0 1 3 14.75v-8A1.75 1.75 0 0 1 4.75 5h14.5A1.75 1.75 0 0 1 21 6.75v8a1.75 1.75 0 0 1-1.75 1.75H18"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m12 14 4.5 6h-9L12 14Z"/></svg>
|
|
293
|
+
}
|
|
294
|
+
@case ('airplay-active') {
|
|
295
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M6 16.5H4.75A1.75 1.75 0 0 1 3 14.75v-8A1.75 1.75 0 0 1 4.75 5h14.5A1.75 1.75 0 0 1 21 6.75v8a1.75 1.75 0 0 1-1.75 1.75H18"/><path fill="currentColor" stroke="none" d="m12 13.4 5.2 6.9H6.8L12 13.4Z"/></svg>
|
|
296
|
+
}
|
|
297
|
+
@case ('cast') {
|
|
298
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 18.25a3.5 3.5 0 0 1 3.5 3.5"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 13.75A8 8 0 0 1 12.75 21.75"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 9.25A12.5 12.5 0 0 1 17.25 21.75"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M5.5 5.5h13a1.75 1.75 0 0 1 1.75 1.75v4.5"/></svg>
|
|
299
|
+
}
|
|
300
|
+
@case ('cast-active') {
|
|
301
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 18.25a3.5 3.5 0 0 1 3.5 3.5"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 13.75A8 8 0 0 1 12.75 21.75"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 9.25A12.5 12.5 0 0 1 17.25 21.75"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M5.5 5.5h13a1.75 1.75 0 0 1 1.75 1.75v4.5"/><circle cx="7" cy="19" r="1.5" fill="currentColor" stroke="none"/></svg>
|
|
302
|
+
}
|
|
303
|
+
@case ('devices') {
|
|
304
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2.75" y="4.75" width="12.5" height="9.5" rx="1.75"/><path d="M6.5 18.75h5M9 14.25v4.5"/><rect x="16.25" y="8.25" width="5" height="12" rx="1.5"/><circle cx="18.75" cy="16.25" r="1.4"/><path d="M18.75 11.25h.01"/></svg>
|
|
305
|
+
}
|
|
306
|
+
@case ('devices-active') {
|
|
307
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2.75" y="4.75" width="12.5" height="9.5" rx="1.75"/><path d="M6.5 18.75h5M9 14.25v4.5"/><rect x="16.25" y="8.25" width="5" height="12" rx="1.5"/><circle cx="18.75" cy="16.25" r="1.6" fill="currentColor" stroke="none"/><path d="M18.75 11.25h.01"/></svg>
|
|
308
|
+
}
|
|
309
|
+
@case ('radio') {
|
|
310
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7.5 8 16.5 3.75"/><rect x="3" y="8" width="18" height="12.5" rx="2.5"/><circle cx="15.25" cy="14.25" r="2.75"/><path d="M6.75 12.75h3.5M6.75 15.75h3.5"/></svg>
|
|
311
|
+
}
|
|
312
|
+
@case ('server') {
|
|
313
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="3.25" width="16" height="11" rx="2"/><path d="M7.5 6.75h.01M7.5 10.75h.01M10.5 6.75h6M10.5 10.75h6"/><path d="M12 14.25v4.5M3.5 20.5h6.75M13.75 20.5h6.75"/><circle cx="12" cy="20.5" r="1.6"/></svg>
|
|
314
|
+
}
|
|
315
|
+
@case ('window-mini') {
|
|
316
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4.5" width="18" height="15" rx="2.5"/><path d="M10 12.5 15 9.5M11 9.5h4v4"/><rect x="5.5" y="13" width="6" height="4.5" rx="1" fill="currentColor" stroke="none"/></svg>
|
|
317
|
+
}
|
|
318
|
+
@case ('window-bar') {
|
|
319
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4.5" width="18" height="15" rx="2.5"/><rect x="5" y="15" width="14" height="2.8" rx="1" fill="currentColor" stroke="none"/></svg>
|
|
320
|
+
}
|
|
321
|
+
@case ('cinema') {
|
|
322
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="7" width="20" height="10" rx="2"/><path d="M6 12h12M6 12l2-2M6 12l2 2M18 12l-2-2M18 12l-2 2"/></svg>
|
|
323
|
+
}
|
|
324
|
+
@case ('cinema-active') {
|
|
325
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="7" width="20" height="10" rx="2" fill="currentColor" fill-opacity=".22"/><path d="M6 12h12M6 12l2-2M6 12l2 2M18 12l-2-2M18 12l-2 2"/></svg>
|
|
326
|
+
}
|
|
327
|
+
@case ('info') {
|
|
328
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 11v5.5M12 7.5h.01"/></svg>
|
|
329
|
+
}
|
|
330
|
+
@case ('lyrics') {
|
|
331
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 5.5h16v10H10l-4.5 4v-4H4z"/><path d="M8 9.5h8M8 12.5h5"/></svg>
|
|
332
|
+
}
|
|
333
|
+
@case ('window-restore') {
|
|
334
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4.5" width="18" height="15" rx="2.5"/><path d="M10 14.5 15 9.5M11 9.5h4v4"/></svg>
|
|
335
|
+
}
|
|
336
|
+
@case ('bell') {
|
|
337
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M6.75 10.4a5.25 5.25 0 0 1 10.5 0c0 4.65 1.75 5.6 1.75 5.6H5s1.75-.95 1.75-5.6Z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M10 19a2.15 2.15 0 0 0 4 0"/></svg>
|
|
338
|
+
}
|
|
339
|
+
@case ('thumb-up') {
|
|
340
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7.25 10.5v9.25H4.7a1.7 1.7 0 0 1-1.7-1.7V12.2a1.7 1.7 0 0 1 1.7-1.7h2.55Zm0 0 4.85-7.05a1.75 1.75 0 0 1 3.18 1.16l-.44 3.2h3.88a2.1 2.1 0 0 1 2.05 2.55l-1.35 6.45a3.7 3.7 0 0 1-3.62 2.94H7.25"/></svg>
|
|
341
|
+
}
|
|
342
|
+
@case ('thumb-down') {
|
|
343
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7.25 13.5V4.25H4.7A1.7 1.7 0 0 0 3 5.95v5.85a1.7 1.7 0 0 0 1.7 1.7h2.55Zm0 0 4.85 7.05a1.75 1.75 0 0 0 3.18-1.16l-.44-3.2h3.88a2.1 2.1 0 0 0 2.05-2.55L19.42 7.2a3.7 3.7 0 0 0-3.62-2.94H7.25"/></svg>
|
|
344
|
+
}
|
|
345
|
+
@case ('share') {
|
|
346
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M18.25 8.25 21 5.5l-2.75-2.75"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20.4 5.5h-5.2A8.2 8.2 0 0 0 7 13.7v.8"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 11.5v6.75A1.75 1.75 0 0 0 5.75 20h12.5A1.75 1.75 0 0 0 20 18.25V14"/></svg>
|
|
347
|
+
}
|
|
348
|
+
@case ('playlist-plus') {
|
|
349
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="M4 6.5h11M4 11.5h11M4 16.5h8"/><path stroke-linecap="round" stroke-width="1.8" d="M18 13.5v6m-3-3h6"/></svg>
|
|
350
|
+
}
|
|
351
|
+
@case ('clock') {
|
|
352
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="12" cy="12" r="8.25" stroke-width="1.8"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 7.75v4.65l3.45 2.05"/></svg>
|
|
353
|
+
}
|
|
354
|
+
@case ('home') {
|
|
355
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m4 11.5 8-6.75 8 6.75"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M6.5 10.5v8.25h11v-8.25"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M10 18.75v-5h4v5"/></svg>
|
|
356
|
+
}
|
|
357
|
+
@case ('layers') {
|
|
358
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linejoin="round" stroke-width="1.8" d="M12 3.75 3.75 8 12 12.25 20.25 8 12 3.75Z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m5 12 7 3.6 7-3.6M5 16l7 3.6 7-3.6"/></svg>
|
|
359
|
+
}
|
|
360
|
+
@case ('video') {
|
|
361
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="4" y="5" width="16" height="14" rx="2.25" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 5v14M16 5v14M4 9h4M4 15h4M16 9h4M16 15h4"/></svg>
|
|
362
|
+
}
|
|
363
|
+
@case ('music') {
|
|
364
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M9 18.25a2.75 2.75 0 1 1-1.6-2.5V6.5l10-2v11.25a2.75 2.75 0 1 1-1.6-2.5V8.2l-8.4 1.68"/></svg>
|
|
365
|
+
}
|
|
366
|
+
@case ('broadcast') {
|
|
367
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="12" cy="12" r="2" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8.7 8.7a4.65 4.65 0 0 0 0 6.6m6.6 0a4.65 4.65 0 0 0 0-6.6M6.1 6.1a8.35 8.35 0 0 0 0 11.8m11.8 0a8.35 8.35 0 0 0 0-11.8"/></svg>
|
|
368
|
+
}
|
|
369
|
+
@case ('heart') {
|
|
370
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20.2 6.2c-1.8-1.9-4.8-1.8-6.5.2L12 8.3l-1.7-1.9c-1.7-2-4.7-2.1-6.5-.2-1.9 2-1.7 5.1.4 6.9L12 20l7.8-6.9c2.1-1.8 2.3-4.9.4-6.9Z"/></svg>
|
|
371
|
+
}
|
|
372
|
+
@case ('download') {
|
|
373
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 4v10m0 0 4-4m-4 4-4-4M5 19h14"/></svg>
|
|
374
|
+
}
|
|
375
|
+
@case ('trash') {
|
|
376
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.5 6.75h15M9.75 6.75V5.25A1.5 1.5 0 0 1 11.25 3.75h1.5a1.5 1.5 0 0 1 1.5 1.5v1.5M6.75 6.75l.7 12a1.5 1.5 0 0 0 1.5 1.5h6.1a1.5 1.5 0 0 0 1.5-1.5l.7-12M10.5 10.5v6M13.5 10.5v6"/></svg>
|
|
377
|
+
}
|
|
378
|
+
@case ('folder') {
|
|
379
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M3.75 7.5A2.25 2.25 0 0 1 6 5.25h4.15l2.1 2.25H18a2.25 2.25 0 0 1 2.25 2.25v6.75A2.25 2.25 0 0 1 18 18.75H6a2.25 2.25 0 0 1-2.25-2.25v-9Z"/></svg>
|
|
380
|
+
}
|
|
381
|
+
@case ('settings') {
|
|
382
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 15.25A3.25 3.25 0 1 0 12 8.75a3.25 3.25 0 0 0 0 6.5Z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M19.4 13.5a7.8 7.8 0 0 0 .05-3l2-1.55-2-3.45-2.42.98a8 8 0 0 0-2.58-1.5L14.1 2.4h-4.2l-.35 2.58a8 8 0 0 0-2.58 1.5L4.55 5.5l-2 3.45 2 1.55a7.8 7.8 0 0 0 .05 3l-2.05 1.55 2 3.45 2.46-.98a8 8 0 0 0 2.54 1.47l.35 2.61h4.2l.35-2.6a8 8 0 0 0 2.54-1.48l2.46.98 2-3.45-2.05-1.55Z"/></svg>
|
|
383
|
+
}
|
|
384
|
+
@case ('search') {
|
|
385
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="10.75" cy="10.75" r="6.25" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="m15.4 15.4 4.85 4.85"/></svg>
|
|
386
|
+
}
|
|
387
|
+
@case ('grid') {
|
|
388
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="4" y="4" width="6" height="6" rx="1.4" stroke-width="1.8"/><rect x="14" y="4" width="6" height="6" rx="1.4" stroke-width="1.8"/><rect x="4" y="14" width="6" height="6" rx="1.4" stroke-width="1.8"/><rect x="14" y="14" width="6" height="6" rx="1.4" stroke-width="1.8"/></svg>
|
|
389
|
+
}
|
|
390
|
+
@case ('list') {
|
|
391
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="M9 6h11M9 12h11M9 18h11"/><circle cx="4.8" cy="6" r="1.2" fill="currentColor" stroke="none"/><circle cx="4.8" cy="12" r="1.2" fill="currentColor" stroke="none"/><circle cx="4.8" cy="18" r="1.2" fill="currentColor" stroke="none"/></svg>
|
|
392
|
+
}
|
|
393
|
+
@case ('more') {
|
|
394
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.7"/><circle cx="12" cy="12" r="1.7"/><circle cx="19" cy="12" r="1.7"/></svg>
|
|
395
|
+
}
|
|
396
|
+
@case ('waveform') {
|
|
397
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="M4 13v-2m4 5V8m4 11V5m4 10V9m4 4v-2"/></svg>
|
|
398
|
+
}
|
|
399
|
+
@case ('sliders') {
|
|
400
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="M4 7h5m6 0h5M4 17h9m6 0h1"/><circle cx="12" cy="7" r="2.25" stroke-width="1.8"/><circle cx="16" cy="17" r="2.25" stroke-width="1.8"/></svg>
|
|
401
|
+
}
|
|
402
|
+
@case ('network') {
|
|
403
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="2.3" stroke-width="1.8"/><circle cx="6" cy="18" r="2.3" stroke-width="1.8"/><circle cx="18" cy="18" r="2.3" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M11 7.1 7.1 15.9M13 7.1l3.9 8.8M8.3 18h7.4"/></svg>
|
|
404
|
+
}
|
|
405
|
+
@case ('cloud') {
|
|
406
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7.25 18.5h10.2a4.05 4.05 0 0 0 .5-8.07 6.25 6.25 0 0 0-12-1.75A4.95 4.95 0 0 0 7.25 18.5Z"/></svg>
|
|
407
|
+
}
|
|
408
|
+
@case ('spark') {
|
|
409
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 3.5 14.3 9l5.7 2.2-5.7 2.3L12 20l-2.3-6.5L4 11.2 9.7 9 12 3.5Z"/></svg>
|
|
410
|
+
}
|
|
411
|
+
@case ('disc') {
|
|
412
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="12" cy="12" r="8.25" stroke-width="1.8"/><circle cx="12" cy="12" r="2.25" stroke-width="1.8"/></svg>
|
|
413
|
+
}
|
|
414
|
+
@case ('user') {
|
|
415
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><circle cx="12" cy="8.5" r="3.75" stroke-width="1.8"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.75 19.75a7.25 7.25 0 0 1 14.5 0"/></svg>
|
|
416
|
+
}
|
|
417
|
+
@case ('tag') {
|
|
418
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linejoin="round" stroke-width="1.8" d="M3.75 5.75v5.1c0 .6.24 1.17.66 1.6l7.4 7.4a2 2 0 0 0 2.83 0l5.1-5.1a2 2 0 0 0 0-2.83l-7.4-7.4a2.25 2.25 0 0 0-1.6-.66h-5a2 2 0 0 0-2 1.89Z"/><circle cx="8.25" cy="8.25" r="1.25" fill="currentColor" stroke="none"/></svg>
|
|
419
|
+
}
|
|
420
|
+
@case ('close') {
|
|
421
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="m6.5 6.5 11 11m0-11-11 11"/></svg>
|
|
422
|
+
}
|
|
423
|
+
@case ('plus') {
|
|
424
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="M12 5v14M5 12h14"/></svg>
|
|
425
|
+
}
|
|
426
|
+
@case ('check') {
|
|
427
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m5 12.5 4.5 4.5L19 7.5"/></svg>
|
|
428
|
+
}
|
|
429
|
+
@case ('menu') {
|
|
430
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-width="1.8" d="M4 7h16M4 12h16M4 17h16"/></svg>
|
|
431
|
+
}
|
|
432
|
+
@case ('panel-right') {
|
|
433
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="4.5" width="17" height="15" rx="2.5" stroke-width="1.8"/><path stroke-width="1.8" d="M14.5 4.5v15"/></svg>
|
|
434
|
+
}
|
|
435
|
+
@case ('chevron-left') {
|
|
436
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m14.5 6-6 6 6 6"/></svg>
|
|
437
|
+
}
|
|
438
|
+
@case ('chevron-right') {
|
|
439
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m9.5 6 6 6-6 6"/></svg>
|
|
440
|
+
}
|
|
441
|
+
@case ('chevron-up') {
|
|
442
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m6 14.5 6-6 6 6"/></svg>
|
|
443
|
+
}
|
|
444
|
+
@case ('chevron-down') {
|
|
445
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m6 9.5 6 6 6-6"/></svg>
|
|
446
|
+
}
|
|
447
|
+
@case ('fullscreen') {
|
|
448
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4.5 9V5.5a1 1 0 0 1 1-1H9m6 0h3.5a1 1 0 0 1 1 1V9m0 6v3.5a1 1 0 0 1-1 1H15m-6 0H5.5a1 1 0 0 1-1-1V15"/></svg>
|
|
449
|
+
}
|
|
450
|
+
@case ('fullscreen-exit') {
|
|
451
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M9 4.5V8a1 1 0 0 1-1 1H4.5m15 0H16a1 1 0 0 1-1-1V4.5m0 15V16a1 1 0 0 1 1-1h3.5m-15 0H8a1 1 0 0 1 1 1v3.5"/></svg>
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;width:1em;height:1em;line-height:0;flex:0 0 auto}svg{display:block;width:100%;height:100%}\n"] }]
|
|
455
|
+
}], propDecorators: { name: [{
|
|
456
|
+
type: Input,
|
|
457
|
+
args: [{ required: true }]
|
|
458
|
+
}] } });
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* The settings of a player, grouped in sections. Colours come from the host through CSS variables
|
|
462
|
+
* (`--ngxsp-settings-surface`, `-text`, `-muted`, `-line`, `-control`, `-accent`, `-accent-text`, `-radius`).
|
|
463
|
+
*/
|
|
464
|
+
class SmartSettingsComponent {
|
|
465
|
+
sections = [];
|
|
466
|
+
settingChange = new EventEmitter();
|
|
467
|
+
settingAction = new EventEmitter();
|
|
468
|
+
/**
|
|
469
|
+
* Sends what was typed and empties the fields at once. Nothing secret stays in the component:
|
|
470
|
+
* the host is the only place it goes, and it does not come back through an input.
|
|
471
|
+
*/
|
|
472
|
+
submitSecret(event, id) {
|
|
473
|
+
event.preventDefault();
|
|
474
|
+
const form = event.target;
|
|
475
|
+
const inputs = Array.from(form.querySelectorAll('input'));
|
|
476
|
+
const value = inputs.at(-1)?.value ?? '';
|
|
477
|
+
const previous = inputs.length > 1 ? inputs[0].value : undefined;
|
|
478
|
+
inputs.forEach((input) => { input.value = ''; });
|
|
479
|
+
if (!value)
|
|
480
|
+
return;
|
|
481
|
+
this.settingChange.emit({ id, value, ...(previous === undefined ? {} : { previous }) });
|
|
482
|
+
}
|
|
483
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: SmartSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
484
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: SmartSettingsComponent, isStandalone: true, selector: "ngxsp-settings", inputs: { sections: "sections" }, outputs: { settingChange: "settingChange", settingAction: "settingAction" }, ngImport: i0, template: `
|
|
485
|
+
@for (section of sections; track section.id) {
|
|
486
|
+
<section class="ngxsp-settings-section" [attr.aria-labelledby]="'ngxsp-settings-' + section.id">
|
|
487
|
+
<h2 [id]="'ngxsp-settings-' + section.id">{{ section.title }}</h2>
|
|
488
|
+
<ul class="ngxsp-settings-items">
|
|
489
|
+
@for (item of section.items; track item.id) {
|
|
490
|
+
<li class="ngxsp-settings-item" [class.stacked]="item.kind === 'list'" [class.wraps]="item.kind === 'choice' || item.kind === 'action'">
|
|
491
|
+
<span class="ngxsp-settings-copy">
|
|
492
|
+
<strong [id]="'ngxsp-setting-' + item.id">{{ item.label }}</strong>
|
|
493
|
+
@if (item.description) { <small [id]="'ngxsp-setting-help-' + item.id">{{ item.description }}</small> }
|
|
494
|
+
</span>
|
|
495
|
+
@switch (item.kind) {
|
|
496
|
+
@case ('switch') {
|
|
497
|
+
<button class="ngxsp-settings-switch" type="button" role="switch" [attr.aria-checked]="item.value" [disabled]="item.disabled" [attr.aria-labelledby]="'ngxsp-setting-' + item.id" [attr.aria-describedby]="item.description ? 'ngxsp-setting-help-' + item.id : null" (click)="settingChange.emit({ id: item.id, value: !item.value })">
|
|
498
|
+
<span class="ngxsp-settings-knob" aria-hidden="true"></span>
|
|
499
|
+
</button>
|
|
500
|
+
}
|
|
501
|
+
@case ('choice') {
|
|
502
|
+
<span class="ngxsp-settings-choice" role="group" [attr.aria-labelledby]="'ngxsp-setting-' + item.id">
|
|
503
|
+
@for (option of item.options; track option.id) {
|
|
504
|
+
<button type="button" [class.active]="option.id === item.value" [attr.aria-pressed]="option.id === item.value" (click)="option.id !== item.value && settingChange.emit({ id: item.id, value: option.id })">{{ option.label }}</button>
|
|
505
|
+
}
|
|
506
|
+
</span>
|
|
507
|
+
}
|
|
508
|
+
@case ('secret') {
|
|
509
|
+
<form class="ngxsp-settings-secret" (submit)="submitSecret($event, item.id)">
|
|
510
|
+
@if (item.secondPlaceholder) {
|
|
511
|
+
<input
|
|
512
|
+
#firstSecret
|
|
513
|
+
type="password"
|
|
514
|
+
autocomplete="off"
|
|
515
|
+
[attr.inputmode]="item.digits ? 'numeric' : null"
|
|
516
|
+
[attr.placeholder]="item.secondPlaceholder"
|
|
517
|
+
[attr.aria-label]="item.secondPlaceholder"
|
|
518
|
+
[disabled]="item.disabled || item.busy"
|
|
519
|
+
/>
|
|
520
|
+
}
|
|
521
|
+
<input
|
|
522
|
+
#secondSecret
|
|
523
|
+
type="password"
|
|
524
|
+
autocomplete="off"
|
|
525
|
+
[attr.inputmode]="item.digits ? 'numeric' : null"
|
|
526
|
+
[attr.placeholder]="item.placeholder ?? null"
|
|
527
|
+
[attr.aria-label]="item.placeholder ?? item.label"
|
|
528
|
+
[attr.aria-describedby]="item.description ? 'ngxsp-setting-help-' + item.id : null"
|
|
529
|
+
[disabled]="item.disabled || item.busy"
|
|
530
|
+
/>
|
|
531
|
+
<button class="ngxsp-settings-button" type="submit" [disabled]="item.disabled || item.busy">{{ item.actionLabel }}</button>
|
|
532
|
+
</form>
|
|
533
|
+
}
|
|
534
|
+
@case ('action') {
|
|
535
|
+
<button class="ngxsp-settings-button" type="button" [disabled]="item.disabled || item.busy" [attr.aria-describedby]="item.description ? 'ngxsp-setting-help-' + item.id : null" (click)="settingAction.emit({ id: item.id })">{{ item.actionLabel }}</button>
|
|
536
|
+
}
|
|
537
|
+
@case ('list') {
|
|
538
|
+
<ul class="ngxsp-settings-entries" [attr.aria-labelledby]="'ngxsp-setting-' + item.id">
|
|
539
|
+
@for (entry of item.entries; track entry.id) {
|
|
540
|
+
<li>
|
|
541
|
+
<span class="ngxsp-settings-entry"><span>{{ entry.label }}</span>@if (entry.detail) { <small>{{ entry.detail }}</small> }</span>
|
|
542
|
+
@if (!entry.fixed) {
|
|
543
|
+
<button class="ngxsp-settings-remove" type="button" [title]="item.removeLabel" [attr.aria-label]="item.removeLabel + ' - ' + entry.label" [disabled]="item.busy" (click)="settingAction.emit({ id: item.id, entryId: entry.id })">
|
|
544
|
+
<ngxsp-smart-icon name="close" />
|
|
545
|
+
</button>
|
|
546
|
+
}
|
|
547
|
+
</li>
|
|
548
|
+
} @empty {
|
|
549
|
+
@if (item.emptyLabel) { <li class="empty">{{ item.emptyLabel }}</li> }
|
|
550
|
+
}
|
|
551
|
+
</ul>
|
|
552
|
+
@if (item.actionLabel) {
|
|
553
|
+
<button class="ngxsp-settings-button" type="button" [disabled]="item.busy" (click)="settingAction.emit({ id: item.id })">{{ item.actionLabel }}</button>
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
</li>
|
|
558
|
+
}
|
|
559
|
+
</ul>
|
|
560
|
+
</section>
|
|
561
|
+
}
|
|
562
|
+
`, isInline: true, styles: [":host{--_surface:var(--ngxsp-settings-surface, rgb(255 255 255 / .04));--_text:var(--ngxsp-settings-text, currentColor);--_muted:var(--ngxsp-settings-muted, rgb(127 127 127));--_line:var(--ngxsp-settings-line, rgb(127 127 127 / .3));--_control:var(--ngxsp-settings-control, rgb(127 127 127 / .2));--_accent:var(--ngxsp-settings-accent, #5b5bd6);--_accent-text:var(--ngxsp-settings-accent-text, #fff);--_radius:var(--ngxsp-settings-radius, 10px);display:grid;gap:28px;max-width:760px;color:var(--_text)}.ngxsp-settings-section{display:grid;gap:10px}h2{margin:0;font-size:13px;font-weight:600;letter-spacing:.02em;color:var(--_muted);text-transform:uppercase}.ngxsp-settings-items{margin:0;padding:0;list-style:none;display:grid;gap:2px;border-radius:var(--_radius);overflow:hidden}.ngxsp-settings-item{display:flex;align-items:center;justify-content:space-between;gap:12px 24px;padding:14px 16px;background:var(--_surface)}.ngxsp-settings-item.stacked{display:grid;justify-content:stretch}.ngxsp-settings-item.wraps{flex-wrap:wrap}.ngxsp-settings-item.wraps .ngxsp-settings-copy{flex:1 1 220px}.ngxsp-settings-copy{min-width:0;display:grid;gap:4px}.ngxsp-settings-copy strong{font-size:14px;font-weight:600}.ngxsp-settings-copy small{font-size:12px;line-height:1.45;color:var(--_muted)}.ngxsp-settings-switch{position:relative;flex:0 0 auto;width:42px;height:24px;padding:0;border:1px solid var(--_line);border-radius:999px;background:var(--_control);cursor:pointer;transition:background-color .16s ease,border-color .16s ease}.ngxsp-settings-knob{position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #0000004d;transition:transform .16s ease}.ngxsp-settings-switch[aria-checked=true]{border-color:var(--_accent);background:var(--_accent)}.ngxsp-settings-switch[aria-checked=true] .ngxsp-settings-knob{transform:translate(18px)}.ngxsp-settings-choice{flex:0 0 auto;display:inline-flex;padding:2px;gap:2px;border:1px solid var(--_line);border-radius:999px;background:var(--_control)}.ngxsp-settings-choice button{height:28px;padding:0 12px;border:0;border-radius:999px;background:transparent;color:var(--_text);font:inherit;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}.ngxsp-settings-choice button.active{background:var(--_accent);color:var(--_accent-text)}.ngxsp-settings-button{justify-self:start;flex:0 0 auto;height:32px;padding:0 14px;border:1px solid var(--_line);border-radius:999px;background:var(--_control);color:var(--_text);font:inherit;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}.ngxsp-settings-button:disabled,.ngxsp-settings-switch:disabled,.ngxsp-settings-remove:disabled{opacity:.5;cursor:default}.ngxsp-settings-secret{display:flex;align-items:center;gap:8px;min-width:0}.ngxsp-settings-secret input{flex:1 1 96px;min-width:0;height:32px;padding:0 10px;border:1px solid var(--_line);border-radius:8px;background:var(--_control);color:var(--_text);font:inherit;font-size:12px;letter-spacing:.14em}.ngxsp-settings-secret input::placeholder{color:var(--_muted);letter-spacing:0}.ngxsp-settings-entries{margin:0;padding:0;list-style:none;display:grid;gap:4px}.ngxsp-settings-entries li{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 8px 8px 12px;border:1px solid var(--_line);border-radius:8px}.ngxsp-settings-entries li.empty{display:block;color:var(--_muted);font-size:12px}.ngxsp-settings-entry{min-width:0;display:grid;gap:2px;font-size:13px}.ngxsp-settings-entry small{overflow:hidden;color:var(--_muted);font-size:11px;text-overflow:ellipsis;white-space:nowrap}.ngxsp-settings-remove{flex:0 0 auto;width:30px;height:30px;display:grid;place-items:center;padding:0;border:0;border-radius:8px;background:transparent;color:var(--_muted);cursor:pointer}.ngxsp-settings-remove:hover{background:var(--_control);color:var(--_text)}.ngxsp-settings-remove ngxsp-smart-icon{width:16px;height:16px}button:focus-visible{outline:2px solid var(--_accent);outline-offset:2px}@media(max-width:560px){.ngxsp-settings-secret,.ngxsp-settings-choice{flex-wrap:wrap}}\n"], dependencies: [{ kind: "component", type: SmartPlayerIconComponent, selector: "ngxsp-smart-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
563
|
+
}
|
|
564
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: SmartSettingsComponent, decorators: [{
|
|
565
|
+
type: Component,
|
|
566
|
+
args: [{ selector: 'ngxsp-settings', standalone: true, imports: [SmartPlayerIconComponent], template: `
|
|
567
|
+
@for (section of sections; track section.id) {
|
|
568
|
+
<section class="ngxsp-settings-section" [attr.aria-labelledby]="'ngxsp-settings-' + section.id">
|
|
569
|
+
<h2 [id]="'ngxsp-settings-' + section.id">{{ section.title }}</h2>
|
|
570
|
+
<ul class="ngxsp-settings-items">
|
|
571
|
+
@for (item of section.items; track item.id) {
|
|
572
|
+
<li class="ngxsp-settings-item" [class.stacked]="item.kind === 'list'" [class.wraps]="item.kind === 'choice' || item.kind === 'action'">
|
|
573
|
+
<span class="ngxsp-settings-copy">
|
|
574
|
+
<strong [id]="'ngxsp-setting-' + item.id">{{ item.label }}</strong>
|
|
575
|
+
@if (item.description) { <small [id]="'ngxsp-setting-help-' + item.id">{{ item.description }}</small> }
|
|
576
|
+
</span>
|
|
577
|
+
@switch (item.kind) {
|
|
578
|
+
@case ('switch') {
|
|
579
|
+
<button class="ngxsp-settings-switch" type="button" role="switch" [attr.aria-checked]="item.value" [disabled]="item.disabled" [attr.aria-labelledby]="'ngxsp-setting-' + item.id" [attr.aria-describedby]="item.description ? 'ngxsp-setting-help-' + item.id : null" (click)="settingChange.emit({ id: item.id, value: !item.value })">
|
|
580
|
+
<span class="ngxsp-settings-knob" aria-hidden="true"></span>
|
|
581
|
+
</button>
|
|
582
|
+
}
|
|
583
|
+
@case ('choice') {
|
|
584
|
+
<span class="ngxsp-settings-choice" role="group" [attr.aria-labelledby]="'ngxsp-setting-' + item.id">
|
|
585
|
+
@for (option of item.options; track option.id) {
|
|
586
|
+
<button type="button" [class.active]="option.id === item.value" [attr.aria-pressed]="option.id === item.value" (click)="option.id !== item.value && settingChange.emit({ id: item.id, value: option.id })">{{ option.label }}</button>
|
|
587
|
+
}
|
|
588
|
+
</span>
|
|
589
|
+
}
|
|
590
|
+
@case ('secret') {
|
|
591
|
+
<form class="ngxsp-settings-secret" (submit)="submitSecret($event, item.id)">
|
|
592
|
+
@if (item.secondPlaceholder) {
|
|
593
|
+
<input
|
|
594
|
+
#firstSecret
|
|
595
|
+
type="password"
|
|
596
|
+
autocomplete="off"
|
|
597
|
+
[attr.inputmode]="item.digits ? 'numeric' : null"
|
|
598
|
+
[attr.placeholder]="item.secondPlaceholder"
|
|
599
|
+
[attr.aria-label]="item.secondPlaceholder"
|
|
600
|
+
[disabled]="item.disabled || item.busy"
|
|
601
|
+
/>
|
|
602
|
+
}
|
|
603
|
+
<input
|
|
604
|
+
#secondSecret
|
|
605
|
+
type="password"
|
|
606
|
+
autocomplete="off"
|
|
607
|
+
[attr.inputmode]="item.digits ? 'numeric' : null"
|
|
608
|
+
[attr.placeholder]="item.placeholder ?? null"
|
|
609
|
+
[attr.aria-label]="item.placeholder ?? item.label"
|
|
610
|
+
[attr.aria-describedby]="item.description ? 'ngxsp-setting-help-' + item.id : null"
|
|
611
|
+
[disabled]="item.disabled || item.busy"
|
|
612
|
+
/>
|
|
613
|
+
<button class="ngxsp-settings-button" type="submit" [disabled]="item.disabled || item.busy">{{ item.actionLabel }}</button>
|
|
614
|
+
</form>
|
|
615
|
+
}
|
|
616
|
+
@case ('action') {
|
|
617
|
+
<button class="ngxsp-settings-button" type="button" [disabled]="item.disabled || item.busy" [attr.aria-describedby]="item.description ? 'ngxsp-setting-help-' + item.id : null" (click)="settingAction.emit({ id: item.id })">{{ item.actionLabel }}</button>
|
|
618
|
+
}
|
|
619
|
+
@case ('list') {
|
|
620
|
+
<ul class="ngxsp-settings-entries" [attr.aria-labelledby]="'ngxsp-setting-' + item.id">
|
|
621
|
+
@for (entry of item.entries; track entry.id) {
|
|
622
|
+
<li>
|
|
623
|
+
<span class="ngxsp-settings-entry"><span>{{ entry.label }}</span>@if (entry.detail) { <small>{{ entry.detail }}</small> }</span>
|
|
624
|
+
@if (!entry.fixed) {
|
|
625
|
+
<button class="ngxsp-settings-remove" type="button" [title]="item.removeLabel" [attr.aria-label]="item.removeLabel + ' - ' + entry.label" [disabled]="item.busy" (click)="settingAction.emit({ id: item.id, entryId: entry.id })">
|
|
626
|
+
<ngxsp-smart-icon name="close" />
|
|
627
|
+
</button>
|
|
628
|
+
}
|
|
629
|
+
</li>
|
|
630
|
+
} @empty {
|
|
631
|
+
@if (item.emptyLabel) { <li class="empty">{{ item.emptyLabel }}</li> }
|
|
632
|
+
}
|
|
633
|
+
</ul>
|
|
634
|
+
@if (item.actionLabel) {
|
|
635
|
+
<button class="ngxsp-settings-button" type="button" [disabled]="item.busy" (click)="settingAction.emit({ id: item.id })">{{ item.actionLabel }}</button>
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
</li>
|
|
640
|
+
}
|
|
641
|
+
</ul>
|
|
642
|
+
</section>
|
|
643
|
+
}
|
|
644
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--_surface:var(--ngxsp-settings-surface, rgb(255 255 255 / .04));--_text:var(--ngxsp-settings-text, currentColor);--_muted:var(--ngxsp-settings-muted, rgb(127 127 127));--_line:var(--ngxsp-settings-line, rgb(127 127 127 / .3));--_control:var(--ngxsp-settings-control, rgb(127 127 127 / .2));--_accent:var(--ngxsp-settings-accent, #5b5bd6);--_accent-text:var(--ngxsp-settings-accent-text, #fff);--_radius:var(--ngxsp-settings-radius, 10px);display:grid;gap:28px;max-width:760px;color:var(--_text)}.ngxsp-settings-section{display:grid;gap:10px}h2{margin:0;font-size:13px;font-weight:600;letter-spacing:.02em;color:var(--_muted);text-transform:uppercase}.ngxsp-settings-items{margin:0;padding:0;list-style:none;display:grid;gap:2px;border-radius:var(--_radius);overflow:hidden}.ngxsp-settings-item{display:flex;align-items:center;justify-content:space-between;gap:12px 24px;padding:14px 16px;background:var(--_surface)}.ngxsp-settings-item.stacked{display:grid;justify-content:stretch}.ngxsp-settings-item.wraps{flex-wrap:wrap}.ngxsp-settings-item.wraps .ngxsp-settings-copy{flex:1 1 220px}.ngxsp-settings-copy{min-width:0;display:grid;gap:4px}.ngxsp-settings-copy strong{font-size:14px;font-weight:600}.ngxsp-settings-copy small{font-size:12px;line-height:1.45;color:var(--_muted)}.ngxsp-settings-switch{position:relative;flex:0 0 auto;width:42px;height:24px;padding:0;border:1px solid var(--_line);border-radius:999px;background:var(--_control);cursor:pointer;transition:background-color .16s ease,border-color .16s ease}.ngxsp-settings-knob{position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #0000004d;transition:transform .16s ease}.ngxsp-settings-switch[aria-checked=true]{border-color:var(--_accent);background:var(--_accent)}.ngxsp-settings-switch[aria-checked=true] .ngxsp-settings-knob{transform:translate(18px)}.ngxsp-settings-choice{flex:0 0 auto;display:inline-flex;padding:2px;gap:2px;border:1px solid var(--_line);border-radius:999px;background:var(--_control)}.ngxsp-settings-choice button{height:28px;padding:0 12px;border:0;border-radius:999px;background:transparent;color:var(--_text);font:inherit;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}.ngxsp-settings-choice button.active{background:var(--_accent);color:var(--_accent-text)}.ngxsp-settings-button{justify-self:start;flex:0 0 auto;height:32px;padding:0 14px;border:1px solid var(--_line);border-radius:999px;background:var(--_control);color:var(--_text);font:inherit;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}.ngxsp-settings-button:disabled,.ngxsp-settings-switch:disabled,.ngxsp-settings-remove:disabled{opacity:.5;cursor:default}.ngxsp-settings-secret{display:flex;align-items:center;gap:8px;min-width:0}.ngxsp-settings-secret input{flex:1 1 96px;min-width:0;height:32px;padding:0 10px;border:1px solid var(--_line);border-radius:8px;background:var(--_control);color:var(--_text);font:inherit;font-size:12px;letter-spacing:.14em}.ngxsp-settings-secret input::placeholder{color:var(--_muted);letter-spacing:0}.ngxsp-settings-entries{margin:0;padding:0;list-style:none;display:grid;gap:4px}.ngxsp-settings-entries li{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 8px 8px 12px;border:1px solid var(--_line);border-radius:8px}.ngxsp-settings-entries li.empty{display:block;color:var(--_muted);font-size:12px}.ngxsp-settings-entry{min-width:0;display:grid;gap:2px;font-size:13px}.ngxsp-settings-entry small{overflow:hidden;color:var(--_muted);font-size:11px;text-overflow:ellipsis;white-space:nowrap}.ngxsp-settings-remove{flex:0 0 auto;width:30px;height:30px;display:grid;place-items:center;padding:0;border:0;border-radius:8px;background:transparent;color:var(--_muted);cursor:pointer}.ngxsp-settings-remove:hover{background:var(--_control);color:var(--_text)}.ngxsp-settings-remove ngxsp-smart-icon{width:16px;height:16px}button:focus-visible{outline:2px solid var(--_accent);outline-offset:2px}@media(max-width:560px){.ngxsp-settings-secret,.ngxsp-settings-choice{flex-wrap:wrap}}\n"] }]
|
|
645
|
+
}], propDecorators: { sections: [{
|
|
646
|
+
type: Input
|
|
647
|
+
}], settingChange: [{
|
|
648
|
+
type: Output
|
|
649
|
+
}], settingAction: [{
|
|
650
|
+
type: Output
|
|
651
|
+
}] } });
|
|
652
|
+
|
|
653
|
+
let nextDialogId = 0;
|
|
654
|
+
/**
|
|
655
|
+
* A question the user answers before anything happens (another device asking to send its playback here). The
|
|
656
|
+
* cautious answer has the focus and Escape; the dialog stays until one of the two buttons is pressed. Colours come
|
|
657
|
+
* from the host through CSS variables (`--ngxsp-dialog-surface`, `-text`, `-muted`, `-line`, `-accent`,
|
|
658
|
+
* `-accent-text`, `-radius`).
|
|
659
|
+
*/
|
|
660
|
+
class SmartConfirmDialogComponent {
|
|
661
|
+
title = '';
|
|
662
|
+
message = '';
|
|
663
|
+
confirmLabel = 'OK';
|
|
664
|
+
cancelLabel = 'Cancel';
|
|
665
|
+
confirmed = new EventEmitter();
|
|
666
|
+
cancelled = new EventEmitter();
|
|
667
|
+
cancelButton;
|
|
668
|
+
id = `ngxsp-dialog-${++nextDialogId}`;
|
|
669
|
+
ngAfterViewInit() {
|
|
670
|
+
this.cancelButton?.nativeElement.focus({ preventScroll: true });
|
|
671
|
+
}
|
|
672
|
+
onEscape(event) {
|
|
673
|
+
event.preventDefault();
|
|
674
|
+
this.cancelled.emit();
|
|
675
|
+
}
|
|
676
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: SmartConfirmDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
677
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.33", type: SmartConfirmDialogComponent, isStandalone: true, selector: "ngxsp-confirm-dialog", inputs: { title: "title", message: "message", confirmLabel: "confirmLabel", cancelLabel: "cancelLabel" }, outputs: { confirmed: "confirmed", cancelled: "cancelled" }, host: { listeners: { "document:keydown.escape": "onEscape($event)" } }, viewQueries: [{ propertyName: "cancelButton", first: true, predicate: ["cancelButton"], descendants: true }], ngImport: i0, template: `
|
|
678
|
+
<div class="ngxsp-dialog-backdrop" role="presentation">
|
|
679
|
+
<section class="ngxsp-dialog" role="alertdialog" aria-modal="true" [attr.aria-labelledby]="id + '-title'" [attr.aria-describedby]="id + '-message'">
|
|
680
|
+
<h2 [id]="id + '-title'">{{ title }}</h2>
|
|
681
|
+
<p [id]="id + '-message'">{{ message }}</p>
|
|
682
|
+
<div class="ngxsp-dialog-actions">
|
|
683
|
+
<button #cancelButton class="ngxsp-dialog-button" type="button" (click)="cancelled.emit()">{{ cancelLabel }}</button>
|
|
684
|
+
<button class="ngxsp-dialog-button primary" type="button" (click)="confirmed.emit()">{{ confirmLabel }}</button>
|
|
685
|
+
</div>
|
|
686
|
+
</section>
|
|
687
|
+
</div>
|
|
688
|
+
`, isInline: true, styles: [":host{--_surface:var(--ngxsp-dialog-surface, #17181d);--_text:var(--ngxsp-dialog-text, #ededf0);--_muted:var(--ngxsp-dialog-muted, #a3a5ae);--_line:var(--ngxsp-dialog-line, rgb(255 255 255 / .13));--_accent:var(--ngxsp-dialog-accent, #5b5bd6);--_accent-text:var(--ngxsp-dialog-accent-text, #fff);--_radius:var(--ngxsp-dialog-radius, 14px)}.ngxsp-dialog-backdrop{position:fixed;inset:0;z-index:100;display:grid;place-items:center;padding:16px;background:#05060899;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.ngxsp-dialog{width:min(420px,100%);display:grid;gap:12px;padding:20px;border:1px solid var(--_line);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);box-shadow:0 24px 64px #00000073}h2{margin:0;font-size:17px;font-weight:650;line-height:1.3}p{margin:0;color:var(--_muted);font-size:14px;line-height:1.5}.ngxsp-dialog-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px;margin-top:4px}.ngxsp-dialog-button{min-height:36px;padding:0 16px;border:1px solid var(--_line);border-radius:999px;background:transparent;color:var(--_text);font:inherit;font-size:13px;font-weight:600;cursor:pointer}.ngxsp-dialog-button.primary{border-color:var(--_accent);background:var(--_accent);color:var(--_accent-text)}.ngxsp-dialog-button:focus-visible{outline:2px solid var(--_accent);outline-offset:2px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
689
|
+
}
|
|
690
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: SmartConfirmDialogComponent, decorators: [{
|
|
691
|
+
type: Component,
|
|
692
|
+
args: [{ selector: 'ngxsp-confirm-dialog', standalone: true, template: `
|
|
693
|
+
<div class="ngxsp-dialog-backdrop" role="presentation">
|
|
694
|
+
<section class="ngxsp-dialog" role="alertdialog" aria-modal="true" [attr.aria-labelledby]="id + '-title'" [attr.aria-describedby]="id + '-message'">
|
|
695
|
+
<h2 [id]="id + '-title'">{{ title }}</h2>
|
|
696
|
+
<p [id]="id + '-message'">{{ message }}</p>
|
|
697
|
+
<div class="ngxsp-dialog-actions">
|
|
698
|
+
<button #cancelButton class="ngxsp-dialog-button" type="button" (click)="cancelled.emit()">{{ cancelLabel }}</button>
|
|
699
|
+
<button class="ngxsp-dialog-button primary" type="button" (click)="confirmed.emit()">{{ confirmLabel }}</button>
|
|
700
|
+
</div>
|
|
701
|
+
</section>
|
|
702
|
+
</div>
|
|
703
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--_surface:var(--ngxsp-dialog-surface, #17181d);--_text:var(--ngxsp-dialog-text, #ededf0);--_muted:var(--ngxsp-dialog-muted, #a3a5ae);--_line:var(--ngxsp-dialog-line, rgb(255 255 255 / .13));--_accent:var(--ngxsp-dialog-accent, #5b5bd6);--_accent-text:var(--ngxsp-dialog-accent-text, #fff);--_radius:var(--ngxsp-dialog-radius, 14px)}.ngxsp-dialog-backdrop{position:fixed;inset:0;z-index:100;display:grid;place-items:center;padding:16px;background:#05060899;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.ngxsp-dialog{width:min(420px,100%);display:grid;gap:12px;padding:20px;border:1px solid var(--_line);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);box-shadow:0 24px 64px #00000073}h2{margin:0;font-size:17px;font-weight:650;line-height:1.3}p{margin:0;color:var(--_muted);font-size:14px;line-height:1.5}.ngxsp-dialog-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px;margin-top:4px}.ngxsp-dialog-button{min-height:36px;padding:0 16px;border:1px solid var(--_line);border-radius:999px;background:transparent;color:var(--_text);font:inherit;font-size:13px;font-weight:600;cursor:pointer}.ngxsp-dialog-button.primary{border-color:var(--_accent);background:var(--_accent);color:var(--_accent-text)}.ngxsp-dialog-button:focus-visible{outline:2px solid var(--_accent);outline-offset:2px}\n"] }]
|
|
704
|
+
}], propDecorators: { title: [{
|
|
705
|
+
type: Input
|
|
706
|
+
}], message: [{
|
|
707
|
+
type: Input
|
|
708
|
+
}], confirmLabel: [{
|
|
709
|
+
type: Input
|
|
710
|
+
}], cancelLabel: [{
|
|
711
|
+
type: Input
|
|
712
|
+
}], confirmed: [{
|
|
713
|
+
type: Output
|
|
714
|
+
}], cancelled: [{
|
|
715
|
+
type: Output
|
|
716
|
+
}], cancelButton: [{
|
|
717
|
+
type: ViewChild,
|
|
718
|
+
args: ['cancelButton']
|
|
719
|
+
}], onEscape: [{
|
|
720
|
+
type: HostListener,
|
|
721
|
+
args: ['document:keydown.escape', ['$event']]
|
|
722
|
+
}] } });
|
|
723
|
+
|
|
724
|
+
const DEFAULT_PLAYER_MENU_LABELS = {
|
|
725
|
+
captions: 'Captions',
|
|
726
|
+
off: 'Off',
|
|
727
|
+
audio: 'Audio',
|
|
728
|
+
singleAudio: 'Single audio track',
|
|
729
|
+
quality: 'Quality',
|
|
730
|
+
auto: 'Auto',
|
|
731
|
+
singleQuality: 'Single quality',
|
|
732
|
+
speed: 'Playback speed',
|
|
733
|
+
subtitles: 'Subtitles',
|
|
734
|
+
current: 'Showing',
|
|
735
|
+
noSubtitles: 'No subtitles in this video yet.',
|
|
736
|
+
loadSubtitles: 'Load a file (SRT, VTT)…',
|
|
737
|
+
tracks: 'Tracks',
|
|
738
|
+
media: 'Media',
|
|
739
|
+
};
|
|
740
|
+
/**
|
|
741
|
+
* Menu de pistes (sous-titres / audio / qualité) et de vitesse de lecture.
|
|
742
|
+
* Extrait du SmartPlayerComponent (décomposition, cf. docs/AUDIT.md).
|
|
743
|
+
* Sans logique de lecture : émet des événements, l'hôte applique l'action.
|
|
744
|
+
*/
|
|
745
|
+
class PlayerMenuComponent {
|
|
746
|
+
openMenu = input(null, ...(ngDevMode ? [{ debugName: "openMenu" }] : []));
|
|
747
|
+
isAudioMode = input(false, ...(ngDevMode ? [{ debugName: "isAudioMode" }] : []));
|
|
748
|
+
textTracks = input([], ...(ngDevMode ? [{ debugName: "textTracks" }] : []));
|
|
749
|
+
audioTracks = input([], ...(ngDevMode ? [{ debugName: "audioTracks" }] : []));
|
|
750
|
+
videoTracks = input([], ...(ngDevMode ? [{ debugName: "videoTracks" }] : []));
|
|
751
|
+
selectedTextTrackId = input(null, ...(ngDevMode ? [{ debugName: "selectedTextTrackId" }] : []));
|
|
752
|
+
selectedAudioTrackId = input(null, ...(ngDevMode ? [{ debugName: "selectedAudioTrackId" }] : []));
|
|
753
|
+
selectedVideoTrackId = input(null, ...(ngDevMode ? [{ debugName: "selectedVideoTrackId" }] : []));
|
|
754
|
+
playbackRates = input([], ...(ngDevMode ? [{ debugName: "playbackRates" }] : []));
|
|
755
|
+
playbackRate = input(1, ...(ngDevMode ? [{ debugName: "playbackRate" }] : []));
|
|
756
|
+
labels = input(DEFAULT_PLAYER_MENU_LABELS, ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
757
|
+
selectText = output();
|
|
758
|
+
selectAudio = output();
|
|
759
|
+
selectVideo = output();
|
|
760
|
+
selectRate = output();
|
|
761
|
+
/** "Load a file" in the subtitles menu: the host opens its file picker. */
|
|
762
|
+
loadSubtitles = output();
|
|
763
|
+
currentTextLabel() {
|
|
764
|
+
const track = this.textTracks().find((entry) => entry.id === this.selectedTextTrackId());
|
|
765
|
+
return track ? this.trackLabel(track) : this.labels().off;
|
|
766
|
+
}
|
|
767
|
+
/** "French" for "fr", in the labels' locale. */
|
|
768
|
+
languageName(code) {
|
|
769
|
+
try {
|
|
770
|
+
return new Intl.DisplayNames(this.labels().locale ? [this.labels().locale] : undefined, { type: 'language' }).of(code) ?? code;
|
|
771
|
+
}
|
|
772
|
+
catch {
|
|
773
|
+
return code;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
trackLabel(track) {
|
|
777
|
+
return track.label || (track.language ? this.languageName(track.language) : track.id);
|
|
778
|
+
}
|
|
779
|
+
qualityLabel(track) {
|
|
780
|
+
if (track.label)
|
|
781
|
+
return track.label;
|
|
782
|
+
const parts = [];
|
|
783
|
+
if (track.height)
|
|
784
|
+
parts.push(`${track.height}p`);
|
|
785
|
+
if (track.bandwidth)
|
|
786
|
+
parts.push(`${(track.bandwidth / 1_000_000).toFixed(1)} Mbps`);
|
|
787
|
+
return parts.join(' • ') || track.id;
|
|
788
|
+
}
|
|
789
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: PlayerMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
790
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: PlayerMenuComponent, isStandalone: true, selector: "ngxsp-player-menu", inputs: { openMenu: { classPropertyName: "openMenu", publicName: "openMenu", isSignal: true, isRequired: false, transformFunction: null }, isAudioMode: { classPropertyName: "isAudioMode", publicName: "isAudioMode", isSignal: true, isRequired: false, transformFunction: null }, textTracks: { classPropertyName: "textTracks", publicName: "textTracks", isSignal: true, isRequired: false, transformFunction: null }, audioTracks: { classPropertyName: "audioTracks", publicName: "audioTracks", isSignal: true, isRequired: false, transformFunction: null }, videoTracks: { classPropertyName: "videoTracks", publicName: "videoTracks", isSignal: true, isRequired: false, transformFunction: null }, selectedTextTrackId: { classPropertyName: "selectedTextTrackId", publicName: "selectedTextTrackId", isSignal: true, isRequired: false, transformFunction: null }, selectedAudioTrackId: { classPropertyName: "selectedAudioTrackId", publicName: "selectedAudioTrackId", isSignal: true, isRequired: false, transformFunction: null }, selectedVideoTrackId: { classPropertyName: "selectedVideoTrackId", publicName: "selectedVideoTrackId", isSignal: true, isRequired: false, transformFunction: null }, playbackRates: { classPropertyName: "playbackRates", publicName: "playbackRates", isSignal: true, isRequired: false, transformFunction: null }, playbackRate: { classPropertyName: "playbackRate", publicName: "playbackRate", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectText: "selectText", selectAudio: "selectAudio", selectVideo: "selectVideo", selectRate: "selectRate", loadSubtitles: "loadSubtitles" }, ngImport: i0, template: `
|
|
791
|
+
@if (openMenu() === 'tracks') {
|
|
792
|
+
<div class="ngxsp-menu">
|
|
793
|
+
<div class="ngxsp-menuSection">
|
|
794
|
+
<div class="ngxsp-menuTitle">{{ labels().captions }}</div>
|
|
795
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId() === null" (click)="selectText.emit(null)">{{ labels().off }}</button>
|
|
796
|
+
@for (track of textTracks(); track track.id) {
|
|
797
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId() === track.id" (click)="selectText.emit(track.id)">
|
|
798
|
+
{{ trackLabel(track) }}
|
|
799
|
+
</button>
|
|
800
|
+
}
|
|
801
|
+
</div>
|
|
802
|
+
|
|
803
|
+
<div class="ngxsp-menuSection">
|
|
804
|
+
<div class="ngxsp-menuTitle">{{ labels().audio }}</div>
|
|
805
|
+
@if (audioTracks().length) {
|
|
806
|
+
@for (track of audioTracks(); track track.id) {
|
|
807
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedAudioTrackId() === track.id" (click)="selectAudio.emit(track.id)">
|
|
808
|
+
{{ trackLabel(track) }}
|
|
809
|
+
</button>
|
|
810
|
+
}
|
|
811
|
+
} @else {
|
|
812
|
+
<div class="ngxsp-menuHint">{{ labels().singleAudio }}</div>
|
|
813
|
+
}
|
|
814
|
+
</div>
|
|
815
|
+
|
|
816
|
+
@if (!isAudioMode()) {
|
|
817
|
+
<div class="ngxsp-menuSection">
|
|
818
|
+
<div class="ngxsp-menuTitle">{{ labels().quality }}</div>
|
|
819
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedVideoTrackId() === null" (click)="selectVideo.emit(null)">{{ labels().auto }}</button>
|
|
820
|
+
@if (videoTracks().length) {
|
|
821
|
+
@for (track of videoTracks(); track track.id) {
|
|
822
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedVideoTrackId() === track.id" (click)="selectVideo.emit(track.id)">
|
|
823
|
+
{{ qualityLabel(track) }}
|
|
824
|
+
</button>
|
|
825
|
+
}
|
|
826
|
+
} @else {
|
|
827
|
+
<div class="ngxsp-menuHint">{{ labels().singleQuality }}</div>
|
|
828
|
+
}
|
|
829
|
+
</div>
|
|
830
|
+
}
|
|
831
|
+
</div>
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
@if (openMenu() === 'speed') {
|
|
835
|
+
<div class="ngxsp-menu ngxsp-menuCompact">
|
|
836
|
+
<div class="ngxsp-menuSection">
|
|
837
|
+
<div class="ngxsp-menuTitle">{{ labels().speed }}</div>
|
|
838
|
+
@for (rate of playbackRates(); track rate) {
|
|
839
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="playbackRate() === rate" (click)="selectRate.emit(rate)">
|
|
840
|
+
{{ rate }}x
|
|
841
|
+
</button>
|
|
842
|
+
}
|
|
843
|
+
</div>
|
|
844
|
+
</div>
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
@if (openMenu() === 'captions') {
|
|
848
|
+
<div class="ngxsp-menu ngxsp-menuCompact ngxsp-menuCaptions">
|
|
849
|
+
<div class="ngxsp-menuSection">
|
|
850
|
+
<div class="ngxsp-menuTitle">{{ labels().subtitles }}</div>
|
|
851
|
+
<div class="ngxsp-menuCurrent">{{ labels().current }} <strong>{{ currentTextLabel() }}</strong></div>
|
|
852
|
+
<div class="ngxsp-menuScroll" role="group" [attr.aria-label]="labels().subtitles">
|
|
853
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId() === null" [attr.aria-pressed]="selectedTextTrackId() === null" (click)="selectText.emit(null)">{{ labels().off }}</button>
|
|
854
|
+
@for (track of textTracks(); track track.id) {
|
|
855
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId() === track.id" [attr.aria-pressed]="selectedTextTrackId() === track.id" (click)="selectText.emit(track.id)">
|
|
856
|
+
<span class="ngxsp-menuItemLabel">{{ trackLabel(track) }}</span>
|
|
857
|
+
@if (track.language && track.label) { <small>{{ languageName(track.language) }}</small> }
|
|
858
|
+
</button>
|
|
859
|
+
}
|
|
860
|
+
</div>
|
|
861
|
+
@if (!textTracks().length) { <div class="ngxsp-menuHint">{{ labels().noSubtitles }}</div> }
|
|
862
|
+
<button type="button" class="ngxsp-menuItem ngxsp-menuAction" (click)="loadSubtitles.emit()">{{ labels().loadSubtitles }}</button>
|
|
863
|
+
</div>
|
|
864
|
+
</div>
|
|
865
|
+
}
|
|
866
|
+
`, isInline: true, styles: [":host{display:block}.ngxsp-menu{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin:6px 0 4px;padding:12px;border-radius:10px;background:#121317eb;box-shadow:0 12px 32px #00000073,0 0 0 1px #ffffff14;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px)}.ngxsp-menuCompact{grid-template-columns:minmax(160px,220px);justify-self:end}.ngxsp-menuSection{display:flex;flex-direction:column;gap:2px}.ngxsp-menuTitle{padding:0 8px 6px;font-size:12px;font-weight:600;color:#fff9}.ngxsp-menuItem{height:32px;padding:0 10px;border:0;border-radius:6px;background:transparent;color:#ffffffe6;text-align:left;cursor:pointer;font:inherit;font-size:13px}.ngxsp-menuItem:hover{background:#ffffff1a}.ngxsp-menuItem.active{background:#8b90ff2e;color:var(--nsp-media-accent, #8b90ff);font-weight:500}.ngxsp-menuItem:focus-visible{outline:2px solid var(--nsp-media-accent, #8b90ff);outline-offset:-2px}.ngxsp-menuHint{font-size:12px;color:#fff9;padding:6px 8px}.ngxsp-menuCaptions{grid-template-columns:minmax(200px,280px)}.ngxsp-menuCurrent{padding:0 8px 8px;font-size:12px;color:#fff9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ngxsp-menuCurrent strong{color:#ffffffeb;font-weight:600}.ngxsp-menuScroll{display:flex;flex-direction:column;gap:2px;max-height:min(36vh,232px);overflow:auto;overscroll-behavior:contain}.ngxsp-menuItem{display:flex;align-items:center;gap:8px;flex:none}.ngxsp-menuItemLabel{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ngxsp-menuItem small{flex:none;font-size:11px;color:#ffffff8c}.ngxsp-menuAction{margin-top:6px;border-top:1px solid rgb(255 255 255 / .1);border-radius:0 0 6px 6px;color:var(--nsp-media-accent, #8b90ff)}@media(max-width:860px){.ngxsp-menu{grid-template-columns:1fr}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
867
|
+
}
|
|
868
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: PlayerMenuComponent, decorators: [{
|
|
869
|
+
type: Component,
|
|
870
|
+
args: [{ selector: 'ngxsp-player-menu', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
871
|
+
@if (openMenu() === 'tracks') {
|
|
872
|
+
<div class="ngxsp-menu">
|
|
873
|
+
<div class="ngxsp-menuSection">
|
|
874
|
+
<div class="ngxsp-menuTitle">{{ labels().captions }}</div>
|
|
875
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId() === null" (click)="selectText.emit(null)">{{ labels().off }}</button>
|
|
876
|
+
@for (track of textTracks(); track track.id) {
|
|
877
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId() === track.id" (click)="selectText.emit(track.id)">
|
|
878
|
+
{{ trackLabel(track) }}
|
|
879
|
+
</button>
|
|
880
|
+
}
|
|
881
|
+
</div>
|
|
882
|
+
|
|
883
|
+
<div class="ngxsp-menuSection">
|
|
884
|
+
<div class="ngxsp-menuTitle">{{ labels().audio }}</div>
|
|
885
|
+
@if (audioTracks().length) {
|
|
886
|
+
@for (track of audioTracks(); track track.id) {
|
|
887
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedAudioTrackId() === track.id" (click)="selectAudio.emit(track.id)">
|
|
888
|
+
{{ trackLabel(track) }}
|
|
889
|
+
</button>
|
|
890
|
+
}
|
|
891
|
+
} @else {
|
|
892
|
+
<div class="ngxsp-menuHint">{{ labels().singleAudio }}</div>
|
|
893
|
+
}
|
|
894
|
+
</div>
|
|
895
|
+
|
|
896
|
+
@if (!isAudioMode()) {
|
|
897
|
+
<div class="ngxsp-menuSection">
|
|
898
|
+
<div class="ngxsp-menuTitle">{{ labels().quality }}</div>
|
|
899
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedVideoTrackId() === null" (click)="selectVideo.emit(null)">{{ labels().auto }}</button>
|
|
900
|
+
@if (videoTracks().length) {
|
|
901
|
+
@for (track of videoTracks(); track track.id) {
|
|
902
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedVideoTrackId() === track.id" (click)="selectVideo.emit(track.id)">
|
|
903
|
+
{{ qualityLabel(track) }}
|
|
904
|
+
</button>
|
|
905
|
+
}
|
|
906
|
+
} @else {
|
|
907
|
+
<div class="ngxsp-menuHint">{{ labels().singleQuality }}</div>
|
|
908
|
+
}
|
|
909
|
+
</div>
|
|
910
|
+
}
|
|
911
|
+
</div>
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
@if (openMenu() === 'speed') {
|
|
915
|
+
<div class="ngxsp-menu ngxsp-menuCompact">
|
|
916
|
+
<div class="ngxsp-menuSection">
|
|
917
|
+
<div class="ngxsp-menuTitle">{{ labels().speed }}</div>
|
|
918
|
+
@for (rate of playbackRates(); track rate) {
|
|
919
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="playbackRate() === rate" (click)="selectRate.emit(rate)">
|
|
920
|
+
{{ rate }}x
|
|
921
|
+
</button>
|
|
922
|
+
}
|
|
923
|
+
</div>
|
|
924
|
+
</div>
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
@if (openMenu() === 'captions') {
|
|
928
|
+
<div class="ngxsp-menu ngxsp-menuCompact ngxsp-menuCaptions">
|
|
929
|
+
<div class="ngxsp-menuSection">
|
|
930
|
+
<div class="ngxsp-menuTitle">{{ labels().subtitles }}</div>
|
|
931
|
+
<div class="ngxsp-menuCurrent">{{ labels().current }} <strong>{{ currentTextLabel() }}</strong></div>
|
|
932
|
+
<div class="ngxsp-menuScroll" role="group" [attr.aria-label]="labels().subtitles">
|
|
933
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId() === null" [attr.aria-pressed]="selectedTextTrackId() === null" (click)="selectText.emit(null)">{{ labels().off }}</button>
|
|
934
|
+
@for (track of textTracks(); track track.id) {
|
|
935
|
+
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId() === track.id" [attr.aria-pressed]="selectedTextTrackId() === track.id" (click)="selectText.emit(track.id)">
|
|
936
|
+
<span class="ngxsp-menuItemLabel">{{ trackLabel(track) }}</span>
|
|
937
|
+
@if (track.language && track.label) { <small>{{ languageName(track.language) }}</small> }
|
|
938
|
+
</button>
|
|
939
|
+
}
|
|
940
|
+
</div>
|
|
941
|
+
@if (!textTracks().length) { <div class="ngxsp-menuHint">{{ labels().noSubtitles }}</div> }
|
|
942
|
+
<button type="button" class="ngxsp-menuItem ngxsp-menuAction" (click)="loadSubtitles.emit()">{{ labels().loadSubtitles }}</button>
|
|
943
|
+
</div>
|
|
944
|
+
</div>
|
|
945
|
+
}
|
|
946
|
+
`, styles: [":host{display:block}.ngxsp-menu{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin:6px 0 4px;padding:12px;border-radius:10px;background:#121317eb;box-shadow:0 12px 32px #00000073,0 0 0 1px #ffffff14;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px)}.ngxsp-menuCompact{grid-template-columns:minmax(160px,220px);justify-self:end}.ngxsp-menuSection{display:flex;flex-direction:column;gap:2px}.ngxsp-menuTitle{padding:0 8px 6px;font-size:12px;font-weight:600;color:#fff9}.ngxsp-menuItem{height:32px;padding:0 10px;border:0;border-radius:6px;background:transparent;color:#ffffffe6;text-align:left;cursor:pointer;font:inherit;font-size:13px}.ngxsp-menuItem:hover{background:#ffffff1a}.ngxsp-menuItem.active{background:#8b90ff2e;color:var(--nsp-media-accent, #8b90ff);font-weight:500}.ngxsp-menuItem:focus-visible{outline:2px solid var(--nsp-media-accent, #8b90ff);outline-offset:-2px}.ngxsp-menuHint{font-size:12px;color:#fff9;padding:6px 8px}.ngxsp-menuCaptions{grid-template-columns:minmax(200px,280px)}.ngxsp-menuCurrent{padding:0 8px 8px;font-size:12px;color:#fff9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ngxsp-menuCurrent strong{color:#ffffffeb;font-weight:600}.ngxsp-menuScroll{display:flex;flex-direction:column;gap:2px;max-height:min(36vh,232px);overflow:auto;overscroll-behavior:contain}.ngxsp-menuItem{display:flex;align-items:center;gap:8px;flex:none}.ngxsp-menuItemLabel{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ngxsp-menuItem small{flex:none;font-size:11px;color:#ffffff8c}.ngxsp-menuAction{margin-top:6px;border-top:1px solid rgb(255 255 255 / .1);border-radius:0 0 6px 6px;color:var(--nsp-media-accent, #8b90ff)}@media(max-width:860px){.ngxsp-menu{grid-template-columns:1fr}}\n"] }]
|
|
947
|
+
}], propDecorators: { openMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "openMenu", required: false }] }], isAudioMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isAudioMode", required: false }] }], textTracks: [{ type: i0.Input, args: [{ isSignal: true, alias: "textTracks", required: false }] }], audioTracks: [{ type: i0.Input, args: [{ isSignal: true, alias: "audioTracks", required: false }] }], videoTracks: [{ type: i0.Input, args: [{ isSignal: true, alias: "videoTracks", required: false }] }], selectedTextTrackId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedTextTrackId", required: false }] }], selectedAudioTrackId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedAudioTrackId", required: false }] }], selectedVideoTrackId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedVideoTrackId", required: false }] }], playbackRates: [{ type: i0.Input, args: [{ isSignal: true, alias: "playbackRates", required: false }] }], playbackRate: [{ type: i0.Input, args: [{ isSignal: true, alias: "playbackRate", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], selectText: [{ type: i0.Output, args: ["selectText"] }], selectAudio: [{ type: i0.Output, args: ["selectAudio"] }], selectVideo: [{ type: i0.Output, args: ["selectVideo"] }], selectRate: [{ type: i0.Output, args: ["selectRate"] }], loadSubtitles: [{ type: i0.Output, args: ["loadSubtitles"] }] } });
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Surcouches du lecteur : carte d'erreur de lecture et panneau de diagnostic.
|
|
951
|
+
* Extrait du SmartPlayerComponent (décomposition, cf. docs/AUDIT.md).
|
|
952
|
+
* Sans accès au moteur : reçoit l'erreur/diagnostic en entrée et émet les actions.
|
|
953
|
+
*/
|
|
954
|
+
class PlayerOverlaysComponent {
|
|
955
|
+
error = input(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
956
|
+
debug = input(false, ...(ngDevMode ? [{ debugName: "debug" }] : []));
|
|
957
|
+
debugJson = input('{}', ...(ngDevMode ? [{ debugName: "debugJson" }] : []));
|
|
958
|
+
retry = output();
|
|
959
|
+
requestPlay = output();
|
|
960
|
+
copyJson = output();
|
|
961
|
+
downloadJson = output();
|
|
962
|
+
code = computed(() => this.error()?.code ?? 'UNKNOWN', ...(ngDevMode ? [{ debugName: "code" }] : []));
|
|
963
|
+
autoplayBlocked = computed(() => this.code() === 'AUTOPLAY_BLOCKED', ...(ngDevMode ? [{ debugName: "autoplayBlocked" }] : []));
|
|
964
|
+
title = computed(() => {
|
|
965
|
+
switch (this.code()) {
|
|
966
|
+
case 'ENGINE_MISSING':
|
|
967
|
+
return 'Playback engine missing';
|
|
968
|
+
case 'AUTOPLAY_BLOCKED':
|
|
969
|
+
return 'Click to start playback';
|
|
970
|
+
case 'NETWORK_HTTP':
|
|
971
|
+
return 'Network error';
|
|
972
|
+
case 'DECODE_ERROR':
|
|
973
|
+
return 'Decode error';
|
|
974
|
+
case 'SRC_NOT_SUPPORTED':
|
|
975
|
+
return 'Format not supported';
|
|
976
|
+
default:
|
|
977
|
+
return 'Playback error';
|
|
978
|
+
}
|
|
979
|
+
}, ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
980
|
+
hint = computed(() => {
|
|
981
|
+
switch (this.code()) {
|
|
982
|
+
case 'ENGINE_MISSING':
|
|
983
|
+
return 'This source requires an additional engine. Install @ngxsp/engine-shaka and pass it via [engines].';
|
|
984
|
+
case 'AUTOPLAY_BLOCKED':
|
|
985
|
+
return 'Your browser blocked autoplay. Press Play to start.';
|
|
986
|
+
case 'NETWORK_HTTP':
|
|
987
|
+
return 'Check your connection or try again.';
|
|
988
|
+
default:
|
|
989
|
+
return this.error()?.message ? String(this.error().message) : 'Unknown error';
|
|
990
|
+
}
|
|
991
|
+
}, ...(ngDevMode ? [{ debugName: "hint" }] : []));
|
|
992
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: PlayerOverlaysComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
993
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: PlayerOverlaysComponent, isStandalone: true, selector: "ngxsp-player-overlays", inputs: { error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null }, debugJson: { classPropertyName: "debugJson", publicName: "debugJson", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { retry: "retry", requestPlay: "requestPlay", copyJson: "copyJson", downloadJson: "downloadJson" }, ngImport: i0, template: `
|
|
994
|
+
@if (error()) {
|
|
995
|
+
<div class="ngxsp-overlay" role="alert">
|
|
996
|
+
<div class="ngxsp-overlay-card">
|
|
997
|
+
<span class="ngxsp-overlay-icon" aria-hidden="true">!</span>
|
|
998
|
+
<div class="ngxsp-overlay-title">{{ title() }}</div>
|
|
999
|
+
<div class="ngxsp-overlay-msg">{{ hint() }}</div>
|
|
1000
|
+
<div class="ngxsp-overlay-actions">
|
|
1001
|
+
<button type="button" class="ngxsp-btn" (click)="retry.emit()">Retry</button>
|
|
1002
|
+
@if (autoplayBlocked()) {
|
|
1003
|
+
<button type="button" class="ngxsp-btn primary" (click)="requestPlay.emit()">Play</button>
|
|
1004
|
+
}
|
|
1005
|
+
</div>
|
|
1006
|
+
</div>
|
|
1007
|
+
</div>
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
@if (debug()) {
|
|
1011
|
+
<details class="ngxsp-debug">
|
|
1012
|
+
<summary>Debug</summary>
|
|
1013
|
+
<div class="ngxsp-debug-actions">
|
|
1014
|
+
<button type="button" class="ngxsp-btn" (click)="copyJson.emit()">Copy JSON</button>
|
|
1015
|
+
<button type="button" class="ngxsp-btn" (click)="downloadJson.emit()">Download</button>
|
|
1016
|
+
</div>
|
|
1017
|
+
<pre>{{ debugJson() }}</pre>
|
|
1018
|
+
</details>
|
|
1019
|
+
}
|
|
1020
|
+
`, isInline: true, styles: [":host{display:contents}.ngxsp-btn{height:32px;padding:0 14px;border:1px solid rgb(255 255 255 / .16);border-radius:6px;background:#ffffff0f;color:#fff;font:inherit;font-size:13px;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:8px}.ngxsp-btn:hover{background:#ffffff1f}.ngxsp-btn.primary{border-color:transparent;background:var(--nsp-accent-strong, #5d63ee)}.ngxsp-btn:focus-visible{outline:2px solid var(--nsp-media-accent, #8b90ff);outline-offset:2px}.ngxsp-overlay{position:absolute;inset:0;z-index:5;display:grid;place-items:center;padding:24px;background:#000000c7;text-align:center}.ngxsp-overlay-card{display:grid;justify-items:center;gap:8px;max-width:440px}.ngxsp-overlay-icon{width:40px;height:40px;display:grid;place-items:center;margin-bottom:4px;border-radius:999px;background:#f25f5c29;color:var(--nsp-danger, #f25f5c);font-size:18px;font-weight:700}.ngxsp-overlay-title{font-size:16px;font-weight:600;color:#fff}.ngxsp-overlay-msg{color:#ffffffb3;font-size:13px;line-height:1.5}.ngxsp-overlay-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-top:8px}.ngxsp-debug{position:absolute;top:12px;right:12px;z-index:6;margin:0;max-width:min(420px,calc(100% - 24px));opacity:0;transform:translateY(-6px);transition:opacity .18s ease,transform .18s ease;color:#fff}:host-context(.ngxsp-ui-visible) .ngxsp-debug,.ngxsp-debug[open]{opacity:1;transform:translateY(0)}.ngxsp-debug summary{cursor:pointer;list-style:none;width:max-content;margin-left:auto;padding:4px 10px;border-radius:999px;background:#0009;color:#fffc;font-size:12px}.ngxsp-debug[open]{padding:12px;max-height:calc(100% - 24px);overflow:auto;border-radius:10px;background:#121317f5;box-shadow:0 12px 32px #00000073,0 0 0 1px #ffffff14}.ngxsp-debug-actions{display:flex;gap:8px;margin:8px 0;flex-wrap:wrap}.ngxsp-debug pre{margin:0;white-space:pre-wrap;word-break:break-word;font-family:var(--nsp-font-mono, ui-monospace, Menlo, monospace);font-size:11px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1021
|
+
}
|
|
1022
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: PlayerOverlaysComponent, decorators: [{
|
|
1023
|
+
type: Component,
|
|
1024
|
+
args: [{ selector: 'ngxsp-player-overlays', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1025
|
+
@if (error()) {
|
|
1026
|
+
<div class="ngxsp-overlay" role="alert">
|
|
1027
|
+
<div class="ngxsp-overlay-card">
|
|
1028
|
+
<span class="ngxsp-overlay-icon" aria-hidden="true">!</span>
|
|
1029
|
+
<div class="ngxsp-overlay-title">{{ title() }}</div>
|
|
1030
|
+
<div class="ngxsp-overlay-msg">{{ hint() }}</div>
|
|
1031
|
+
<div class="ngxsp-overlay-actions">
|
|
1032
|
+
<button type="button" class="ngxsp-btn" (click)="retry.emit()">Retry</button>
|
|
1033
|
+
@if (autoplayBlocked()) {
|
|
1034
|
+
<button type="button" class="ngxsp-btn primary" (click)="requestPlay.emit()">Play</button>
|
|
1035
|
+
}
|
|
1036
|
+
</div>
|
|
1037
|
+
</div>
|
|
1038
|
+
</div>
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
@if (debug()) {
|
|
1042
|
+
<details class="ngxsp-debug">
|
|
1043
|
+
<summary>Debug</summary>
|
|
1044
|
+
<div class="ngxsp-debug-actions">
|
|
1045
|
+
<button type="button" class="ngxsp-btn" (click)="copyJson.emit()">Copy JSON</button>
|
|
1046
|
+
<button type="button" class="ngxsp-btn" (click)="downloadJson.emit()">Download</button>
|
|
1047
|
+
</div>
|
|
1048
|
+
<pre>{{ debugJson() }}</pre>
|
|
1049
|
+
</details>
|
|
1050
|
+
}
|
|
1051
|
+
`, styles: [":host{display:contents}.ngxsp-btn{height:32px;padding:0 14px;border:1px solid rgb(255 255 255 / .16);border-radius:6px;background:#ffffff0f;color:#fff;font:inherit;font-size:13px;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:8px}.ngxsp-btn:hover{background:#ffffff1f}.ngxsp-btn.primary{border-color:transparent;background:var(--nsp-accent-strong, #5d63ee)}.ngxsp-btn:focus-visible{outline:2px solid var(--nsp-media-accent, #8b90ff);outline-offset:2px}.ngxsp-overlay{position:absolute;inset:0;z-index:5;display:grid;place-items:center;padding:24px;background:#000000c7;text-align:center}.ngxsp-overlay-card{display:grid;justify-items:center;gap:8px;max-width:440px}.ngxsp-overlay-icon{width:40px;height:40px;display:grid;place-items:center;margin-bottom:4px;border-radius:999px;background:#f25f5c29;color:var(--nsp-danger, #f25f5c);font-size:18px;font-weight:700}.ngxsp-overlay-title{font-size:16px;font-weight:600;color:#fff}.ngxsp-overlay-msg{color:#ffffffb3;font-size:13px;line-height:1.5}.ngxsp-overlay-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-top:8px}.ngxsp-debug{position:absolute;top:12px;right:12px;z-index:6;margin:0;max-width:min(420px,calc(100% - 24px));opacity:0;transform:translateY(-6px);transition:opacity .18s ease,transform .18s ease;color:#fff}:host-context(.ngxsp-ui-visible) .ngxsp-debug,.ngxsp-debug[open]{opacity:1;transform:translateY(0)}.ngxsp-debug summary{cursor:pointer;list-style:none;width:max-content;margin-left:auto;padding:4px 10px;border-radius:999px;background:#0009;color:#fffc;font-size:12px}.ngxsp-debug[open]{padding:12px;max-height:calc(100% - 24px);overflow:auto;border-radius:10px;background:#121317f5;box-shadow:0 12px 32px #00000073,0 0 0 1px #ffffff14}.ngxsp-debug-actions{display:flex;gap:8px;margin:8px 0;flex-wrap:wrap}.ngxsp-debug pre{margin:0;white-space:pre-wrap;word-break:break-word;font-family:var(--nsp-font-mono, ui-monospace, Menlo, monospace);font-size:11px}\n"] }]
|
|
1052
|
+
}], propDecorators: { error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }], debugJson: [{ type: i0.Input, args: [{ isSignal: true, alias: "debugJson", required: false }] }], retry: [{ type: i0.Output, args: ["retry"] }], requestPlay: [{ type: i0.Output, args: ["requestPlay"] }], copyJson: [{ type: i0.Output, args: ["copyJson"] }], downloadJson: [{ type: i0.Output, args: ["downloadJson"] }] } });
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Barre de contrôle du skin classique, extraite du SmartPlayerComponent
|
|
1056
|
+
* (décomposition, cf. docs/AUDIT.md). Sans accès au moteur ni au <video> :
|
|
1057
|
+
* reçoit l'état + les capacités (cast/PiP) en entrée et émet les commandes ;
|
|
1058
|
+
* l'hôte applique l'action (et possède l'élément vidéo pour PiP/cast).
|
|
1059
|
+
*/
|
|
1060
|
+
class PlayerControlsComponent {
|
|
1061
|
+
playing = input(false, ...(ngDevMode ? [{ debugName: "playing" }] : []));
|
|
1062
|
+
isAudioMode = input(false, ...(ngDevMode ? [{ debugName: "isAudioMode" }] : []));
|
|
1063
|
+
hasPlaylistControls = input(false, ...(ngDevMode ? [{ debugName: "hasPlaylistControls" }] : []));
|
|
1064
|
+
canPrev = input(false, ...(ngDevMode ? [{ debugName: "canPrev" }] : []));
|
|
1065
|
+
canNext = input(false, ...(ngDevMode ? [{ debugName: "canNext" }] : []));
|
|
1066
|
+
shuffleActive = input(false, ...(ngDevMode ? [{ debugName: "shuffleActive" }] : []));
|
|
1067
|
+
shuffleLabel = input('Shuffle: off', ...(ngDevMode ? [{ debugName: "shuffleLabel" }] : []));
|
|
1068
|
+
repeatMode = input('off', ...(ngDevMode ? [{ debugName: "repeatMode" }] : []));
|
|
1069
|
+
repeatLabel = input('Repeat: off', ...(ngDevMode ? [{ debugName: "repeatLabel" }] : []));
|
|
1070
|
+
captionsActive = input(false, ...(ngDevMode ? [{ debugName: "captionsActive" }] : []));
|
|
1071
|
+
tracksLabel = input('Tracks', ...(ngDevMode ? [{ debugName: "tracksLabel" }] : []));
|
|
1072
|
+
captionsLabel = input('Load subtitles', ...(ngDevMode ? [{ debugName: "captionsLabel" }] : []));
|
|
1073
|
+
speedLabel = input('1x', ...(ngDevMode ? [{ debugName: "speedLabel" }] : []));
|
|
1074
|
+
volumeLabel = input('Volume', ...(ngDevMode ? [{ debugName: "volumeLabel" }] : []));
|
|
1075
|
+
muteLabel = input('Mute', ...(ngDevMode ? [{ debugName: "muteLabel" }] : []));
|
|
1076
|
+
unmuteLabel = input('Restore sound', ...(ngDevMode ? [{ debugName: "unmuteLabel" }] : []));
|
|
1077
|
+
showAirPlay = input(false, ...(ngDevMode ? [{ debugName: "showAirPlay" }] : []));
|
|
1078
|
+
airPlayActive = input(false, ...(ngDevMode ? [{ debugName: "airPlayActive" }] : []));
|
|
1079
|
+
airPlayLabel = input('AirPlay', ...(ngDevMode ? [{ debugName: "airPlayLabel" }] : []));
|
|
1080
|
+
showCast = input(false, ...(ngDevMode ? [{ debugName: "showCast" }] : []));
|
|
1081
|
+
castActive = input(false, ...(ngDevMode ? [{ debugName: "castActive" }] : []));
|
|
1082
|
+
castLabel = input('Play on device', ...(ngDevMode ? [{ debugName: "castLabel" }] : []));
|
|
1083
|
+
castHint = input('', ...(ngDevMode ? [{ debugName: "castHint" }] : []));
|
|
1084
|
+
pipSupported = input(false, ...(ngDevMode ? [{ debugName: "pipSupported" }] : []));
|
|
1085
|
+
pipActive = input(false, ...(ngDevMode ? [{ debugName: "pipActive" }] : []));
|
|
1086
|
+
pipLabel = input('PiP', ...(ngDevMode ? [{ debugName: "pipLabel" }] : []));
|
|
1087
|
+
volume = input(1, ...(ngDevMode ? [{ debugName: "volume" }] : []));
|
|
1088
|
+
currentTime = input(0, ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
|
|
1089
|
+
duration = input(0, ...(ngDevMode ? [{ debugName: "duration" }] : []));
|
|
1090
|
+
buffered = input(0, ...(ngDevMode ? [{ debugName: "buffered" }] : []));
|
|
1091
|
+
profile = input('vod', ...(ngDevMode ? [{ debugName: "profile" }] : []));
|
|
1092
|
+
playToggle = output();
|
|
1093
|
+
seekRelative = output();
|
|
1094
|
+
prev = output();
|
|
1095
|
+
next = output();
|
|
1096
|
+
toggleShuffle = output();
|
|
1097
|
+
cycleRepeat = output();
|
|
1098
|
+
openMenu = output();
|
|
1099
|
+
captions = output();
|
|
1100
|
+
airPlay = output();
|
|
1101
|
+
cast = output();
|
|
1102
|
+
pip = output();
|
|
1103
|
+
mute = output();
|
|
1104
|
+
scrub = output();
|
|
1105
|
+
volumeChange = output();
|
|
1106
|
+
fmtTime(sec) {
|
|
1107
|
+
const s = Math.max(0, Math.floor(sec || 0));
|
|
1108
|
+
const h = Math.floor(s / 3600);
|
|
1109
|
+
const m = Math.floor((s % 3600) / 60);
|
|
1110
|
+
const r = s % 60;
|
|
1111
|
+
if (h > 0)
|
|
1112
|
+
return `${h}:${String(m).padStart(2, '0')}:${String(r).padStart(2, '0')}`;
|
|
1113
|
+
return `${m}:${String(r).padStart(2, '0')}`;
|
|
1114
|
+
}
|
|
1115
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: PlayerControlsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1116
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: PlayerControlsComponent, isStandalone: true, selector: "ngxsp-player-controls", inputs: { playing: { classPropertyName: "playing", publicName: "playing", isSignal: true, isRequired: false, transformFunction: null }, isAudioMode: { classPropertyName: "isAudioMode", publicName: "isAudioMode", isSignal: true, isRequired: false, transformFunction: null }, hasPlaylistControls: { classPropertyName: "hasPlaylistControls", publicName: "hasPlaylistControls", isSignal: true, isRequired: false, transformFunction: null }, canPrev: { classPropertyName: "canPrev", publicName: "canPrev", isSignal: true, isRequired: false, transformFunction: null }, canNext: { classPropertyName: "canNext", publicName: "canNext", isSignal: true, isRequired: false, transformFunction: null }, shuffleActive: { classPropertyName: "shuffleActive", publicName: "shuffleActive", isSignal: true, isRequired: false, transformFunction: null }, shuffleLabel: { classPropertyName: "shuffleLabel", publicName: "shuffleLabel", isSignal: true, isRequired: false, transformFunction: null }, repeatMode: { classPropertyName: "repeatMode", publicName: "repeatMode", isSignal: true, isRequired: false, transformFunction: null }, repeatLabel: { classPropertyName: "repeatLabel", publicName: "repeatLabel", isSignal: true, isRequired: false, transformFunction: null }, captionsActive: { classPropertyName: "captionsActive", publicName: "captionsActive", isSignal: true, isRequired: false, transformFunction: null }, tracksLabel: { classPropertyName: "tracksLabel", publicName: "tracksLabel", isSignal: true, isRequired: false, transformFunction: null }, captionsLabel: { classPropertyName: "captionsLabel", publicName: "captionsLabel", isSignal: true, isRequired: false, transformFunction: null }, speedLabel: { classPropertyName: "speedLabel", publicName: "speedLabel", isSignal: true, isRequired: false, transformFunction: null }, volumeLabel: { classPropertyName: "volumeLabel", publicName: "volumeLabel", isSignal: true, isRequired: false, transformFunction: null }, muteLabel: { classPropertyName: "muteLabel", publicName: "muteLabel", isSignal: true, isRequired: false, transformFunction: null }, unmuteLabel: { classPropertyName: "unmuteLabel", publicName: "unmuteLabel", isSignal: true, isRequired: false, transformFunction: null }, showAirPlay: { classPropertyName: "showAirPlay", publicName: "showAirPlay", isSignal: true, isRequired: false, transformFunction: null }, airPlayActive: { classPropertyName: "airPlayActive", publicName: "airPlayActive", isSignal: true, isRequired: false, transformFunction: null }, airPlayLabel: { classPropertyName: "airPlayLabel", publicName: "airPlayLabel", isSignal: true, isRequired: false, transformFunction: null }, showCast: { classPropertyName: "showCast", publicName: "showCast", isSignal: true, isRequired: false, transformFunction: null }, castActive: { classPropertyName: "castActive", publicName: "castActive", isSignal: true, isRequired: false, transformFunction: null }, castLabel: { classPropertyName: "castLabel", publicName: "castLabel", isSignal: true, isRequired: false, transformFunction: null }, castHint: { classPropertyName: "castHint", publicName: "castHint", isSignal: true, isRequired: false, transformFunction: null }, pipSupported: { classPropertyName: "pipSupported", publicName: "pipSupported", isSignal: true, isRequired: false, transformFunction: null }, pipActive: { classPropertyName: "pipActive", publicName: "pipActive", isSignal: true, isRequired: false, transformFunction: null }, pipLabel: { classPropertyName: "pipLabel", publicName: "pipLabel", isSignal: true, isRequired: false, transformFunction: null }, volume: { classPropertyName: "volume", publicName: "volume", isSignal: true, isRequired: false, transformFunction: null }, currentTime: { classPropertyName: "currentTime", publicName: "currentTime", isSignal: true, isRequired: false, transformFunction: null }, duration: { classPropertyName: "duration", publicName: "duration", isSignal: true, isRequired: false, transformFunction: null }, buffered: { classPropertyName: "buffered", publicName: "buffered", isSignal: true, isRequired: false, transformFunction: null }, profile: { classPropertyName: "profile", publicName: "profile", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { playToggle: "playToggle", seekRelative: "seekRelative", prev: "prev", next: "next", toggleShuffle: "toggleShuffle", cycleRepeat: "cycleRepeat", openMenu: "openMenu", captions: "captions", airPlay: "airPlay", cast: "cast", pip: "pip", mute: "mute", scrub: "scrub", volumeChange: "volumeChange" }, ngImport: i0, template: `
|
|
1117
|
+
<div class="ngxsp-controls">
|
|
1118
|
+
<ngxsp-play-button class="ngxsp-btn" [playing]="playing()" (playToggle)="playToggle.emit()">
|
|
1119
|
+
@if (playing()) {
|
|
1120
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="pause" />
|
|
1121
|
+
} @else {
|
|
1122
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="play" />
|
|
1123
|
+
}
|
|
1124
|
+
</ngxsp-play-button>
|
|
1125
|
+
<button type="button" class="ngxsp-btn" (click)="seekRelative.emit(-10)" aria-label="Seek backward 10 seconds" title="Seek backward 10 seconds">
|
|
1126
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="seek-back" />
|
|
1127
|
+
</button>
|
|
1128
|
+
<button type="button" class="ngxsp-btn" (click)="seekRelative.emit(10)" aria-label="Seek forward 10 seconds" title="Seek forward 10 seconds">
|
|
1129
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="seek-forward" />
|
|
1130
|
+
</button>
|
|
1131
|
+
@if (hasPlaylistControls()) {
|
|
1132
|
+
<button type="button" class="ngxsp-btn" (click)="prev.emit()" [disabled]="!canPrev()" aria-label="Previous item" title="Previous item"><ngxsp-smart-icon class="ngxsp-icon" name="prev" /></button>
|
|
1133
|
+
<button type="button" class="ngxsp-btn" (click)="next.emit()" [disabled]="!canNext()" aria-label="Next item" title="Next item"><ngxsp-smart-icon class="ngxsp-icon" name="next" /></button>
|
|
1134
|
+
<button type="button" class="ngxsp-btn" [class.accent]="shuffleActive()" (click)="toggleShuffle.emit()" [attr.title]="shuffleLabel()" [attr.aria-label]="shuffleLabel()">
|
|
1135
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="shuffle" />
|
|
1136
|
+
</button>
|
|
1137
|
+
<button type="button" class="ngxsp-btn" [class.accent]="repeatMode() !== 'off'" (click)="cycleRepeat.emit()" [attr.title]="repeatLabel()" [attr.aria-label]="repeatLabel()">
|
|
1138
|
+
@if (repeatMode() === 'one') {
|
|
1139
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="repeat-one" />
|
|
1140
|
+
} @else {
|
|
1141
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="repeat" />
|
|
1142
|
+
}
|
|
1143
|
+
</button>
|
|
1144
|
+
}
|
|
1145
|
+
<button type="button" class="ngxsp-btn" (click)="openMenu.emit('tracks')" [attr.title]="tracksLabel()" [attr.aria-label]="tracksLabel()"><ngxsp-smart-icon class="ngxsp-icon" name="tracks" /></button>
|
|
1146
|
+
<button type="button" class="ngxsp-btn" (click)="captions.emit()" [attr.title]="captionsLabel()" [attr.aria-label]="captionsLabel()">
|
|
1147
|
+
@if (captionsActive()) {
|
|
1148
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="captions" />
|
|
1149
|
+
} @else {
|
|
1150
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="captions-off" />
|
|
1151
|
+
}
|
|
1152
|
+
</button>
|
|
1153
|
+
<button type="button" class="ngxsp-btn" (click)="openMenu.emit('speed')" [attr.title]="speedLabel()" [attr.aria-label]="speedLabel()"><ngxsp-smart-icon class="ngxsp-icon" name="speed" /></button>
|
|
1154
|
+
@if (showAirPlay()) {
|
|
1155
|
+
<button type="button" class="ngxsp-btn" [class.accent]="airPlayActive()" (click)="airPlay.emit()" [attr.title]="airPlayLabel()" [attr.aria-label]="airPlayLabel()">
|
|
1156
|
+
@if (airPlayActive()) {
|
|
1157
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="airplay-active" />
|
|
1158
|
+
} @else {
|
|
1159
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="airplay" />
|
|
1160
|
+
}
|
|
1161
|
+
</button>
|
|
1162
|
+
}
|
|
1163
|
+
@if (showCast()) {
|
|
1164
|
+
<button type="button" class="ngxsp-btn" [class.accent]="castActive()" (click)="cast.emit()" [attr.title]="castHint()" [attr.aria-label]="castLabel()">
|
|
1165
|
+
@if (castActive()) {
|
|
1166
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="cast-active" />
|
|
1167
|
+
} @else {
|
|
1168
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="cast" />
|
|
1169
|
+
}
|
|
1170
|
+
</button>
|
|
1171
|
+
}
|
|
1172
|
+
@if (!isAudioMode() && pipSupported()) {
|
|
1173
|
+
<button type="button" class="ngxsp-btn" (click)="pip.emit()" [attr.title]="pipLabel()" [attr.aria-label]="pipLabel()">
|
|
1174
|
+
@if (pipActive()) {
|
|
1175
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="pip-active" />
|
|
1176
|
+
} @else {
|
|
1177
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="pip" />
|
|
1178
|
+
}
|
|
1179
|
+
</button>
|
|
1180
|
+
}
|
|
1181
|
+
<ngxsp-volume-control
|
|
1182
|
+
class="ngxsp-volumeControl"
|
|
1183
|
+
[volume]="volume()"
|
|
1184
|
+
[volumeLabel]="volumeLabel()"
|
|
1185
|
+
[muteLabel]="muteLabel()"
|
|
1186
|
+
[unmuteLabel]="unmuteLabel()"
|
|
1187
|
+
(muteToggle)="mute.emit()"
|
|
1188
|
+
(volumeChange)="volumeChange.emit($event)"
|
|
1189
|
+
/>
|
|
1190
|
+
|
|
1191
|
+
<ngxsp-time-slider
|
|
1192
|
+
style="flex:1 1 160px; min-width:160px"
|
|
1193
|
+
[currentTime]="currentTime()"
|
|
1194
|
+
[duration]="duration()"
|
|
1195
|
+
[buffered]="buffered()"
|
|
1196
|
+
[profile]="profile()"
|
|
1197
|
+
(seek)="scrub.emit($event)"
|
|
1198
|
+
/>
|
|
1199
|
+
|
|
1200
|
+
<div class="ngxsp-time">{{ fmtTime(currentTime()) }} / {{ fmtTime(duration()) }}</div>
|
|
1201
|
+
|
|
1202
|
+
</div>
|
|
1203
|
+
`, isInline: true, styles: [":host{display:block}.ngxsp-controls{display:flex;gap:8px;align-items:center;padding:10px;flex-wrap:wrap}.ngxsp-btn{background:linear-gradient(180deg,#132236eb,#0b1523eb);border:1px solid var(--ngxsp-border, rgba(148,163,184,.18));color:var(--ngxsp-text, #e6eef8);border-radius:12px;padding:8px 10px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:8px;box-shadow:inset 0 1px #ffffff0d,0 10px 24px #0206172e;transition:border-color .18s ease,background .18s ease,transform .18s ease,color .18s ease}.ngxsp-btn.accent{background:linear-gradient(180deg,#14304af5,#0d2135f5);border-color:var(--ngxsp-border-strong, rgba(191,219,254,.24));color:#d8f3ff}.ngxsp-btn:hover{border-color:#bfdbfe57;background:linear-gradient(180deg,#182e47f0,#0c1b2cf0)}.ngxsp-btn:disabled{opacity:.38;cursor:not-allowed}.ngxsp-btn:focus-visible{outline:none;box-shadow:var(--ngxsp-focus-ring, 0 0 0 3px rgb(108 182 255 / .5))}.ngxsp-icon{width:18px;height:18px;display:block;flex:0 0 auto}.ngxsp-time{color:var(--ngxsp-text-dim, rgba(230,238,248,.72));font-variant-numeric:tabular-nums}.ngxsp-volumeControl{--ngxsp-control-size: 38px;--ngxsp-volume-range-max: 140px;--ngxsp-volume-button-bg: linear-gradient(180deg, rgba(19,34,54,.92), rgba(11,21,35,.92));--ngxsp-volume-button-border: 1px solid var(--ngxsp-border, rgba(148,163,184,.18));--ngxsp-volume-accent: var(--nsp-accent, #7dd3fc);--ngxsp-volume-thumb-bg: var(--nsp-text, #f8fbff);color:var(--ngxsp-text, #e6eef8)}@media(max-width:640px){.ngxsp-controls{padding:8px;gap:6px}.ngxsp-btn{padding:7px 9px;border-radius:10px}.ngxsp-icon{width:16px;height:16px}}\n"], dependencies: [{ kind: "component", type: SmartPlayerIconComponent, selector: "ngxsp-smart-icon", inputs: ["name"] }, { kind: "component", type: NgxspPlayButton, selector: "ngxsp-play-button", inputs: ["playing", "disabled", "playLabel", "pauseLabel"], outputs: ["playToggle"] }, { kind: "component", type: NgxspTimeSlider, selector: "ngxsp-time-slider", inputs: ["currentTime", "duration", "buffered", "profile", "disabled", "durationHintSec", "markers", "step", "bigStep", "ariaLabel", "liveLabel"], outputs: ["seek"] }, { kind: "component", type: NgxspVolumeControl, selector: "ngxsp-volume-control", inputs: ["volume", "muted", "disabled", "compact", "showValue", "step", "volumeLabel", "muteLabel", "unmuteLabel"], outputs: ["volumeChange", "muteToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1204
|
+
}
|
|
1205
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: PlayerControlsComponent, decorators: [{
|
|
1206
|
+
type: Component,
|
|
1207
|
+
args: [{ selector: 'ngxsp-player-controls', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [SmartPlayerIconComponent, NgxspPlayButton, NgxspTimeSlider, NgxspVolumeControl], template: `
|
|
1208
|
+
<div class="ngxsp-controls">
|
|
1209
|
+
<ngxsp-play-button class="ngxsp-btn" [playing]="playing()" (playToggle)="playToggle.emit()">
|
|
1210
|
+
@if (playing()) {
|
|
1211
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="pause" />
|
|
1212
|
+
} @else {
|
|
1213
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="play" />
|
|
1214
|
+
}
|
|
1215
|
+
</ngxsp-play-button>
|
|
1216
|
+
<button type="button" class="ngxsp-btn" (click)="seekRelative.emit(-10)" aria-label="Seek backward 10 seconds" title="Seek backward 10 seconds">
|
|
1217
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="seek-back" />
|
|
1218
|
+
</button>
|
|
1219
|
+
<button type="button" class="ngxsp-btn" (click)="seekRelative.emit(10)" aria-label="Seek forward 10 seconds" title="Seek forward 10 seconds">
|
|
1220
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="seek-forward" />
|
|
1221
|
+
</button>
|
|
1222
|
+
@if (hasPlaylistControls()) {
|
|
1223
|
+
<button type="button" class="ngxsp-btn" (click)="prev.emit()" [disabled]="!canPrev()" aria-label="Previous item" title="Previous item"><ngxsp-smart-icon class="ngxsp-icon" name="prev" /></button>
|
|
1224
|
+
<button type="button" class="ngxsp-btn" (click)="next.emit()" [disabled]="!canNext()" aria-label="Next item" title="Next item"><ngxsp-smart-icon class="ngxsp-icon" name="next" /></button>
|
|
1225
|
+
<button type="button" class="ngxsp-btn" [class.accent]="shuffleActive()" (click)="toggleShuffle.emit()" [attr.title]="shuffleLabel()" [attr.aria-label]="shuffleLabel()">
|
|
1226
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="shuffle" />
|
|
1227
|
+
</button>
|
|
1228
|
+
<button type="button" class="ngxsp-btn" [class.accent]="repeatMode() !== 'off'" (click)="cycleRepeat.emit()" [attr.title]="repeatLabel()" [attr.aria-label]="repeatLabel()">
|
|
1229
|
+
@if (repeatMode() === 'one') {
|
|
1230
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="repeat-one" />
|
|
1231
|
+
} @else {
|
|
1232
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="repeat" />
|
|
1233
|
+
}
|
|
1234
|
+
</button>
|
|
1235
|
+
}
|
|
1236
|
+
<button type="button" class="ngxsp-btn" (click)="openMenu.emit('tracks')" [attr.title]="tracksLabel()" [attr.aria-label]="tracksLabel()"><ngxsp-smart-icon class="ngxsp-icon" name="tracks" /></button>
|
|
1237
|
+
<button type="button" class="ngxsp-btn" (click)="captions.emit()" [attr.title]="captionsLabel()" [attr.aria-label]="captionsLabel()">
|
|
1238
|
+
@if (captionsActive()) {
|
|
1239
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="captions" />
|
|
1240
|
+
} @else {
|
|
1241
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="captions-off" />
|
|
1242
|
+
}
|
|
1243
|
+
</button>
|
|
1244
|
+
<button type="button" class="ngxsp-btn" (click)="openMenu.emit('speed')" [attr.title]="speedLabel()" [attr.aria-label]="speedLabel()"><ngxsp-smart-icon class="ngxsp-icon" name="speed" /></button>
|
|
1245
|
+
@if (showAirPlay()) {
|
|
1246
|
+
<button type="button" class="ngxsp-btn" [class.accent]="airPlayActive()" (click)="airPlay.emit()" [attr.title]="airPlayLabel()" [attr.aria-label]="airPlayLabel()">
|
|
1247
|
+
@if (airPlayActive()) {
|
|
1248
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="airplay-active" />
|
|
1249
|
+
} @else {
|
|
1250
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="airplay" />
|
|
1251
|
+
}
|
|
1252
|
+
</button>
|
|
1253
|
+
}
|
|
1254
|
+
@if (showCast()) {
|
|
1255
|
+
<button type="button" class="ngxsp-btn" [class.accent]="castActive()" (click)="cast.emit()" [attr.title]="castHint()" [attr.aria-label]="castLabel()">
|
|
1256
|
+
@if (castActive()) {
|
|
1257
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="cast-active" />
|
|
1258
|
+
} @else {
|
|
1259
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="cast" />
|
|
1260
|
+
}
|
|
1261
|
+
</button>
|
|
1262
|
+
}
|
|
1263
|
+
@if (!isAudioMode() && pipSupported()) {
|
|
1264
|
+
<button type="button" class="ngxsp-btn" (click)="pip.emit()" [attr.title]="pipLabel()" [attr.aria-label]="pipLabel()">
|
|
1265
|
+
@if (pipActive()) {
|
|
1266
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="pip-active" />
|
|
1267
|
+
} @else {
|
|
1268
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="pip" />
|
|
1269
|
+
}
|
|
1270
|
+
</button>
|
|
1271
|
+
}
|
|
1272
|
+
<ngxsp-volume-control
|
|
1273
|
+
class="ngxsp-volumeControl"
|
|
1274
|
+
[volume]="volume()"
|
|
1275
|
+
[volumeLabel]="volumeLabel()"
|
|
1276
|
+
[muteLabel]="muteLabel()"
|
|
1277
|
+
[unmuteLabel]="unmuteLabel()"
|
|
1278
|
+
(muteToggle)="mute.emit()"
|
|
1279
|
+
(volumeChange)="volumeChange.emit($event)"
|
|
1280
|
+
/>
|
|
1281
|
+
|
|
1282
|
+
<ngxsp-time-slider
|
|
1283
|
+
style="flex:1 1 160px; min-width:160px"
|
|
1284
|
+
[currentTime]="currentTime()"
|
|
1285
|
+
[duration]="duration()"
|
|
1286
|
+
[buffered]="buffered()"
|
|
1287
|
+
[profile]="profile()"
|
|
1288
|
+
(seek)="scrub.emit($event)"
|
|
1289
|
+
/>
|
|
1290
|
+
|
|
1291
|
+
<div class="ngxsp-time">{{ fmtTime(currentTime()) }} / {{ fmtTime(duration()) }}</div>
|
|
1292
|
+
|
|
1293
|
+
</div>
|
|
1294
|
+
`, styles: [":host{display:block}.ngxsp-controls{display:flex;gap:8px;align-items:center;padding:10px;flex-wrap:wrap}.ngxsp-btn{background:linear-gradient(180deg,#132236eb,#0b1523eb);border:1px solid var(--ngxsp-border, rgba(148,163,184,.18));color:var(--ngxsp-text, #e6eef8);border-radius:12px;padding:8px 10px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:8px;box-shadow:inset 0 1px #ffffff0d,0 10px 24px #0206172e;transition:border-color .18s ease,background .18s ease,transform .18s ease,color .18s ease}.ngxsp-btn.accent{background:linear-gradient(180deg,#14304af5,#0d2135f5);border-color:var(--ngxsp-border-strong, rgba(191,219,254,.24));color:#d8f3ff}.ngxsp-btn:hover{border-color:#bfdbfe57;background:linear-gradient(180deg,#182e47f0,#0c1b2cf0)}.ngxsp-btn:disabled{opacity:.38;cursor:not-allowed}.ngxsp-btn:focus-visible{outline:none;box-shadow:var(--ngxsp-focus-ring, 0 0 0 3px rgb(108 182 255 / .5))}.ngxsp-icon{width:18px;height:18px;display:block;flex:0 0 auto}.ngxsp-time{color:var(--ngxsp-text-dim, rgba(230,238,248,.72));font-variant-numeric:tabular-nums}.ngxsp-volumeControl{--ngxsp-control-size: 38px;--ngxsp-volume-range-max: 140px;--ngxsp-volume-button-bg: linear-gradient(180deg, rgba(19,34,54,.92), rgba(11,21,35,.92));--ngxsp-volume-button-border: 1px solid var(--ngxsp-border, rgba(148,163,184,.18));--ngxsp-volume-accent: var(--nsp-accent, #7dd3fc);--ngxsp-volume-thumb-bg: var(--nsp-text, #f8fbff);color:var(--ngxsp-text, #e6eef8)}@media(max-width:640px){.ngxsp-controls{padding:8px;gap:6px}.ngxsp-btn{padding:7px 9px;border-radius:10px}.ngxsp-icon{width:16px;height:16px}}\n"] }]
|
|
1295
|
+
}], propDecorators: { playing: [{ type: i0.Input, args: [{ isSignal: true, alias: "playing", required: false }] }], isAudioMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isAudioMode", required: false }] }], hasPlaylistControls: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasPlaylistControls", required: false }] }], canPrev: [{ type: i0.Input, args: [{ isSignal: true, alias: "canPrev", required: false }] }], canNext: [{ type: i0.Input, args: [{ isSignal: true, alias: "canNext", required: false }] }], shuffleActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "shuffleActive", required: false }] }], shuffleLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "shuffleLabel", required: false }] }], repeatMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "repeatMode", required: false }] }], repeatLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "repeatLabel", required: false }] }], captionsActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionsActive", required: false }] }], tracksLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "tracksLabel", required: false }] }], captionsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionsLabel", required: false }] }], speedLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "speedLabel", required: false }] }], volumeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "volumeLabel", required: false }] }], muteLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "muteLabel", required: false }] }], unmuteLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "unmuteLabel", required: false }] }], showAirPlay: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAirPlay", required: false }] }], airPlayActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "airPlayActive", required: false }] }], airPlayLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "airPlayLabel", required: false }] }], showCast: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCast", required: false }] }], castActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "castActive", required: false }] }], castLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "castLabel", required: false }] }], castHint: [{ type: i0.Input, args: [{ isSignal: true, alias: "castHint", required: false }] }], pipSupported: [{ type: i0.Input, args: [{ isSignal: true, alias: "pipSupported", required: false }] }], pipActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "pipActive", required: false }] }], pipLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pipLabel", required: false }] }], volume: [{ type: i0.Input, args: [{ isSignal: true, alias: "volume", required: false }] }], currentTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTime", required: false }] }], duration: [{ type: i0.Input, args: [{ isSignal: true, alias: "duration", required: false }] }], buffered: [{ type: i0.Input, args: [{ isSignal: true, alias: "buffered", required: false }] }], profile: [{ type: i0.Input, args: [{ isSignal: true, alias: "profile", required: false }] }], playToggle: [{ type: i0.Output, args: ["playToggle"] }], seekRelative: [{ type: i0.Output, args: ["seekRelative"] }], prev: [{ type: i0.Output, args: ["prev"] }], next: [{ type: i0.Output, args: ["next"] }], toggleShuffle: [{ type: i0.Output, args: ["toggleShuffle"] }], cycleRepeat: [{ type: i0.Output, args: ["cycleRepeat"] }], openMenu: [{ type: i0.Output, args: ["openMenu"] }], captions: [{ type: i0.Output, args: ["captions"] }], airPlay: [{ type: i0.Output, args: ["airPlay"] }], cast: [{ type: i0.Output, args: ["cast"] }], pip: [{ type: i0.Output, args: ["pip"] }], mute: [{ type: i0.Output, args: ["mute"] }], scrub: [{ type: i0.Output, args: ["scrub"] }], volumeChange: [{ type: i0.Output, args: ["volumeChange"] }] } });
|
|
1296
|
+
|
|
1297
|
+
/**
|
|
1298
|
+
* Barre de contrôle du skin vod (top bar + center dock + bottom bar),
|
|
1299
|
+
* extraite du SmartPlayerComponent (décomposition, cf. docs/AUDIT.md).
|
|
1300
|
+
* Le menu pistes/vitesse est projeté par l'hôte via <ng-content>.
|
|
1301
|
+
* PiP/cast restent au parent (propriétaire du <video>) : capacités en entrée,
|
|
1302
|
+
* commandes en sortie.
|
|
1303
|
+
*/
|
|
1304
|
+
class PlayerControlsVodComponent {
|
|
1305
|
+
playing = input(false, ...(ngDevMode ? [{ debugName: "playing" }] : []));
|
|
1306
|
+
isAudioMode = input(false, ...(ngDevMode ? [{ debugName: "isAudioMode" }] : []));
|
|
1307
|
+
displayTitle = input('Player', ...(ngDevMode ? [{ debugName: "displayTitle" }] : []));
|
|
1308
|
+
nowPlayingLabel = input('Now playing', ...(ngDevMode ? [{ debugName: "nowPlayingLabel" }] : []));
|
|
1309
|
+
nowListeningLabel = input('Now listening', ...(ngDevMode ? [{ debugName: "nowListeningLabel" }] : []));
|
|
1310
|
+
fullscreenHintLabel = input('Double-click for fullscreen', ...(ngDevMode ? [{ debugName: "fullscreenHintLabel" }] : []));
|
|
1311
|
+
statusLabel = input('Ready', ...(ngDevMode ? [{ debugName: "statusLabel" }] : []));
|
|
1312
|
+
keyboardHint = input('', ...(ngDevMode ? [{ debugName: "keyboardHint" }] : []));
|
|
1313
|
+
hasPlaylistControls = input(false, ...(ngDevMode ? [{ debugName: "hasPlaylistControls" }] : []));
|
|
1314
|
+
canPrev = input(false, ...(ngDevMode ? [{ debugName: "canPrev" }] : []));
|
|
1315
|
+
canNext = input(false, ...(ngDevMode ? [{ debugName: "canNext" }] : []));
|
|
1316
|
+
previousLabel = input('Previous item', ...(ngDevMode ? [{ debugName: "previousLabel" }] : []));
|
|
1317
|
+
nextLabel = input('Next item', ...(ngDevMode ? [{ debugName: "nextLabel" }] : []));
|
|
1318
|
+
playLabel = input('Play', ...(ngDevMode ? [{ debugName: "playLabel" }] : []));
|
|
1319
|
+
pauseLabel = input('Pause', ...(ngDevMode ? [{ debugName: "pauseLabel" }] : []));
|
|
1320
|
+
shuffleActive = input(false, ...(ngDevMode ? [{ debugName: "shuffleActive" }] : []));
|
|
1321
|
+
shuffleLabel = input('Shuffle: off', ...(ngDevMode ? [{ debugName: "shuffleLabel" }] : []));
|
|
1322
|
+
repeatMode = input('off', ...(ngDevMode ? [{ debugName: "repeatMode" }] : []));
|
|
1323
|
+
repeatLabel = input('Repeat: off', ...(ngDevMode ? [{ debugName: "repeatLabel" }] : []));
|
|
1324
|
+
captionsActive = input(false, ...(ngDevMode ? [{ debugName: "captionsActive" }] : []));
|
|
1325
|
+
tracksLabel = input('Tracks', ...(ngDevMode ? [{ debugName: "tracksLabel" }] : []));
|
|
1326
|
+
captionsLabel = input('Load subtitles', ...(ngDevMode ? [{ debugName: "captionsLabel" }] : []));
|
|
1327
|
+
speedLabel = input('1x', ...(ngDevMode ? [{ debugName: "speedLabel" }] : []));
|
|
1328
|
+
/** "1×", "1.5×": the rate on the speed pill. */
|
|
1329
|
+
speedBadge = computed(() => this.speedLabel().replace(/x$/i, '×'), ...(ngDevMode ? [{ debugName: "speedBadge" }] : []));
|
|
1330
|
+
volumeLabel = input('Volume', ...(ngDevMode ? [{ debugName: "volumeLabel" }] : []));
|
|
1331
|
+
muteLabel = input('Mute', ...(ngDevMode ? [{ debugName: "muteLabel" }] : []));
|
|
1332
|
+
unmuteLabel = input('Restore sound', ...(ngDevMode ? [{ debugName: "unmuteLabel" }] : []));
|
|
1333
|
+
showAirPlay = input(false, ...(ngDevMode ? [{ debugName: "showAirPlay" }] : []));
|
|
1334
|
+
airPlayActive = input(false, ...(ngDevMode ? [{ debugName: "airPlayActive" }] : []));
|
|
1335
|
+
airPlayLabel = input('AirPlay', ...(ngDevMode ? [{ debugName: "airPlayLabel" }] : []));
|
|
1336
|
+
showCast = input(false, ...(ngDevMode ? [{ debugName: "showCast" }] : []));
|
|
1337
|
+
castActive = input(false, ...(ngDevMode ? [{ debugName: "castActive" }] : []));
|
|
1338
|
+
castLabel = input('Play on device', ...(ngDevMode ? [{ debugName: "castLabel" }] : []));
|
|
1339
|
+
castHint = input('', ...(ngDevMode ? [{ debugName: "castHint" }] : []));
|
|
1340
|
+
pipSupported = input(false, ...(ngDevMode ? [{ debugName: "pipSupported" }] : []));
|
|
1341
|
+
pipActive = input(false, ...(ngDevMode ? [{ debugName: "pipActive" }] : []));
|
|
1342
|
+
pipLabel = input('PiP', ...(ngDevMode ? [{ debugName: "pipLabel" }] : []));
|
|
1343
|
+
fullscreenLabel = input('Fullscreen', ...(ngDevMode ? [{ debugName: "fullscreenLabel" }] : []));
|
|
1344
|
+
volume = input(1, ...(ngDevMode ? [{ debugName: "volume" }] : []));
|
|
1345
|
+
currentTime = input(0, ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
|
|
1346
|
+
duration = input(0, ...(ngDevMode ? [{ debugName: "duration" }] : []));
|
|
1347
|
+
buffered = input(0, ...(ngDevMode ? [{ debugName: "buffered" }] : []));
|
|
1348
|
+
bufferPercent = input(0, ...(ngDevMode ? [{ debugName: "bufferPercent" }] : []));
|
|
1349
|
+
profile = input('vod', ...(ngDevMode ? [{ debugName: "profile" }] : []));
|
|
1350
|
+
timelineDurationSec = input(0, ...(ngDevMode ? [{ debugName: "timelineDurationSec" }] : []));
|
|
1351
|
+
timelineMarkers = input([], ...(ngDevMode ? [{ debugName: "timelineMarkers" }] : []));
|
|
1352
|
+
playToggle = output();
|
|
1353
|
+
seekRelative = output();
|
|
1354
|
+
prev = output();
|
|
1355
|
+
next = output();
|
|
1356
|
+
toggleShuffle = output();
|
|
1357
|
+
cycleRepeat = output();
|
|
1358
|
+
openMenu = output();
|
|
1359
|
+
captions = output();
|
|
1360
|
+
airPlay = output();
|
|
1361
|
+
cast = output();
|
|
1362
|
+
pip = output();
|
|
1363
|
+
fullscreen = output();
|
|
1364
|
+
mute = output();
|
|
1365
|
+
scrub = output();
|
|
1366
|
+
volumeChange = output();
|
|
1367
|
+
fmtTime(sec) {
|
|
1368
|
+
const s = Math.max(0, Math.floor(sec || 0));
|
|
1369
|
+
const h = Math.floor(s / 3600);
|
|
1370
|
+
const m = Math.floor((s % 3600) / 60);
|
|
1371
|
+
const r = s % 60;
|
|
1372
|
+
if (h > 0)
|
|
1373
|
+
return `${h}:${String(m).padStart(2, '0')}:${String(r).padStart(2, '0')}`;
|
|
1374
|
+
return `${m}:${String(r).padStart(2, '0')}`;
|
|
1375
|
+
}
|
|
1376
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: PlayerControlsVodComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1377
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: PlayerControlsVodComponent, isStandalone: true, selector: "ngxsp-player-controls-vod", inputs: { playing: { classPropertyName: "playing", publicName: "playing", isSignal: true, isRequired: false, transformFunction: null }, isAudioMode: { classPropertyName: "isAudioMode", publicName: "isAudioMode", isSignal: true, isRequired: false, transformFunction: null }, displayTitle: { classPropertyName: "displayTitle", publicName: "displayTitle", isSignal: true, isRequired: false, transformFunction: null }, nowPlayingLabel: { classPropertyName: "nowPlayingLabel", publicName: "nowPlayingLabel", isSignal: true, isRequired: false, transformFunction: null }, nowListeningLabel: { classPropertyName: "nowListeningLabel", publicName: "nowListeningLabel", isSignal: true, isRequired: false, transformFunction: null }, fullscreenHintLabel: { classPropertyName: "fullscreenHintLabel", publicName: "fullscreenHintLabel", isSignal: true, isRequired: false, transformFunction: null }, statusLabel: { classPropertyName: "statusLabel", publicName: "statusLabel", isSignal: true, isRequired: false, transformFunction: null }, keyboardHint: { classPropertyName: "keyboardHint", publicName: "keyboardHint", isSignal: true, isRequired: false, transformFunction: null }, hasPlaylistControls: { classPropertyName: "hasPlaylistControls", publicName: "hasPlaylistControls", isSignal: true, isRequired: false, transformFunction: null }, canPrev: { classPropertyName: "canPrev", publicName: "canPrev", isSignal: true, isRequired: false, transformFunction: null }, canNext: { classPropertyName: "canNext", publicName: "canNext", isSignal: true, isRequired: false, transformFunction: null }, previousLabel: { classPropertyName: "previousLabel", publicName: "previousLabel", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, playLabel: { classPropertyName: "playLabel", publicName: "playLabel", isSignal: true, isRequired: false, transformFunction: null }, pauseLabel: { classPropertyName: "pauseLabel", publicName: "pauseLabel", isSignal: true, isRequired: false, transformFunction: null }, shuffleActive: { classPropertyName: "shuffleActive", publicName: "shuffleActive", isSignal: true, isRequired: false, transformFunction: null }, shuffleLabel: { classPropertyName: "shuffleLabel", publicName: "shuffleLabel", isSignal: true, isRequired: false, transformFunction: null }, repeatMode: { classPropertyName: "repeatMode", publicName: "repeatMode", isSignal: true, isRequired: false, transformFunction: null }, repeatLabel: { classPropertyName: "repeatLabel", publicName: "repeatLabel", isSignal: true, isRequired: false, transformFunction: null }, captionsActive: { classPropertyName: "captionsActive", publicName: "captionsActive", isSignal: true, isRequired: false, transformFunction: null }, tracksLabel: { classPropertyName: "tracksLabel", publicName: "tracksLabel", isSignal: true, isRequired: false, transformFunction: null }, captionsLabel: { classPropertyName: "captionsLabel", publicName: "captionsLabel", isSignal: true, isRequired: false, transformFunction: null }, speedLabel: { classPropertyName: "speedLabel", publicName: "speedLabel", isSignal: true, isRequired: false, transformFunction: null }, volumeLabel: { classPropertyName: "volumeLabel", publicName: "volumeLabel", isSignal: true, isRequired: false, transformFunction: null }, muteLabel: { classPropertyName: "muteLabel", publicName: "muteLabel", isSignal: true, isRequired: false, transformFunction: null }, unmuteLabel: { classPropertyName: "unmuteLabel", publicName: "unmuteLabel", isSignal: true, isRequired: false, transformFunction: null }, showAirPlay: { classPropertyName: "showAirPlay", publicName: "showAirPlay", isSignal: true, isRequired: false, transformFunction: null }, airPlayActive: { classPropertyName: "airPlayActive", publicName: "airPlayActive", isSignal: true, isRequired: false, transformFunction: null }, airPlayLabel: { classPropertyName: "airPlayLabel", publicName: "airPlayLabel", isSignal: true, isRequired: false, transformFunction: null }, showCast: { classPropertyName: "showCast", publicName: "showCast", isSignal: true, isRequired: false, transformFunction: null }, castActive: { classPropertyName: "castActive", publicName: "castActive", isSignal: true, isRequired: false, transformFunction: null }, castLabel: { classPropertyName: "castLabel", publicName: "castLabel", isSignal: true, isRequired: false, transformFunction: null }, castHint: { classPropertyName: "castHint", publicName: "castHint", isSignal: true, isRequired: false, transformFunction: null }, pipSupported: { classPropertyName: "pipSupported", publicName: "pipSupported", isSignal: true, isRequired: false, transformFunction: null }, pipActive: { classPropertyName: "pipActive", publicName: "pipActive", isSignal: true, isRequired: false, transformFunction: null }, pipLabel: { classPropertyName: "pipLabel", publicName: "pipLabel", isSignal: true, isRequired: false, transformFunction: null }, fullscreenLabel: { classPropertyName: "fullscreenLabel", publicName: "fullscreenLabel", isSignal: true, isRequired: false, transformFunction: null }, volume: { classPropertyName: "volume", publicName: "volume", isSignal: true, isRequired: false, transformFunction: null }, currentTime: { classPropertyName: "currentTime", publicName: "currentTime", isSignal: true, isRequired: false, transformFunction: null }, duration: { classPropertyName: "duration", publicName: "duration", isSignal: true, isRequired: false, transformFunction: null }, buffered: { classPropertyName: "buffered", publicName: "buffered", isSignal: true, isRequired: false, transformFunction: null }, bufferPercent: { classPropertyName: "bufferPercent", publicName: "bufferPercent", isSignal: true, isRequired: false, transformFunction: null }, profile: { classPropertyName: "profile", publicName: "profile", isSignal: true, isRequired: false, transformFunction: null }, timelineDurationSec: { classPropertyName: "timelineDurationSec", publicName: "timelineDurationSec", isSignal: true, isRequired: false, transformFunction: null }, timelineMarkers: { classPropertyName: "timelineMarkers", publicName: "timelineMarkers", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { playToggle: "playToggle", seekRelative: "seekRelative", prev: "prev", next: "next", toggleShuffle: "toggleShuffle", cycleRepeat: "cycleRepeat", openMenu: "openMenu", captions: "captions", airPlay: "airPlay", cast: "cast", pip: "pip", fullscreen: "fullscreen", mute: "mute", scrub: "scrub", volumeChange: "volumeChange" }, ngImport: i0, template: `
|
|
1378
|
+
<div class="ngxsp-vod-top">
|
|
1379
|
+
<div class="ngxsp-vod-titleWrap">
|
|
1380
|
+
<div class="ngxsp-vod-eyebrow">{{ isAudioMode() ? nowListeningLabel() : nowPlayingLabel() }}</div>
|
|
1381
|
+
<div class="ngxsp-vod-title">{{ displayTitle() }}</div>
|
|
1382
|
+
</div>
|
|
1383
|
+
<div class="ngxsp-vod-spacer"></div>
|
|
1384
|
+
<div class="ngxsp-vod-status" [attr.data-state]="playing() ? 'playing' : 'idle'">{{ statusLabel() }}</div>
|
|
1385
|
+
@if (!isAudioMode()) {
|
|
1386
|
+
<div class="ngxsp-vod-topHint">{{ fullscreenHintLabel() }}</div>
|
|
1387
|
+
}
|
|
1388
|
+
</div>
|
|
1389
|
+
|
|
1390
|
+
<div class="ngxsp-vod-centerDock">
|
|
1391
|
+
@if (hasPlaylistControls()) {
|
|
1392
|
+
<button type="button" class="ngxsp-vod-side" (click)="prev.emit()" [disabled]="!canPrev()" [attr.aria-label]="previousLabel()">
|
|
1393
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="prev" />
|
|
1394
|
+
</button>
|
|
1395
|
+
}
|
|
1396
|
+
<button type="button" class="ngxsp-vod-side ngxsp-vod-side-wide" (click)="seekRelative.emit(-10)" aria-label="Seek backward 10 seconds">
|
|
1397
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="seek-back" />
|
|
1398
|
+
</button>
|
|
1399
|
+
<ngxsp-play-button class="ngxsp-vod-center" [playing]="playing()" [playLabel]="playLabel()" [pauseLabel]="pauseLabel()" (playToggle)="playToggle.emit()">
|
|
1400
|
+
@if (playing()) {
|
|
1401
|
+
<ngxsp-smart-icon class="ngxsp-icon ngxsp-icon-lg" name="pause" />
|
|
1402
|
+
} @else {
|
|
1403
|
+
<ngxsp-smart-icon class="ngxsp-icon ngxsp-icon-lg" name="play" />
|
|
1404
|
+
}
|
|
1405
|
+
</ngxsp-play-button>
|
|
1406
|
+
<button type="button" class="ngxsp-vod-side ngxsp-vod-side-wide" (click)="seekRelative.emit(10)" aria-label="Seek forward 10 seconds">
|
|
1407
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="seek-forward" />
|
|
1408
|
+
</button>
|
|
1409
|
+
@if (hasPlaylistControls()) {
|
|
1410
|
+
<button type="button" class="ngxsp-vod-side" (click)="next.emit()" [disabled]="!canNext()" [attr.aria-label]="nextLabel()">
|
|
1411
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="next" />
|
|
1412
|
+
</button>
|
|
1413
|
+
}
|
|
1414
|
+
</div>
|
|
1415
|
+
|
|
1416
|
+
<div class="ngxsp-controls ngxsp-vod-bottom">
|
|
1417
|
+
<div class="ngxsp-vod-metaRow">
|
|
1418
|
+
@if (hasPlaylistControls()) {
|
|
1419
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-prev" (click)="prev.emit()" [disabled]="!canPrev()" [attr.title]="previousLabel()" [attr.aria-label]="previousLabel()">
|
|
1420
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="prev" />
|
|
1421
|
+
</button>
|
|
1422
|
+
}
|
|
1423
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-play" (click)="playToggle.emit()" [attr.title]="playing() ? pauseLabel() : playLabel()" [attr.aria-label]="playing() ? pauseLabel() : playLabel()">
|
|
1424
|
+
@if (playing()) {
|
|
1425
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="pause" />
|
|
1426
|
+
} @else {
|
|
1427
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="play" />
|
|
1428
|
+
}
|
|
1429
|
+
</button>
|
|
1430
|
+
@if (hasPlaylistControls()) {
|
|
1431
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-next" (click)="next.emit()" [disabled]="!canNext()" [attr.title]="nextLabel()" [attr.aria-label]="nextLabel()">
|
|
1432
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="next" />
|
|
1433
|
+
</button>
|
|
1434
|
+
}
|
|
1435
|
+
<div class="ngxsp-vod-time">{{ fmtTime(currentTime()) }} / {{ fmtTime(duration()) }}</div>
|
|
1436
|
+
<div class="ngxsp-vod-volumeGroup">
|
|
1437
|
+
<ngxsp-volume-control
|
|
1438
|
+
class="ngxsp-vod-volumeControl"
|
|
1439
|
+
[compact]="true"
|
|
1440
|
+
[volume]="volume()"
|
|
1441
|
+
[volumeLabel]="volumeLabel()"
|
|
1442
|
+
[muteLabel]="muteLabel()"
|
|
1443
|
+
[unmuteLabel]="unmuteLabel()"
|
|
1444
|
+
(muteToggle)="mute.emit()"
|
|
1445
|
+
(volumeChange)="volumeChange.emit($event)"
|
|
1446
|
+
/>
|
|
1447
|
+
</div>
|
|
1448
|
+
<ng-content select="[ngxsp-player-inline-controls]" />
|
|
1449
|
+
<div class="ngxsp-vod-spacer"></div>
|
|
1450
|
+
@if (hasPlaylistControls()) {
|
|
1451
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-playlist" [class.accent]="shuffleActive()" (click)="toggleShuffle.emit()" [attr.title]="shuffleLabel()" [attr.aria-label]="shuffleLabel()">
|
|
1452
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="shuffle" />
|
|
1453
|
+
</button>
|
|
1454
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-playlist" [class.accent]="repeatMode() !== 'off'" (click)="cycleRepeat.emit()" [attr.title]="repeatLabel()" [attr.aria-label]="repeatLabel()">
|
|
1455
|
+
@if (repeatMode() === 'one') {
|
|
1456
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="repeat-one" />
|
|
1457
|
+
} @else {
|
|
1458
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="repeat" />
|
|
1459
|
+
}
|
|
1460
|
+
</button>
|
|
1461
|
+
}
|
|
1462
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-tracks" (click)="openMenu.emit('tracks')" [attr.title]="tracksLabel()" [attr.aria-label]="tracksLabel()">
|
|
1463
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="tracks" />
|
|
1464
|
+
</button>
|
|
1465
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-captions" (click)="captions.emit()" [attr.title]="captionsLabel()" [attr.aria-label]="captionsLabel()">
|
|
1466
|
+
@if (captionsActive()) {
|
|
1467
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="captions" />
|
|
1468
|
+
} @else {
|
|
1469
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="captions-off" />
|
|
1470
|
+
}
|
|
1471
|
+
</button>
|
|
1472
|
+
<!-- The rate itself, as a pill: what it is shows at a glance. -->
|
|
1473
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-speed" (click)="openMenu.emit('speed')" [attr.title]="speedLabel()" [attr.aria-label]="speedLabel()">
|
|
1474
|
+
<span class="ngxsp-vod-speedBadge">{{ speedBadge() }}</span>
|
|
1475
|
+
</button>
|
|
1476
|
+
@if (showAirPlay()) {
|
|
1477
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-airplay" [class.accent]="airPlayActive()" (click)="airPlay.emit()" [attr.title]="airPlayLabel()" [attr.aria-label]="airPlayLabel()">
|
|
1478
|
+
<ngxsp-smart-icon class="ngxsp-icon" [name]="airPlayActive() ? 'airplay-active' : 'airplay'" />
|
|
1479
|
+
</button>
|
|
1480
|
+
}
|
|
1481
|
+
@if (showCast()) {
|
|
1482
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-cast" [class.accent]="castActive()" (click)="cast.emit()" [attr.title]="castHint()" [attr.aria-label]="castLabel()">
|
|
1483
|
+
<ngxsp-smart-icon class="ngxsp-icon" [name]="castActive() ? 'devices-active' : 'devices'" />
|
|
1484
|
+
</button>
|
|
1485
|
+
}
|
|
1486
|
+
@if (!isAudioMode() && pipSupported()) {
|
|
1487
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-pip" (click)="pip.emit()" [attr.title]="pipLabel()" [attr.aria-label]="pipLabel()">
|
|
1488
|
+
@if (pipActive()) {
|
|
1489
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="pip-active" />
|
|
1490
|
+
} @else {
|
|
1491
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="pip" />
|
|
1492
|
+
}
|
|
1493
|
+
</button>
|
|
1494
|
+
}
|
|
1495
|
+
@if (!isAudioMode()) {
|
|
1496
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-fullscreen" (click)="fullscreen.emit()" [attr.title]="fullscreenLabel()" [attr.aria-label]="fullscreenLabel()">
|
|
1497
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="fullscreen" />
|
|
1498
|
+
</button>
|
|
1499
|
+
}
|
|
1500
|
+
</div>
|
|
1501
|
+
|
|
1502
|
+
<ngxsp-time-slider
|
|
1503
|
+
class="ngxsp-vod-range"
|
|
1504
|
+
[currentTime]="currentTime()"
|
|
1505
|
+
[duration]="duration()"
|
|
1506
|
+
[buffered]="buffered()"
|
|
1507
|
+
[profile]="profile()"
|
|
1508
|
+
[durationHintSec]="timelineDurationSec()"
|
|
1509
|
+
[markers]="timelineMarkers()"
|
|
1510
|
+
(seek)="scrub.emit($event)"
|
|
1511
|
+
/>
|
|
1512
|
+
|
|
1513
|
+
<ng-content select="ngxsp-player-menu" />
|
|
1514
|
+
</div>
|
|
1515
|
+
`, isInline: true, styles: [":host{display:contents}.ngxsp-icon{width:20px;height:20px;display:block;flex:0 0 auto}.ngxsp-icon-lg{width:28px;height:28px}.ngxsp-vod-top{position:absolute;left:0;right:0;top:0;display:flex;align-items:flex-start;gap:12px;padding:16px 18px 40px;background:linear-gradient(to bottom,#0000009e,#0000);pointer-events:none;opacity:0;transition:opacity .2s ease;z-index:3}:host-context(.ngxsp-ui-visible) .ngxsp-vod-top{opacity:1}.ngxsp-vod-titleWrap{min-width:0;display:flex;flex-direction:column;gap:2px}.ngxsp-vod-eyebrow{font-size:12px;font-weight:600;color:#ffffffb8}.ngxsp-vod-title{font-weight:600;font-size:16px;color:#fff;text-shadow:0 1px 2px rgb(0 0 0 / .5);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ngxsp-vod-topHint{display:none}.ngxsp-vod-status{display:inline-flex;align-items:center;gap:6px;height:24px;padding:0 10px;border-radius:999px;background:#00000073;color:#ffffffdb;font-size:11px;font-weight:500;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.ngxsp-vod-status:before{content:\"\";width:6px;height:6px;border-radius:50%;background:#ffffff80}.ngxsp-vod-status[data-state=playing]:before{background:var(--nsp-success, #3ecf8e)}.ngxsp-vod-spacer{flex:1}.ngxsp-vod-centerDock{position:absolute;inset:0;margin:auto;width:max-content;height:max-content;display:flex;align-items:center;gap:20px;z-index:4;opacity:0;visibility:hidden;transform:scale(.96);transition:opacity .2s ease,transform .2s ease,visibility 0s linear .2s;pointer-events:none}:host-context(.ngxsp-ui-visible) .ngxsp-vod-centerDock{opacity:1;visibility:visible;transform:none;pointer-events:auto;transition:opacity .2s ease,transform .2s ease,visibility 0s}:host-context(.ngxsp-root[data-kind=\"error\"]) .ngxsp-vod-centerDock,:host-context(.ngxsp-root[data-kind=\"error\"]) .ngxsp-vod-top{display:none}.ngxsp-vod-center{height:68px;width:68px;border-radius:999px;border:0;background:#fffffff0;color:#0d0e11;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 12px 40px #00000073;transition:transform .15s ease,background-color .15s ease}.ngxsp-vod-center:hover{transform:scale(1.05);background:#fff}.ngxsp-vod-side{position:relative;height:48px;min-width:48px;width:48px;border-radius:999px;border:0;background:#00000073;color:#fff;padding:0;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);transition:background-color .15s ease}.ngxsp-vod-side-wide{min-width:48px}.ngxsp-vod-side-wide .ngxsp-icon{width:24px;height:24px}.ngxsp-vod-side:hover{background:#000000a6}.ngxsp-vod-bottom{position:absolute;left:0;right:0;bottom:0;padding:40px 14px 8px;background:linear-gradient(180deg,#0000,#0000008c 45%,#000000d1);display:flex;flex-direction:column;align-items:stretch;gap:0;z-index:4;opacity:0;transition:opacity .2s ease}:host-context(.ngxsp-ui-visible) .ngxsp-vod-bottom{opacity:1}.ngxsp-vod-metaRow{display:flex;align-items:center;gap:4px;width:100%;min-height:40px}.ngxsp-vod-speedBadge{box-sizing:border-box;min-width:30px;padding:2px 5px;border:1.5px solid currentColor;border-radius:999px;font-size:11px;font-weight:700;line-height:1.2;font-variant-numeric:tabular-nums;white-space:nowrap}.ngxsp-vod-time{padding:0 8px;color:#ffffffeb;font-variant-numeric:tabular-nums;font-size:12px;font-weight:500;white-space:nowrap}.ngxsp-vod-range{order:-1;min-width:100%;min-height:32px;--ngxsp-track-size:4px;--ngxsp-track-bg:rgb(255 255 255 / .22);--ngxsp-buffered-bg:rgb(255 255 255 / .34);--ngxsp-accent:var(--nsp-media-accent, #8b90ff);--ngxsp-thumb-size:13px;--ngxsp-thumb-bg:#fff;--ngxsp-marker-width:2px;--ngxsp-marker-height:8px;--ngxsp-marker-bg:rgb(0 0 0 / .55);--ngxsp-marker-active-bg:#fff;--ngxsp-marker-active-shadow:none;--ngxsp-preview-bg:rgb(13 14 17 / .92);--ngxsp-preview-border:rgb(255 255 255 / .12);--ngxsp-preview-muted:rgb(255 255 255 / .64)}.ngxsp-vod-range:hover{--ngxsp-track-size:6px}.ngxsp-vod-volumeGroup{display:inline-flex;align-items:center}.ngxsp-vod-volumeControl{--ngxsp-control-size:36px;--ngxsp-volume-compact-min:72px;--ngxsp-volume-compact-max:104px;--ngxsp-volume-button-bg:transparent;--ngxsp-volume-button-border:0;--ngxsp-volume-button-radius:6px;--ngxsp-volume-accent:#fff;--ngxsp-volume-track-bg:rgb(255 255 255 / .28);--ngxsp-volume-thumb-bg:#fff;--ngxsp-volume-thumb-size:12px;--ngxsp-volume-thumb-shadow:none;--ngxsp-focus-ring:0 0 0 2px var(--nsp-media-accent, #8b90ff);color:#fff}.ngxsp-vod-label{font-size:12px;color:#ffffffa3}.ngxsp-vod-chip{position:relative;width:36px;height:36px;min-width:36px;min-height:36px;padding:0;border:0;border-radius:6px;background:transparent;color:#ffffffeb;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background-color .15s ease,color .15s ease}.ngxsp-vod-chip:hover:not(:disabled){background:#ffffff1f;color:#fff}.ngxsp-vod-chip:disabled{opacity:.4;cursor:default}.ngxsp-vod-chip.accent{color:var(--nsp-media-accent, #8b90ff)}.ngxsp-vod-chip-quiet{background:transparent}.ngxsp-vod-center:disabled,.ngxsp-vod-side:disabled{opacity:.38;cursor:not-allowed}.ngxsp-vod-center:focus-visible,.ngxsp-vod-side:focus-visible,.ngxsp-vod-chip:focus-visible{outline:2px solid var(--nsp-media-accent, #8b90ff);outline-offset:1px}.ngxsp-buffer{height:4px;border-radius:999px;background:#ffffff1f;overflow:hidden}.ngxsp-bufferFill{height:100%;border-radius:999px;background:var(--nsp-media-accent, #8b90ff)}@media(max-width:860px){.ngxsp-vod-top{padding:12px 14px 28px}.ngxsp-vod-centerDock{gap:14px}.ngxsp-vod-center{height:58px;width:58px}.ngxsp-vod-side{height:42px;width:42px;min-width:42px}.ngxsp-vod-bottom{padding:36px 8px 6px}.ngxsp-vod-chip-speed,.ngxsp-vod-chip-tracks{display:none}}@media(max-width:640px){.ngxsp-icon{width:18px;height:18px}.ngxsp-icon-lg{width:24px;height:24px}.ngxsp-vod-title{font-size:14px}.ngxsp-vod-status{display:none}.ngxsp-vod-time{font-size:11px;padding:0 4px}.ngxsp-vod-center{height:52px;width:52px}.ngxsp-vod-side{height:38px;width:38px;min-width:38px}.ngxsp-vod-metaRow{min-height:34px;gap:2px}.ngxsp-vod-chip{width:32px;height:32px;min-width:32px;min-height:32px}.ngxsp-vod-bottom .ngxsp-vod-volumeControl.ngxsp-vc-compact ::ng-deep .ngxsp-vc-shell{grid-template-columns:32px 64px}.ngxsp-vod-bottom .ngxsp-vod-volumeControl.ngxsp-vc-compact ::ng-deep .ngxsp-vc-range{opacity:1;visibility:visible;transform:none;pointer-events:auto}.ngxsp-vod-chip-playlist,.ngxsp-vod-chip-captions,.ngxsp-vod-chip-pip,.ngxsp-vod-top,.ngxsp-vod-side{display:none}}@media(prefers-reduced-motion:reduce){.ngxsp-vod-top,.ngxsp-vod-centerDock,.ngxsp-vod-bottom,.ngxsp-vod-center,.ngxsp-vod-side,.ngxsp-vod-chip{transition:none}}\n"], dependencies: [{ kind: "component", type: SmartPlayerIconComponent, selector: "ngxsp-smart-icon", inputs: ["name"] }, { kind: "component", type: NgxspPlayButton, selector: "ngxsp-play-button", inputs: ["playing", "disabled", "playLabel", "pauseLabel"], outputs: ["playToggle"] }, { kind: "component", type: NgxspTimeSlider, selector: "ngxsp-time-slider", inputs: ["currentTime", "duration", "buffered", "profile", "disabled", "durationHintSec", "markers", "step", "bigStep", "ariaLabel", "liveLabel"], outputs: ["seek"] }, { kind: "component", type: NgxspVolumeControl, selector: "ngxsp-volume-control", inputs: ["volume", "muted", "disabled", "compact", "showValue", "step", "volumeLabel", "muteLabel", "unmuteLabel"], outputs: ["volumeChange", "muteToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1516
|
+
}
|
|
1517
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: PlayerControlsVodComponent, decorators: [{
|
|
1518
|
+
type: Component,
|
|
1519
|
+
args: [{ selector: 'ngxsp-player-controls-vod', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [SmartPlayerIconComponent, NgxspPlayButton, NgxspTimeSlider, NgxspVolumeControl], template: `
|
|
1520
|
+
<div class="ngxsp-vod-top">
|
|
1521
|
+
<div class="ngxsp-vod-titleWrap">
|
|
1522
|
+
<div class="ngxsp-vod-eyebrow">{{ isAudioMode() ? nowListeningLabel() : nowPlayingLabel() }}</div>
|
|
1523
|
+
<div class="ngxsp-vod-title">{{ displayTitle() }}</div>
|
|
1524
|
+
</div>
|
|
1525
|
+
<div class="ngxsp-vod-spacer"></div>
|
|
1526
|
+
<div class="ngxsp-vod-status" [attr.data-state]="playing() ? 'playing' : 'idle'">{{ statusLabel() }}</div>
|
|
1527
|
+
@if (!isAudioMode()) {
|
|
1528
|
+
<div class="ngxsp-vod-topHint">{{ fullscreenHintLabel() }}</div>
|
|
1529
|
+
}
|
|
1530
|
+
</div>
|
|
1531
|
+
|
|
1532
|
+
<div class="ngxsp-vod-centerDock">
|
|
1533
|
+
@if (hasPlaylistControls()) {
|
|
1534
|
+
<button type="button" class="ngxsp-vod-side" (click)="prev.emit()" [disabled]="!canPrev()" [attr.aria-label]="previousLabel()">
|
|
1535
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="prev" />
|
|
1536
|
+
</button>
|
|
1537
|
+
}
|
|
1538
|
+
<button type="button" class="ngxsp-vod-side ngxsp-vod-side-wide" (click)="seekRelative.emit(-10)" aria-label="Seek backward 10 seconds">
|
|
1539
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="seek-back" />
|
|
1540
|
+
</button>
|
|
1541
|
+
<ngxsp-play-button class="ngxsp-vod-center" [playing]="playing()" [playLabel]="playLabel()" [pauseLabel]="pauseLabel()" (playToggle)="playToggle.emit()">
|
|
1542
|
+
@if (playing()) {
|
|
1543
|
+
<ngxsp-smart-icon class="ngxsp-icon ngxsp-icon-lg" name="pause" />
|
|
1544
|
+
} @else {
|
|
1545
|
+
<ngxsp-smart-icon class="ngxsp-icon ngxsp-icon-lg" name="play" />
|
|
1546
|
+
}
|
|
1547
|
+
</ngxsp-play-button>
|
|
1548
|
+
<button type="button" class="ngxsp-vod-side ngxsp-vod-side-wide" (click)="seekRelative.emit(10)" aria-label="Seek forward 10 seconds">
|
|
1549
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="seek-forward" />
|
|
1550
|
+
</button>
|
|
1551
|
+
@if (hasPlaylistControls()) {
|
|
1552
|
+
<button type="button" class="ngxsp-vod-side" (click)="next.emit()" [disabled]="!canNext()" [attr.aria-label]="nextLabel()">
|
|
1553
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="next" />
|
|
1554
|
+
</button>
|
|
1555
|
+
}
|
|
1556
|
+
</div>
|
|
1557
|
+
|
|
1558
|
+
<div class="ngxsp-controls ngxsp-vod-bottom">
|
|
1559
|
+
<div class="ngxsp-vod-metaRow">
|
|
1560
|
+
@if (hasPlaylistControls()) {
|
|
1561
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-prev" (click)="prev.emit()" [disabled]="!canPrev()" [attr.title]="previousLabel()" [attr.aria-label]="previousLabel()">
|
|
1562
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="prev" />
|
|
1563
|
+
</button>
|
|
1564
|
+
}
|
|
1565
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-play" (click)="playToggle.emit()" [attr.title]="playing() ? pauseLabel() : playLabel()" [attr.aria-label]="playing() ? pauseLabel() : playLabel()">
|
|
1566
|
+
@if (playing()) {
|
|
1567
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="pause" />
|
|
1568
|
+
} @else {
|
|
1569
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="play" />
|
|
1570
|
+
}
|
|
1571
|
+
</button>
|
|
1572
|
+
@if (hasPlaylistControls()) {
|
|
1573
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-next" (click)="next.emit()" [disabled]="!canNext()" [attr.title]="nextLabel()" [attr.aria-label]="nextLabel()">
|
|
1574
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="next" />
|
|
1575
|
+
</button>
|
|
1576
|
+
}
|
|
1577
|
+
<div class="ngxsp-vod-time">{{ fmtTime(currentTime()) }} / {{ fmtTime(duration()) }}</div>
|
|
1578
|
+
<div class="ngxsp-vod-volumeGroup">
|
|
1579
|
+
<ngxsp-volume-control
|
|
1580
|
+
class="ngxsp-vod-volumeControl"
|
|
1581
|
+
[compact]="true"
|
|
1582
|
+
[volume]="volume()"
|
|
1583
|
+
[volumeLabel]="volumeLabel()"
|
|
1584
|
+
[muteLabel]="muteLabel()"
|
|
1585
|
+
[unmuteLabel]="unmuteLabel()"
|
|
1586
|
+
(muteToggle)="mute.emit()"
|
|
1587
|
+
(volumeChange)="volumeChange.emit($event)"
|
|
1588
|
+
/>
|
|
1589
|
+
</div>
|
|
1590
|
+
<ng-content select="[ngxsp-player-inline-controls]" />
|
|
1591
|
+
<div class="ngxsp-vod-spacer"></div>
|
|
1592
|
+
@if (hasPlaylistControls()) {
|
|
1593
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-playlist" [class.accent]="shuffleActive()" (click)="toggleShuffle.emit()" [attr.title]="shuffleLabel()" [attr.aria-label]="shuffleLabel()">
|
|
1594
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="shuffle" />
|
|
1595
|
+
</button>
|
|
1596
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-playlist" [class.accent]="repeatMode() !== 'off'" (click)="cycleRepeat.emit()" [attr.title]="repeatLabel()" [attr.aria-label]="repeatLabel()">
|
|
1597
|
+
@if (repeatMode() === 'one') {
|
|
1598
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="repeat-one" />
|
|
1599
|
+
} @else {
|
|
1600
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="repeat" />
|
|
1601
|
+
}
|
|
1602
|
+
</button>
|
|
1603
|
+
}
|
|
1604
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-tracks" (click)="openMenu.emit('tracks')" [attr.title]="tracksLabel()" [attr.aria-label]="tracksLabel()">
|
|
1605
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="tracks" />
|
|
1606
|
+
</button>
|
|
1607
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-captions" (click)="captions.emit()" [attr.title]="captionsLabel()" [attr.aria-label]="captionsLabel()">
|
|
1608
|
+
@if (captionsActive()) {
|
|
1609
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="captions" />
|
|
1610
|
+
} @else {
|
|
1611
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="captions-off" />
|
|
1612
|
+
}
|
|
1613
|
+
</button>
|
|
1614
|
+
<!-- The rate itself, as a pill: what it is shows at a glance. -->
|
|
1615
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-speed" (click)="openMenu.emit('speed')" [attr.title]="speedLabel()" [attr.aria-label]="speedLabel()">
|
|
1616
|
+
<span class="ngxsp-vod-speedBadge">{{ speedBadge() }}</span>
|
|
1617
|
+
</button>
|
|
1618
|
+
@if (showAirPlay()) {
|
|
1619
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-airplay" [class.accent]="airPlayActive()" (click)="airPlay.emit()" [attr.title]="airPlayLabel()" [attr.aria-label]="airPlayLabel()">
|
|
1620
|
+
<ngxsp-smart-icon class="ngxsp-icon" [name]="airPlayActive() ? 'airplay-active' : 'airplay'" />
|
|
1621
|
+
</button>
|
|
1622
|
+
}
|
|
1623
|
+
@if (showCast()) {
|
|
1624
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-cast" [class.accent]="castActive()" (click)="cast.emit()" [attr.title]="castHint()" [attr.aria-label]="castLabel()">
|
|
1625
|
+
<ngxsp-smart-icon class="ngxsp-icon" [name]="castActive() ? 'devices-active' : 'devices'" />
|
|
1626
|
+
</button>
|
|
1627
|
+
}
|
|
1628
|
+
@if (!isAudioMode() && pipSupported()) {
|
|
1629
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-pip" (click)="pip.emit()" [attr.title]="pipLabel()" [attr.aria-label]="pipLabel()">
|
|
1630
|
+
@if (pipActive()) {
|
|
1631
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="pip-active" />
|
|
1632
|
+
} @else {
|
|
1633
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="pip" />
|
|
1634
|
+
}
|
|
1635
|
+
</button>
|
|
1636
|
+
}
|
|
1637
|
+
@if (!isAudioMode()) {
|
|
1638
|
+
<button type="button" class="ngxsp-vod-chip ngxsp-vod-chip-fullscreen" (click)="fullscreen.emit()" [attr.title]="fullscreenLabel()" [attr.aria-label]="fullscreenLabel()">
|
|
1639
|
+
<ngxsp-smart-icon class="ngxsp-icon" name="fullscreen" />
|
|
1640
|
+
</button>
|
|
1641
|
+
}
|
|
1642
|
+
</div>
|
|
1643
|
+
|
|
1644
|
+
<ngxsp-time-slider
|
|
1645
|
+
class="ngxsp-vod-range"
|
|
1646
|
+
[currentTime]="currentTime()"
|
|
1647
|
+
[duration]="duration()"
|
|
1648
|
+
[buffered]="buffered()"
|
|
1649
|
+
[profile]="profile()"
|
|
1650
|
+
[durationHintSec]="timelineDurationSec()"
|
|
1651
|
+
[markers]="timelineMarkers()"
|
|
1652
|
+
(seek)="scrub.emit($event)"
|
|
1653
|
+
/>
|
|
1654
|
+
|
|
1655
|
+
<ng-content select="ngxsp-player-menu" />
|
|
1656
|
+
</div>
|
|
1657
|
+
`, styles: [":host{display:contents}.ngxsp-icon{width:20px;height:20px;display:block;flex:0 0 auto}.ngxsp-icon-lg{width:28px;height:28px}.ngxsp-vod-top{position:absolute;left:0;right:0;top:0;display:flex;align-items:flex-start;gap:12px;padding:16px 18px 40px;background:linear-gradient(to bottom,#0000009e,#0000);pointer-events:none;opacity:0;transition:opacity .2s ease;z-index:3}:host-context(.ngxsp-ui-visible) .ngxsp-vod-top{opacity:1}.ngxsp-vod-titleWrap{min-width:0;display:flex;flex-direction:column;gap:2px}.ngxsp-vod-eyebrow{font-size:12px;font-weight:600;color:#ffffffb8}.ngxsp-vod-title{font-weight:600;font-size:16px;color:#fff;text-shadow:0 1px 2px rgb(0 0 0 / .5);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ngxsp-vod-topHint{display:none}.ngxsp-vod-status{display:inline-flex;align-items:center;gap:6px;height:24px;padding:0 10px;border-radius:999px;background:#00000073;color:#ffffffdb;font-size:11px;font-weight:500;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.ngxsp-vod-status:before{content:\"\";width:6px;height:6px;border-radius:50%;background:#ffffff80}.ngxsp-vod-status[data-state=playing]:before{background:var(--nsp-success, #3ecf8e)}.ngxsp-vod-spacer{flex:1}.ngxsp-vod-centerDock{position:absolute;inset:0;margin:auto;width:max-content;height:max-content;display:flex;align-items:center;gap:20px;z-index:4;opacity:0;visibility:hidden;transform:scale(.96);transition:opacity .2s ease,transform .2s ease,visibility 0s linear .2s;pointer-events:none}:host-context(.ngxsp-ui-visible) .ngxsp-vod-centerDock{opacity:1;visibility:visible;transform:none;pointer-events:auto;transition:opacity .2s ease,transform .2s ease,visibility 0s}:host-context(.ngxsp-root[data-kind=\"error\"]) .ngxsp-vod-centerDock,:host-context(.ngxsp-root[data-kind=\"error\"]) .ngxsp-vod-top{display:none}.ngxsp-vod-center{height:68px;width:68px;border-radius:999px;border:0;background:#fffffff0;color:#0d0e11;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 12px 40px #00000073;transition:transform .15s ease,background-color .15s ease}.ngxsp-vod-center:hover{transform:scale(1.05);background:#fff}.ngxsp-vod-side{position:relative;height:48px;min-width:48px;width:48px;border-radius:999px;border:0;background:#00000073;color:#fff;padding:0;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);transition:background-color .15s ease}.ngxsp-vod-side-wide{min-width:48px}.ngxsp-vod-side-wide .ngxsp-icon{width:24px;height:24px}.ngxsp-vod-side:hover{background:#000000a6}.ngxsp-vod-bottom{position:absolute;left:0;right:0;bottom:0;padding:40px 14px 8px;background:linear-gradient(180deg,#0000,#0000008c 45%,#000000d1);display:flex;flex-direction:column;align-items:stretch;gap:0;z-index:4;opacity:0;transition:opacity .2s ease}:host-context(.ngxsp-ui-visible) .ngxsp-vod-bottom{opacity:1}.ngxsp-vod-metaRow{display:flex;align-items:center;gap:4px;width:100%;min-height:40px}.ngxsp-vod-speedBadge{box-sizing:border-box;min-width:30px;padding:2px 5px;border:1.5px solid currentColor;border-radius:999px;font-size:11px;font-weight:700;line-height:1.2;font-variant-numeric:tabular-nums;white-space:nowrap}.ngxsp-vod-time{padding:0 8px;color:#ffffffeb;font-variant-numeric:tabular-nums;font-size:12px;font-weight:500;white-space:nowrap}.ngxsp-vod-range{order:-1;min-width:100%;min-height:32px;--ngxsp-track-size:4px;--ngxsp-track-bg:rgb(255 255 255 / .22);--ngxsp-buffered-bg:rgb(255 255 255 / .34);--ngxsp-accent:var(--nsp-media-accent, #8b90ff);--ngxsp-thumb-size:13px;--ngxsp-thumb-bg:#fff;--ngxsp-marker-width:2px;--ngxsp-marker-height:8px;--ngxsp-marker-bg:rgb(0 0 0 / .55);--ngxsp-marker-active-bg:#fff;--ngxsp-marker-active-shadow:none;--ngxsp-preview-bg:rgb(13 14 17 / .92);--ngxsp-preview-border:rgb(255 255 255 / .12);--ngxsp-preview-muted:rgb(255 255 255 / .64)}.ngxsp-vod-range:hover{--ngxsp-track-size:6px}.ngxsp-vod-volumeGroup{display:inline-flex;align-items:center}.ngxsp-vod-volumeControl{--ngxsp-control-size:36px;--ngxsp-volume-compact-min:72px;--ngxsp-volume-compact-max:104px;--ngxsp-volume-button-bg:transparent;--ngxsp-volume-button-border:0;--ngxsp-volume-button-radius:6px;--ngxsp-volume-accent:#fff;--ngxsp-volume-track-bg:rgb(255 255 255 / .28);--ngxsp-volume-thumb-bg:#fff;--ngxsp-volume-thumb-size:12px;--ngxsp-volume-thumb-shadow:none;--ngxsp-focus-ring:0 0 0 2px var(--nsp-media-accent, #8b90ff);color:#fff}.ngxsp-vod-label{font-size:12px;color:#ffffffa3}.ngxsp-vod-chip{position:relative;width:36px;height:36px;min-width:36px;min-height:36px;padding:0;border:0;border-radius:6px;background:transparent;color:#ffffffeb;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background-color .15s ease,color .15s ease}.ngxsp-vod-chip:hover:not(:disabled){background:#ffffff1f;color:#fff}.ngxsp-vod-chip:disabled{opacity:.4;cursor:default}.ngxsp-vod-chip.accent{color:var(--nsp-media-accent, #8b90ff)}.ngxsp-vod-chip-quiet{background:transparent}.ngxsp-vod-center:disabled,.ngxsp-vod-side:disabled{opacity:.38;cursor:not-allowed}.ngxsp-vod-center:focus-visible,.ngxsp-vod-side:focus-visible,.ngxsp-vod-chip:focus-visible{outline:2px solid var(--nsp-media-accent, #8b90ff);outline-offset:1px}.ngxsp-buffer{height:4px;border-radius:999px;background:#ffffff1f;overflow:hidden}.ngxsp-bufferFill{height:100%;border-radius:999px;background:var(--nsp-media-accent, #8b90ff)}@media(max-width:860px){.ngxsp-vod-top{padding:12px 14px 28px}.ngxsp-vod-centerDock{gap:14px}.ngxsp-vod-center{height:58px;width:58px}.ngxsp-vod-side{height:42px;width:42px;min-width:42px}.ngxsp-vod-bottom{padding:36px 8px 6px}.ngxsp-vod-chip-speed,.ngxsp-vod-chip-tracks{display:none}}@media(max-width:640px){.ngxsp-icon{width:18px;height:18px}.ngxsp-icon-lg{width:24px;height:24px}.ngxsp-vod-title{font-size:14px}.ngxsp-vod-status{display:none}.ngxsp-vod-time{font-size:11px;padding:0 4px}.ngxsp-vod-center{height:52px;width:52px}.ngxsp-vod-side{height:38px;width:38px;min-width:38px}.ngxsp-vod-metaRow{min-height:34px;gap:2px}.ngxsp-vod-chip{width:32px;height:32px;min-width:32px;min-height:32px}.ngxsp-vod-bottom .ngxsp-vod-volumeControl.ngxsp-vc-compact ::ng-deep .ngxsp-vc-shell{grid-template-columns:32px 64px}.ngxsp-vod-bottom .ngxsp-vod-volumeControl.ngxsp-vc-compact ::ng-deep .ngxsp-vc-range{opacity:1;visibility:visible;transform:none;pointer-events:auto}.ngxsp-vod-chip-playlist,.ngxsp-vod-chip-captions,.ngxsp-vod-chip-pip,.ngxsp-vod-top,.ngxsp-vod-side{display:none}}@media(prefers-reduced-motion:reduce){.ngxsp-vod-top,.ngxsp-vod-centerDock,.ngxsp-vod-bottom,.ngxsp-vod-center,.ngxsp-vod-side,.ngxsp-vod-chip{transition:none}}\n"] }]
|
|
1658
|
+
}], propDecorators: { playing: [{ type: i0.Input, args: [{ isSignal: true, alias: "playing", required: false }] }], isAudioMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isAudioMode", required: false }] }], displayTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayTitle", required: false }] }], nowPlayingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nowPlayingLabel", required: false }] }], nowListeningLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nowListeningLabel", required: false }] }], fullscreenHintLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullscreenHintLabel", required: false }] }], statusLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "statusLabel", required: false }] }], keyboardHint: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardHint", required: false }] }], hasPlaylistControls: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasPlaylistControls", required: false }] }], canPrev: [{ type: i0.Input, args: [{ isSignal: true, alias: "canPrev", required: false }] }], canNext: [{ type: i0.Input, args: [{ isSignal: true, alias: "canNext", required: false }] }], previousLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "previousLabel", required: false }] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], playLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "playLabel", required: false }] }], pauseLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pauseLabel", required: false }] }], shuffleActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "shuffleActive", required: false }] }], shuffleLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "shuffleLabel", required: false }] }], repeatMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "repeatMode", required: false }] }], repeatLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "repeatLabel", required: false }] }], captionsActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionsActive", required: false }] }], tracksLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "tracksLabel", required: false }] }], captionsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionsLabel", required: false }] }], speedLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "speedLabel", required: false }] }], volumeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "volumeLabel", required: false }] }], muteLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "muteLabel", required: false }] }], unmuteLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "unmuteLabel", required: false }] }], showAirPlay: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAirPlay", required: false }] }], airPlayActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "airPlayActive", required: false }] }], airPlayLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "airPlayLabel", required: false }] }], showCast: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCast", required: false }] }], castActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "castActive", required: false }] }], castLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "castLabel", required: false }] }], castHint: [{ type: i0.Input, args: [{ isSignal: true, alias: "castHint", required: false }] }], pipSupported: [{ type: i0.Input, args: [{ isSignal: true, alias: "pipSupported", required: false }] }], pipActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "pipActive", required: false }] }], pipLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pipLabel", required: false }] }], fullscreenLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullscreenLabel", required: false }] }], volume: [{ type: i0.Input, args: [{ isSignal: true, alias: "volume", required: false }] }], currentTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTime", required: false }] }], duration: [{ type: i0.Input, args: [{ isSignal: true, alias: "duration", required: false }] }], buffered: [{ type: i0.Input, args: [{ isSignal: true, alias: "buffered", required: false }] }], bufferPercent: [{ type: i0.Input, args: [{ isSignal: true, alias: "bufferPercent", required: false }] }], profile: [{ type: i0.Input, args: [{ isSignal: true, alias: "profile", required: false }] }], timelineDurationSec: [{ type: i0.Input, args: [{ isSignal: true, alias: "timelineDurationSec", required: false }] }], timelineMarkers: [{ type: i0.Input, args: [{ isSignal: true, alias: "timelineMarkers", required: false }] }], playToggle: [{ type: i0.Output, args: ["playToggle"] }], seekRelative: [{ type: i0.Output, args: ["seekRelative"] }], prev: [{ type: i0.Output, args: ["prev"] }], next: [{ type: i0.Output, args: ["next"] }], toggleShuffle: [{ type: i0.Output, args: ["toggleShuffle"] }], cycleRepeat: [{ type: i0.Output, args: ["cycleRepeat"] }], openMenu: [{ type: i0.Output, args: ["openMenu"] }], captions: [{ type: i0.Output, args: ["captions"] }], airPlay: [{ type: i0.Output, args: ["airPlay"] }], cast: [{ type: i0.Output, args: ["cast"] }], pip: [{ type: i0.Output, args: ["pip"] }], fullscreen: [{ type: i0.Output, args: ["fullscreen"] }], mute: [{ type: i0.Output, args: ["mute"] }], scrub: [{ type: i0.Output, args: ["scrub"] }], volumeChange: [{ type: i0.Output, args: ["volumeChange"] }] } });
|
|
1659
|
+
|
|
1660
|
+
/**
|
|
1661
|
+
* Skin « édition » du lecteur : registre instrument (cf. docs/UX_VISION.md).
|
|
1662
|
+
* Titre display, coins francs, libellés en capitales, accent champagne, et le
|
|
1663
|
+
* traitement direct (NgxspLiveBadge + « Au direct ») en profil `live`.
|
|
1664
|
+
* Compose les primitives de @ngxsp/ui-core. Le menu est projeté via <ng-content>.
|
|
1665
|
+
*/
|
|
1666
|
+
class PlayerControlsEditionComponent {
|
|
1667
|
+
profile = input('vod', ...(ngDevMode ? [{ debugName: "profile" }] : []));
|
|
1668
|
+
playing = input(false, ...(ngDevMode ? [{ debugName: "playing" }] : []));
|
|
1669
|
+
displayTitle = input('Player', ...(ngDevMode ? [{ debugName: "displayTitle" }] : []));
|
|
1670
|
+
metaLabel = input('', ...(ngDevMode ? [{ debugName: "metaLabel" }] : []));
|
|
1671
|
+
statusLabel = input('', ...(ngDevMode ? [{ debugName: "statusLabel" }] : []));
|
|
1672
|
+
behindSec = input(0, ...(ngDevMode ? [{ debugName: "behindSec" }] : []));
|
|
1673
|
+
liveLabel = input('Direct', ...(ngDevMode ? [{ debugName: "liveLabel" }] : []));
|
|
1674
|
+
captionsActive = input(false, ...(ngDevMode ? [{ debugName: "captionsActive" }] : []));
|
|
1675
|
+
speedLabel = input('1x', ...(ngDevMode ? [{ debugName: "speedLabel" }] : []));
|
|
1676
|
+
showAirPlay = input(false, ...(ngDevMode ? [{ debugName: "showAirPlay" }] : []));
|
|
1677
|
+
airPlayActive = input(false, ...(ngDevMode ? [{ debugName: "airPlayActive" }] : []));
|
|
1678
|
+
airPlayLabel = input('AirPlay', ...(ngDevMode ? [{ debugName: "airPlayLabel" }] : []));
|
|
1679
|
+
showCast = input(false, ...(ngDevMode ? [{ debugName: "showCast" }] : []));
|
|
1680
|
+
castActive = input(false, ...(ngDevMode ? [{ debugName: "castActive" }] : []));
|
|
1681
|
+
pipSupported = input(false, ...(ngDevMode ? [{ debugName: "pipSupported" }] : []));
|
|
1682
|
+
pipActive = input(false, ...(ngDevMode ? [{ debugName: "pipActive" }] : []));
|
|
1683
|
+
volume = input(1, ...(ngDevMode ? [{ debugName: "volume" }] : []));
|
|
1684
|
+
volumeLabel = input('Volume', ...(ngDevMode ? [{ debugName: "volumeLabel" }] : []));
|
|
1685
|
+
muteLabel = input('Mute', ...(ngDevMode ? [{ debugName: "muteLabel" }] : []));
|
|
1686
|
+
unmuteLabel = input('Restore sound', ...(ngDevMode ? [{ debugName: "unmuteLabel" }] : []));
|
|
1687
|
+
currentTime = input(0, ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
|
|
1688
|
+
duration = input(0, ...(ngDevMode ? [{ debugName: "duration" }] : []));
|
|
1689
|
+
buffered = input(0, ...(ngDevMode ? [{ debugName: "buffered" }] : []));
|
|
1690
|
+
playToggle = output();
|
|
1691
|
+
seekRelative = output();
|
|
1692
|
+
openMenu = output();
|
|
1693
|
+
captions = output();
|
|
1694
|
+
airPlay = output();
|
|
1695
|
+
cast = output();
|
|
1696
|
+
pip = output();
|
|
1697
|
+
mute = output();
|
|
1698
|
+
scrub = output();
|
|
1699
|
+
volumeChange = output();
|
|
1700
|
+
goLive = output();
|
|
1701
|
+
fmtTime(sec) {
|
|
1702
|
+
const s = Math.max(0, Math.floor(sec || 0));
|
|
1703
|
+
const h = Math.floor(s / 3600);
|
|
1704
|
+
const m = Math.floor((s % 3600) / 60);
|
|
1705
|
+
const r = s % 60;
|
|
1706
|
+
if (h > 0)
|
|
1707
|
+
return `${h}:${String(m).padStart(2, '0')}:${String(r).padStart(2, '0')}`;
|
|
1708
|
+
return `${m}:${String(r).padStart(2, '0')}`;
|
|
1709
|
+
}
|
|
1710
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: PlayerControlsEditionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1711
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: PlayerControlsEditionComponent, isStandalone: true, selector: "ngxsp-player-controls-edition", inputs: { profile: { classPropertyName: "profile", publicName: "profile", isSignal: true, isRequired: false, transformFunction: null }, playing: { classPropertyName: "playing", publicName: "playing", isSignal: true, isRequired: false, transformFunction: null }, displayTitle: { classPropertyName: "displayTitle", publicName: "displayTitle", isSignal: true, isRequired: false, transformFunction: null }, metaLabel: { classPropertyName: "metaLabel", publicName: "metaLabel", isSignal: true, isRequired: false, transformFunction: null }, statusLabel: { classPropertyName: "statusLabel", publicName: "statusLabel", isSignal: true, isRequired: false, transformFunction: null }, behindSec: { classPropertyName: "behindSec", publicName: "behindSec", isSignal: true, isRequired: false, transformFunction: null }, liveLabel: { classPropertyName: "liveLabel", publicName: "liveLabel", isSignal: true, isRequired: false, transformFunction: null }, captionsActive: { classPropertyName: "captionsActive", publicName: "captionsActive", isSignal: true, isRequired: false, transformFunction: null }, speedLabel: { classPropertyName: "speedLabel", publicName: "speedLabel", isSignal: true, isRequired: false, transformFunction: null }, showAirPlay: { classPropertyName: "showAirPlay", publicName: "showAirPlay", isSignal: true, isRequired: false, transformFunction: null }, airPlayActive: { classPropertyName: "airPlayActive", publicName: "airPlayActive", isSignal: true, isRequired: false, transformFunction: null }, airPlayLabel: { classPropertyName: "airPlayLabel", publicName: "airPlayLabel", isSignal: true, isRequired: false, transformFunction: null }, showCast: { classPropertyName: "showCast", publicName: "showCast", isSignal: true, isRequired: false, transformFunction: null }, castActive: { classPropertyName: "castActive", publicName: "castActive", isSignal: true, isRequired: false, transformFunction: null }, pipSupported: { classPropertyName: "pipSupported", publicName: "pipSupported", isSignal: true, isRequired: false, transformFunction: null }, pipActive: { classPropertyName: "pipActive", publicName: "pipActive", isSignal: true, isRequired: false, transformFunction: null }, volume: { classPropertyName: "volume", publicName: "volume", isSignal: true, isRequired: false, transformFunction: null }, volumeLabel: { classPropertyName: "volumeLabel", publicName: "volumeLabel", isSignal: true, isRequired: false, transformFunction: null }, muteLabel: { classPropertyName: "muteLabel", publicName: "muteLabel", isSignal: true, isRequired: false, transformFunction: null }, unmuteLabel: { classPropertyName: "unmuteLabel", publicName: "unmuteLabel", isSignal: true, isRequired: false, transformFunction: null }, currentTime: { classPropertyName: "currentTime", publicName: "currentTime", isSignal: true, isRequired: false, transformFunction: null }, duration: { classPropertyName: "duration", publicName: "duration", isSignal: true, isRequired: false, transformFunction: null }, buffered: { classPropertyName: "buffered", publicName: "buffered", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { playToggle: "playToggle", seekRelative: "seekRelative", openMenu: "openMenu", captions: "captions", airPlay: "airPlay", cast: "cast", pip: "pip", mute: "mute", scrub: "scrub", volumeChange: "volumeChange", goLive: "goLive" }, ngImport: i0, template: `
|
|
1712
|
+
<div class="ed-top">
|
|
1713
|
+
@if (profile() === 'live') {
|
|
1714
|
+
<ngxsp-live-badge [behindSec]="behindSec()" />
|
|
1715
|
+
}
|
|
1716
|
+
<div class="ed-titleWrap">
|
|
1717
|
+
<div class="ed-title">{{ displayTitle() }}</div>
|
|
1718
|
+
<div class="ed-meta">{{ metaLabel() }}</div>
|
|
1719
|
+
</div>
|
|
1720
|
+
<span class="ed-spacer"></span>
|
|
1721
|
+
<div class="ed-status">{{ statusLabel() }}</div>
|
|
1722
|
+
</div>
|
|
1723
|
+
|
|
1724
|
+
<div class="ed-bottom">
|
|
1725
|
+
<div class="ed-scrubRow">
|
|
1726
|
+
<span class="ed-time">{{ fmtTime(currentTime()) }}</span>
|
|
1727
|
+
<ngxsp-time-slider
|
|
1728
|
+
style="flex:1"
|
|
1729
|
+
[currentTime]="currentTime()"
|
|
1730
|
+
[duration]="duration()"
|
|
1731
|
+
[buffered]="buffered()"
|
|
1732
|
+
[profile]="profile()"
|
|
1733
|
+
(seek)="scrub.emit($event)"
|
|
1734
|
+
/>
|
|
1735
|
+
<span class="ed-time ed-time-dim">{{ profile() === 'live' ? liveLabel() : fmtTime(duration()) }}</span>
|
|
1736
|
+
</div>
|
|
1737
|
+
|
|
1738
|
+
<div class="ed-controlsRow">
|
|
1739
|
+
@if (profile() === 'live') {
|
|
1740
|
+
<button type="button" class="ed-live" (click)="goLive.emit()">
|
|
1741
|
+
<ngxsp-smart-icon class="ed-icon" name="next" /> Au direct
|
|
1742
|
+
</button>
|
|
1743
|
+
<ngxsp-play-button class="ed-transport" [playing]="playing()" (playToggle)="playToggle.emit()">
|
|
1744
|
+
@if (playing()) { <ngxsp-smart-icon class="ed-icon" name="pause" /> } @else { <ngxsp-smart-icon class="ed-icon" name="play" /> }
|
|
1745
|
+
</ngxsp-play-button>
|
|
1746
|
+
} @else {
|
|
1747
|
+
<button type="button" class="ed-transport" (click)="seekRelative.emit(-10)" aria-label="Reculer 10 secondes"><ngxsp-smart-icon class="ed-icon" name="seek-back" /></button>
|
|
1748
|
+
<ngxsp-play-button class="ed-transport ed-transport-primary" [playing]="playing()" (playToggle)="playToggle.emit()">
|
|
1749
|
+
@if (playing()) { <ngxsp-smart-icon class="ed-icon" name="pause" /> } @else { <ngxsp-smart-icon class="ed-icon" name="play" /> }
|
|
1750
|
+
</ngxsp-play-button>
|
|
1751
|
+
<button type="button" class="ed-transport" (click)="seekRelative.emit(10)" aria-label="Avancer 10 secondes"><ngxsp-smart-icon class="ed-icon" name="seek-forward" /></button>
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
<span class="ed-spacer"></span>
|
|
1755
|
+
|
|
1756
|
+
<ngxsp-labeled-control label="Sous-titres" [pressed]="captionsActive()" (activate)="captions.emit()"><ngxsp-smart-icon class="ed-icon" [name]="captionsActive() ? 'captions' : 'captions-off'" /></ngxsp-labeled-control>
|
|
1757
|
+
<ngxsp-labeled-control label="Pistes" (activate)="openMenu.emit('tracks')"><ngxsp-smart-icon class="ed-icon" name="tracks" /></ngxsp-labeled-control>
|
|
1758
|
+
<ngxsp-labeled-control [label]="speedLabel()" (activate)="openMenu.emit('speed')"><ngxsp-smart-icon class="ed-icon" name="speed" /></ngxsp-labeled-control>
|
|
1759
|
+
@if (showAirPlay()) {
|
|
1760
|
+
<ngxsp-labeled-control [label]="airPlayLabel()" [active]="airPlayActive()" (activate)="airPlay.emit()"><ngxsp-smart-icon class="ed-icon" [name]="airPlayActive() ? 'airplay-active' : 'airplay'" /></ngxsp-labeled-control>
|
|
1761
|
+
}
|
|
1762
|
+
@if (showCast()) {
|
|
1763
|
+
<ngxsp-labeled-control label="Diffuser" [active]="castActive()" (activate)="cast.emit()"><ngxsp-smart-icon class="ed-icon" [name]="castActive() ? 'cast-active' : 'cast'" /></ngxsp-labeled-control>
|
|
1764
|
+
}
|
|
1765
|
+
@if (pipSupported()) {
|
|
1766
|
+
<ngxsp-labeled-control label="PiP" [active]="pipActive()" (activate)="pip.emit()"><ngxsp-smart-icon class="ed-icon" [name]="pipActive() ? 'pip-active' : 'pip'" /></ngxsp-labeled-control>
|
|
1767
|
+
}
|
|
1768
|
+
<ngxsp-volume-control
|
|
1769
|
+
class="ed-volumeControl"
|
|
1770
|
+
[compact]="true"
|
|
1771
|
+
[volume]="volume()"
|
|
1772
|
+
[volumeLabel]="volumeLabel()"
|
|
1773
|
+
[muteLabel]="muteLabel()"
|
|
1774
|
+
[unmuteLabel]="unmuteLabel()"
|
|
1775
|
+
(muteToggle)="mute.emit()"
|
|
1776
|
+
(volumeChange)="volumeChange.emit($event)"
|
|
1777
|
+
/>
|
|
1778
|
+
</div>
|
|
1779
|
+
|
|
1780
|
+
<ng-content />
|
|
1781
|
+
</div>
|
|
1782
|
+
`, isInline: true, styles: [":host{display:contents}.ed-icon{width:20px;height:20px;display:block}.ed-top{position:absolute;left:0;right:0;top:0;z-index:3;display:flex;align-items:center;gap:14px;padding:18px 22px 14px;background:color-mix(in srgb,var(--nsp-bg, #14161b) 55%,transparent);border-bottom:1px solid var(--nsp-line, rgb(255 255 255 / .1));opacity:0;transition:opacity var(--nsp-dur, .18s) var(--nsp-ease, ease)}:host-context(.ngxsp-ui-visible) .ed-top{opacity:1}.ed-titleWrap{display:flex;flex-direction:column;gap:6px}.ed-title{font-size:24px;font-weight:500;letter-spacing:-.025em;line-height:1;color:var(--nsp-text, #f4f2ec)}.ed-meta{font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--nsp-text-2, #b4afa6)}.ed-status{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--nsp-text-3, #7c776f)}.ed-spacer{flex:1}.ed-bottom{position:absolute;left:0;right:0;bottom:0;z-index:4;display:flex;flex-direction:column;gap:14px;padding:16px 22px 18px;background:color-mix(in srgb,var(--nsp-bg, #14161b) 82%,transparent);border-top:1px solid var(--nsp-line-strong, rgb(255 255 255 / .18));opacity:0;transform:translateY(8px);transition:opacity var(--nsp-dur, .18s) var(--nsp-ease, ease),transform var(--nsp-dur, .18s) var(--nsp-ease, ease)}:host-context(.ngxsp-ui-visible) .ed-bottom{opacity:1;transform:translateY(0)}.ed-scrubRow{display:flex;align-items:center;gap:14px}.ed-time{font-size:13px;font-weight:500;font-variant-numeric:tabular-nums;color:var(--nsp-text, #f4f2ec)}.ed-time-dim{color:var(--nsp-text-2, #b4afa6)}.ed-controlsRow{display:flex;align-items:center;gap:6px}.ed-transport{all:unset;box-sizing:border-box;width:46px;height:46px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--nsp-line-strong, rgb(255 255 255 / .18));border-radius:var(--nsp-radius-sharp, 3px);color:var(--nsp-text, #f4f2ec);cursor:pointer}.ed-transport-primary{background:var(--nsp-accent, #e3b26a);color:var(--nsp-on-accent, #14161b);border:none}.ed-transport:focus-visible{outline:none;box-shadow:var(--nsp-focus-ring, 0 0 0 3px rgb(227 178 106 / .55))}.ed-live{all:unset;box-sizing:border-box;height:46px;padding:0 16px;display:inline-flex;align-items:center;gap:8px;background:var(--nsp-live, #e5402a);color:#fff;cursor:pointer;border-radius:var(--nsp-radius-sharp, 3px);font-size:12px;font-weight:500;letter-spacing:.1em;text-transform:uppercase}.ed-live:focus-visible{outline:none;box-shadow:var(--nsp-focus-ring, 0 0 0 3px rgb(227 178 106 / .55))}.ed-volumeControl{--ngxsp-control-size: 46px;--ngxsp-volume-button-radius: var(--nsp-radius-sharp, 3px);--ngxsp-volume-button-bg: transparent;--ngxsp-volume-button-border: 1px solid var(--nsp-line-strong, rgb(255 255 255 / .18));--ngxsp-volume-accent: var(--nsp-accent, #e3b26a);--ngxsp-volume-compact-min: 96px;--ngxsp-volume-compact-max: 150px;--ngxsp-volume-color: var(--nsp-text, #f4f2ec);--ngxsp-focus-ring: var(--nsp-focus-ring, 0 0 0 3px rgb(227 178 106 / .55))}\n"], dependencies: [{ kind: "component", type: SmartPlayerIconComponent, selector: "ngxsp-smart-icon", inputs: ["name"] }, { kind: "component", type: NgxspPlayButton, selector: "ngxsp-play-button", inputs: ["playing", "disabled", "playLabel", "pauseLabel"], outputs: ["playToggle"] }, { kind: "component", type: NgxspTimeSlider, selector: "ngxsp-time-slider", inputs: ["currentTime", "duration", "buffered", "profile", "disabled", "durationHintSec", "markers", "step", "bigStep", "ariaLabel", "liveLabel"], outputs: ["seek"] }, { kind: "component", type: NgxspVolumeControl, selector: "ngxsp-volume-control", inputs: ["volume", "muted", "disabled", "compact", "showValue", "step", "volumeLabel", "muteLabel", "unmuteLabel"], outputs: ["volumeChange", "muteToggle"] }, { kind: "component", type: NgxspLabeledControl, selector: "ngxsp-labeled-control", inputs: ["label", "active", "pressed", "disabled"], outputs: ["activate"] }, { kind: "component", type: NgxspLiveBadge, selector: "ngxsp-live-badge", inputs: ["liveLabel", "behindSec"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1783
|
+
}
|
|
1784
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: PlayerControlsEditionComponent, decorators: [{
|
|
1785
|
+
type: Component,
|
|
1786
|
+
args: [{ selector: 'ngxsp-player-controls-edition', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [SmartPlayerIconComponent, NgxspPlayButton, NgxspTimeSlider, NgxspVolumeControl, NgxspLabeledControl, NgxspLiveBadge], template: `
|
|
1787
|
+
<div class="ed-top">
|
|
1788
|
+
@if (profile() === 'live') {
|
|
1789
|
+
<ngxsp-live-badge [behindSec]="behindSec()" />
|
|
1790
|
+
}
|
|
1791
|
+
<div class="ed-titleWrap">
|
|
1792
|
+
<div class="ed-title">{{ displayTitle() }}</div>
|
|
1793
|
+
<div class="ed-meta">{{ metaLabel() }}</div>
|
|
1794
|
+
</div>
|
|
1795
|
+
<span class="ed-spacer"></span>
|
|
1796
|
+
<div class="ed-status">{{ statusLabel() }}</div>
|
|
1797
|
+
</div>
|
|
1798
|
+
|
|
1799
|
+
<div class="ed-bottom">
|
|
1800
|
+
<div class="ed-scrubRow">
|
|
1801
|
+
<span class="ed-time">{{ fmtTime(currentTime()) }}</span>
|
|
1802
|
+
<ngxsp-time-slider
|
|
1803
|
+
style="flex:1"
|
|
1804
|
+
[currentTime]="currentTime()"
|
|
1805
|
+
[duration]="duration()"
|
|
1806
|
+
[buffered]="buffered()"
|
|
1807
|
+
[profile]="profile()"
|
|
1808
|
+
(seek)="scrub.emit($event)"
|
|
1809
|
+
/>
|
|
1810
|
+
<span class="ed-time ed-time-dim">{{ profile() === 'live' ? liveLabel() : fmtTime(duration()) }}</span>
|
|
1811
|
+
</div>
|
|
1812
|
+
|
|
1813
|
+
<div class="ed-controlsRow">
|
|
1814
|
+
@if (profile() === 'live') {
|
|
1815
|
+
<button type="button" class="ed-live" (click)="goLive.emit()">
|
|
1816
|
+
<ngxsp-smart-icon class="ed-icon" name="next" /> Au direct
|
|
1817
|
+
</button>
|
|
1818
|
+
<ngxsp-play-button class="ed-transport" [playing]="playing()" (playToggle)="playToggle.emit()">
|
|
1819
|
+
@if (playing()) { <ngxsp-smart-icon class="ed-icon" name="pause" /> } @else { <ngxsp-smart-icon class="ed-icon" name="play" /> }
|
|
1820
|
+
</ngxsp-play-button>
|
|
1821
|
+
} @else {
|
|
1822
|
+
<button type="button" class="ed-transport" (click)="seekRelative.emit(-10)" aria-label="Reculer 10 secondes"><ngxsp-smart-icon class="ed-icon" name="seek-back" /></button>
|
|
1823
|
+
<ngxsp-play-button class="ed-transport ed-transport-primary" [playing]="playing()" (playToggle)="playToggle.emit()">
|
|
1824
|
+
@if (playing()) { <ngxsp-smart-icon class="ed-icon" name="pause" /> } @else { <ngxsp-smart-icon class="ed-icon" name="play" /> }
|
|
1825
|
+
</ngxsp-play-button>
|
|
1826
|
+
<button type="button" class="ed-transport" (click)="seekRelative.emit(10)" aria-label="Avancer 10 secondes"><ngxsp-smart-icon class="ed-icon" name="seek-forward" /></button>
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
<span class="ed-spacer"></span>
|
|
1830
|
+
|
|
1831
|
+
<ngxsp-labeled-control label="Sous-titres" [pressed]="captionsActive()" (activate)="captions.emit()"><ngxsp-smart-icon class="ed-icon" [name]="captionsActive() ? 'captions' : 'captions-off'" /></ngxsp-labeled-control>
|
|
1832
|
+
<ngxsp-labeled-control label="Pistes" (activate)="openMenu.emit('tracks')"><ngxsp-smart-icon class="ed-icon" name="tracks" /></ngxsp-labeled-control>
|
|
1833
|
+
<ngxsp-labeled-control [label]="speedLabel()" (activate)="openMenu.emit('speed')"><ngxsp-smart-icon class="ed-icon" name="speed" /></ngxsp-labeled-control>
|
|
1834
|
+
@if (showAirPlay()) {
|
|
1835
|
+
<ngxsp-labeled-control [label]="airPlayLabel()" [active]="airPlayActive()" (activate)="airPlay.emit()"><ngxsp-smart-icon class="ed-icon" [name]="airPlayActive() ? 'airplay-active' : 'airplay'" /></ngxsp-labeled-control>
|
|
1836
|
+
}
|
|
1837
|
+
@if (showCast()) {
|
|
1838
|
+
<ngxsp-labeled-control label="Diffuser" [active]="castActive()" (activate)="cast.emit()"><ngxsp-smart-icon class="ed-icon" [name]="castActive() ? 'cast-active' : 'cast'" /></ngxsp-labeled-control>
|
|
1839
|
+
}
|
|
1840
|
+
@if (pipSupported()) {
|
|
1841
|
+
<ngxsp-labeled-control label="PiP" [active]="pipActive()" (activate)="pip.emit()"><ngxsp-smart-icon class="ed-icon" [name]="pipActive() ? 'pip-active' : 'pip'" /></ngxsp-labeled-control>
|
|
1842
|
+
}
|
|
1843
|
+
<ngxsp-volume-control
|
|
1844
|
+
class="ed-volumeControl"
|
|
1845
|
+
[compact]="true"
|
|
1846
|
+
[volume]="volume()"
|
|
1847
|
+
[volumeLabel]="volumeLabel()"
|
|
1848
|
+
[muteLabel]="muteLabel()"
|
|
1849
|
+
[unmuteLabel]="unmuteLabel()"
|
|
1850
|
+
(muteToggle)="mute.emit()"
|
|
1851
|
+
(volumeChange)="volumeChange.emit($event)"
|
|
1852
|
+
/>
|
|
1853
|
+
</div>
|
|
1854
|
+
|
|
1855
|
+
<ng-content />
|
|
1856
|
+
</div>
|
|
1857
|
+
`, styles: [":host{display:contents}.ed-icon{width:20px;height:20px;display:block}.ed-top{position:absolute;left:0;right:0;top:0;z-index:3;display:flex;align-items:center;gap:14px;padding:18px 22px 14px;background:color-mix(in srgb,var(--nsp-bg, #14161b) 55%,transparent);border-bottom:1px solid var(--nsp-line, rgb(255 255 255 / .1));opacity:0;transition:opacity var(--nsp-dur, .18s) var(--nsp-ease, ease)}:host-context(.ngxsp-ui-visible) .ed-top{opacity:1}.ed-titleWrap{display:flex;flex-direction:column;gap:6px}.ed-title{font-size:24px;font-weight:500;letter-spacing:-.025em;line-height:1;color:var(--nsp-text, #f4f2ec)}.ed-meta{font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--nsp-text-2, #b4afa6)}.ed-status{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--nsp-text-3, #7c776f)}.ed-spacer{flex:1}.ed-bottom{position:absolute;left:0;right:0;bottom:0;z-index:4;display:flex;flex-direction:column;gap:14px;padding:16px 22px 18px;background:color-mix(in srgb,var(--nsp-bg, #14161b) 82%,transparent);border-top:1px solid var(--nsp-line-strong, rgb(255 255 255 / .18));opacity:0;transform:translateY(8px);transition:opacity var(--nsp-dur, .18s) var(--nsp-ease, ease),transform var(--nsp-dur, .18s) var(--nsp-ease, ease)}:host-context(.ngxsp-ui-visible) .ed-bottom{opacity:1;transform:translateY(0)}.ed-scrubRow{display:flex;align-items:center;gap:14px}.ed-time{font-size:13px;font-weight:500;font-variant-numeric:tabular-nums;color:var(--nsp-text, #f4f2ec)}.ed-time-dim{color:var(--nsp-text-2, #b4afa6)}.ed-controlsRow{display:flex;align-items:center;gap:6px}.ed-transport{all:unset;box-sizing:border-box;width:46px;height:46px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--nsp-line-strong, rgb(255 255 255 / .18));border-radius:var(--nsp-radius-sharp, 3px);color:var(--nsp-text, #f4f2ec);cursor:pointer}.ed-transport-primary{background:var(--nsp-accent, #e3b26a);color:var(--nsp-on-accent, #14161b);border:none}.ed-transport:focus-visible{outline:none;box-shadow:var(--nsp-focus-ring, 0 0 0 3px rgb(227 178 106 / .55))}.ed-live{all:unset;box-sizing:border-box;height:46px;padding:0 16px;display:inline-flex;align-items:center;gap:8px;background:var(--nsp-live, #e5402a);color:#fff;cursor:pointer;border-radius:var(--nsp-radius-sharp, 3px);font-size:12px;font-weight:500;letter-spacing:.1em;text-transform:uppercase}.ed-live:focus-visible{outline:none;box-shadow:var(--nsp-focus-ring, 0 0 0 3px rgb(227 178 106 / .55))}.ed-volumeControl{--ngxsp-control-size: 46px;--ngxsp-volume-button-radius: var(--nsp-radius-sharp, 3px);--ngxsp-volume-button-bg: transparent;--ngxsp-volume-button-border: 1px solid var(--nsp-line-strong, rgb(255 255 255 / .18));--ngxsp-volume-accent: var(--nsp-accent, #e3b26a);--ngxsp-volume-compact-min: 96px;--ngxsp-volume-compact-max: 150px;--ngxsp-volume-color: var(--nsp-text, #f4f2ec);--ngxsp-focus-ring: var(--nsp-focus-ring, 0 0 0 3px rgb(227 178 106 / .55))}\n"] }]
|
|
1858
|
+
}], propDecorators: { profile: [{ type: i0.Input, args: [{ isSignal: true, alias: "profile", required: false }] }], playing: [{ type: i0.Input, args: [{ isSignal: true, alias: "playing", required: false }] }], displayTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayTitle", required: false }] }], metaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "metaLabel", required: false }] }], statusLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "statusLabel", required: false }] }], behindSec: [{ type: i0.Input, args: [{ isSignal: true, alias: "behindSec", required: false }] }], liveLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "liveLabel", required: false }] }], captionsActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionsActive", required: false }] }], speedLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "speedLabel", required: false }] }], showAirPlay: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAirPlay", required: false }] }], airPlayActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "airPlayActive", required: false }] }], airPlayLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "airPlayLabel", required: false }] }], showCast: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCast", required: false }] }], castActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "castActive", required: false }] }], pipSupported: [{ type: i0.Input, args: [{ isSignal: true, alias: "pipSupported", required: false }] }], pipActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "pipActive", required: false }] }], volume: [{ type: i0.Input, args: [{ isSignal: true, alias: "volume", required: false }] }], volumeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "volumeLabel", required: false }] }], muteLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "muteLabel", required: false }] }], unmuteLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "unmuteLabel", required: false }] }], currentTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTime", required: false }] }], duration: [{ type: i0.Input, args: [{ isSignal: true, alias: "duration", required: false }] }], buffered: [{ type: i0.Input, args: [{ isSignal: true, alias: "buffered", required: false }] }], playToggle: [{ type: i0.Output, args: ["playToggle"] }], seekRelative: [{ type: i0.Output, args: ["seekRelative"] }], openMenu: [{ type: i0.Output, args: ["openMenu"] }], captions: [{ type: i0.Output, args: ["captions"] }], airPlay: [{ type: i0.Output, args: ["airPlay"] }], cast: [{ type: i0.Output, args: ["cast"] }], pip: [{ type: i0.Output, args: ["pip"] }], mute: [{ type: i0.Output, args: ["mute"] }], scrub: [{ type: i0.Output, args: ["scrub"] }], volumeChange: [{ type: i0.Output, args: ["volumeChange"] }], goLive: [{ type: i0.Output, args: ["goLive"] }] } });
|
|
7
1859
|
|
|
8
1860
|
class SmartPlayerComponent {
|
|
9
1861
|
config;
|
|
@@ -13,23 +1865,73 @@ class SmartPlayerComponent {
|
|
|
13
1865
|
debug = false;
|
|
14
1866
|
/** Optional start time (seconds) used for "resume playback". */
|
|
15
1867
|
startTimeSec = null;
|
|
1868
|
+
/** Starts the first source as soon as it is loaded. WebViews and desktop shells do not block it like browsers do. */
|
|
1869
|
+
autoplayOnInit = true;
|
|
1870
|
+
/** Increment when the user picks something to play: a new source starts, the current one resumes. */
|
|
1871
|
+
playRequest = 0;
|
|
16
1872
|
canPrev = false;
|
|
17
1873
|
canNext = false;
|
|
1874
|
+
previousLabel = 'Previous item';
|
|
1875
|
+
nextLabel = 'Next item';
|
|
1876
|
+
playLabel = 'Play';
|
|
1877
|
+
pauseLabel = 'Pause';
|
|
18
1878
|
shuffleActive = false;
|
|
19
1879
|
shuffleLabel = 'Shuffle: off';
|
|
20
1880
|
repeatMode = 'off';
|
|
21
1881
|
repeatLabel = 'Repeat: off';
|
|
22
1882
|
hasPlaylistControls = false;
|
|
1883
|
+
mediaMode = 'video';
|
|
1884
|
+
castEnabled = false;
|
|
1885
|
+
/** The media plays on a device the app drives (castEnabled): the devices button shows it. */
|
|
1886
|
+
castConnected = false;
|
|
1887
|
+
airPlayAvailable = false;
|
|
1888
|
+
airPlayActive = false;
|
|
1889
|
+
documentShortcuts = true;
|
|
1890
|
+
nowPlayingLabel = 'Now playing';
|
|
1891
|
+
nowListeningLabel = 'Now listening';
|
|
1892
|
+
fullscreenHintLabel = 'Double-click for fullscreen';
|
|
1893
|
+
fullscreenLabel = 'Fullscreen';
|
|
1894
|
+
readyStatusLabel = 'Ready';
|
|
1895
|
+
playingStatusLabel = 'Playing';
|
|
1896
|
+
pausedStatusLabel = 'Paused';
|
|
1897
|
+
bufferingStatusLabel = 'Buffering';
|
|
1898
|
+
loadingStatusLabel = 'Loading';
|
|
1899
|
+
endedStatusLabel = 'Ended';
|
|
1900
|
+
errorStatusLabel = 'Error';
|
|
1901
|
+
outputVolume = null;
|
|
1902
|
+
outputVolumeLabel = '';
|
|
1903
|
+
outputMuteLabel = 'Mute';
|
|
1904
|
+
outputUnmuteLabel = 'Restore sound';
|
|
1905
|
+
timelineDurationSec = null;
|
|
1906
|
+
timelineMarkers = [];
|
|
1907
|
+
// Noms volontairement alignés sur les événements média natifs (API publique).
|
|
1908
|
+
// eslint-disable-next-line @angular-eslint/no-output-native
|
|
23
1909
|
ended = new EventEmitter();
|
|
24
1910
|
videoSize = new EventEmitter();
|
|
25
|
-
/**
|
|
1911
|
+
/**
|
|
1912
|
+
* Emits playback position updates, tagged with the media they belong to: a host switching tracks can still receive
|
|
1913
|
+
* a position of the previous one, and must not file it under the new one.
|
|
1914
|
+
*/
|
|
1915
|
+
// eslint-disable-next-line @angular-eslint/no-output-native
|
|
26
1916
|
progress = new EventEmitter();
|
|
27
1917
|
prev = new EventEmitter();
|
|
28
1918
|
next = new EventEmitter();
|
|
29
1919
|
toggleShuffle = new EventEmitter();
|
|
30
1920
|
cycleRepeat = new EventEmitter();
|
|
1921
|
+
/** CC pressed; the vod and edition skins also open their subtitles menu. */
|
|
31
1922
|
subtitles = new EventEmitter();
|
|
1923
|
+
/** A subtitle file was read (ok) or could not be (error). */
|
|
1924
|
+
subtitlesLoaded = new EventEmitter();
|
|
1925
|
+
/** The words of the player's menus and of its subtitles button. */
|
|
1926
|
+
menuLabels = {};
|
|
1927
|
+
cast = new EventEmitter();
|
|
1928
|
+
stateChange = new EventEmitter();
|
|
1929
|
+
playbackToggle = new EventEmitter();
|
|
1930
|
+
outputVolumeChange = new EventEmitter();
|
|
32
1931
|
videoEl;
|
|
1932
|
+
captionsLayer;
|
|
1933
|
+
/** Absent in audio mode, which has no subtitles. */
|
|
1934
|
+
subtitleFileInput;
|
|
33
1935
|
state = null;
|
|
34
1936
|
duration = 0;
|
|
35
1937
|
currentTime = 0;
|
|
@@ -40,12 +1942,33 @@ class SmartPlayerComponent {
|
|
|
40
1942
|
tracks = [];
|
|
41
1943
|
openMenu = null;
|
|
42
1944
|
controlsVisible = true;
|
|
1945
|
+
surfaceGesture = null;
|
|
1946
|
+
castState = 'disconnected';
|
|
43
1947
|
playbackRates = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
|
44
1948
|
facade = new PlayerFacade();
|
|
1949
|
+
cdr = inject(ChangeDetectorRef);
|
|
45
1950
|
sub = new Subscription();
|
|
46
1951
|
hideControlsTimer = null;
|
|
1952
|
+
surfaceClickTimer = null;
|
|
1953
|
+
gestureTimer = null;
|
|
1954
|
+
remotePlaybackCleanup = null;
|
|
1955
|
+
initialized = false;
|
|
1956
|
+
loadedSourceSignature = '';
|
|
1957
|
+
loadSequence = 0;
|
|
1958
|
+
loading = false;
|
|
1959
|
+
pendingPlay = false;
|
|
1960
|
+
/** Whether the load in progress was asked to start playback. */
|
|
1961
|
+
loadingAutoplay = false;
|
|
1962
|
+
/** Load whose end was already reported; reset when playback starts again (repeat one). */
|
|
1963
|
+
endedLoadSequence = -1;
|
|
1964
|
+
pendingSeekSec = null;
|
|
47
1965
|
get uiSkin() {
|
|
48
|
-
|
|
1966
|
+
const skin = this.config?.ui?.skin;
|
|
1967
|
+
return skin === 'vod' || skin === 'edition' ? skin : 'classic';
|
|
1968
|
+
}
|
|
1969
|
+
/** Libellé méta du skin édition (genre · format). */
|
|
1970
|
+
get editionMeta() {
|
|
1971
|
+
return this.isAudioMode ? 'Audio' : 'Vidéo';
|
|
49
1972
|
}
|
|
50
1973
|
get uiVisible() {
|
|
51
1974
|
const kind = this.state?.kind;
|
|
@@ -54,11 +1977,27 @@ class SmartPlayerComponent {
|
|
|
54
1977
|
get displayTitle() {
|
|
55
1978
|
const ui = this.config?.ui;
|
|
56
1979
|
const src = this.config?.source;
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
1980
|
+
return ui?.title ?? ui?.displayTitle ?? src?.title ?? src?.contentId ?? 'Player';
|
|
1981
|
+
}
|
|
1982
|
+
get displaySubtitle() {
|
|
1983
|
+
const ui = this.config?.ui;
|
|
1984
|
+
const src = this.config?.source;
|
|
1985
|
+
return (ui?.subtitle ??
|
|
1986
|
+
ui?.description ??
|
|
1987
|
+
src?.subtitle ??
|
|
1988
|
+
src?.artist ??
|
|
1989
|
+
src?.album ??
|
|
1990
|
+
this.audioTracks.find((track) => track.active)?.label ??
|
|
1991
|
+
'Audio playback');
|
|
1992
|
+
}
|
|
1993
|
+
get isAudioMode() {
|
|
1994
|
+
return this.mediaMode === 'audio';
|
|
1995
|
+
}
|
|
1996
|
+
/** Profil de source transmis aux primitives UI (cf. @ngxsp/ui-core). */
|
|
1997
|
+
get sourceProfile() {
|
|
1998
|
+
if (this.config?.source?.live)
|
|
1999
|
+
return 'live';
|
|
2000
|
+
return this.isAudioMode ? 'audio' : 'vod';
|
|
62
2001
|
}
|
|
63
2002
|
get audioTracks() {
|
|
64
2003
|
return this.tracks.filter((track) => track.kind === 'audio');
|
|
@@ -78,45 +2017,109 @@ class SmartPlayerComponent {
|
|
|
78
2017
|
get selectedVideoTrackId() {
|
|
79
2018
|
return this.videoTracks.find((track) => track.active)?.id ?? null;
|
|
80
2019
|
}
|
|
2020
|
+
ngOnChanges(changes) {
|
|
2021
|
+
if (changes['outputVolume'])
|
|
2022
|
+
this.syncOutputVolumeInput();
|
|
2023
|
+
if (!this.initialized)
|
|
2024
|
+
return;
|
|
2025
|
+
const playRequested = !!changes['playRequest'] && !changes['playRequest'].firstChange;
|
|
2026
|
+
if (changes['engines'] && !changes['engines'].firstChange) {
|
|
2027
|
+
this.facade.setEngines(this.engines);
|
|
2028
|
+
// Playback moves to a device (or comes back): the media is loaded again from the same position. It keeps playing
|
|
2029
|
+
// when it moves to a device, and waits on this one when it comes back, unless the host asks for playback.
|
|
2030
|
+
// A different media arriving in the same change is not a move: it loads below from its own start, or it would
|
|
2031
|
+
// inherit the position of the one it replaces.
|
|
2032
|
+
if (this.sourceSignature(this.config) === this.loadedSourceSignature) {
|
|
2033
|
+
const kind = this.state?.kind;
|
|
2034
|
+
const wasPlaying = kind === 'playing' || kind === 'buffering' || (this.loading && (this.loadingAutoplay || this.pendingPlay));
|
|
2035
|
+
// Hosts keep their own engines (adaptive streaming) in the list: only the remote engine means a device.
|
|
2036
|
+
const toDevice = (this.engines ?? []).some((engine) => engine.id === 'remote');
|
|
2037
|
+
if (this.pendingSeekSec === null && this.currentTime > 0.5)
|
|
2038
|
+
this.pendingSeekSec = this.currentTime;
|
|
2039
|
+
void this.loadConfig(playRequested || (toDevice && wasPlaying));
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
if (changes['config'] && this.sourceSignature(this.config) !== this.loadedSourceSignature) {
|
|
2044
|
+
// Keeps playing when the source is swapped while playing or stalled, right after the previous media ended (queue
|
|
2045
|
+
// chaining) or while a load meant to start is still in progress (a local copy replacing the network source),
|
|
2046
|
+
// and starts when the host asked for playback; nothing starts on its own otherwise.
|
|
2047
|
+
const kind = this.state?.kind;
|
|
2048
|
+
const autoplay = playRequested || kind === 'playing' || kind === 'buffering' || kind === 'ended'
|
|
2049
|
+
|| (this.loading && (this.loadingAutoplay || this.pendingPlay));
|
|
2050
|
+
// A position still waiting for the previous media belongs to it: a session restored on a
|
|
2051
|
+
// paused track must not send the next file there (past its end, it would "finish" at once).
|
|
2052
|
+
// The new media starts where `startTimeSec` says.
|
|
2053
|
+
this.pendingSeekSec = null;
|
|
2054
|
+
void this.loadConfig(autoplay);
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
if (playRequested) {
|
|
2058
|
+
// Same source picked again (a paused track the listener clicks): play it, once any load has finished.
|
|
2059
|
+
if (this.loading)
|
|
2060
|
+
this.pendingPlay = true;
|
|
2061
|
+
else
|
|
2062
|
+
void this.facade.play().catch(() => { });
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
81
2065
|
ngOnInit() {
|
|
82
2066
|
const plugins = [...(this.plugins ?? [])];
|
|
83
2067
|
// v1.11: SSAI (server-side stitched streams) – rewrite manifest URL if configured.
|
|
84
2068
|
if (this.config?.ads?.enabled && this.config?.ads?.mode === 'ssai') {
|
|
85
2069
|
plugins.push(createSsaiManifestRewritePlugin());
|
|
86
2070
|
}
|
|
87
|
-
|
|
2071
|
+
const media = this.mediaElementForMode();
|
|
2072
|
+
this.watchAirPlay(media);
|
|
2073
|
+
this.facade.attach(media, plugins, this.config, this.ports, this.engines);
|
|
2074
|
+
// markForCheck() requis : la stratégie OnPush ne détecte pas les mutations
|
|
2075
|
+
// poussées depuis des souscriptions RxJS (cf. docs/AUDIT.md, manquement #7).
|
|
88
2076
|
this.sub.add(this.facade.state$.subscribe((s) => {
|
|
89
2077
|
this.state = s;
|
|
2078
|
+
// Engines without a media element (remote devices) only report the end through their state.
|
|
2079
|
+
if (s.kind === 'ended')
|
|
2080
|
+
this.onEnded();
|
|
2081
|
+
else if (s.kind === 'playing')
|
|
2082
|
+
this.endedLoadSequence = -1;
|
|
2083
|
+
this.stateChange.emit(s);
|
|
90
2084
|
this.syncControlVisibility();
|
|
2085
|
+
this.cdr.markForCheck();
|
|
91
2086
|
}));
|
|
92
2087
|
this.sub.add(this.facade.time$.subscribe((t) => {
|
|
2088
|
+
// While a new source loads, the time still reported is the previous media's. Passed on, a
|
|
2089
|
+
// host would file it under the new track and resume that track there — past its end for a
|
|
2090
|
+
// short file, which then "finishes" at once.
|
|
2091
|
+
if (this.loading)
|
|
2092
|
+
return;
|
|
93
2093
|
this.currentTime = t.currentTime;
|
|
94
2094
|
this.duration = t.duration;
|
|
95
|
-
this.progress.emit({
|
|
2095
|
+
this.progress.emit({
|
|
2096
|
+
currentTime: this.currentTime,
|
|
2097
|
+
duration: this.duration,
|
|
2098
|
+
contentId: this.config?.source?.contentId,
|
|
2099
|
+
sourceUrl: this.config?.source?.url,
|
|
2100
|
+
});
|
|
2101
|
+
this.cdr.markForCheck();
|
|
96
2102
|
}));
|
|
97
2103
|
this.sub.add(this.facade.bufferAheadSec$.subscribe((value) => {
|
|
98
2104
|
this.bufferAheadSec = value;
|
|
2105
|
+
this.cdr.markForCheck();
|
|
99
2106
|
}));
|
|
100
2107
|
this.sub.add(this.facade.tracks$.subscribe((tracks) => {
|
|
101
2108
|
this.tracks = tracks;
|
|
2109
|
+
this.cdr.markForCheck();
|
|
102
2110
|
}));
|
|
103
|
-
this.facade.
|
|
104
|
-
this.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
const safe = Math.max(0, Math.min(start, (this.duration || start + 1) - 0.3));
|
|
110
|
-
await this.facade.seekTo(safe);
|
|
111
|
-
}
|
|
112
|
-
catch { /* ignore */ }
|
|
113
|
-
}
|
|
114
|
-
await this.facade.play().catch(() => { });
|
|
115
|
-
})
|
|
116
|
-
.catch(() => { });
|
|
2111
|
+
this.sub.add(this.facade.captionCues$.subscribe((cues) => this.drawCaptions(cues)));
|
|
2112
|
+
this.bindRemotePlaybackState();
|
|
2113
|
+
this.applyVolume();
|
|
2114
|
+
this.initialized = true;
|
|
2115
|
+
void this.loadConfig(this.autoplayOnInit);
|
|
117
2116
|
}
|
|
118
2117
|
ngOnDestroy() {
|
|
2118
|
+
this.controlsObserver?.disconnect();
|
|
119
2119
|
this.clearHideControlsTimer();
|
|
2120
|
+
this.clearSurfaceClickTimer();
|
|
2121
|
+
this.clearGestureTimer();
|
|
2122
|
+
this.remotePlaybackCleanup?.();
|
|
120
2123
|
this.sub.unsubscribe();
|
|
121
2124
|
this.facade.destroy();
|
|
122
2125
|
}
|
|
@@ -125,7 +2128,7 @@ class SmartPlayerComponent {
|
|
|
125
2128
|
}
|
|
126
2129
|
get debugJson() {
|
|
127
2130
|
try {
|
|
128
|
-
const d = this.facade.diagnostics
|
|
2131
|
+
const d = this.facade.diagnostics();
|
|
129
2132
|
const tel = this.ports?.telemetry?.export?.();
|
|
130
2133
|
return JSON.stringify({ ...d, telemetry: tel }, null, 2);
|
|
131
2134
|
}
|
|
@@ -133,9 +2136,8 @@ class SmartPlayerComponent {
|
|
|
133
2136
|
return '{}';
|
|
134
2137
|
}
|
|
135
2138
|
}
|
|
136
|
-
get
|
|
137
|
-
|
|
138
|
-
return s?.kind === 'error' && s?.error?.code === 'AUTOPLAY_BLOCKED';
|
|
2139
|
+
get currentError() {
|
|
2140
|
+
return this.state?.kind === 'error' ? this.state.error : null;
|
|
139
2141
|
}
|
|
140
2142
|
async copyDebug() {
|
|
141
2143
|
try {
|
|
@@ -165,20 +2167,36 @@ class SmartPlayerComponent {
|
|
|
165
2167
|
else
|
|
166
2168
|
this.facade.play();
|
|
167
2169
|
}
|
|
2170
|
+
requestPlaybackToggle() {
|
|
2171
|
+
const intent = this.playbackToggleIntent();
|
|
2172
|
+
this.togglePlay();
|
|
2173
|
+
this.playbackToggle.emit(intent);
|
|
2174
|
+
this.showSurfaceGesture(intent);
|
|
2175
|
+
}
|
|
168
2176
|
seekRel(deltaSec) {
|
|
169
|
-
this.
|
|
2177
|
+
this.seekTo(this.currentTime + deltaSec);
|
|
2178
|
+
}
|
|
2179
|
+
seekTo(timeSec) {
|
|
2180
|
+
const value = Number(timeSec);
|
|
2181
|
+
if (!Number.isFinite(value))
|
|
2182
|
+
return;
|
|
2183
|
+
const target = Math.max(0, value);
|
|
2184
|
+
this.pendingSeekSec = target;
|
|
2185
|
+
// A load in progress applies the pending position once the media is ready: seeking now would hit no engine.
|
|
2186
|
+
if (this.loading)
|
|
2187
|
+
return;
|
|
2188
|
+
void this.applySeek(target).then((applied) => {
|
|
2189
|
+
if (applied && this.pendingSeekSec === target)
|
|
2190
|
+
this.pendingSeekSec = null;
|
|
2191
|
+
});
|
|
170
2192
|
}
|
|
171
2193
|
onScrub(value) {
|
|
172
2194
|
const v = Number(value);
|
|
173
2195
|
if (Number.isFinite(v))
|
|
174
|
-
this.
|
|
2196
|
+
this.seekTo(v);
|
|
175
2197
|
}
|
|
176
2198
|
onVolume(value) {
|
|
177
|
-
|
|
178
|
-
this.volume = Number.isFinite(v) ? v : this.volume;
|
|
179
|
-
if (this.volume > 0)
|
|
180
|
-
this.lastVolumeBeforeMute = this.volume;
|
|
181
|
-
this.facade.setVolume(this.volume);
|
|
2199
|
+
this.setVolume(value, true);
|
|
182
2200
|
}
|
|
183
2201
|
toggleMute() {
|
|
184
2202
|
if (this.volume <= 0.001) {
|
|
@@ -188,7 +2206,8 @@ class SmartPlayerComponent {
|
|
|
188
2206
|
this.lastVolumeBeforeMute = this.volume;
|
|
189
2207
|
this.volume = 0;
|
|
190
2208
|
}
|
|
191
|
-
this.
|
|
2209
|
+
this.applyVolume();
|
|
2210
|
+
this.outputVolumeChange.emit(this.volume);
|
|
192
2211
|
this.onInteraction();
|
|
193
2212
|
}
|
|
194
2213
|
async setPlaybackRate(rate) {
|
|
@@ -204,6 +2223,14 @@ class SmartPlayerComponent {
|
|
|
204
2223
|
const v = this.videoEl?.nativeElement;
|
|
205
2224
|
if (!v)
|
|
206
2225
|
return;
|
|
2226
|
+
// A position requested while the media was loading and not applied yet is applied now that it can be.
|
|
2227
|
+
if (this.pendingSeekSec !== null && !this.loading) {
|
|
2228
|
+
const target = this.pendingSeekSec;
|
|
2229
|
+
void this.applySeek(target).then((applied) => {
|
|
2230
|
+
if (applied && this.pendingSeekSec === target)
|
|
2231
|
+
this.pendingSeekSec = null;
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
207
2234
|
const width = v.videoWidth || 0;
|
|
208
2235
|
const height = v.videoHeight || 0;
|
|
209
2236
|
if (width > 0 && height > 0) {
|
|
@@ -211,10 +2238,20 @@ class SmartPlayerComponent {
|
|
|
211
2238
|
}
|
|
212
2239
|
}
|
|
213
2240
|
onEnded() {
|
|
214
|
-
//
|
|
2241
|
+
// The media element's event and the engine state both announce the same end: handle it once per playback.
|
|
2242
|
+
if (this.endedLoadSequence === this.loadSequence)
|
|
2243
|
+
return;
|
|
2244
|
+
this.endedLoadSequence = this.loadSequence;
|
|
2245
|
+
// Repeat one replays the same media; otherwise hosts are told so they can chain their queue.
|
|
2246
|
+
if (this.repeatMode === 'one') {
|
|
2247
|
+
void this.facade.seekTo(0).then(() => this.facade.play()).catch(() => { });
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
215
2250
|
this.ended.emit();
|
|
216
2251
|
}
|
|
217
2252
|
toggleFullscreen() {
|
|
2253
|
+
if (this.isAudioMode)
|
|
2254
|
+
return;
|
|
218
2255
|
const host = this.videoEl.nativeElement.parentElement;
|
|
219
2256
|
if (!host)
|
|
220
2257
|
return;
|
|
@@ -231,6 +2268,72 @@ class SmartPlayerComponent {
|
|
|
231
2268
|
this.openMenu = this.openMenu === menu ? null : menu;
|
|
232
2269
|
this.onInteraction(this.openMenu !== null);
|
|
233
2270
|
}
|
|
2271
|
+
get playerMenuLabels() {
|
|
2272
|
+
return { ...DEFAULT_PLAYER_MENU_LABELS, ...this.menuLabels };
|
|
2273
|
+
}
|
|
2274
|
+
onCaptionsClick() {
|
|
2275
|
+
this.toggleMenu('captions');
|
|
2276
|
+
this.subtitles.emit();
|
|
2277
|
+
}
|
|
2278
|
+
openSubtitlePicker() {
|
|
2279
|
+
this.openMenu = null;
|
|
2280
|
+
this.onInteraction();
|
|
2281
|
+
this.subtitleFileInput?.nativeElement.click();
|
|
2282
|
+
}
|
|
2283
|
+
async onSubtitleFileChosen(event) {
|
|
2284
|
+
const input = event.target;
|
|
2285
|
+
const file = input.files?.[0];
|
|
2286
|
+
input.value = '';
|
|
2287
|
+
if (file)
|
|
2288
|
+
await this.loadSubtitleFile(file);
|
|
2289
|
+
}
|
|
2290
|
+
/** Reads a SubRip or WebVTT file and shows it over the video. */
|
|
2291
|
+
async loadSubtitleFile(file) {
|
|
2292
|
+
const { label, language } = describeSubtitleFile(file.name);
|
|
2293
|
+
try {
|
|
2294
|
+
const vtt = subtitleTextToVtt(decodeSubtitleBytes(new Uint8Array(await file.arrayBuffer())));
|
|
2295
|
+
if (!/-->/.test(vtt))
|
|
2296
|
+
throw new Error('No subtitle in this file');
|
|
2297
|
+
await this.facade.addExternalTextTrack({ label, ...(language ? { language } : {}), vtt });
|
|
2298
|
+
this.subtitlesLoaded.emit({ label, ok: true });
|
|
2299
|
+
}
|
|
2300
|
+
catch (error) {
|
|
2301
|
+
this.subtitlesLoaded.emit({ label, ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
controlsObserver;
|
|
2305
|
+
/** While the controls show, the subtitles sit just above them, however tall they are (a menu makes them taller). */
|
|
2306
|
+
followControlsHeight() {
|
|
2307
|
+
if (this.controlsObserver || typeof ResizeObserver !== 'function')
|
|
2308
|
+
return;
|
|
2309
|
+
const bar = this.videoEl.nativeElement.parentElement?.querySelector('.ngxsp-vod-bottom');
|
|
2310
|
+
const layer = this.captionsLayer?.nativeElement;
|
|
2311
|
+
if (!bar || !layer)
|
|
2312
|
+
return;
|
|
2313
|
+
// The top of the bar is a fade over the picture: the subtitles may overlap it.
|
|
2314
|
+
const lift = () => layer.style.setProperty('--ngxsp-captions-lift', `${Math.max(0, bar.offsetHeight - 24)}px`);
|
|
2315
|
+
this.controlsObserver = new ResizeObserver(lift);
|
|
2316
|
+
this.controlsObserver.observe(bar);
|
|
2317
|
+
lift();
|
|
2318
|
+
}
|
|
2319
|
+
/** Draws the cues of the loaded subtitles (their markup parsed by the browser: italics, bold, no script). */
|
|
2320
|
+
drawCaptions(cues) {
|
|
2321
|
+
const layer = this.captionsLayer?.nativeElement;
|
|
2322
|
+
if (!layer)
|
|
2323
|
+
return;
|
|
2324
|
+
if (cues.length)
|
|
2325
|
+
this.followControlsHeight();
|
|
2326
|
+
layer.replaceChildren(...cues.map((cue) => {
|
|
2327
|
+
const line = document.createElement('span');
|
|
2328
|
+
line.className = 'ngxsp-cue';
|
|
2329
|
+
const html = cue.getCueAsHTML?.();
|
|
2330
|
+
if (html)
|
|
2331
|
+
line.append(html);
|
|
2332
|
+
else
|
|
2333
|
+
line.textContent = cue.text ?? '';
|
|
2334
|
+
return line;
|
|
2335
|
+
}));
|
|
2336
|
+
}
|
|
234
2337
|
async selectTextTrack(trackId) {
|
|
235
2338
|
this.openMenu = null;
|
|
236
2339
|
this.onInteraction();
|
|
@@ -263,6 +2366,8 @@ class SmartPlayerComponent {
|
|
|
263
2366
|
this.onInteraction();
|
|
264
2367
|
}
|
|
265
2368
|
isPiPSupported() {
|
|
2369
|
+
if (this.isAudioMode)
|
|
2370
|
+
return false;
|
|
266
2371
|
const video = this.videoEl?.nativeElement;
|
|
267
2372
|
const doc = document;
|
|
268
2373
|
return !!doc.pictureInPictureEnabled && !video?.disablePictureInPicture;
|
|
@@ -275,8 +2380,96 @@ class SmartPlayerComponent {
|
|
|
275
2380
|
const doc = document;
|
|
276
2381
|
return !!doc.pictureInPictureElement;
|
|
277
2382
|
}
|
|
2383
|
+
async requestCast() {
|
|
2384
|
+
const video = this.videoEl?.nativeElement;
|
|
2385
|
+
if (!video)
|
|
2386
|
+
return;
|
|
2387
|
+
try {
|
|
2388
|
+
if (video.remote?.prompt) {
|
|
2389
|
+
this.castState = video.remote.state ?? 'connecting';
|
|
2390
|
+
await video.remote.prompt();
|
|
2391
|
+
this.castState = video.remote.state ?? this.castState;
|
|
2392
|
+
}
|
|
2393
|
+
else if (typeof video.webkitShowPlaybackTargetPicker === 'function') {
|
|
2394
|
+
video.webkitShowPlaybackTargetPicker();
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
catch {
|
|
2398
|
+
this.castState = video.remote?.state ?? 'disconnected';
|
|
2399
|
+
}
|
|
2400
|
+
this.onInteraction();
|
|
2401
|
+
}
|
|
2402
|
+
isCastSupported() {
|
|
2403
|
+
const video = this.videoEl?.nativeElement;
|
|
2404
|
+
return !!video && (!!video.remote?.prompt || typeof video.webkitShowPlaybackTargetPicker === 'function');
|
|
2405
|
+
}
|
|
2406
|
+
isCastActive() {
|
|
2407
|
+
return this.castConnected || this.castState === 'connected';
|
|
2408
|
+
}
|
|
2409
|
+
showCastButton() {
|
|
2410
|
+
return this.castEnabled || this.isCastSupported();
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* AirPlay, as WebKit offers it: the system decides what is reachable and draws the picker
|
|
2414
|
+
* itself. Nothing is discovered here, and nothing is sent — the route belongs to the device.
|
|
2415
|
+
* Everywhere else these stay false and the button never appears.
|
|
2416
|
+
*/
|
|
2417
|
+
showAirPlayButton() {
|
|
2418
|
+
return this.airPlayAvailable;
|
|
2419
|
+
}
|
|
2420
|
+
isAirPlayActive() {
|
|
2421
|
+
return this.airPlayActive;
|
|
2422
|
+
}
|
|
2423
|
+
airPlayLabel() {
|
|
2424
|
+
return this.airPlayActive ? 'Stop AirPlay' : 'AirPlay';
|
|
2425
|
+
}
|
|
2426
|
+
openAirPlayPicker() {
|
|
2427
|
+
const media = this.videoEl?.nativeElement;
|
|
2428
|
+
media?.webkitShowPlaybackTargetPicker?.();
|
|
2429
|
+
}
|
|
2430
|
+
/** Wired to the media element whenever one is created, for video and for audio alike. */
|
|
2431
|
+
watchAirPlay(media) {
|
|
2432
|
+
const target = media;
|
|
2433
|
+
if (!('webkitShowPlaybackTargetPicker' in target))
|
|
2434
|
+
return;
|
|
2435
|
+
this.airPlayActive = target.webkitCurrentPlaybackTargetIsWireless === true;
|
|
2436
|
+
media.addEventListener('webkitplaybacktargetavailabilitychanged', (event) => {
|
|
2437
|
+
this.airPlayAvailable = event.availability === 'available';
|
|
2438
|
+
});
|
|
2439
|
+
media.addEventListener('webkitcurrentplaybacktargetiswirelesschanged', () => {
|
|
2440
|
+
this.airPlayActive = target.webkitCurrentPlaybackTargetIsWireless === true;
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2443
|
+
castLabel() {
|
|
2444
|
+
if (this.castEnabled)
|
|
2445
|
+
return 'Play on device';
|
|
2446
|
+
if (this.castState === 'connected')
|
|
2447
|
+
return 'Playing on remote device';
|
|
2448
|
+
if (this.castState === 'connecting')
|
|
2449
|
+
return 'Connecting to remote device';
|
|
2450
|
+
return 'Play on device';
|
|
2451
|
+
}
|
|
2452
|
+
castHintLabel() {
|
|
2453
|
+
if (this.castEnabled)
|
|
2454
|
+
return 'Choose a device on your local network.';
|
|
2455
|
+
if (this.castState === 'connected')
|
|
2456
|
+
return 'This media is currently playing on a nearby device.';
|
|
2457
|
+
if (this.castState === 'connecting')
|
|
2458
|
+
return 'Trying to connect to a nearby playback device.';
|
|
2459
|
+
return 'Available when your platform supports remote playback on the same network.';
|
|
2460
|
+
}
|
|
2461
|
+
onCastClick() {
|
|
2462
|
+
if (this.castEnabled) {
|
|
2463
|
+
this.cast.emit();
|
|
2464
|
+
this.onInteraction();
|
|
2465
|
+
return;
|
|
2466
|
+
}
|
|
2467
|
+
void this.requestCast();
|
|
2468
|
+
}
|
|
278
2469
|
captionsLabel() {
|
|
279
|
-
|
|
2470
|
+
const labels = this.playerMenuLabels;
|
|
2471
|
+
const shown = this.textTracks.find((track) => track.id === this.selectedTextTrackId);
|
|
2472
|
+
return shown ? `${labels.subtitles} - ${shown.label || shown.language || shown.id}` : labels.subtitles;
|
|
280
2473
|
}
|
|
281
2474
|
volumeLabel() {
|
|
282
2475
|
if (this.volume <= 0.001)
|
|
@@ -285,50 +2478,36 @@ class SmartPlayerComponent {
|
|
|
285
2478
|
return 'Vol';
|
|
286
2479
|
return 'Volume';
|
|
287
2480
|
}
|
|
288
|
-
volumeIcon() {
|
|
289
|
-
if (this.volume <= 0.001)
|
|
290
|
-
return '🔇';
|
|
291
|
-
if (this.volume < 0.5)
|
|
292
|
-
return '🔉';
|
|
293
|
-
return '🔊';
|
|
294
|
-
}
|
|
295
2481
|
playbackRateLabel() {
|
|
296
2482
|
return `${this.playbackRate}x`;
|
|
297
2483
|
}
|
|
298
2484
|
statusLabel() {
|
|
299
2485
|
const kind = this.state?.kind;
|
|
300
2486
|
if (kind === 'playing')
|
|
301
|
-
return this.playbackRate === 1 ?
|
|
2487
|
+
return this.playbackRate === 1 ? this.playingStatusLabel : `${this.playingStatusLabel} ${this.playbackRate}x`;
|
|
302
2488
|
if (kind === 'paused')
|
|
303
|
-
return
|
|
2489
|
+
return this.pausedStatusLabel;
|
|
304
2490
|
if (kind === 'buffering')
|
|
305
|
-
return
|
|
2491
|
+
return this.bufferingStatusLabel;
|
|
306
2492
|
if (kind === 'loading')
|
|
307
|
-
return
|
|
2493
|
+
return this.loadingStatusLabel;
|
|
308
2494
|
if (kind === 'ended')
|
|
309
|
-
return
|
|
2495
|
+
return this.endedStatusLabel;
|
|
310
2496
|
if (kind === 'error')
|
|
311
|
-
return
|
|
312
|
-
return
|
|
313
|
-
}
|
|
314
|
-
keyboardHintLabel() {
|
|
315
|
-
return 'Space play • Arrows seek • F fullscreen';
|
|
316
|
-
}
|
|
317
|
-
tracksButtonLabel() {
|
|
318
|
-
return this.textTracks.length || this.audioTracks.length ? 'Tracks' : 'Media';
|
|
319
|
-
}
|
|
320
|
-
trackLabel(track) {
|
|
321
|
-
return track.label || track.language || track.id;
|
|
2497
|
+
return this.errorStatusLabel;
|
|
2498
|
+
return this.readyStatusLabel;
|
|
322
2499
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
2500
|
+
keyboardHintLabel() {
|
|
2501
|
+
return this.isAudioMode ? 'Space play • Arrows seek • C device' : 'Space play • Arrows seek • F fullscreen • C device';
|
|
2502
|
+
}
|
|
2503
|
+
tracksButtonLabel() {
|
|
2504
|
+
return this.textTracks.length || this.audioTracks.length ? this.playerMenuLabels.tracks : this.playerMenuLabels.media;
|
|
2505
|
+
}
|
|
2506
|
+
onPrimarySurfaceDoubleClick() {
|
|
2507
|
+
if (this.isAudioMode)
|
|
2508
|
+
return;
|
|
2509
|
+
this.clearSurfaceClickTimer();
|
|
2510
|
+
this.toggleFullscreen();
|
|
332
2511
|
}
|
|
333
2512
|
bufferPercent() {
|
|
334
2513
|
if (!this.duration || !Number.isFinite(this.duration) || this.duration <= 0)
|
|
@@ -345,12 +2524,32 @@ class SmartPlayerComponent {
|
|
|
345
2524
|
return `${m}:${String(r).padStart(2, '0')}`;
|
|
346
2525
|
}
|
|
347
2526
|
noop() { }
|
|
2527
|
+
/**
|
|
2528
|
+
* Music plays in a real <audio> element: mobile browsers and Android WebViews pause <video> elements as soon as
|
|
2529
|
+
* the page is hidden, which would stop the music when the app goes to the background or the screen turns off.
|
|
2530
|
+
*/
|
|
2531
|
+
mediaElementForMode() {
|
|
2532
|
+
const video = this.videoEl.nativeElement;
|
|
2533
|
+
if (!this.isAudioMode || typeof document === 'undefined')
|
|
2534
|
+
return video;
|
|
2535
|
+
const audio = document.createElement('audio');
|
|
2536
|
+
audio.preload = 'metadata';
|
|
2537
|
+
audio.setAttribute('x-webkit-airplay', 'allow');
|
|
2538
|
+
audio.addEventListener('timeupdate', () => this.noop());
|
|
2539
|
+
audio.addEventListener('loadedmetadata', () => this.onLoadedMetadata());
|
|
2540
|
+
audio.addEventListener('ended', () => this.onEnded());
|
|
2541
|
+
video.replaceWith(audio);
|
|
2542
|
+
// The facade and helpers only use HTMLMediaElement members for audio (video-only APIs are feature-checked).
|
|
2543
|
+
this.videoEl = new ElementRef(audio);
|
|
2544
|
+
return this.videoEl.nativeElement;
|
|
2545
|
+
}
|
|
348
2546
|
onInteraction(persist = false) {
|
|
349
2547
|
this.controlsVisible = true;
|
|
350
2548
|
this.clearHideControlsTimer();
|
|
351
2549
|
if (!persist && this.state?.kind === 'playing' && this.openMenu === null) {
|
|
352
2550
|
this.hideControlsTimer = setTimeout(() => {
|
|
353
2551
|
this.controlsVisible = false;
|
|
2552
|
+
this.cdr.markForCheck();
|
|
354
2553
|
}, 2200);
|
|
355
2554
|
}
|
|
356
2555
|
}
|
|
@@ -364,11 +2563,18 @@ class SmartPlayerComponent {
|
|
|
364
2563
|
const target = event.target;
|
|
365
2564
|
if (!target)
|
|
366
2565
|
return;
|
|
367
|
-
if (target.closest('
|
|
2566
|
+
if (target.closest('button, input, textarea, select, summary, [contenteditable="true"], .ngxsp-menu, .ngxsp-controls, .ngxsp-debug, .ngxsp-overlay, .ngxsp-range')) {
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
if (event.detail > 1) {
|
|
2570
|
+
this.clearSurfaceClickTimer();
|
|
368
2571
|
return;
|
|
369
2572
|
}
|
|
370
2573
|
this.openMenu = null;
|
|
371
2574
|
this.onInteraction();
|
|
2575
|
+
if (this.uiSkin === 'vod' && !this.isAudioMode) {
|
|
2576
|
+
this.queueSurfacePlaybackToggle();
|
|
2577
|
+
}
|
|
372
2578
|
}
|
|
373
2579
|
onEscape() {
|
|
374
2580
|
if (this.openMenu !== null) {
|
|
@@ -377,11 +2583,13 @@ class SmartPlayerComponent {
|
|
|
377
2583
|
}
|
|
378
2584
|
}
|
|
379
2585
|
onKeydown(event) {
|
|
2586
|
+
if (!this.documentShortcuts)
|
|
2587
|
+
return;
|
|
380
2588
|
if (this.isInteractiveTarget(event.target))
|
|
381
2589
|
return;
|
|
382
2590
|
if (event.code === 'Space') {
|
|
383
2591
|
event.preventDefault();
|
|
384
|
-
this.
|
|
2592
|
+
this.requestPlaybackToggle();
|
|
385
2593
|
this.onInteraction();
|
|
386
2594
|
return;
|
|
387
2595
|
}
|
|
@@ -398,6 +2606,8 @@ class SmartPlayerComponent {
|
|
|
398
2606
|
return;
|
|
399
2607
|
}
|
|
400
2608
|
if (event.code === 'KeyF') {
|
|
2609
|
+
if (this.isAudioMode)
|
|
2610
|
+
return;
|
|
401
2611
|
event.preventDefault();
|
|
402
2612
|
this.toggleFullscreen();
|
|
403
2613
|
this.onInteraction();
|
|
@@ -406,8 +2616,110 @@ class SmartPlayerComponent {
|
|
|
406
2616
|
if (event.code === 'KeyM') {
|
|
407
2617
|
event.preventDefault();
|
|
408
2618
|
this.toggleMute();
|
|
2619
|
+
return;
|
|
2620
|
+
}
|
|
2621
|
+
if (event.code === 'KeyC') {
|
|
2622
|
+
if (!this.showCastButton())
|
|
2623
|
+
return;
|
|
2624
|
+
event.preventDefault();
|
|
2625
|
+
this.onCastClick();
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
async loadConfig(autoplay) {
|
|
2629
|
+
const sequence = ++this.loadSequence;
|
|
2630
|
+
this.loading = true;
|
|
2631
|
+
this.loadingAutoplay = autoplay;
|
|
2632
|
+
this.loadedSourceSignature = this.sourceSignature(this.config);
|
|
2633
|
+
this.duration = 0;
|
|
2634
|
+
this.currentTime = 0;
|
|
2635
|
+
this.bufferAheadSec = 0;
|
|
2636
|
+
this.tracks = [];
|
|
2637
|
+
this.cdr.markForCheck();
|
|
2638
|
+
try {
|
|
2639
|
+
await this.facade.load(this.config);
|
|
2640
|
+
if (this.pendingSeekSec !== null) {
|
|
2641
|
+
const target = this.pendingSeekSec;
|
|
2642
|
+
// Cleared only when the position really reached an engine; otherwise the metadata event retries it.
|
|
2643
|
+
if (await this.applySeek(target) && this.pendingSeekSec === target)
|
|
2644
|
+
this.pendingSeekSec = null;
|
|
2645
|
+
}
|
|
2646
|
+
else {
|
|
2647
|
+
await this.applyStartTime();
|
|
2648
|
+
}
|
|
2649
|
+
// A newer load owns playback: only the latest request may start it.
|
|
2650
|
+
if (sequence === this.loadSequence && (autoplay || this.pendingPlay)) {
|
|
2651
|
+
this.pendingPlay = false;
|
|
2652
|
+
await this.facade.play().catch(() => { });
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
catch {
|
|
2656
|
+
// The facade has already emitted a typed player error.
|
|
2657
|
+
}
|
|
2658
|
+
finally {
|
|
2659
|
+
if (sequence === this.loadSequence) {
|
|
2660
|
+
this.loading = false;
|
|
2661
|
+
// A position asked for while this load was finishing, after it looked for one, is applied now.
|
|
2662
|
+
const target = this.pendingSeekSec;
|
|
2663
|
+
if (target !== null) {
|
|
2664
|
+
void this.applySeek(target).then((applied) => {
|
|
2665
|
+
if (applied && this.pendingSeekSec === target)
|
|
2666
|
+
this.pendingSeekSec = null;
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
async applyStartTime() {
|
|
2673
|
+
const start = Number(this.startTimeSec ?? 0);
|
|
2674
|
+
if (!Number.isFinite(start) || start <= 0.2)
|
|
2675
|
+
return;
|
|
2676
|
+
await this.applySeek(start);
|
|
2677
|
+
}
|
|
2678
|
+
async applySeek(timeSec) {
|
|
2679
|
+
try {
|
|
2680
|
+
return await this.facade.seekTo(this.safeSeekTime(timeSec));
|
|
2681
|
+
}
|
|
2682
|
+
catch {
|
|
2683
|
+
// Best effort: some engines only allow seeking after enough metadata is loaded.
|
|
2684
|
+
return false;
|
|
409
2685
|
}
|
|
410
2686
|
}
|
|
2687
|
+
safeSeekTime(timeSec) {
|
|
2688
|
+
return Math.max(0, Math.min(timeSec, (this.duration || timeSec + 1) - 0.3));
|
|
2689
|
+
}
|
|
2690
|
+
sourceSignature(config) {
|
|
2691
|
+
const source = config?.source;
|
|
2692
|
+
if (!source)
|
|
2693
|
+
return '';
|
|
2694
|
+
return [
|
|
2695
|
+
source.type,
|
|
2696
|
+
source.url,
|
|
2697
|
+
source.contentId,
|
|
2698
|
+
source.live ? 'live' : 'vod',
|
|
2699
|
+
source.offlineEligible ? 'offline' : 'online',
|
|
2700
|
+
].join('|');
|
|
2701
|
+
}
|
|
2702
|
+
bindRemotePlaybackState() {
|
|
2703
|
+
const video = this.videoEl?.nativeElement;
|
|
2704
|
+
const remote = video?.remote;
|
|
2705
|
+
if (!video || !remote)
|
|
2706
|
+
return;
|
|
2707
|
+
const sync = () => {
|
|
2708
|
+
this.castState = remote.state ?? 'disconnected';
|
|
2709
|
+
this.cdr.markForCheck();
|
|
2710
|
+
};
|
|
2711
|
+
sync();
|
|
2712
|
+
remote.addEventListener?.('connecting', sync);
|
|
2713
|
+
remote.addEventListener?.('connect', sync);
|
|
2714
|
+
remote.addEventListener?.('disconnect', sync);
|
|
2715
|
+
video.addEventListener('webkitcurrentplaybacktargetiswirelesschanged', sync);
|
|
2716
|
+
this.remotePlaybackCleanup = () => {
|
|
2717
|
+
remote.removeEventListener?.('connecting', sync);
|
|
2718
|
+
remote.removeEventListener?.('connect', sync);
|
|
2719
|
+
remote.removeEventListener?.('disconnect', sync);
|
|
2720
|
+
video.removeEventListener('webkitcurrentplaybacktargetiswirelesschanged', sync);
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
411
2723
|
syncControlVisibility() {
|
|
412
2724
|
if (this.state?.kind === 'playing' && this.openMenu === null) {
|
|
413
2725
|
this.onInteraction();
|
|
@@ -416,647 +2728,562 @@ class SmartPlayerComponent {
|
|
|
416
2728
|
this.clearHideControlsTimer();
|
|
417
2729
|
this.controlsVisible = true;
|
|
418
2730
|
}
|
|
2731
|
+
applyVolume() {
|
|
2732
|
+
try {
|
|
2733
|
+
const media = this.videoEl.nativeElement;
|
|
2734
|
+
media.volume = this.volume;
|
|
2735
|
+
// iOS ignores volume writes: without this, muting and the volume slider have no effect on iPhone and iPad.
|
|
2736
|
+
media.muted = this.volume <= 0.001;
|
|
2737
|
+
}
|
|
2738
|
+
catch {
|
|
2739
|
+
// Some browser environments reject direct volume writes; the engine path remains available.
|
|
2740
|
+
}
|
|
2741
|
+
this.facade.setVolume(this.volume);
|
|
2742
|
+
}
|
|
2743
|
+
syncOutputVolumeInput() {
|
|
2744
|
+
this.setVolume(this.outputVolume, false);
|
|
2745
|
+
}
|
|
2746
|
+
setOutputVolume(value) {
|
|
2747
|
+
this.setVolume(value, false);
|
|
2748
|
+
}
|
|
2749
|
+
setVolume(value, emitChange) {
|
|
2750
|
+
const nextVolume = this.normalizeVolume(value, this.volume);
|
|
2751
|
+
if (Math.abs(nextVolume - this.volume) < 0.001)
|
|
2752
|
+
return;
|
|
2753
|
+
this.volume = nextVolume;
|
|
2754
|
+
if (this.volume > 0)
|
|
2755
|
+
this.lastVolumeBeforeMute = this.volume;
|
|
2756
|
+
if (this.initialized)
|
|
2757
|
+
this.applyVolume();
|
|
2758
|
+
if (emitChange)
|
|
2759
|
+
this.outputVolumeChange.emit(this.volume);
|
|
2760
|
+
}
|
|
2761
|
+
normalizeVolume(value, fallback) {
|
|
2762
|
+
const numeric = Number(value);
|
|
2763
|
+
if (!Number.isFinite(numeric))
|
|
2764
|
+
return fallback;
|
|
2765
|
+
return Math.max(0, Math.min(1, numeric));
|
|
2766
|
+
}
|
|
419
2767
|
clearHideControlsTimer() {
|
|
420
2768
|
if (this.hideControlsTimer) {
|
|
421
2769
|
clearTimeout(this.hideControlsTimer);
|
|
422
2770
|
this.hideControlsTimer = null;
|
|
423
2771
|
}
|
|
424
2772
|
}
|
|
2773
|
+
queueSurfacePlaybackToggle() {
|
|
2774
|
+
this.clearSurfaceClickTimer();
|
|
2775
|
+
this.surfaceClickTimer = setTimeout(() => {
|
|
2776
|
+
this.surfaceClickTimer = null;
|
|
2777
|
+
this.requestPlaybackToggle();
|
|
2778
|
+
this.cdr.markForCheck();
|
|
2779
|
+
}, 180);
|
|
2780
|
+
}
|
|
2781
|
+
clearSurfaceClickTimer() {
|
|
2782
|
+
if (this.surfaceClickTimer) {
|
|
2783
|
+
clearTimeout(this.surfaceClickTimer);
|
|
2784
|
+
this.surfaceClickTimer = null;
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
playbackToggleIntent() {
|
|
2788
|
+
return this.state?.kind === 'playing' ? 'pause' : 'play';
|
|
2789
|
+
}
|
|
2790
|
+
showSurfaceGesture(intent) {
|
|
2791
|
+
this.surfaceGesture = intent;
|
|
2792
|
+
this.clearGestureTimer();
|
|
2793
|
+
this.gestureTimer = setTimeout(() => {
|
|
2794
|
+
this.surfaceGesture = null;
|
|
2795
|
+
this.cdr.markForCheck();
|
|
2796
|
+
}, 520);
|
|
2797
|
+
this.cdr.markForCheck();
|
|
2798
|
+
}
|
|
2799
|
+
clearGestureTimer() {
|
|
2800
|
+
if (this.gestureTimer) {
|
|
2801
|
+
clearTimeout(this.gestureTimer);
|
|
2802
|
+
this.gestureTimer = null;
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
425
2805
|
isInteractiveTarget(target) {
|
|
426
2806
|
const el = target;
|
|
427
2807
|
if (!el)
|
|
428
2808
|
return false;
|
|
429
2809
|
return !!el.closest('input, button, textarea, select, summary, [contenteditable="true"]');
|
|
430
2810
|
}
|
|
431
|
-
errorTitle() {
|
|
432
|
-
const s = this.state;
|
|
433
|
-
const code = s?.error?.code ?? s?.error?.name ?? 'UNKNOWN';
|
|
434
|
-
if (code === 'ENGINE_MISSING')
|
|
435
|
-
return 'Playback engine missing';
|
|
436
|
-
if (code === 'AUTOPLAY_BLOCKED')
|
|
437
|
-
return 'Click to start playback';
|
|
438
|
-
if (code === 'NETWORK_HTTP')
|
|
439
|
-
return 'Network error';
|
|
440
|
-
if (code === 'DECODE_ERROR')
|
|
441
|
-
return 'Decode error';
|
|
442
|
-
if (code === 'SRC_NOT_SUPPORTED')
|
|
443
|
-
return 'Format not supported';
|
|
444
|
-
return 'Playback error';
|
|
445
|
-
}
|
|
446
|
-
errorHint() {
|
|
447
|
-
const s = this.state;
|
|
448
|
-
const code = s?.error?.code ?? s?.error?.name ?? 'UNKNOWN';
|
|
449
|
-
if (code === 'ENGINE_MISSING') {
|
|
450
|
-
return 'This source requires an additional engine. Install @ngxsp/engine-shaka and pass it via [engines].';
|
|
451
|
-
}
|
|
452
|
-
if (code === 'AUTOPLAY_BLOCKED')
|
|
453
|
-
return 'Your browser blocked autoplay. Press Play to start.';
|
|
454
|
-
if (code === 'NETWORK_HTTP')
|
|
455
|
-
return 'Check your connection or try again.';
|
|
456
|
-
return s?.error?.message ? String(s.error.message) : 'Unknown error';
|
|
457
|
-
}
|
|
458
|
-
async onRetry() {
|
|
459
|
-
try {
|
|
460
|
-
await this.facade.reload();
|
|
461
|
-
}
|
|
462
|
-
catch { }
|
|
463
|
-
}
|
|
464
|
-
async onUserPlay() {
|
|
465
|
-
try {
|
|
466
|
-
await this.facade.play();
|
|
467
|
-
}
|
|
468
|
-
catch { }
|
|
469
|
-
}
|
|
470
2811
|
emitNext() {
|
|
471
2812
|
this.next.emit();
|
|
472
2813
|
}
|
|
473
2814
|
emitPrev() {
|
|
474
2815
|
this.prev.emit();
|
|
475
2816
|
}
|
|
476
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
477
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
478
|
-
<div class="ngxsp-root" [class.ngxsp-skin-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
2817
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: SmartPlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2818
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: SmartPlayerComponent, isStandalone: true, selector: "ngx-smart-player", inputs: { config: "config", plugins: "plugins", ports: "ports", engines: "engines", debug: "debug", startTimeSec: "startTimeSec", autoplayOnInit: "autoplayOnInit", playRequest: "playRequest", canPrev: "canPrev", canNext: "canNext", previousLabel: "previousLabel", nextLabel: "nextLabel", playLabel: "playLabel", pauseLabel: "pauseLabel", shuffleActive: "shuffleActive", shuffleLabel: "shuffleLabel", repeatMode: "repeatMode", repeatLabel: "repeatLabel", hasPlaylistControls: "hasPlaylistControls", mediaMode: "mediaMode", castEnabled: "castEnabled", castConnected: "castConnected", documentShortcuts: "documentShortcuts", nowPlayingLabel: "nowPlayingLabel", nowListeningLabel: "nowListeningLabel", fullscreenHintLabel: "fullscreenHintLabel", fullscreenLabel: "fullscreenLabel", readyStatusLabel: "readyStatusLabel", playingStatusLabel: "playingStatusLabel", pausedStatusLabel: "pausedStatusLabel", bufferingStatusLabel: "bufferingStatusLabel", loadingStatusLabel: "loadingStatusLabel", endedStatusLabel: "endedStatusLabel", errorStatusLabel: "errorStatusLabel", outputVolume: "outputVolume", outputVolumeLabel: "outputVolumeLabel", outputMuteLabel: "outputMuteLabel", outputUnmuteLabel: "outputUnmuteLabel", timelineDurationSec: "timelineDurationSec", timelineMarkers: "timelineMarkers", menuLabels: "menuLabels" }, outputs: { ended: "ended", videoSize: "videoSize", progress: "progress", prev: "prev", next: "next", toggleShuffle: "toggleShuffle", cycleRepeat: "cycleRepeat", subtitles: "subtitles", subtitlesLoaded: "subtitlesLoaded", cast: "cast", stateChange: "stateChange", playbackToggle: "playbackToggle", outputVolumeChange: "outputVolumeChange" }, host: { listeners: { "document:keydown.escape": "onEscape()", "document:keydown": "onKeydown($event)" } }, viewQueries: [{ propertyName: "videoEl", first: true, predicate: ["video"], descendants: true, static: true }, { propertyName: "captionsLayer", first: true, predicate: ["captionsLayer"], descendants: true, static: true }, { propertyName: "subtitleFileInput", first: true, predicate: ["subtitleFileInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
|
|
2819
|
+
<div class="ngxsp-root" [class.ngxsp-skin-vod]="uiSkin==='vod'" [class.ngxsp-ui-visible]="uiVisible" [class.ngxsp-mode-audio]="isAudioMode" [class.ngxsp-mode-video]="!isAudioMode" [attr.data-kind]="state?.kind">
|
|
2820
|
+
<!-- Surface de présentation : interactions pointeur non essentielles, toutes
|
|
2821
|
+
dupliquées au clavier par @HostListener('document:keydown') (Espace/flèches/F/M/C)
|
|
2822
|
+
et par les contrôles focusables. -->
|
|
2823
|
+
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
|
|
2824
|
+
<div class="ngxsp-video" (dblclick)="onPrimarySurfaceDoubleClick()" (mousemove)="onInteraction()" (mouseenter)="onInteraction()" (mouseleave)="onPointerLeave()" (focusin)="onInteraction()" (touchstart)="onInteraction(true)" (click)="onRootClick($event)">
|
|
2825
|
+
<video #video playsinline x-webkit-airplay="allow" (timeupdate)="noop()" (loadedmetadata)="onLoadedMetadata()" (ended)="onEnded()"></video>
|
|
2826
|
+
<!-- Subtitles loaded from a file, drawn here at the bottom of the picture, above the controls when they show. -->
|
|
2827
|
+
<div #captionsLayer class="ngxsp-captions" aria-hidden="true"></div>
|
|
2828
|
+
@if (!isAudioMode) {
|
|
2829
|
+
<input #subtitleFileInput class="ngxsp-subtitleInput" type="file" accept=".srt,.vtt,text/vtt,application/x-subrip" hidden (change)="onSubtitleFileChosen($event)" />
|
|
2830
|
+
}
|
|
2831
|
+
@if (isAudioMode) {
|
|
2832
|
+
<div class="ngxsp-audio-stage" aria-hidden="true">
|
|
2833
|
+
<div class="ngxsp-audio-disc"></div>
|
|
2834
|
+
<div class="ngxsp-audio-copy">
|
|
2835
|
+
<div class="ngxsp-audio-eyebrow">Now Listening</div>
|
|
2836
|
+
<div class="ngxsp-audio-title">{{displayTitle}}</div>
|
|
2837
|
+
<div class="ngxsp-audio-subtitle">{{displaySubtitle}}</div>
|
|
2838
|
+
</div>
|
|
2839
|
+
<div class="ngxsp-audio-bars">
|
|
2840
|
+
<span></span><span></span><span></span><span></span><span></span>
|
|
495
2841
|
</div>
|
|
496
2842
|
</div>
|
|
497
2843
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
2844
|
+
<ngxsp-player-overlays
|
|
2845
|
+
[error]="currentError"
|
|
2846
|
+
[debug]="debug"
|
|
2847
|
+
[debugJson]="debugJson"
|
|
2848
|
+
(retry)="reload()"
|
|
2849
|
+
(requestPlay)="tryPlay()"
|
|
2850
|
+
(copyJson)="copyDebug()"
|
|
2851
|
+
(downloadJson)="downloadDebug()"
|
|
2852
|
+
/>
|
|
2853
|
+
@if (surfaceGesture) {
|
|
2854
|
+
<div class="ngxsp-gesturePulse" aria-hidden="true">
|
|
2855
|
+
@if (surfaceGesture === 'pause') {
|
|
2856
|
+
<ngxsp-smart-icon name="pause" />
|
|
2857
|
+
} @else {
|
|
2858
|
+
<ngxsp-smart-icon name="play" />
|
|
2859
|
+
}
|
|
2860
|
+
</div>
|
|
508
2861
|
}
|
|
509
2862
|
|
|
510
|
-
@if (uiSkin==='
|
|
511
|
-
<
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
<
|
|
575
|
-
@if (repeatMode === 'one') {
|
|
576
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true">
|
|
577
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 4h3v3"/>
|
|
578
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20 7l-2.8-2.8A4 4 0 0 0 14.37 3H8a4 4 0 0 0-4 4v1"/>
|
|
579
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7 20H4v-3"/>
|
|
580
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m4 17 2.8 2.8A4 4 0 0 0 9.63 21H16a4 4 0 0 0 4-4v-1"/>
|
|
581
|
-
<text x="16.8" y="17.6" fill="currentColor" stroke="none" font-size="7.5" font-weight="700" text-anchor="middle">1</text>
|
|
582
|
-
</svg>
|
|
583
|
-
} @else {
|
|
584
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 4h3v3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20 7l-2.8-2.8A4 4 0 0 0 14.37 3H8a4 4 0 0 0-4 4v1"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7 20H4v-3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m4 17 2.8 2.8A4 4 0 0 0 9.63 21H16a4 4 0 0 0 4-4v-1"/></svg>
|
|
585
|
-
}
|
|
586
|
-
</button>
|
|
587
|
-
}
|
|
588
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="toggleMenu('tracks')" [attr.title]="tracksButtonLabel()" [attr.aria-label]="tracksButtonLabel()">
|
|
589
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 6.75h16M4 12h16M4 17.25h16"/></svg>
|
|
590
|
-
</button>
|
|
591
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="subtitles.emit()" [attr.title]="captionsLabel()" [attr.aria-label]="captionsLabel()">
|
|
592
|
-
@if (selectedTextTrackId !== null) {
|
|
593
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 11.25h2.5m2 0H14m2 0h.5M8 14.75h3.5m2 0H16"/></svg>
|
|
594
|
-
} @else {
|
|
595
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 11.25h2.5m2 0H14m2 0h.5M8 14.75h3.5m2 0H16"/><path stroke-linecap="round" stroke-width="1.8" d="M6 6l12 12"/></svg>
|
|
596
|
-
}
|
|
597
|
-
</button>
|
|
598
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="toggleMenu('speed')" [attr.title]="playbackRateLabel()" [attr.aria-label]="playbackRateLabel()">
|
|
599
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M5.25 5.82c0-1.3 1.43-2.1 2.54-1.43l5.71 3.47a1.96 1.96 0 0 1 0 3.36L7.79 14.7a1.67 1.67 0 0 1-2.54-1.43V5.82Zm8.5 0c0-1.3 1.43-2.1 2.54-1.43L22 7.86a1.96 1.96 0 0 1 0 3.36l-5.71 3.47a1.67 1.67 0 0 1-2.54-1.43V5.82Z"/></svg>
|
|
600
|
-
</button>
|
|
601
|
-
@if (isPiPSupported()) {
|
|
602
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="togglePiP()" [attr.title]="pipLabel()" [attr.aria-label]="pipLabel()">
|
|
603
|
-
@if (isPiPActive()) {
|
|
604
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><rect x="12.25" y="11.25" width="5.25" height="4.5" rx="1" fill="currentColor" stroke="none"/></svg>
|
|
605
|
-
} @else {
|
|
606
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><rect x="12.25" y="11.25" width="5.25" height="4.5" rx="1" stroke-width="1.8"/></svg>
|
|
607
|
-
}
|
|
608
|
-
</button>
|
|
609
|
-
}
|
|
610
|
-
</div>
|
|
611
|
-
|
|
612
|
-
<input class="ngxsp-range ngxsp-nfx-range" type="range"
|
|
613
|
-
min="0" [max]="duration || 0" step="0.1"
|
|
614
|
-
[value]="currentTime"
|
|
615
|
-
(input)="onScrub(($any($event.target).value))"
|
|
616
|
-
aria-label="Seek"
|
|
617
|
-
/>
|
|
618
|
-
|
|
619
|
-
<div class="ngxsp-nfx-metaRow">
|
|
620
|
-
<div class="ngxsp-nfx-spacer"></div>
|
|
621
|
-
<div class="ngxsp-nfx-label">{{ keyboardHintLabel() }}</div>
|
|
622
|
-
</div>
|
|
623
|
-
|
|
624
|
-
<div class="ngxsp-buffer" aria-hidden="true">
|
|
625
|
-
<div class="ngxsp-bufferFill" [style.width.%]="bufferPercent()"></div>
|
|
626
|
-
</div>
|
|
627
|
-
|
|
628
|
-
@if (openMenu === 'tracks') {
|
|
629
|
-
<div class="ngxsp-menu" (click)="$event.stopPropagation()">
|
|
630
|
-
<div class="ngxsp-menuSection">
|
|
631
|
-
<div class="ngxsp-menuTitle">Captions</div>
|
|
632
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId === null" (click)="selectTextTrack(null)">Off</button>
|
|
633
|
-
@for (track of textTracks; track track.id) {
|
|
634
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId === track.id" (click)="selectTextTrack(track.id)">
|
|
635
|
-
{{ trackLabel(track) }}
|
|
636
|
-
</button>
|
|
637
|
-
}
|
|
638
|
-
</div>
|
|
639
|
-
|
|
640
|
-
<div class="ngxsp-menuSection">
|
|
641
|
-
<div class="ngxsp-menuTitle">Audio</div>
|
|
642
|
-
@if (audioTracks.length) {
|
|
643
|
-
@for (track of audioTracks; track track.id) {
|
|
644
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedAudioTrackId === track.id" (click)="selectAudioTrack(track.id)">
|
|
645
|
-
{{ trackLabel(track) }}
|
|
646
|
-
</button>
|
|
647
|
-
}
|
|
648
|
-
} @else {
|
|
649
|
-
<div class="ngxsp-menuHint">Single audio track</div>
|
|
650
|
-
}
|
|
651
|
-
</div>
|
|
652
|
-
|
|
653
|
-
<div class="ngxsp-menuSection">
|
|
654
|
-
<div class="ngxsp-menuTitle">Quality</div>
|
|
655
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedVideoTrackId === null" (click)="selectVideoTrack(null)">Auto</button>
|
|
656
|
-
@if (videoTracks.length) {
|
|
657
|
-
@for (track of videoTracks; track track.id) {
|
|
658
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedVideoTrackId === track.id" (click)="selectVideoTrack(track.id)">
|
|
659
|
-
{{ qualityLabel(track) }}
|
|
660
|
-
</button>
|
|
661
|
-
}
|
|
662
|
-
} @else {
|
|
663
|
-
<div class="ngxsp-menuHint">Single quality</div>
|
|
664
|
-
}
|
|
665
|
-
</div>
|
|
666
|
-
</div>
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
@if (openMenu === 'speed') {
|
|
670
|
-
<div class="ngxsp-menu ngxsp-menuCompact" (click)="$event.stopPropagation()">
|
|
671
|
-
<div class="ngxsp-menuSection">
|
|
672
|
-
<div class="ngxsp-menuTitle">Playback speed</div>
|
|
673
|
-
@for (rate of playbackRates; track rate) {
|
|
674
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="playbackRate === rate" (click)="setPlaybackRate(rate)">
|
|
675
|
-
{{ rate }}x
|
|
676
|
-
</button>
|
|
677
|
-
}
|
|
678
|
-
</div>
|
|
2863
|
+
@if (uiSkin==='vod') {
|
|
2864
|
+
<ngxsp-player-controls-vod
|
|
2865
|
+
[playing]="state?.kind==='playing'"
|
|
2866
|
+
[isAudioMode]="isAudioMode"
|
|
2867
|
+
[displayTitle]="displayTitle"
|
|
2868
|
+
[nowPlayingLabel]="nowPlayingLabel"
|
|
2869
|
+
[nowListeningLabel]="nowListeningLabel"
|
|
2870
|
+
[fullscreenHintLabel]="fullscreenHintLabel"
|
|
2871
|
+
[statusLabel]="statusLabel()"
|
|
2872
|
+
[keyboardHint]="keyboardHintLabel()"
|
|
2873
|
+
[hasPlaylistControls]="hasPlaylistControls"
|
|
2874
|
+
[canPrev]="canPrev"
|
|
2875
|
+
[canNext]="canNext"
|
|
2876
|
+
[previousLabel]="previousLabel"
|
|
2877
|
+
[nextLabel]="nextLabel"
|
|
2878
|
+
[playLabel]="playLabel"
|
|
2879
|
+
[pauseLabel]="pauseLabel"
|
|
2880
|
+
[shuffleActive]="shuffleActive"
|
|
2881
|
+
[shuffleLabel]="shuffleLabel"
|
|
2882
|
+
[repeatMode]="repeatMode"
|
|
2883
|
+
[repeatLabel]="repeatLabel"
|
|
2884
|
+
[captionsActive]="selectedTextTrackId !== null"
|
|
2885
|
+
[tracksLabel]="tracksButtonLabel()"
|
|
2886
|
+
[captionsLabel]="captionsLabel()"
|
|
2887
|
+
[speedLabel]="playbackRateLabel()"
|
|
2888
|
+
[volumeLabel]="outputVolumeLabel || volumeLabel()"
|
|
2889
|
+
[muteLabel]="outputMuteLabel"
|
|
2890
|
+
[unmuteLabel]="outputUnmuteLabel"
|
|
2891
|
+
[showAirPlay]="showAirPlayButton()"
|
|
2892
|
+
[airPlayActive]="isAirPlayActive()"
|
|
2893
|
+
[airPlayLabel]="airPlayLabel()"
|
|
2894
|
+
(airPlay)="openAirPlayPicker()"
|
|
2895
|
+
[showCast]="showCastButton()"
|
|
2896
|
+
[castActive]="isCastActive()"
|
|
2897
|
+
[castLabel]="castLabel()"
|
|
2898
|
+
[castHint]="castHintLabel()"
|
|
2899
|
+
[pipSupported]="isPiPSupported()"
|
|
2900
|
+
[pipActive]="isPiPActive()"
|
|
2901
|
+
[pipLabel]="pipLabel()"
|
|
2902
|
+
[fullscreenLabel]="fullscreenLabel"
|
|
2903
|
+
[volume]="volume"
|
|
2904
|
+
[currentTime]="currentTime"
|
|
2905
|
+
[duration]="duration"
|
|
2906
|
+
[buffered]="bufferAheadSec"
|
|
2907
|
+
[bufferPercent]="bufferPercent()"
|
|
2908
|
+
[profile]="sourceProfile"
|
|
2909
|
+
[timelineDurationSec]="timelineDurationSec ?? 0"
|
|
2910
|
+
[timelineMarkers]="timelineMarkers"
|
|
2911
|
+
(playToggle)="requestPlaybackToggle()"
|
|
2912
|
+
(seekRelative)="seekRel($event)"
|
|
2913
|
+
(prev)="emitPrev()"
|
|
2914
|
+
(next)="emitNext()"
|
|
2915
|
+
(toggleShuffle)="toggleShuffle.emit()"
|
|
2916
|
+
(cycleRepeat)="cycleRepeat.emit()"
|
|
2917
|
+
(openMenu)="toggleMenu($event)"
|
|
2918
|
+
(captions)="onCaptionsClick()"
|
|
2919
|
+
(cast)="onCastClick()"
|
|
2920
|
+
(pip)="togglePiP()"
|
|
2921
|
+
(fullscreen)="toggleFullscreen()"
|
|
2922
|
+
(mute)="toggleMute()"
|
|
2923
|
+
(scrub)="onScrub($event)"
|
|
2924
|
+
(volumeChange)="onVolume($event)"
|
|
2925
|
+
>
|
|
2926
|
+
<div ngxsp-player-inline-controls class="ngxsp-inline-projection">
|
|
2927
|
+
<ng-content select="[ngxsp-player-inline-controls]" />
|
|
679
2928
|
</div>
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M15 5h4m0 0v4m0-4-4.5 4.5A7 7 0 1 0 19 12"/></svg>
|
|
698
|
-
</button>
|
|
699
|
-
@if (hasPlaylistControls) {
|
|
700
|
-
<button type="button" class="ngxsp-btn" (click)="emitPrev()" [disabled]="!canPrev" aria-label="Previous item" title="Previous item"><svg class="ngxsp-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9.53 12 18 18.47v-12L9.53 12Zm-1.53 6.25a.75.75 0 0 0 1.25-.56V6.31a.75.75 0 0 0-1.25-.56L1.06 11.44a.75.75 0 0 0 0 1.12L8 18.25Z"/></svg></button>
|
|
701
|
-
<button type="button" class="ngxsp-btn" (click)="emitNext()" [disabled]="!canNext" aria-label="Next item" title="Next item"><svg class="ngxsp-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M14.47 12 6 5.53v12L14.47 12ZM16 5.75a.75.75 0 0 0-1.25.56v11.38a.75.75 0 0 0 1.25.56l6.94-5.69a.75.75 0 0 0 0-1.12L16 5.75Z"/></svg></button>
|
|
702
|
-
<button type="button" class="ngxsp-btn" [class.accent]="shuffleActive" (click)="toggleShuffle.emit()" [attr.title]="shuffleLabel" [attr.aria-label]="shuffleLabel">
|
|
703
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 6h3v3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m20 6-4.5 4.5"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 7h3.2c1 0 1.95.4 2.66 1.1l6.28 6.3A3.75 3.75 0 0 0 18.82 15H20"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 15h3v3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m20 18-4.56-4.56"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 17h3.2c1 0 1.95-.4 2.66-1.1l1.54-1.54"/></svg>
|
|
704
|
-
</button>
|
|
705
|
-
<button type="button" class="ngxsp-btn" [class.accent]="repeatMode !== 'off'" (click)="cycleRepeat.emit()" [attr.title]="repeatLabel" [attr.aria-label]="repeatLabel">
|
|
706
|
-
@if (repeatMode === 'one') {
|
|
707
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true">
|
|
708
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 4h3v3"/>
|
|
709
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20 7l-2.8-2.8A4 4 0 0 0 14.37 3H8a4 4 0 0 0-4 4v1"/>
|
|
710
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7 20H4v-3"/>
|
|
711
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m4 17 2.8 2.8A4 4 0 0 0 9.63 21H16a4 4 0 0 0 4-4v-1"/>
|
|
712
|
-
<text x="16.8" y="17.6" fill="currentColor" stroke="none" font-size="7.5" font-weight="700" text-anchor="middle">1</text>
|
|
713
|
-
</svg>
|
|
714
|
-
} @else {
|
|
715
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 4h3v3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20 7l-2.8-2.8A4 4 0 0 0 14.37 3H8a4 4 0 0 0-4 4v1"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7 20H4v-3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m4 17 2.8 2.8A4 4 0 0 0 9.63 21H16a4 4 0 0 0 4-4v-1"/></svg>
|
|
716
|
-
}
|
|
717
|
-
</button>
|
|
718
|
-
}
|
|
719
|
-
<button type="button" class="ngxsp-btn" (click)="toggleMenu('tracks')" [attr.title]="tracksButtonLabel()" [attr.aria-label]="tracksButtonLabel()"><svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 6.75h16M4 12h16M4 17.25h16"/></svg></button>
|
|
720
|
-
<button type="button" class="ngxsp-btn" (click)="subtitles.emit()" [attr.title]="captionsLabel()" [attr.aria-label]="captionsLabel()">
|
|
721
|
-
@if (selectedTextTrackId !== null) {
|
|
722
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 11.25h2.5m2 0H14m2 0h.5M8 14.75h3.5m2 0H16"/></svg>
|
|
723
|
-
} @else {
|
|
724
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 11.25h2.5m2 0H14m2 0h.5M8 14.75h3.5m2 0H16"/><path stroke-linecap="round" stroke-width="1.8" d="M6 6l12 12"/></svg>
|
|
725
|
-
}
|
|
726
|
-
</button>
|
|
727
|
-
<button type="button" class="ngxsp-btn" (click)="toggleMenu('speed')" [attr.title]="playbackRateLabel()" [attr.aria-label]="playbackRateLabel()"><svg class="ngxsp-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M5.25 5.82c0-1.3 1.43-2.1 2.54-1.43l5.71 3.47a1.96 1.96 0 0 1 0 3.36L7.79 14.7a1.67 1.67 0 0 1-2.54-1.43V5.82Zm8.5 0c0-1.3 1.43-2.1 2.54-1.43L22 7.86a1.96 1.96 0 0 1 0 3.36l-5.71 3.47a1.67 1.67 0 0 1-2.54-1.43V5.82Z"/></svg></button>
|
|
728
|
-
@if (isPiPSupported()) {
|
|
729
|
-
<button type="button" class="ngxsp-btn" (click)="togglePiP()" [attr.title]="pipLabel()" [attr.aria-label]="pipLabel()">
|
|
730
|
-
@if (isPiPActive()) {
|
|
731
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><rect x="12.25" y="11.25" width="5.25" height="4.5" rx="1" fill="currentColor" stroke="none"/></svg>
|
|
732
|
-
} @else {
|
|
733
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><rect x="12.25" y="11.25" width="5.25" height="4.5" rx="1" stroke-width="1.8"/></svg>
|
|
734
|
-
}
|
|
735
|
-
</button>
|
|
736
|
-
}
|
|
737
|
-
<button type="button" class="ngxsp-btn" (click)="toggleMute()" [attr.title]="volumeLabel()" [attr.aria-label]="volumeLabel()">
|
|
738
|
-
@if (volume <= 0.001) {
|
|
739
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11 5 6.5 9H3.75v6h2.75L11 19V5Zm3.5 5.5 5 5m0-5-5 5"/></svg>
|
|
740
|
-
} @else if (volume < 0.5) {
|
|
741
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11 5 6.5 9H3.75v6h2.75L11 19V5Zm4.5 4.75a3.5 3.5 0 0 1 0 4.95"/></svg>
|
|
742
|
-
} @else {
|
|
743
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11 5 6.5 9H3.75v6h2.75L11 19V5Zm4.5 4.75a3.5 3.5 0 0 1 0 4.95m2.75-7.7a7.5 7.5 0 0 1 0 10.45"/></svg>
|
|
744
|
-
}
|
|
745
|
-
</button>
|
|
746
|
-
|
|
747
|
-
<input class="ngxsp-range" type="range"
|
|
748
|
-
min="0" [max]="duration || 0" step="0.1"
|
|
749
|
-
[value]="currentTime"
|
|
750
|
-
(input)="onScrub(($any($event.target).value))"
|
|
2929
|
+
<ngxsp-player-menu
|
|
2930
|
+
[openMenu]="openMenu"
|
|
2931
|
+
[isAudioMode]="isAudioMode"
|
|
2932
|
+
[textTracks]="textTracks"
|
|
2933
|
+
[audioTracks]="audioTracks"
|
|
2934
|
+
[videoTracks]="videoTracks"
|
|
2935
|
+
[selectedTextTrackId]="selectedTextTrackId"
|
|
2936
|
+
[selectedAudioTrackId]="selectedAudioTrackId"
|
|
2937
|
+
[selectedVideoTrackId]="selectedVideoTrackId"
|
|
2938
|
+
[playbackRates]="playbackRates"
|
|
2939
|
+
[playbackRate]="playbackRate"
|
|
2940
|
+
[labels]="playerMenuLabels"
|
|
2941
|
+
(loadSubtitles)="openSubtitlePicker()"
|
|
2942
|
+
(selectText)="selectTextTrack($event)"
|
|
2943
|
+
(selectAudio)="selectAudioTrack($event)"
|
|
2944
|
+
(selectVideo)="selectVideoTrack($event)"
|
|
2945
|
+
(selectRate)="setPlaybackRate($event)"
|
|
751
2946
|
/>
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
2947
|
+
</ngxsp-player-controls-vod>
|
|
2948
|
+
} @else if (uiSkin==='edition') {
|
|
2949
|
+
<ngxsp-player-controls-edition
|
|
2950
|
+
[profile]="sourceProfile"
|
|
2951
|
+
[playing]="state?.kind==='playing'"
|
|
2952
|
+
[displayTitle]="displayTitle"
|
|
2953
|
+
[metaLabel]="editionMeta"
|
|
2954
|
+
[statusLabel]="statusLabel()"
|
|
2955
|
+
[captionsActive]="selectedTextTrackId !== null"
|
|
2956
|
+
[speedLabel]="playbackRateLabel()"
|
|
2957
|
+
[showAirPlay]="showAirPlayButton()"
|
|
2958
|
+
[airPlayActive]="isAirPlayActive()"
|
|
2959
|
+
[airPlayLabel]="airPlayLabel()"
|
|
2960
|
+
(airPlay)="openAirPlayPicker()"
|
|
2961
|
+
[showCast]="showCastButton()"
|
|
2962
|
+
[castActive]="isCastActive()"
|
|
2963
|
+
[pipSupported]="isPiPSupported()"
|
|
2964
|
+
[pipActive]="isPiPActive()"
|
|
2965
|
+
[volume]="volume"
|
|
2966
|
+
[volumeLabel]="outputVolumeLabel || volumeLabel()"
|
|
2967
|
+
[muteLabel]="outputMuteLabel"
|
|
2968
|
+
[unmuteLabel]="outputUnmuteLabel"
|
|
2969
|
+
[currentTime]="currentTime"
|
|
2970
|
+
[duration]="duration"
|
|
2971
|
+
[buffered]="bufferAheadSec"
|
|
2972
|
+
(playToggle)="requestPlaybackToggle()"
|
|
2973
|
+
(seekRelative)="seekRel($event)"
|
|
2974
|
+
(openMenu)="toggleMenu($event)"
|
|
2975
|
+
(captions)="onCaptionsClick()"
|
|
2976
|
+
(cast)="onCastClick()"
|
|
2977
|
+
(pip)="togglePiP()"
|
|
2978
|
+
(mute)="toggleMute()"
|
|
2979
|
+
(scrub)="onScrub($event)"
|
|
2980
|
+
(volumeChange)="onVolume($event)"
|
|
2981
|
+
(goLive)="reload()"
|
|
2982
|
+
>
|
|
2983
|
+
<ngxsp-player-menu
|
|
2984
|
+
[openMenu]="openMenu"
|
|
2985
|
+
[isAudioMode]="isAudioMode"
|
|
2986
|
+
[textTracks]="textTracks"
|
|
2987
|
+
[audioTracks]="audioTracks"
|
|
2988
|
+
[videoTracks]="videoTracks"
|
|
2989
|
+
[selectedTextTrackId]="selectedTextTrackId"
|
|
2990
|
+
[selectedAudioTrackId]="selectedAudioTrackId"
|
|
2991
|
+
[selectedVideoTrackId]="selectedVideoTrackId"
|
|
2992
|
+
[playbackRates]="playbackRates"
|
|
2993
|
+
[playbackRate]="playbackRate"
|
|
2994
|
+
[labels]="playerMenuLabels"
|
|
2995
|
+
(loadSubtitles)="openSubtitlePicker()"
|
|
2996
|
+
(selectText)="selectTextTrack($event)"
|
|
2997
|
+
(selectAudio)="selectAudioTrack($event)"
|
|
2998
|
+
(selectVideo)="selectVideoTrack($event)"
|
|
2999
|
+
(selectRate)="setPlaybackRate($event)"
|
|
760
3000
|
/>
|
|
761
|
-
</
|
|
3001
|
+
</ngxsp-player-controls-edition>
|
|
3002
|
+
} @else {
|
|
3003
|
+
<ngxsp-player-controls
|
|
3004
|
+
[playing]="state?.kind==='playing'"
|
|
3005
|
+
[isAudioMode]="isAudioMode"
|
|
3006
|
+
[hasPlaylistControls]="hasPlaylistControls"
|
|
3007
|
+
[canPrev]="canPrev"
|
|
3008
|
+
[canNext]="canNext"
|
|
3009
|
+
[shuffleActive]="shuffleActive"
|
|
3010
|
+
[shuffleLabel]="shuffleLabel"
|
|
3011
|
+
[repeatMode]="repeatMode"
|
|
3012
|
+
[repeatLabel]="repeatLabel"
|
|
3013
|
+
[captionsActive]="selectedTextTrackId !== null"
|
|
3014
|
+
[tracksLabel]="tracksButtonLabel()"
|
|
3015
|
+
[captionsLabel]="captionsLabel()"
|
|
3016
|
+
[speedLabel]="playbackRateLabel()"
|
|
3017
|
+
[volumeLabel]="volumeLabel()"
|
|
3018
|
+
[showCast]="showCastButton()"
|
|
3019
|
+
[castActive]="isCastActive()"
|
|
3020
|
+
[castLabel]="castLabel()"
|
|
3021
|
+
[castHint]="castHintLabel()"
|
|
3022
|
+
[pipSupported]="isPiPSupported()"
|
|
3023
|
+
[pipActive]="isPiPActive()"
|
|
3024
|
+
[pipLabel]="pipLabel()"
|
|
3025
|
+
[volume]="volume"
|
|
3026
|
+
[muteLabel]="outputMuteLabel"
|
|
3027
|
+
[unmuteLabel]="outputUnmuteLabel"
|
|
3028
|
+
[currentTime]="currentTime"
|
|
3029
|
+
[duration]="duration"
|
|
3030
|
+
[buffered]="bufferAheadSec"
|
|
3031
|
+
[profile]="sourceProfile"
|
|
3032
|
+
(playToggle)="requestPlaybackToggle()"
|
|
3033
|
+
(seekRelative)="seekRel($event)"
|
|
3034
|
+
(prev)="emitPrev()"
|
|
3035
|
+
(next)="emitNext()"
|
|
3036
|
+
(toggleShuffle)="toggleShuffle.emit()"
|
|
3037
|
+
(cycleRepeat)="cycleRepeat.emit()"
|
|
3038
|
+
(openMenu)="toggleMenu($event)"
|
|
3039
|
+
(captions)="subtitles.emit()"
|
|
3040
|
+
(cast)="onCastClick()"
|
|
3041
|
+
(pip)="togglePiP()"
|
|
3042
|
+
(mute)="toggleMute()"
|
|
3043
|
+
(scrub)="onScrub($event)"
|
|
3044
|
+
(volumeChange)="onVolume($event)"
|
|
3045
|
+
/>
|
|
762
3046
|
}
|
|
763
3047
|
|
|
764
3048
|
</div>
|
|
765
3049
|
</div>
|
|
766
|
-
`, isInline: true, styles: [".ngxsp-root{
|
|
3050
|
+
`, isInline: true, styles: [".ngxsp-root{--ngxsp-bg:#000;--ngxsp-bg-elev:var(--nsp-surface-1, #17181d);--ngxsp-bg-soft:var(--nsp-surface-2, #1e1f25);--ngxsp-border:var(--nsp-line, rgb(255 255 255 / .07));--ngxsp-border-strong:var(--nsp-line-strong, rgb(255 255 255 / .13));--ngxsp-text:var(--nsp-text, #ededf0);--ngxsp-text-dim:var(--nsp-text-2, #a3a5ae);--ngxsp-accent:var(--nsp-media-accent, #8b90ff);--ngxsp-accent-soft:rgb(139 144 255 / .14);--ngxsp-focus-ring:0 0 0 2px var(--nsp-media-accent, #8b90ff);position:relative;width:100%;height:100%;background:#000;color:var(--ngxsp-text);font-family:var(--nsp-font, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Ubuntu, sans-serif)}.ngxsp-video{position:relative;width:100%;height:100%;isolation:isolate;container-type:inline-size}.ngxsp-captions{position:absolute;left:6%;right:6%;bottom:5%;z-index:3;display:flex;flex-direction:column;align-items:center;gap:.15em;pointer-events:none;text-align:center;font-size:clamp(13px,2.6cqw,34px);line-height:1.3;transition:bottom .2s ease}.ngxsp-captions:empty{display:none}.ngxsp-ui-visible .ngxsp-captions{bottom:calc(2% + var(--ngxsp-captions-lift, 96px))}.ngxsp-captions ::ng-deep .ngxsp-cue{max-width:100%;padding:.08em .45em;border-radius:.2em;background:#000000bd;color:#fff;white-space:pre-line;text-shadow:0 1px 2px rgb(0 0 0 / .5)}.ngxsp-mode-audio .ngxsp-captions{display:none}.ngxsp-video video{width:100%;height:100%;display:block;background:#000;object-fit:contain}.ngxsp-mode-audio .ngxsp-video video{opacity:.02;filter:blur(10px) saturate(120%);transform:scale(1.08)}.ngxsp-audio-stage{position:absolute;inset:0;z-index:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;padding:40px 24px 140px;background:radial-gradient(circle at 50% 24%,rgba(96,165,250,.22),transparent 30%),radial-gradient(circle at 22% 78%,rgba(45,212,191,.12),transparent 28%),linear-gradient(180deg,#040c18eb,#050a12d1);pointer-events:none;text-align:center}.ngxsp-audio-disc{width:min(32vw,220px);aspect-ratio:1;border-radius:50%;background:radial-gradient(circle at center,#f8fafce6 0 10%,#0f172aeb 11% 20%,#64748bd9 21% 22%,#0f172afa 23% 100%);box-shadow:0 24px 80px #0f172a80,inset 0 0 0 10px #ffffff0d;animation:ngxspSpin 18s linear infinite}.ngxsp-audio-copy{display:flex;flex-direction:column;gap:6px;max-width:min(76ch,100%)}.ngxsp-audio-eyebrow{font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:#bfdbfecc;font-weight:700}.ngxsp-audio-title{font-size:36px;font-weight:800;letter-spacing:0;color:#f8fbff;text-shadow:0 18px 60px rgba(15,23,42,.6)}.ngxsp-audio-subtitle{font-size:16px;color:#e2e8f0c7}.ngxsp-audio-bars{display:flex;align-items:flex-end;gap:8px;height:40px}.ngxsp-audio-bars span{width:6px;height:16px;border-radius:999px;background:linear-gradient(180deg,#93c5fdf2,#22d3ee8c);animation:ngxspBars 1.15s ease-in-out infinite;box-shadow:0 0 18px #38bdf838}.ngxsp-audio-bars span:nth-child(2){animation-delay:.12s}.ngxsp-audio-bars span:nth-child(3){animation-delay:.24s}.ngxsp-audio-bars span:nth-child(4){animation-delay:.36s}.ngxsp-audio-bars span:nth-child(5){animation-delay:.48s}.ngxsp-skin-vod .ngxsp-video{position:relative}.ngxsp-skin-vod .ngxsp-video:before{content:none}.ngxsp-skin-vod .ngxsp-video:after{content:none}.ngxsp-mode-audio.ngxsp-skin-vod .ngxsp-video:before{background:linear-gradient(180deg,#03081259,#0308121f);opacity:1}.ngxsp-inline-projection{display:inline-flex;flex:0 0 auto;align-items:center;gap:8px;width:max-content}.ngxsp-inline-projection:empty{display:none}.ngxsp-gesturePulse{position:absolute;inset:0;z-index:5;display:grid;place-items:center;pointer-events:none;color:#f8fbff;animation:ngxspGesturePulse .52s ease both}.ngxsp-gesturePulse ngxsp-smart-icon{width:64px;height:64px;padding:18px;box-sizing:border-box;display:grid;place-items:center;border-radius:50%;background:#00000080;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}@media(max-width:640px){.ngxsp-audio-stage{padding:28px 16px 128px;gap:14px}.ngxsp-audio-disc{width:min(48vw,168px)}.ngxsp-audio-title{font-size:28px}.ngxsp-audio-subtitle{font-size:14px}.ngxsp-skin-vod .ngxsp-inline-projection{display:none}.ngxsp-gesturePulse ngxsp-smart-icon{width:64px;height:64px}}@keyframes ngxspSpin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes ngxspBars{0%,to{height:14px;opacity:.45}50%{height:40px;opacity:1}}@keyframes ngxspGesturePulse{0%{opacity:0;transform:scale(.82)}24%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(1.16)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PlayerMenuComponent, selector: "ngxsp-player-menu", inputs: ["openMenu", "isAudioMode", "textTracks", "audioTracks", "videoTracks", "selectedTextTrackId", "selectedAudioTrackId", "selectedVideoTrackId", "playbackRates", "playbackRate", "labels"], outputs: ["selectText", "selectAudio", "selectVideo", "selectRate", "loadSubtitles"] }, { kind: "component", type: PlayerOverlaysComponent, selector: "ngxsp-player-overlays", inputs: ["error", "debug", "debugJson"], outputs: ["retry", "requestPlay", "copyJson", "downloadJson"] }, { kind: "component", type: PlayerControlsComponent, selector: "ngxsp-player-controls", inputs: ["playing", "isAudioMode", "hasPlaylistControls", "canPrev", "canNext", "shuffleActive", "shuffleLabel", "repeatMode", "repeatLabel", "captionsActive", "tracksLabel", "captionsLabel", "speedLabel", "volumeLabel", "muteLabel", "unmuteLabel", "showAirPlay", "airPlayActive", "airPlayLabel", "showCast", "castActive", "castLabel", "castHint", "pipSupported", "pipActive", "pipLabel", "volume", "currentTime", "duration", "buffered", "profile"], outputs: ["playToggle", "seekRelative", "prev", "next", "toggleShuffle", "cycleRepeat", "openMenu", "captions", "airPlay", "cast", "pip", "mute", "scrub", "volumeChange"] }, { kind: "component", type: PlayerControlsVodComponent, selector: "ngxsp-player-controls-vod", inputs: ["playing", "isAudioMode", "displayTitle", "nowPlayingLabel", "nowListeningLabel", "fullscreenHintLabel", "statusLabel", "keyboardHint", "hasPlaylistControls", "canPrev", "canNext", "previousLabel", "nextLabel", "playLabel", "pauseLabel", "shuffleActive", "shuffleLabel", "repeatMode", "repeatLabel", "captionsActive", "tracksLabel", "captionsLabel", "speedLabel", "volumeLabel", "muteLabel", "unmuteLabel", "showAirPlay", "airPlayActive", "airPlayLabel", "showCast", "castActive", "castLabel", "castHint", "pipSupported", "pipActive", "pipLabel", "fullscreenLabel", "volume", "currentTime", "duration", "buffered", "bufferPercent", "profile", "timelineDurationSec", "timelineMarkers"], outputs: ["playToggle", "seekRelative", "prev", "next", "toggleShuffle", "cycleRepeat", "openMenu", "captions", "airPlay", "cast", "pip", "fullscreen", "mute", "scrub", "volumeChange"] }, { kind: "component", type: PlayerControlsEditionComponent, selector: "ngxsp-player-controls-edition", inputs: ["profile", "playing", "displayTitle", "metaLabel", "statusLabel", "behindSec", "liveLabel", "captionsActive", "speedLabel", "showAirPlay", "airPlayActive", "airPlayLabel", "showCast", "castActive", "pipSupported", "pipActive", "volume", "volumeLabel", "muteLabel", "unmuteLabel", "currentTime", "duration", "buffered"], outputs: ["playToggle", "seekRelative", "openMenu", "captions", "airPlay", "cast", "pip", "mute", "scrub", "volumeChange", "goLive"] }, { kind: "component", type: SmartPlayerIconComponent, selector: "ngxsp-smart-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
767
3051
|
}
|
|
768
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3052
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: SmartPlayerComponent, decorators: [{
|
|
769
3053
|
type: Component,
|
|
770
|
-
args: [{ selector: 'ngx-smart-player', standalone: true, imports: [CommonModule], template: `
|
|
771
|
-
<div class="ngxsp-root" [class.ngxsp-skin-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
3054
|
+
args: [{ selector: 'ngx-smart-player', standalone: true, imports: [CommonModule, PlayerMenuComponent, PlayerOverlaysComponent, PlayerControlsComponent, PlayerControlsVodComponent, PlayerControlsEditionComponent, SmartPlayerIconComponent], template: `
|
|
3055
|
+
<div class="ngxsp-root" [class.ngxsp-skin-vod]="uiSkin==='vod'" [class.ngxsp-ui-visible]="uiVisible" [class.ngxsp-mode-audio]="isAudioMode" [class.ngxsp-mode-video]="!isAudioMode" [attr.data-kind]="state?.kind">
|
|
3056
|
+
<!-- Surface de présentation : interactions pointeur non essentielles, toutes
|
|
3057
|
+
dupliquées au clavier par @HostListener('document:keydown') (Espace/flèches/F/M/C)
|
|
3058
|
+
et par les contrôles focusables. -->
|
|
3059
|
+
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
|
|
3060
|
+
<div class="ngxsp-video" (dblclick)="onPrimarySurfaceDoubleClick()" (mousemove)="onInteraction()" (mouseenter)="onInteraction()" (mouseleave)="onPointerLeave()" (focusin)="onInteraction()" (touchstart)="onInteraction(true)" (click)="onRootClick($event)">
|
|
3061
|
+
<video #video playsinline x-webkit-airplay="allow" (timeupdate)="noop()" (loadedmetadata)="onLoadedMetadata()" (ended)="onEnded()"></video>
|
|
3062
|
+
<!-- Subtitles loaded from a file, drawn here at the bottom of the picture, above the controls when they show. -->
|
|
3063
|
+
<div #captionsLayer class="ngxsp-captions" aria-hidden="true"></div>
|
|
3064
|
+
@if (!isAudioMode) {
|
|
3065
|
+
<input #subtitleFileInput class="ngxsp-subtitleInput" type="file" accept=".srt,.vtt,text/vtt,application/x-subrip" hidden (change)="onSubtitleFileChosen($event)" />
|
|
3066
|
+
}
|
|
3067
|
+
@if (isAudioMode) {
|
|
3068
|
+
<div class="ngxsp-audio-stage" aria-hidden="true">
|
|
3069
|
+
<div class="ngxsp-audio-disc"></div>
|
|
3070
|
+
<div class="ngxsp-audio-copy">
|
|
3071
|
+
<div class="ngxsp-audio-eyebrow">Now Listening</div>
|
|
3072
|
+
<div class="ngxsp-audio-title">{{displayTitle}}</div>
|
|
3073
|
+
<div class="ngxsp-audio-subtitle">{{displaySubtitle}}</div>
|
|
3074
|
+
</div>
|
|
3075
|
+
<div class="ngxsp-audio-bars">
|
|
3076
|
+
<span></span><span></span><span></span><span></span><span></span>
|
|
788
3077
|
</div>
|
|
789
3078
|
</div>
|
|
790
3079
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
3080
|
+
<ngxsp-player-overlays
|
|
3081
|
+
[error]="currentError"
|
|
3082
|
+
[debug]="debug"
|
|
3083
|
+
[debugJson]="debugJson"
|
|
3084
|
+
(retry)="reload()"
|
|
3085
|
+
(requestPlay)="tryPlay()"
|
|
3086
|
+
(copyJson)="copyDebug()"
|
|
3087
|
+
(downloadJson)="downloadDebug()"
|
|
3088
|
+
/>
|
|
3089
|
+
@if (surfaceGesture) {
|
|
3090
|
+
<div class="ngxsp-gesturePulse" aria-hidden="true">
|
|
3091
|
+
@if (surfaceGesture === 'pause') {
|
|
3092
|
+
<ngxsp-smart-icon name="pause" />
|
|
3093
|
+
} @else {
|
|
3094
|
+
<ngxsp-smart-icon name="play" />
|
|
3095
|
+
}
|
|
3096
|
+
</div>
|
|
801
3097
|
}
|
|
802
3098
|
|
|
803
|
-
@if (uiSkin==='
|
|
804
|
-
<
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
<
|
|
868
|
-
@if (repeatMode === 'one') {
|
|
869
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true">
|
|
870
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 4h3v3"/>
|
|
871
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20 7l-2.8-2.8A4 4 0 0 0 14.37 3H8a4 4 0 0 0-4 4v1"/>
|
|
872
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7 20H4v-3"/>
|
|
873
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m4 17 2.8 2.8A4 4 0 0 0 9.63 21H16a4 4 0 0 0 4-4v-1"/>
|
|
874
|
-
<text x="16.8" y="17.6" fill="currentColor" stroke="none" font-size="7.5" font-weight="700" text-anchor="middle">1</text>
|
|
875
|
-
</svg>
|
|
876
|
-
} @else {
|
|
877
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 4h3v3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20 7l-2.8-2.8A4 4 0 0 0 14.37 3H8a4 4 0 0 0-4 4v1"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7 20H4v-3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m4 17 2.8 2.8A4 4 0 0 0 9.63 21H16a4 4 0 0 0 4-4v-1"/></svg>
|
|
878
|
-
}
|
|
879
|
-
</button>
|
|
880
|
-
}
|
|
881
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="toggleMenu('tracks')" [attr.title]="tracksButtonLabel()" [attr.aria-label]="tracksButtonLabel()">
|
|
882
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 6.75h16M4 12h16M4 17.25h16"/></svg>
|
|
883
|
-
</button>
|
|
884
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="subtitles.emit()" [attr.title]="captionsLabel()" [attr.aria-label]="captionsLabel()">
|
|
885
|
-
@if (selectedTextTrackId !== null) {
|
|
886
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 11.25h2.5m2 0H14m2 0h.5M8 14.75h3.5m2 0H16"/></svg>
|
|
887
|
-
} @else {
|
|
888
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 11.25h2.5m2 0H14m2 0h.5M8 14.75h3.5m2 0H16"/><path stroke-linecap="round" stroke-width="1.8" d="M6 6l12 12"/></svg>
|
|
889
|
-
}
|
|
890
|
-
</button>
|
|
891
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="toggleMenu('speed')" [attr.title]="playbackRateLabel()" [attr.aria-label]="playbackRateLabel()">
|
|
892
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M5.25 5.82c0-1.3 1.43-2.1 2.54-1.43l5.71 3.47a1.96 1.96 0 0 1 0 3.36L7.79 14.7a1.67 1.67 0 0 1-2.54-1.43V5.82Zm8.5 0c0-1.3 1.43-2.1 2.54-1.43L22 7.86a1.96 1.96 0 0 1 0 3.36l-5.71 3.47a1.67 1.67 0 0 1-2.54-1.43V5.82Z"/></svg>
|
|
893
|
-
</button>
|
|
894
|
-
@if (isPiPSupported()) {
|
|
895
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="togglePiP()" [attr.title]="pipLabel()" [attr.aria-label]="pipLabel()">
|
|
896
|
-
@if (isPiPActive()) {
|
|
897
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><rect x="12.25" y="11.25" width="5.25" height="4.5" rx="1" fill="currentColor" stroke="none"/></svg>
|
|
898
|
-
} @else {
|
|
899
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><rect x="12.25" y="11.25" width="5.25" height="4.5" rx="1" stroke-width="1.8"/></svg>
|
|
900
|
-
}
|
|
901
|
-
</button>
|
|
902
|
-
}
|
|
903
|
-
</div>
|
|
904
|
-
|
|
905
|
-
<input class="ngxsp-range ngxsp-nfx-range" type="range"
|
|
906
|
-
min="0" [max]="duration || 0" step="0.1"
|
|
907
|
-
[value]="currentTime"
|
|
908
|
-
(input)="onScrub(($any($event.target).value))"
|
|
909
|
-
aria-label="Seek"
|
|
910
|
-
/>
|
|
911
|
-
|
|
912
|
-
<div class="ngxsp-nfx-metaRow">
|
|
913
|
-
<div class="ngxsp-nfx-spacer"></div>
|
|
914
|
-
<div class="ngxsp-nfx-label">{{ keyboardHintLabel() }}</div>
|
|
915
|
-
</div>
|
|
916
|
-
|
|
917
|
-
<div class="ngxsp-buffer" aria-hidden="true">
|
|
918
|
-
<div class="ngxsp-bufferFill" [style.width.%]="bufferPercent()"></div>
|
|
919
|
-
</div>
|
|
920
|
-
|
|
921
|
-
@if (openMenu === 'tracks') {
|
|
922
|
-
<div class="ngxsp-menu" (click)="$event.stopPropagation()">
|
|
923
|
-
<div class="ngxsp-menuSection">
|
|
924
|
-
<div class="ngxsp-menuTitle">Captions</div>
|
|
925
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId === null" (click)="selectTextTrack(null)">Off</button>
|
|
926
|
-
@for (track of textTracks; track track.id) {
|
|
927
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId === track.id" (click)="selectTextTrack(track.id)">
|
|
928
|
-
{{ trackLabel(track) }}
|
|
929
|
-
</button>
|
|
930
|
-
}
|
|
931
|
-
</div>
|
|
932
|
-
|
|
933
|
-
<div class="ngxsp-menuSection">
|
|
934
|
-
<div class="ngxsp-menuTitle">Audio</div>
|
|
935
|
-
@if (audioTracks.length) {
|
|
936
|
-
@for (track of audioTracks; track track.id) {
|
|
937
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedAudioTrackId === track.id" (click)="selectAudioTrack(track.id)">
|
|
938
|
-
{{ trackLabel(track) }}
|
|
939
|
-
</button>
|
|
940
|
-
}
|
|
941
|
-
} @else {
|
|
942
|
-
<div class="ngxsp-menuHint">Single audio track</div>
|
|
943
|
-
}
|
|
944
|
-
</div>
|
|
945
|
-
|
|
946
|
-
<div class="ngxsp-menuSection">
|
|
947
|
-
<div class="ngxsp-menuTitle">Quality</div>
|
|
948
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedVideoTrackId === null" (click)="selectVideoTrack(null)">Auto</button>
|
|
949
|
-
@if (videoTracks.length) {
|
|
950
|
-
@for (track of videoTracks; track track.id) {
|
|
951
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedVideoTrackId === track.id" (click)="selectVideoTrack(track.id)">
|
|
952
|
-
{{ qualityLabel(track) }}
|
|
953
|
-
</button>
|
|
954
|
-
}
|
|
955
|
-
} @else {
|
|
956
|
-
<div class="ngxsp-menuHint">Single quality</div>
|
|
957
|
-
}
|
|
958
|
-
</div>
|
|
959
|
-
</div>
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
@if (openMenu === 'speed') {
|
|
963
|
-
<div class="ngxsp-menu ngxsp-menuCompact" (click)="$event.stopPropagation()">
|
|
964
|
-
<div class="ngxsp-menuSection">
|
|
965
|
-
<div class="ngxsp-menuTitle">Playback speed</div>
|
|
966
|
-
@for (rate of playbackRates; track rate) {
|
|
967
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="playbackRate === rate" (click)="setPlaybackRate(rate)">
|
|
968
|
-
{{ rate }}x
|
|
969
|
-
</button>
|
|
970
|
-
}
|
|
971
|
-
</div>
|
|
3099
|
+
@if (uiSkin==='vod') {
|
|
3100
|
+
<ngxsp-player-controls-vod
|
|
3101
|
+
[playing]="state?.kind==='playing'"
|
|
3102
|
+
[isAudioMode]="isAudioMode"
|
|
3103
|
+
[displayTitle]="displayTitle"
|
|
3104
|
+
[nowPlayingLabel]="nowPlayingLabel"
|
|
3105
|
+
[nowListeningLabel]="nowListeningLabel"
|
|
3106
|
+
[fullscreenHintLabel]="fullscreenHintLabel"
|
|
3107
|
+
[statusLabel]="statusLabel()"
|
|
3108
|
+
[keyboardHint]="keyboardHintLabel()"
|
|
3109
|
+
[hasPlaylistControls]="hasPlaylistControls"
|
|
3110
|
+
[canPrev]="canPrev"
|
|
3111
|
+
[canNext]="canNext"
|
|
3112
|
+
[previousLabel]="previousLabel"
|
|
3113
|
+
[nextLabel]="nextLabel"
|
|
3114
|
+
[playLabel]="playLabel"
|
|
3115
|
+
[pauseLabel]="pauseLabel"
|
|
3116
|
+
[shuffleActive]="shuffleActive"
|
|
3117
|
+
[shuffleLabel]="shuffleLabel"
|
|
3118
|
+
[repeatMode]="repeatMode"
|
|
3119
|
+
[repeatLabel]="repeatLabel"
|
|
3120
|
+
[captionsActive]="selectedTextTrackId !== null"
|
|
3121
|
+
[tracksLabel]="tracksButtonLabel()"
|
|
3122
|
+
[captionsLabel]="captionsLabel()"
|
|
3123
|
+
[speedLabel]="playbackRateLabel()"
|
|
3124
|
+
[volumeLabel]="outputVolumeLabel || volumeLabel()"
|
|
3125
|
+
[muteLabel]="outputMuteLabel"
|
|
3126
|
+
[unmuteLabel]="outputUnmuteLabel"
|
|
3127
|
+
[showAirPlay]="showAirPlayButton()"
|
|
3128
|
+
[airPlayActive]="isAirPlayActive()"
|
|
3129
|
+
[airPlayLabel]="airPlayLabel()"
|
|
3130
|
+
(airPlay)="openAirPlayPicker()"
|
|
3131
|
+
[showCast]="showCastButton()"
|
|
3132
|
+
[castActive]="isCastActive()"
|
|
3133
|
+
[castLabel]="castLabel()"
|
|
3134
|
+
[castHint]="castHintLabel()"
|
|
3135
|
+
[pipSupported]="isPiPSupported()"
|
|
3136
|
+
[pipActive]="isPiPActive()"
|
|
3137
|
+
[pipLabel]="pipLabel()"
|
|
3138
|
+
[fullscreenLabel]="fullscreenLabel"
|
|
3139
|
+
[volume]="volume"
|
|
3140
|
+
[currentTime]="currentTime"
|
|
3141
|
+
[duration]="duration"
|
|
3142
|
+
[buffered]="bufferAheadSec"
|
|
3143
|
+
[bufferPercent]="bufferPercent()"
|
|
3144
|
+
[profile]="sourceProfile"
|
|
3145
|
+
[timelineDurationSec]="timelineDurationSec ?? 0"
|
|
3146
|
+
[timelineMarkers]="timelineMarkers"
|
|
3147
|
+
(playToggle)="requestPlaybackToggle()"
|
|
3148
|
+
(seekRelative)="seekRel($event)"
|
|
3149
|
+
(prev)="emitPrev()"
|
|
3150
|
+
(next)="emitNext()"
|
|
3151
|
+
(toggleShuffle)="toggleShuffle.emit()"
|
|
3152
|
+
(cycleRepeat)="cycleRepeat.emit()"
|
|
3153
|
+
(openMenu)="toggleMenu($event)"
|
|
3154
|
+
(captions)="onCaptionsClick()"
|
|
3155
|
+
(cast)="onCastClick()"
|
|
3156
|
+
(pip)="togglePiP()"
|
|
3157
|
+
(fullscreen)="toggleFullscreen()"
|
|
3158
|
+
(mute)="toggleMute()"
|
|
3159
|
+
(scrub)="onScrub($event)"
|
|
3160
|
+
(volumeChange)="onVolume($event)"
|
|
3161
|
+
>
|
|
3162
|
+
<div ngxsp-player-inline-controls class="ngxsp-inline-projection">
|
|
3163
|
+
<ng-content select="[ngxsp-player-inline-controls]" />
|
|
972
3164
|
</div>
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M15 5h4m0 0v4m0-4-4.5 4.5A7 7 0 1 0 19 12"/></svg>
|
|
991
|
-
</button>
|
|
992
|
-
@if (hasPlaylistControls) {
|
|
993
|
-
<button type="button" class="ngxsp-btn" (click)="emitPrev()" [disabled]="!canPrev" aria-label="Previous item" title="Previous item"><svg class="ngxsp-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9.53 12 18 18.47v-12L9.53 12Zm-1.53 6.25a.75.75 0 0 0 1.25-.56V6.31a.75.75 0 0 0-1.25-.56L1.06 11.44a.75.75 0 0 0 0 1.12L8 18.25Z"/></svg></button>
|
|
994
|
-
<button type="button" class="ngxsp-btn" (click)="emitNext()" [disabled]="!canNext" aria-label="Next item" title="Next item"><svg class="ngxsp-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M14.47 12 6 5.53v12L14.47 12ZM16 5.75a.75.75 0 0 0-1.25.56v11.38a.75.75 0 0 0 1.25.56l6.94-5.69a.75.75 0 0 0 0-1.12L16 5.75Z"/></svg></button>
|
|
995
|
-
<button type="button" class="ngxsp-btn" [class.accent]="shuffleActive" (click)="toggleShuffle.emit()" [attr.title]="shuffleLabel" [attr.aria-label]="shuffleLabel">
|
|
996
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 6h3v3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m20 6-4.5 4.5"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 7h3.2c1 0 1.95.4 2.66 1.1l6.28 6.3A3.75 3.75 0 0 0 18.82 15H20"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 15h3v3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m20 18-4.56-4.56"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 17h3.2c1 0 1.95-.4 2.66-1.1l1.54-1.54"/></svg>
|
|
997
|
-
</button>
|
|
998
|
-
<button type="button" class="ngxsp-btn" [class.accent]="repeatMode !== 'off'" (click)="cycleRepeat.emit()" [attr.title]="repeatLabel" [attr.aria-label]="repeatLabel">
|
|
999
|
-
@if (repeatMode === 'one') {
|
|
1000
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true">
|
|
1001
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 4h3v3"/>
|
|
1002
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20 7l-2.8-2.8A4 4 0 0 0 14.37 3H8a4 4 0 0 0-4 4v1"/>
|
|
1003
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7 20H4v-3"/>
|
|
1004
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m4 17 2.8 2.8A4 4 0 0 0 9.63 21H16a4 4 0 0 0 4-4v-1"/>
|
|
1005
|
-
<text x="16.8" y="17.6" fill="currentColor" stroke="none" font-size="7.5" font-weight="700" text-anchor="middle">1</text>
|
|
1006
|
-
</svg>
|
|
1007
|
-
} @else {
|
|
1008
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 4h3v3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20 7l-2.8-2.8A4 4 0 0 0 14.37 3H8a4 4 0 0 0-4 4v1"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7 20H4v-3"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m4 17 2.8 2.8A4 4 0 0 0 9.63 21H16a4 4 0 0 0 4-4v-1"/></svg>
|
|
1009
|
-
}
|
|
1010
|
-
</button>
|
|
1011
|
-
}
|
|
1012
|
-
<button type="button" class="ngxsp-btn" (click)="toggleMenu('tracks')" [attr.title]="tracksButtonLabel()" [attr.aria-label]="tracksButtonLabel()"><svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 6.75h16M4 12h16M4 17.25h16"/></svg></button>
|
|
1013
|
-
<button type="button" class="ngxsp-btn" (click)="subtitles.emit()" [attr.title]="captionsLabel()" [attr.aria-label]="captionsLabel()">
|
|
1014
|
-
@if (selectedTextTrackId !== null) {
|
|
1015
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 11.25h2.5m2 0H14m2 0h.5M8 14.75h3.5m2 0H16"/></svg>
|
|
1016
|
-
} @else {
|
|
1017
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><path stroke-linecap="round" stroke-width="1.8" d="M8 11.25h2.5m2 0H14m2 0h.5M8 14.75h3.5m2 0H16"/><path stroke-linecap="round" stroke-width="1.8" d="M6 6l12 12"/></svg>
|
|
1018
|
-
}
|
|
1019
|
-
</button>
|
|
1020
|
-
<button type="button" class="ngxsp-btn" (click)="toggleMenu('speed')" [attr.title]="playbackRateLabel()" [attr.aria-label]="playbackRateLabel()"><svg class="ngxsp-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M5.25 5.82c0-1.3 1.43-2.1 2.54-1.43l5.71 3.47a1.96 1.96 0 0 1 0 3.36L7.79 14.7a1.67 1.67 0 0 1-2.54-1.43V5.82Zm8.5 0c0-1.3 1.43-2.1 2.54-1.43L22 7.86a1.96 1.96 0 0 1 0 3.36l-5.71 3.47a1.67 1.67 0 0 1-2.54-1.43V5.82Z"/></svg></button>
|
|
1021
|
-
@if (isPiPSupported()) {
|
|
1022
|
-
<button type="button" class="ngxsp-btn" (click)="togglePiP()" [attr.title]="pipLabel()" [attr.aria-label]="pipLabel()">
|
|
1023
|
-
@if (isPiPActive()) {
|
|
1024
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><rect x="12.25" y="11.25" width="5.25" height="4.5" rx="1" fill="currentColor" stroke="none"/></svg>
|
|
1025
|
-
} @else {
|
|
1026
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke-width="1.8"/><rect x="12.25" y="11.25" width="5.25" height="4.5" rx="1" stroke-width="1.8"/></svg>
|
|
1027
|
-
}
|
|
1028
|
-
</button>
|
|
1029
|
-
}
|
|
1030
|
-
<button type="button" class="ngxsp-btn" (click)="toggleMute()" [attr.title]="volumeLabel()" [attr.aria-label]="volumeLabel()">
|
|
1031
|
-
@if (volume <= 0.001) {
|
|
1032
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11 5 6.5 9H3.75v6h2.75L11 19V5Zm3.5 5.5 5 5m0-5-5 5"/></svg>
|
|
1033
|
-
} @else if (volume < 0.5) {
|
|
1034
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11 5 6.5 9H3.75v6h2.75L11 19V5Zm4.5 4.75a3.5 3.5 0 0 1 0 4.95"/></svg>
|
|
1035
|
-
} @else {
|
|
1036
|
-
<svg class="ngxsp-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11 5 6.5 9H3.75v6h2.75L11 19V5Zm4.5 4.75a3.5 3.5 0 0 1 0 4.95m2.75-7.7a7.5 7.5 0 0 1 0 10.45"/></svg>
|
|
1037
|
-
}
|
|
1038
|
-
</button>
|
|
1039
|
-
|
|
1040
|
-
<input class="ngxsp-range" type="range"
|
|
1041
|
-
min="0" [max]="duration || 0" step="0.1"
|
|
1042
|
-
[value]="currentTime"
|
|
1043
|
-
(input)="onScrub(($any($event.target).value))"
|
|
3165
|
+
<ngxsp-player-menu
|
|
3166
|
+
[openMenu]="openMenu"
|
|
3167
|
+
[isAudioMode]="isAudioMode"
|
|
3168
|
+
[textTracks]="textTracks"
|
|
3169
|
+
[audioTracks]="audioTracks"
|
|
3170
|
+
[videoTracks]="videoTracks"
|
|
3171
|
+
[selectedTextTrackId]="selectedTextTrackId"
|
|
3172
|
+
[selectedAudioTrackId]="selectedAudioTrackId"
|
|
3173
|
+
[selectedVideoTrackId]="selectedVideoTrackId"
|
|
3174
|
+
[playbackRates]="playbackRates"
|
|
3175
|
+
[playbackRate]="playbackRate"
|
|
3176
|
+
[labels]="playerMenuLabels"
|
|
3177
|
+
(loadSubtitles)="openSubtitlePicker()"
|
|
3178
|
+
(selectText)="selectTextTrack($event)"
|
|
3179
|
+
(selectAudio)="selectAudioTrack($event)"
|
|
3180
|
+
(selectVideo)="selectVideoTrack($event)"
|
|
3181
|
+
(selectRate)="setPlaybackRate($event)"
|
|
1044
3182
|
/>
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
3183
|
+
</ngxsp-player-controls-vod>
|
|
3184
|
+
} @else if (uiSkin==='edition') {
|
|
3185
|
+
<ngxsp-player-controls-edition
|
|
3186
|
+
[profile]="sourceProfile"
|
|
3187
|
+
[playing]="state?.kind==='playing'"
|
|
3188
|
+
[displayTitle]="displayTitle"
|
|
3189
|
+
[metaLabel]="editionMeta"
|
|
3190
|
+
[statusLabel]="statusLabel()"
|
|
3191
|
+
[captionsActive]="selectedTextTrackId !== null"
|
|
3192
|
+
[speedLabel]="playbackRateLabel()"
|
|
3193
|
+
[showAirPlay]="showAirPlayButton()"
|
|
3194
|
+
[airPlayActive]="isAirPlayActive()"
|
|
3195
|
+
[airPlayLabel]="airPlayLabel()"
|
|
3196
|
+
(airPlay)="openAirPlayPicker()"
|
|
3197
|
+
[showCast]="showCastButton()"
|
|
3198
|
+
[castActive]="isCastActive()"
|
|
3199
|
+
[pipSupported]="isPiPSupported()"
|
|
3200
|
+
[pipActive]="isPiPActive()"
|
|
3201
|
+
[volume]="volume"
|
|
3202
|
+
[volumeLabel]="outputVolumeLabel || volumeLabel()"
|
|
3203
|
+
[muteLabel]="outputMuteLabel"
|
|
3204
|
+
[unmuteLabel]="outputUnmuteLabel"
|
|
3205
|
+
[currentTime]="currentTime"
|
|
3206
|
+
[duration]="duration"
|
|
3207
|
+
[buffered]="bufferAheadSec"
|
|
3208
|
+
(playToggle)="requestPlaybackToggle()"
|
|
3209
|
+
(seekRelative)="seekRel($event)"
|
|
3210
|
+
(openMenu)="toggleMenu($event)"
|
|
3211
|
+
(captions)="onCaptionsClick()"
|
|
3212
|
+
(cast)="onCastClick()"
|
|
3213
|
+
(pip)="togglePiP()"
|
|
3214
|
+
(mute)="toggleMute()"
|
|
3215
|
+
(scrub)="onScrub($event)"
|
|
3216
|
+
(volumeChange)="onVolume($event)"
|
|
3217
|
+
(goLive)="reload()"
|
|
3218
|
+
>
|
|
3219
|
+
<ngxsp-player-menu
|
|
3220
|
+
[openMenu]="openMenu"
|
|
3221
|
+
[isAudioMode]="isAudioMode"
|
|
3222
|
+
[textTracks]="textTracks"
|
|
3223
|
+
[audioTracks]="audioTracks"
|
|
3224
|
+
[videoTracks]="videoTracks"
|
|
3225
|
+
[selectedTextTrackId]="selectedTextTrackId"
|
|
3226
|
+
[selectedAudioTrackId]="selectedAudioTrackId"
|
|
3227
|
+
[selectedVideoTrackId]="selectedVideoTrackId"
|
|
3228
|
+
[playbackRates]="playbackRates"
|
|
3229
|
+
[playbackRate]="playbackRate"
|
|
3230
|
+
[labels]="playerMenuLabels"
|
|
3231
|
+
(loadSubtitles)="openSubtitlePicker()"
|
|
3232
|
+
(selectText)="selectTextTrack($event)"
|
|
3233
|
+
(selectAudio)="selectAudioTrack($event)"
|
|
3234
|
+
(selectVideo)="selectVideoTrack($event)"
|
|
3235
|
+
(selectRate)="setPlaybackRate($event)"
|
|
1053
3236
|
/>
|
|
1054
|
-
</
|
|
3237
|
+
</ngxsp-player-controls-edition>
|
|
3238
|
+
} @else {
|
|
3239
|
+
<ngxsp-player-controls
|
|
3240
|
+
[playing]="state?.kind==='playing'"
|
|
3241
|
+
[isAudioMode]="isAudioMode"
|
|
3242
|
+
[hasPlaylistControls]="hasPlaylistControls"
|
|
3243
|
+
[canPrev]="canPrev"
|
|
3244
|
+
[canNext]="canNext"
|
|
3245
|
+
[shuffleActive]="shuffleActive"
|
|
3246
|
+
[shuffleLabel]="shuffleLabel"
|
|
3247
|
+
[repeatMode]="repeatMode"
|
|
3248
|
+
[repeatLabel]="repeatLabel"
|
|
3249
|
+
[captionsActive]="selectedTextTrackId !== null"
|
|
3250
|
+
[tracksLabel]="tracksButtonLabel()"
|
|
3251
|
+
[captionsLabel]="captionsLabel()"
|
|
3252
|
+
[speedLabel]="playbackRateLabel()"
|
|
3253
|
+
[volumeLabel]="volumeLabel()"
|
|
3254
|
+
[showCast]="showCastButton()"
|
|
3255
|
+
[castActive]="isCastActive()"
|
|
3256
|
+
[castLabel]="castLabel()"
|
|
3257
|
+
[castHint]="castHintLabel()"
|
|
3258
|
+
[pipSupported]="isPiPSupported()"
|
|
3259
|
+
[pipActive]="isPiPActive()"
|
|
3260
|
+
[pipLabel]="pipLabel()"
|
|
3261
|
+
[volume]="volume"
|
|
3262
|
+
[muteLabel]="outputMuteLabel"
|
|
3263
|
+
[unmuteLabel]="outputUnmuteLabel"
|
|
3264
|
+
[currentTime]="currentTime"
|
|
3265
|
+
[duration]="duration"
|
|
3266
|
+
[buffered]="bufferAheadSec"
|
|
3267
|
+
[profile]="sourceProfile"
|
|
3268
|
+
(playToggle)="requestPlaybackToggle()"
|
|
3269
|
+
(seekRelative)="seekRel($event)"
|
|
3270
|
+
(prev)="emitPrev()"
|
|
3271
|
+
(next)="emitNext()"
|
|
3272
|
+
(toggleShuffle)="toggleShuffle.emit()"
|
|
3273
|
+
(cycleRepeat)="cycleRepeat.emit()"
|
|
3274
|
+
(openMenu)="toggleMenu($event)"
|
|
3275
|
+
(captions)="subtitles.emit()"
|
|
3276
|
+
(cast)="onCastClick()"
|
|
3277
|
+
(pip)="togglePiP()"
|
|
3278
|
+
(mute)="toggleMute()"
|
|
3279
|
+
(scrub)="onScrub($event)"
|
|
3280
|
+
(volumeChange)="onVolume($event)"
|
|
3281
|
+
/>
|
|
1055
3282
|
}
|
|
1056
3283
|
|
|
1057
3284
|
</div>
|
|
1058
3285
|
</div>
|
|
1059
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".ngxsp-root{
|
|
3286
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".ngxsp-root{--ngxsp-bg:#000;--ngxsp-bg-elev:var(--nsp-surface-1, #17181d);--ngxsp-bg-soft:var(--nsp-surface-2, #1e1f25);--ngxsp-border:var(--nsp-line, rgb(255 255 255 / .07));--ngxsp-border-strong:var(--nsp-line-strong, rgb(255 255 255 / .13));--ngxsp-text:var(--nsp-text, #ededf0);--ngxsp-text-dim:var(--nsp-text-2, #a3a5ae);--ngxsp-accent:var(--nsp-media-accent, #8b90ff);--ngxsp-accent-soft:rgb(139 144 255 / .14);--ngxsp-focus-ring:0 0 0 2px var(--nsp-media-accent, #8b90ff);position:relative;width:100%;height:100%;background:#000;color:var(--ngxsp-text);font-family:var(--nsp-font, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Ubuntu, sans-serif)}.ngxsp-video{position:relative;width:100%;height:100%;isolation:isolate;container-type:inline-size}.ngxsp-captions{position:absolute;left:6%;right:6%;bottom:5%;z-index:3;display:flex;flex-direction:column;align-items:center;gap:.15em;pointer-events:none;text-align:center;font-size:clamp(13px,2.6cqw,34px);line-height:1.3;transition:bottom .2s ease}.ngxsp-captions:empty{display:none}.ngxsp-ui-visible .ngxsp-captions{bottom:calc(2% + var(--ngxsp-captions-lift, 96px))}.ngxsp-captions ::ng-deep .ngxsp-cue{max-width:100%;padding:.08em .45em;border-radius:.2em;background:#000000bd;color:#fff;white-space:pre-line;text-shadow:0 1px 2px rgb(0 0 0 / .5)}.ngxsp-mode-audio .ngxsp-captions{display:none}.ngxsp-video video{width:100%;height:100%;display:block;background:#000;object-fit:contain}.ngxsp-mode-audio .ngxsp-video video{opacity:.02;filter:blur(10px) saturate(120%);transform:scale(1.08)}.ngxsp-audio-stage{position:absolute;inset:0;z-index:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;padding:40px 24px 140px;background:radial-gradient(circle at 50% 24%,rgba(96,165,250,.22),transparent 30%),radial-gradient(circle at 22% 78%,rgba(45,212,191,.12),transparent 28%),linear-gradient(180deg,#040c18eb,#050a12d1);pointer-events:none;text-align:center}.ngxsp-audio-disc{width:min(32vw,220px);aspect-ratio:1;border-radius:50%;background:radial-gradient(circle at center,#f8fafce6 0 10%,#0f172aeb 11% 20%,#64748bd9 21% 22%,#0f172afa 23% 100%);box-shadow:0 24px 80px #0f172a80,inset 0 0 0 10px #ffffff0d;animation:ngxspSpin 18s linear infinite}.ngxsp-audio-copy{display:flex;flex-direction:column;gap:6px;max-width:min(76ch,100%)}.ngxsp-audio-eyebrow{font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:#bfdbfecc;font-weight:700}.ngxsp-audio-title{font-size:36px;font-weight:800;letter-spacing:0;color:#f8fbff;text-shadow:0 18px 60px rgba(15,23,42,.6)}.ngxsp-audio-subtitle{font-size:16px;color:#e2e8f0c7}.ngxsp-audio-bars{display:flex;align-items:flex-end;gap:8px;height:40px}.ngxsp-audio-bars span{width:6px;height:16px;border-radius:999px;background:linear-gradient(180deg,#93c5fdf2,#22d3ee8c);animation:ngxspBars 1.15s ease-in-out infinite;box-shadow:0 0 18px #38bdf838}.ngxsp-audio-bars span:nth-child(2){animation-delay:.12s}.ngxsp-audio-bars span:nth-child(3){animation-delay:.24s}.ngxsp-audio-bars span:nth-child(4){animation-delay:.36s}.ngxsp-audio-bars span:nth-child(5){animation-delay:.48s}.ngxsp-skin-vod .ngxsp-video{position:relative}.ngxsp-skin-vod .ngxsp-video:before{content:none}.ngxsp-skin-vod .ngxsp-video:after{content:none}.ngxsp-mode-audio.ngxsp-skin-vod .ngxsp-video:before{background:linear-gradient(180deg,#03081259,#0308121f);opacity:1}.ngxsp-inline-projection{display:inline-flex;flex:0 0 auto;align-items:center;gap:8px;width:max-content}.ngxsp-inline-projection:empty{display:none}.ngxsp-gesturePulse{position:absolute;inset:0;z-index:5;display:grid;place-items:center;pointer-events:none;color:#f8fbff;animation:ngxspGesturePulse .52s ease both}.ngxsp-gesturePulse ngxsp-smart-icon{width:64px;height:64px;padding:18px;box-sizing:border-box;display:grid;place-items:center;border-radius:50%;background:#00000080;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}@media(max-width:640px){.ngxsp-audio-stage{padding:28px 16px 128px;gap:14px}.ngxsp-audio-disc{width:min(48vw,168px)}.ngxsp-audio-title{font-size:28px}.ngxsp-audio-subtitle{font-size:14px}.ngxsp-skin-vod .ngxsp-inline-projection{display:none}.ngxsp-gesturePulse ngxsp-smart-icon{width:64px;height:64px}}@keyframes ngxspSpin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes ngxspBars{0%,to{height:14px;opacity:.45}50%{height:40px;opacity:1}}@keyframes ngxspGesturePulse{0%{opacity:0;transform:scale(.82)}24%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(1.16)}}\n"] }]
|
|
1060
3287
|
}], propDecorators: { config: [{
|
|
1061
3288
|
type: Input,
|
|
1062
3289
|
args: [{ required: true }]
|
|
@@ -1070,10 +3297,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1070
3297
|
type: Input
|
|
1071
3298
|
}], startTimeSec: [{
|
|
1072
3299
|
type: Input
|
|
3300
|
+
}], autoplayOnInit: [{
|
|
3301
|
+
type: Input
|
|
3302
|
+
}], playRequest: [{
|
|
3303
|
+
type: Input
|
|
1073
3304
|
}], canPrev: [{
|
|
1074
3305
|
type: Input
|
|
1075
3306
|
}], canNext: [{
|
|
1076
3307
|
type: Input
|
|
3308
|
+
}], previousLabel: [{
|
|
3309
|
+
type: Input
|
|
3310
|
+
}], nextLabel: [{
|
|
3311
|
+
type: Input
|
|
3312
|
+
}], playLabel: [{
|
|
3313
|
+
type: Input
|
|
3314
|
+
}], pauseLabel: [{
|
|
3315
|
+
type: Input
|
|
1077
3316
|
}], shuffleActive: [{
|
|
1078
3317
|
type: Input
|
|
1079
3318
|
}], shuffleLabel: [{
|
|
@@ -1084,6 +3323,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1084
3323
|
type: Input
|
|
1085
3324
|
}], hasPlaylistControls: [{
|
|
1086
3325
|
type: Input
|
|
3326
|
+
}], mediaMode: [{
|
|
3327
|
+
type: Input
|
|
3328
|
+
}], castEnabled: [{
|
|
3329
|
+
type: Input
|
|
3330
|
+
}], castConnected: [{
|
|
3331
|
+
type: Input
|
|
3332
|
+
}], documentShortcuts: [{
|
|
3333
|
+
type: Input
|
|
3334
|
+
}], nowPlayingLabel: [{
|
|
3335
|
+
type: Input
|
|
3336
|
+
}], nowListeningLabel: [{
|
|
3337
|
+
type: Input
|
|
3338
|
+
}], fullscreenHintLabel: [{
|
|
3339
|
+
type: Input
|
|
3340
|
+
}], fullscreenLabel: [{
|
|
3341
|
+
type: Input
|
|
3342
|
+
}], readyStatusLabel: [{
|
|
3343
|
+
type: Input
|
|
3344
|
+
}], playingStatusLabel: [{
|
|
3345
|
+
type: Input
|
|
3346
|
+
}], pausedStatusLabel: [{
|
|
3347
|
+
type: Input
|
|
3348
|
+
}], bufferingStatusLabel: [{
|
|
3349
|
+
type: Input
|
|
3350
|
+
}], loadingStatusLabel: [{
|
|
3351
|
+
type: Input
|
|
3352
|
+
}], endedStatusLabel: [{
|
|
3353
|
+
type: Input
|
|
3354
|
+
}], errorStatusLabel: [{
|
|
3355
|
+
type: Input
|
|
3356
|
+
}], outputVolume: [{
|
|
3357
|
+
type: Input
|
|
3358
|
+
}], outputVolumeLabel: [{
|
|
3359
|
+
type: Input
|
|
3360
|
+
}], outputMuteLabel: [{
|
|
3361
|
+
type: Input
|
|
3362
|
+
}], outputUnmuteLabel: [{
|
|
3363
|
+
type: Input
|
|
3364
|
+
}], timelineDurationSec: [{
|
|
3365
|
+
type: Input
|
|
3366
|
+
}], timelineMarkers: [{
|
|
3367
|
+
type: Input
|
|
1087
3368
|
}], ended: [{
|
|
1088
3369
|
type: Output
|
|
1089
3370
|
}], videoSize: [{
|
|
@@ -1100,9 +3381,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1100
3381
|
type: Output
|
|
1101
3382
|
}], subtitles: [{
|
|
1102
3383
|
type: Output
|
|
3384
|
+
}], subtitlesLoaded: [{
|
|
3385
|
+
type: Output
|
|
3386
|
+
}], menuLabels: [{
|
|
3387
|
+
type: Input
|
|
3388
|
+
}], cast: [{
|
|
3389
|
+
type: Output
|
|
3390
|
+
}], stateChange: [{
|
|
3391
|
+
type: Output
|
|
3392
|
+
}], playbackToggle: [{
|
|
3393
|
+
type: Output
|
|
3394
|
+
}], outputVolumeChange: [{
|
|
3395
|
+
type: Output
|
|
1103
3396
|
}], videoEl: [{
|
|
1104
3397
|
type: ViewChild,
|
|
1105
3398
|
args: ['video', { static: true }]
|
|
3399
|
+
}], captionsLayer: [{
|
|
3400
|
+
type: ViewChild,
|
|
3401
|
+
args: ['captionsLayer', { static: true }]
|
|
3402
|
+
}], subtitleFileInput: [{
|
|
3403
|
+
type: ViewChild,
|
|
3404
|
+
args: ['subtitleFileInput']
|
|
1106
3405
|
}], onEscape: [{
|
|
1107
3406
|
type: HostListener,
|
|
1108
3407
|
args: ['document:keydown.escape']
|
|
@@ -1112,13 +3411,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1112
3411
|
}] } });
|
|
1113
3412
|
|
|
1114
3413
|
/**
|
|
1115
|
-
*
|
|
1116
|
-
*
|
|
3414
|
+
* Jetons d'injection typés pour les providers de sous-titres / métadonnées.
|
|
3415
|
+
* (Remplace les jetons `string` non typés — cf. docs/AUDIT.md, manquement #10.)
|
|
3416
|
+
* Un consommateur peut les injecter directement :
|
|
3417
|
+
* `inject(SMART_PLAYER_SUBTITLE_PROVIDERS)`.
|
|
3418
|
+
*/
|
|
3419
|
+
const SMART_PLAYER_SUBTITLE_PROVIDERS = new InjectionToken('SMART_PLAYER_SUBTITLE_PROVIDERS', { factory: () => [] });
|
|
3420
|
+
const SMART_PLAYER_METADATA_PROVIDERS = new InjectionToken('SMART_PLAYER_METADATA_PROVIDERS', { factory: () => [] });
|
|
3421
|
+
/**
|
|
3422
|
+
* Enregistre les implémentations de providers (sous-titres / métadonnées).
|
|
3423
|
+
* À appeler dans le tableau `providers` de `bootstrapApplication()`.
|
|
1117
3424
|
*/
|
|
1118
3425
|
function provideSmartPlayer(cfg) {
|
|
1119
3426
|
return makeEnvironmentProviders([
|
|
1120
|
-
{ provide:
|
|
1121
|
-
{ provide:
|
|
3427
|
+
{ provide: SMART_PLAYER_SUBTITLE_PROVIDERS, useValue: cfg.subtitleProviders ?? [] },
|
|
3428
|
+
{ provide: SMART_PLAYER_METADATA_PROVIDERS, useValue: cfg.metadataProviders ?? [] },
|
|
1122
3429
|
]);
|
|
1123
3430
|
}
|
|
1124
3431
|
|
|
@@ -1126,5 +3433,5 @@ function provideSmartPlayer(cfg) {
|
|
|
1126
3433
|
* Generated bundle index. Do not edit.
|
|
1127
3434
|
*/
|
|
1128
3435
|
|
|
1129
|
-
export { SmartPlayerComponent, provideSmartPlayer };
|
|
3436
|
+
export { DEFAULT_PLAYER_MENU_LABELS, PlayerControlsComponent, PlayerControlsEditionComponent, PlayerControlsVodComponent, SMART_PLAYER_METADATA_PROVIDERS, SMART_PLAYER_SUBTITLE_PROVIDERS, SmartConfirmDialogComponent, SmartPlayerComponent, SmartPlayerIconComponent, SmartSettingsComponent, provideSmartPlayer };
|
|
1130
3437
|
//# sourceMappingURL=ngxsplayer-ngx-smart-player.mjs.map
|