@ngxsplayer/ngx-smart-player 0.0.1-next.1 → 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/fesm2022/ngxsplayer-ngx-smart-player.mjs +2421 -548
- package/fesm2022/ngxsplayer-ngx-smart-player.mjs.map +1 -1
- package/index.d.ts +457 -16
- package/package.json +3 -3
|
@@ -1,15 +1,15 @@
|
|
|
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 { Input, ChangeDetectionStrategy, Component,
|
|
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 } from '@ngxsplayer/ngx-smart-player-ui-core';
|
|
7
|
+
import { NgxspPlayButton, NgxspTimeSlider, NgxspVolumeControl, NgxspLabeledControl, NgxspLiveBadge } from '@ngxsplayer/ngx-smart-player-ui-core';
|
|
8
8
|
|
|
9
9
|
class SmartPlayerIconComponent {
|
|
10
10
|
name = 'play';
|
|
11
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
12
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
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
13
|
@switch (name) {
|
|
14
14
|
@case ('play') {
|
|
15
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>
|
|
@@ -18,16 +18,16 @@ class SmartPlayerIconComponent {
|
|
|
18
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
19
|
}
|
|
20
20
|
@case ('prev') {
|
|
21
|
-
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="
|
|
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
22
|
}
|
|
23
23
|
@case ('next') {
|
|
24
|
-
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="
|
|
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
25
|
}
|
|
26
26
|
@case ('seek-back') {
|
|
27
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
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
28
|
}
|
|
29
29
|
@case ('seek-forward') {
|
|
30
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
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
31
|
}
|
|
32
32
|
@case ('volume-mute') {
|
|
33
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>
|
|
@@ -42,10 +42,10 @@ class SmartPlayerIconComponent {
|
|
|
42
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
43
|
}
|
|
44
44
|
@case ('repeat') {
|
|
45
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
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
46
|
}
|
|
47
47
|
@case ('repeat-one') {
|
|
48
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
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
49
|
}
|
|
50
50
|
@case ('tracks') {
|
|
51
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>
|
|
@@ -60,10 +60,16 @@ class SmartPlayerIconComponent {
|
|
|
60
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
61
|
}
|
|
62
62
|
@case ('pip') {
|
|
63
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3
|
|
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
64
|
}
|
|
65
65
|
@case ('pip-active') {
|
|
66
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3
|
|
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>
|
|
67
73
|
}
|
|
68
74
|
@case ('cast') {
|
|
69
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>
|
|
@@ -71,10 +77,160 @@ class SmartPlayerIconComponent {
|
|
|
71
77
|
@case ('cast-active') {
|
|
72
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>
|
|
73
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
|
+
}
|
|
74
230
|
}
|
|
75
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 });
|
|
76
232
|
}
|
|
77
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
233
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: SmartPlayerIconComponent, decorators: [{
|
|
78
234
|
type: Component,
|
|
79
235
|
args: [{ selector: 'ngxsp-smart-icon', standalone: true, template: `
|
|
80
236
|
@switch (name) {
|
|
@@ -85,16 +241,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
85
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>
|
|
86
242
|
}
|
|
87
243
|
@case ('prev') {
|
|
88
|
-
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="
|
|
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>
|
|
89
245
|
}
|
|
90
246
|
@case ('next') {
|
|
91
|
-
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="
|
|
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>
|
|
92
248
|
}
|
|
93
249
|
@case ('seek-back') {
|
|
94
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
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>
|
|
95
251
|
}
|
|
96
252
|
@case ('seek-forward') {
|
|
97
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
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>
|
|
98
254
|
}
|
|
99
255
|
@case ('volume-mute') {
|
|
100
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>
|
|
@@ -109,10 +265,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
109
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>
|
|
110
266
|
}
|
|
111
267
|
@case ('repeat') {
|
|
112
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
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>
|
|
113
269
|
}
|
|
114
270
|
@case ('repeat-one') {
|
|
115
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
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>
|
|
116
272
|
}
|
|
117
273
|
@case ('tracks') {
|
|
118
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>
|
|
@@ -127,10 +283,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
127
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>
|
|
128
284
|
}
|
|
129
285
|
@case ('pip') {
|
|
130
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3
|
|
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>
|
|
131
287
|
}
|
|
132
288
|
@case ('pip-active') {
|
|
133
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true"><rect x="3
|
|
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>
|
|
134
296
|
}
|
|
135
297
|
@case ('cast') {
|
|
136
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>
|
|
@@ -138,6 +300,156 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
138
300
|
@case ('cast-active') {
|
|
139
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>
|
|
140
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
|
+
}
|
|
141
453
|
}
|
|
142
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"] }]
|
|
143
455
|
}], propDecorators: { name: [{
|
|
@@ -145,6 +457,286 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
145
457
|
args: [{ required: true }]
|
|
146
458
|
}] } });
|
|
147
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
|
+
};
|
|
148
740
|
/**
|
|
149
741
|
* Menu de pistes (sous-titres / audio / qualité) et de vitesse de lecture.
|
|
150
742
|
* Extrait du SmartPlayerComponent (décomposition, cf. docs/AUDIT.md).
|
|
@@ -161,12 +753,28 @@ class PlayerMenuComponent {
|
|
|
161
753
|
selectedVideoTrackId = input(null, ...(ngDevMode ? [{ debugName: "selectedVideoTrackId" }] : []));
|
|
162
754
|
playbackRates = input([], ...(ngDevMode ? [{ debugName: "playbackRates" }] : []));
|
|
163
755
|
playbackRate = input(1, ...(ngDevMode ? [{ debugName: "playbackRate" }] : []));
|
|
756
|
+
labels = input(DEFAULT_PLAYER_MENU_LABELS, ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
164
757
|
selectText = output();
|
|
165
758
|
selectAudio = output();
|
|
166
759
|
selectVideo = output();
|
|
167
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
|
+
}
|
|
168
776
|
trackLabel(track) {
|
|
169
|
-
return track.label || track.language
|
|
777
|
+
return track.label || (track.language ? this.languageName(track.language) : track.id);
|
|
170
778
|
}
|
|
171
779
|
qualityLabel(track) {
|
|
172
780
|
if (track.label)
|
|
@@ -178,13 +786,13 @@ class PlayerMenuComponent {
|
|
|
178
786
|
parts.push(`${(track.bandwidth / 1_000_000).toFixed(1)} Mbps`);
|
|
179
787
|
return parts.join(' • ') || track.id;
|
|
180
788
|
}
|
|
181
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
182
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
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: `
|
|
183
791
|
@if (openMenu() === 'tracks') {
|
|
184
792
|
<div class="ngxsp-menu">
|
|
185
793
|
<div class="ngxsp-menuSection">
|
|
186
|
-
<div class="ngxsp-menuTitle">
|
|
187
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId() === null" (click)="selectText.emit(null)">
|
|
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>
|
|
188
796
|
@for (track of textTracks(); track track.id) {
|
|
189
797
|
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId() === track.id" (click)="selectText.emit(track.id)">
|
|
190
798
|
{{ trackLabel(track) }}
|
|
@@ -193,7 +801,7 @@ class PlayerMenuComponent {
|
|
|
193
801
|
</div>
|
|
194
802
|
|
|
195
803
|
<div class="ngxsp-menuSection">
|
|
196
|
-
<div class="ngxsp-menuTitle">
|
|
804
|
+
<div class="ngxsp-menuTitle">{{ labels().audio }}</div>
|
|
197
805
|
@if (audioTracks().length) {
|
|
198
806
|
@for (track of audioTracks(); track track.id) {
|
|
199
807
|
<button type="button" class="ngxsp-menuItem" [class.active]="selectedAudioTrackId() === track.id" (click)="selectAudio.emit(track.id)">
|
|
@@ -201,14 +809,14 @@ class PlayerMenuComponent {
|
|
|
201
809
|
</button>
|
|
202
810
|
}
|
|
203
811
|
} @else {
|
|
204
|
-
<div class="ngxsp-menuHint">
|
|
812
|
+
<div class="ngxsp-menuHint">{{ labels().singleAudio }}</div>
|
|
205
813
|
}
|
|
206
814
|
</div>
|
|
207
815
|
|
|
208
816
|
@if (!isAudioMode()) {
|
|
209
817
|
<div class="ngxsp-menuSection">
|
|
210
|
-
<div class="ngxsp-menuTitle">
|
|
211
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedVideoTrackId() === null" (click)="selectVideo.emit(null)">
|
|
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>
|
|
212
820
|
@if (videoTracks().length) {
|
|
213
821
|
@for (track of videoTracks(); track track.id) {
|
|
214
822
|
<button type="button" class="ngxsp-menuItem" [class.active]="selectedVideoTrackId() === track.id" (click)="selectVideo.emit(track.id)">
|
|
@@ -216,7 +824,7 @@ class PlayerMenuComponent {
|
|
|
216
824
|
</button>
|
|
217
825
|
}
|
|
218
826
|
} @else {
|
|
219
|
-
<div class="ngxsp-menuHint">
|
|
827
|
+
<div class="ngxsp-menuHint">{{ labels().singleQuality }}</div>
|
|
220
828
|
}
|
|
221
829
|
</div>
|
|
222
830
|
}
|
|
@@ -226,7 +834,7 @@ class PlayerMenuComponent {
|
|
|
226
834
|
@if (openMenu() === 'speed') {
|
|
227
835
|
<div class="ngxsp-menu ngxsp-menuCompact">
|
|
228
836
|
<div class="ngxsp-menuSection">
|
|
229
|
-
<div class="ngxsp-menuTitle">
|
|
837
|
+
<div class="ngxsp-menuTitle">{{ labels().speed }}</div>
|
|
230
838
|
@for (rate of playbackRates(); track rate) {
|
|
231
839
|
<button type="button" class="ngxsp-menuItem" [class.active]="playbackRate() === rate" (click)="selectRate.emit(rate)">
|
|
232
840
|
{{ rate }}x
|
|
@@ -235,16 +843,36 @@ class PlayerMenuComponent {
|
|
|
235
843
|
</div>
|
|
236
844
|
</div>
|
|
237
845
|
}
|
|
238
|
-
|
|
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 });
|
|
239
867
|
}
|
|
240
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
868
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: PlayerMenuComponent, decorators: [{
|
|
241
869
|
type: Component,
|
|
242
870
|
args: [{ selector: 'ngxsp-player-menu', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
243
871
|
@if (openMenu() === 'tracks') {
|
|
244
872
|
<div class="ngxsp-menu">
|
|
245
873
|
<div class="ngxsp-menuSection">
|
|
246
|
-
<div class="ngxsp-menuTitle">
|
|
247
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId() === null" (click)="selectText.emit(null)">
|
|
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>
|
|
248
876
|
@for (track of textTracks(); track track.id) {
|
|
249
877
|
<button type="button" class="ngxsp-menuItem" [class.active]="selectedTextTrackId() === track.id" (click)="selectText.emit(track.id)">
|
|
250
878
|
{{ trackLabel(track) }}
|
|
@@ -253,7 +881,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
253
881
|
</div>
|
|
254
882
|
|
|
255
883
|
<div class="ngxsp-menuSection">
|
|
256
|
-
<div class="ngxsp-menuTitle">
|
|
884
|
+
<div class="ngxsp-menuTitle">{{ labels().audio }}</div>
|
|
257
885
|
@if (audioTracks().length) {
|
|
258
886
|
@for (track of audioTracks(); track track.id) {
|
|
259
887
|
<button type="button" class="ngxsp-menuItem" [class.active]="selectedAudioTrackId() === track.id" (click)="selectAudio.emit(track.id)">
|
|
@@ -261,14 +889,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
261
889
|
</button>
|
|
262
890
|
}
|
|
263
891
|
} @else {
|
|
264
|
-
<div class="ngxsp-menuHint">
|
|
892
|
+
<div class="ngxsp-menuHint">{{ labels().singleAudio }}</div>
|
|
265
893
|
}
|
|
266
894
|
</div>
|
|
267
895
|
|
|
268
896
|
@if (!isAudioMode()) {
|
|
269
897
|
<div class="ngxsp-menuSection">
|
|
270
|
-
<div class="ngxsp-menuTitle">
|
|
271
|
-
<button type="button" class="ngxsp-menuItem" [class.active]="selectedVideoTrackId() === null" (click)="selectVideo.emit(null)">
|
|
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>
|
|
272
900
|
@if (videoTracks().length) {
|
|
273
901
|
@for (track of videoTracks(); track track.id) {
|
|
274
902
|
<button type="button" class="ngxsp-menuItem" [class.active]="selectedVideoTrackId() === track.id" (click)="selectVideo.emit(track.id)">
|
|
@@ -276,7 +904,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
276
904
|
</button>
|
|
277
905
|
}
|
|
278
906
|
} @else {
|
|
279
|
-
<div class="ngxsp-menuHint">
|
|
907
|
+
<div class="ngxsp-menuHint">{{ labels().singleQuality }}</div>
|
|
280
908
|
}
|
|
281
909
|
</div>
|
|
282
910
|
}
|
|
@@ -286,7 +914,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
286
914
|
@if (openMenu() === 'speed') {
|
|
287
915
|
<div class="ngxsp-menu ngxsp-menuCompact">
|
|
288
916
|
<div class="ngxsp-menuSection">
|
|
289
|
-
<div class="ngxsp-menuTitle">
|
|
917
|
+
<div class="ngxsp-menuTitle">{{ labels().speed }}</div>
|
|
290
918
|
@for (rate of playbackRates(); track rate) {
|
|
291
919
|
<button type="button" class="ngxsp-menuItem" [class.active]="playbackRate() === rate" (click)="selectRate.emit(rate)">
|
|
292
920
|
{{ rate }}x
|
|
@@ -295,8 +923,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
295
923
|
</div>
|
|
296
924
|
</div>
|
|
297
925
|
}
|
|
298
|
-
|
|
299
|
-
|
|
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"] }] } });
|
|
300
948
|
|
|
301
949
|
/**
|
|
302
950
|
* Surcouches du lecteur : carte d'erreur de lecture et panneau de diagnostic.
|
|
@@ -341,21 +989,19 @@ class PlayerOverlaysComponent {
|
|
|
341
989
|
return this.error()?.message ? String(this.error().message) : 'Unknown error';
|
|
342
990
|
}
|
|
343
991
|
}, ...(ngDevMode ? [{ debugName: "hint" }] : []));
|
|
344
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
345
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
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: `
|
|
346
994
|
@if (error()) {
|
|
347
995
|
<div class="ngxsp-overlay" role="alert">
|
|
348
996
|
<div class="ngxsp-overlay-card">
|
|
349
|
-
<
|
|
350
|
-
<div class="ngxsp-overlay-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
<
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
</div>
|
|
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
|
+
}
|
|
359
1005
|
</div>
|
|
360
1006
|
</div>
|
|
361
1007
|
</div>
|
|
@@ -364,31 +1010,29 @@ class PlayerOverlaysComponent {
|
|
|
364
1010
|
@if (debug()) {
|
|
365
1011
|
<details class="ngxsp-debug">
|
|
366
1012
|
<summary>Debug</summary>
|
|
367
|
-
<div
|
|
1013
|
+
<div class="ngxsp-debug-actions">
|
|
368
1014
|
<button type="button" class="ngxsp-btn" (click)="copyJson.emit()">Copy JSON</button>
|
|
369
1015
|
<button type="button" class="ngxsp-btn" (click)="downloadJson.emit()">Download</button>
|
|
370
1016
|
</div>
|
|
371
1017
|
<pre>{{ debugJson() }}</pre>
|
|
372
1018
|
</details>
|
|
373
1019
|
}
|
|
374
|
-
`, isInline: true, styles: [":host{display:contents}.ngxsp-btn{
|
|
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 });
|
|
375
1021
|
}
|
|
376
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1022
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: PlayerOverlaysComponent, decorators: [{
|
|
377
1023
|
type: Component,
|
|
378
1024
|
args: [{ selector: 'ngxsp-player-overlays', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
379
1025
|
@if (error()) {
|
|
380
1026
|
<div class="ngxsp-overlay" role="alert">
|
|
381
1027
|
<div class="ngxsp-overlay-card">
|
|
382
|
-
<
|
|
383
|
-
<div class="ngxsp-overlay-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
<
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
</div>
|
|
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
|
+
}
|
|
392
1036
|
</div>
|
|
393
1037
|
</div>
|
|
394
1038
|
</div>
|
|
@@ -397,16 +1041,822 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
397
1041
|
@if (debug()) {
|
|
398
1042
|
<details class="ngxsp-debug">
|
|
399
1043
|
<summary>Debug</summary>
|
|
400
|
-
<div
|
|
1044
|
+
<div class="ngxsp-debug-actions">
|
|
401
1045
|
<button type="button" class="ngxsp-btn" (click)="copyJson.emit()">Copy JSON</button>
|
|
402
1046
|
<button type="button" class="ngxsp-btn" (click)="downloadJson.emit()">Download</button>
|
|
403
1047
|
</div>
|
|
404
1048
|
<pre>{{ debugJson() }}</pre>
|
|
405
1049
|
</details>
|
|
406
1050
|
}
|
|
407
|
-
`, styles: [":host{display:contents}.ngxsp-btn{
|
|
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"] }]
|
|
408
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"] }] } });
|
|
409
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"] }] } });
|
|
1859
|
+
|
|
410
1860
|
class SmartPlayerComponent {
|
|
411
1861
|
config;
|
|
412
1862
|
plugins = [];
|
|
@@ -415,8 +1865,16 @@ class SmartPlayerComponent {
|
|
|
415
1865
|
debug = false;
|
|
416
1866
|
/** Optional start time (seconds) used for "resume playback". */
|
|
417
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;
|
|
418
1872
|
canPrev = false;
|
|
419
1873
|
canNext = false;
|
|
1874
|
+
previousLabel = 'Previous item';
|
|
1875
|
+
nextLabel = 'Next item';
|
|
1876
|
+
playLabel = 'Play';
|
|
1877
|
+
pauseLabel = 'Pause';
|
|
420
1878
|
shuffleActive = false;
|
|
421
1879
|
shuffleLabel = 'Shuffle: off';
|
|
422
1880
|
repeatMode = 'off';
|
|
@@ -424,20 +1882,56 @@ class SmartPlayerComponent {
|
|
|
424
1882
|
hasPlaylistControls = false;
|
|
425
1883
|
mediaMode = 'video';
|
|
426
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 = [];
|
|
427
1907
|
// Noms volontairement alignés sur les événements média natifs (API publique).
|
|
428
1908
|
// eslint-disable-next-line @angular-eslint/no-output-native
|
|
429
1909
|
ended = new EventEmitter();
|
|
430
1910
|
videoSize = new EventEmitter();
|
|
431
|
-
/**
|
|
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
|
+
*/
|
|
432
1915
|
// eslint-disable-next-line @angular-eslint/no-output-native
|
|
433
1916
|
progress = new EventEmitter();
|
|
434
1917
|
prev = new EventEmitter();
|
|
435
1918
|
next = new EventEmitter();
|
|
436
1919
|
toggleShuffle = new EventEmitter();
|
|
437
1920
|
cycleRepeat = new EventEmitter();
|
|
1921
|
+
/** CC pressed; the vod and edition skins also open their subtitles menu. */
|
|
438
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 = {};
|
|
439
1927
|
cast = new EventEmitter();
|
|
1928
|
+
stateChange = new EventEmitter();
|
|
1929
|
+
playbackToggle = new EventEmitter();
|
|
1930
|
+
outputVolumeChange = new EventEmitter();
|
|
440
1931
|
videoEl;
|
|
1932
|
+
captionsLayer;
|
|
1933
|
+
/** Absent in audio mode, which has no subtitles. */
|
|
1934
|
+
subtitleFileInput;
|
|
441
1935
|
state = null;
|
|
442
1936
|
duration = 0;
|
|
443
1937
|
currentTime = 0;
|
|
@@ -448,15 +1942,33 @@ class SmartPlayerComponent {
|
|
|
448
1942
|
tracks = [];
|
|
449
1943
|
openMenu = null;
|
|
450
1944
|
controlsVisible = true;
|
|
1945
|
+
surfaceGesture = null;
|
|
451
1946
|
castState = 'disconnected';
|
|
452
1947
|
playbackRates = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
|
453
1948
|
facade = new PlayerFacade();
|
|
454
1949
|
cdr = inject(ChangeDetectorRef);
|
|
455
1950
|
sub = new Subscription();
|
|
456
1951
|
hideControlsTimer = null;
|
|
1952
|
+
surfaceClickTimer = null;
|
|
1953
|
+
gestureTimer = null;
|
|
457
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;
|
|
458
1965
|
get uiSkin() {
|
|
459
|
-
|
|
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';
|
|
460
1972
|
}
|
|
461
1973
|
get uiVisible() {
|
|
462
1974
|
const kind = this.state?.kind;
|
|
@@ -465,11 +1977,7 @@ class SmartPlayerComponent {
|
|
|
465
1977
|
get displayTitle() {
|
|
466
1978
|
const ui = this.config?.ui;
|
|
467
1979
|
const src = this.config?.source;
|
|
468
|
-
return
|
|
469
|
-
ui?.displayTitle ??
|
|
470
|
-
src?.title ??
|
|
471
|
-
src?.contentId ??
|
|
472
|
-
'Player');
|
|
1980
|
+
return ui?.title ?? ui?.displayTitle ?? src?.title ?? src?.contentId ?? 'Player';
|
|
473
1981
|
}
|
|
474
1982
|
get displaySubtitle() {
|
|
475
1983
|
const ui = this.config?.ui;
|
|
@@ -487,6 +1995,8 @@ class SmartPlayerComponent {
|
|
|
487
1995
|
}
|
|
488
1996
|
/** Profil de source transmis aux primitives UI (cf. @ngxsp/ui-core). */
|
|
489
1997
|
get sourceProfile() {
|
|
1998
|
+
if (this.config?.source?.live)
|
|
1999
|
+
return 'live';
|
|
490
2000
|
return this.isAudioMode ? 'audio' : 'vod';
|
|
491
2001
|
}
|
|
492
2002
|
get audioTracks() {
|
|
@@ -507,24 +2017,87 @@ class SmartPlayerComponent {
|
|
|
507
2017
|
get selectedVideoTrackId() {
|
|
508
2018
|
return this.videoTracks.find((track) => track.active)?.id ?? null;
|
|
509
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
|
+
}
|
|
510
2065
|
ngOnInit() {
|
|
511
2066
|
const plugins = [...(this.plugins ?? [])];
|
|
512
2067
|
// v1.11: SSAI (server-side stitched streams) – rewrite manifest URL if configured.
|
|
513
2068
|
if (this.config?.ads?.enabled && this.config?.ads?.mode === 'ssai') {
|
|
514
2069
|
plugins.push(createSsaiManifestRewritePlugin());
|
|
515
2070
|
}
|
|
516
|
-
|
|
2071
|
+
const media = this.mediaElementForMode();
|
|
2072
|
+
this.watchAirPlay(media);
|
|
2073
|
+
this.facade.attach(media, plugins, this.config, this.ports, this.engines);
|
|
517
2074
|
// markForCheck() requis : la stratégie OnPush ne détecte pas les mutations
|
|
518
2075
|
// poussées depuis des souscriptions RxJS (cf. docs/AUDIT.md, manquement #7).
|
|
519
2076
|
this.sub.add(this.facade.state$.subscribe((s) => {
|
|
520
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);
|
|
521
2084
|
this.syncControlVisibility();
|
|
522
2085
|
this.cdr.markForCheck();
|
|
523
2086
|
}));
|
|
524
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;
|
|
525
2093
|
this.currentTime = t.currentTime;
|
|
526
2094
|
this.duration = t.duration;
|
|
527
|
-
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
|
+
});
|
|
528
2101
|
this.cdr.markForCheck();
|
|
529
2102
|
}));
|
|
530
2103
|
this.sub.add(this.facade.bufferAheadSec$.subscribe((value) => {
|
|
@@ -535,24 +2108,17 @@ class SmartPlayerComponent {
|
|
|
535
2108
|
this.tracks = tracks;
|
|
536
2109
|
this.cdr.markForCheck();
|
|
537
2110
|
}));
|
|
2111
|
+
this.sub.add(this.facade.captionCues$.subscribe((cues) => this.drawCaptions(cues)));
|
|
538
2112
|
this.bindRemotePlaybackState();
|
|
539
|
-
this.
|
|
540
|
-
this.
|
|
541
|
-
|
|
542
|
-
const start = Number(this.startTimeSec ?? 0);
|
|
543
|
-
if (Number.isFinite(start) && start > 0.2) {
|
|
544
|
-
try {
|
|
545
|
-
const safe = Math.max(0, Math.min(start, (this.duration || start + 1) - 0.3));
|
|
546
|
-
await this.facade.seekTo(safe);
|
|
547
|
-
}
|
|
548
|
-
catch { /* ignore */ }
|
|
549
|
-
}
|
|
550
|
-
await this.facade.play().catch(() => { });
|
|
551
|
-
})
|
|
552
|
-
.catch(() => { });
|
|
2113
|
+
this.applyVolume();
|
|
2114
|
+
this.initialized = true;
|
|
2115
|
+
void this.loadConfig(this.autoplayOnInit);
|
|
553
2116
|
}
|
|
554
2117
|
ngOnDestroy() {
|
|
2118
|
+
this.controlsObserver?.disconnect();
|
|
555
2119
|
this.clearHideControlsTimer();
|
|
2120
|
+
this.clearSurfaceClickTimer();
|
|
2121
|
+
this.clearGestureTimer();
|
|
556
2122
|
this.remotePlaybackCleanup?.();
|
|
557
2123
|
this.sub.unsubscribe();
|
|
558
2124
|
this.facade.destroy();
|
|
@@ -562,7 +2128,7 @@ class SmartPlayerComponent {
|
|
|
562
2128
|
}
|
|
563
2129
|
get debugJson() {
|
|
564
2130
|
try {
|
|
565
|
-
const d = this.facade.diagnostics
|
|
2131
|
+
const d = this.facade.diagnostics();
|
|
566
2132
|
const tel = this.ports?.telemetry?.export?.();
|
|
567
2133
|
return JSON.stringify({ ...d, telemetry: tel }, null, 2);
|
|
568
2134
|
}
|
|
@@ -601,20 +2167,36 @@ class SmartPlayerComponent {
|
|
|
601
2167
|
else
|
|
602
2168
|
this.facade.play();
|
|
603
2169
|
}
|
|
2170
|
+
requestPlaybackToggle() {
|
|
2171
|
+
const intent = this.playbackToggleIntent();
|
|
2172
|
+
this.togglePlay();
|
|
2173
|
+
this.playbackToggle.emit(intent);
|
|
2174
|
+
this.showSurfaceGesture(intent);
|
|
2175
|
+
}
|
|
604
2176
|
seekRel(deltaSec) {
|
|
605
|
-
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
|
+
});
|
|
606
2192
|
}
|
|
607
2193
|
onScrub(value) {
|
|
608
2194
|
const v = Number(value);
|
|
609
2195
|
if (Number.isFinite(v))
|
|
610
|
-
this.
|
|
2196
|
+
this.seekTo(v);
|
|
611
2197
|
}
|
|
612
2198
|
onVolume(value) {
|
|
613
|
-
|
|
614
|
-
this.volume = Number.isFinite(v) ? v : this.volume;
|
|
615
|
-
if (this.volume > 0)
|
|
616
|
-
this.lastVolumeBeforeMute = this.volume;
|
|
617
|
-
this.facade.setVolume(this.volume);
|
|
2199
|
+
this.setVolume(value, true);
|
|
618
2200
|
}
|
|
619
2201
|
toggleMute() {
|
|
620
2202
|
if (this.volume <= 0.001) {
|
|
@@ -624,7 +2206,8 @@ class SmartPlayerComponent {
|
|
|
624
2206
|
this.lastVolumeBeforeMute = this.volume;
|
|
625
2207
|
this.volume = 0;
|
|
626
2208
|
}
|
|
627
|
-
this.
|
|
2209
|
+
this.applyVolume();
|
|
2210
|
+
this.outputVolumeChange.emit(this.volume);
|
|
628
2211
|
this.onInteraction();
|
|
629
2212
|
}
|
|
630
2213
|
async setPlaybackRate(rate) {
|
|
@@ -640,14 +2223,30 @@ class SmartPlayerComponent {
|
|
|
640
2223
|
const v = this.videoEl?.nativeElement;
|
|
641
2224
|
if (!v)
|
|
642
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
|
+
}
|
|
643
2234
|
const width = v.videoWidth || 0;
|
|
644
2235
|
const height = v.videoHeight || 0;
|
|
645
2236
|
if (width > 0 && height > 0) {
|
|
646
2237
|
this.videoSize.emit({ width, height });
|
|
647
2238
|
}
|
|
648
|
-
}
|
|
649
|
-
onEnded() {
|
|
650
|
-
//
|
|
2239
|
+
}
|
|
2240
|
+
onEnded() {
|
|
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
|
+
}
|
|
651
2250
|
this.ended.emit();
|
|
652
2251
|
}
|
|
653
2252
|
toggleFullscreen() {
|
|
@@ -669,6 +2268,72 @@ class SmartPlayerComponent {
|
|
|
669
2268
|
this.openMenu = this.openMenu === menu ? null : menu;
|
|
670
2269
|
this.onInteraction(this.openMenu !== null);
|
|
671
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
|
+
}
|
|
672
2337
|
async selectTextTrack(trackId) {
|
|
673
2338
|
this.openMenu = null;
|
|
674
2339
|
this.onInteraction();
|
|
@@ -739,11 +2404,42 @@ class SmartPlayerComponent {
|
|
|
739
2404
|
return !!video && (!!video.remote?.prompt || typeof video.webkitShowPlaybackTargetPicker === 'function');
|
|
740
2405
|
}
|
|
741
2406
|
isCastActive() {
|
|
742
|
-
return this.
|
|
2407
|
+
return this.castConnected || this.castState === 'connected';
|
|
743
2408
|
}
|
|
744
2409
|
showCastButton() {
|
|
745
2410
|
return this.castEnabled || this.isCastSupported();
|
|
746
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
|
+
}
|
|
747
2443
|
castLabel() {
|
|
748
2444
|
if (this.castEnabled)
|
|
749
2445
|
return 'Play on device';
|
|
@@ -771,7 +2467,9 @@ class SmartPlayerComponent {
|
|
|
771
2467
|
void this.requestCast();
|
|
772
2468
|
}
|
|
773
2469
|
captionsLabel() {
|
|
774
|
-
|
|
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;
|
|
775
2473
|
}
|
|
776
2474
|
volumeLabel() {
|
|
777
2475
|
if (this.volume <= 0.001)
|
|
@@ -786,28 +2484,29 @@ class SmartPlayerComponent {
|
|
|
786
2484
|
statusLabel() {
|
|
787
2485
|
const kind = this.state?.kind;
|
|
788
2486
|
if (kind === 'playing')
|
|
789
|
-
return this.playbackRate === 1 ?
|
|
2487
|
+
return this.playbackRate === 1 ? this.playingStatusLabel : `${this.playingStatusLabel} ${this.playbackRate}x`;
|
|
790
2488
|
if (kind === 'paused')
|
|
791
|
-
return
|
|
2489
|
+
return this.pausedStatusLabel;
|
|
792
2490
|
if (kind === 'buffering')
|
|
793
|
-
return
|
|
2491
|
+
return this.bufferingStatusLabel;
|
|
794
2492
|
if (kind === 'loading')
|
|
795
|
-
return
|
|
2493
|
+
return this.loadingStatusLabel;
|
|
796
2494
|
if (kind === 'ended')
|
|
797
|
-
return
|
|
2495
|
+
return this.endedStatusLabel;
|
|
798
2496
|
if (kind === 'error')
|
|
799
|
-
return
|
|
800
|
-
return
|
|
2497
|
+
return this.errorStatusLabel;
|
|
2498
|
+
return this.readyStatusLabel;
|
|
801
2499
|
}
|
|
802
2500
|
keyboardHintLabel() {
|
|
803
2501
|
return this.isAudioMode ? 'Space play • Arrows seek • C device' : 'Space play • Arrows seek • F fullscreen • C device';
|
|
804
2502
|
}
|
|
805
2503
|
tracksButtonLabel() {
|
|
806
|
-
return this.textTracks.length || this.audioTracks.length ?
|
|
2504
|
+
return this.textTracks.length || this.audioTracks.length ? this.playerMenuLabels.tracks : this.playerMenuLabels.media;
|
|
807
2505
|
}
|
|
808
2506
|
onPrimarySurfaceDoubleClick() {
|
|
809
2507
|
if (this.isAudioMode)
|
|
810
2508
|
return;
|
|
2509
|
+
this.clearSurfaceClickTimer();
|
|
811
2510
|
this.toggleFullscreen();
|
|
812
2511
|
}
|
|
813
2512
|
bufferPercent() {
|
|
@@ -825,6 +2524,25 @@ class SmartPlayerComponent {
|
|
|
825
2524
|
return `${m}:${String(r).padStart(2, '0')}`;
|
|
826
2525
|
}
|
|
827
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
|
+
}
|
|
828
2546
|
onInteraction(persist = false) {
|
|
829
2547
|
this.controlsVisible = true;
|
|
830
2548
|
this.clearHideControlsTimer();
|
|
@@ -845,11 +2563,18 @@ class SmartPlayerComponent {
|
|
|
845
2563
|
const target = event.target;
|
|
846
2564
|
if (!target)
|
|
847
2565
|
return;
|
|
848
|
-
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();
|
|
849
2571
|
return;
|
|
850
2572
|
}
|
|
851
2573
|
this.openMenu = null;
|
|
852
2574
|
this.onInteraction();
|
|
2575
|
+
if (this.uiSkin === 'vod' && !this.isAudioMode) {
|
|
2576
|
+
this.queueSurfacePlaybackToggle();
|
|
2577
|
+
}
|
|
853
2578
|
}
|
|
854
2579
|
onEscape() {
|
|
855
2580
|
if (this.openMenu !== null) {
|
|
@@ -858,11 +2583,13 @@ class SmartPlayerComponent {
|
|
|
858
2583
|
}
|
|
859
2584
|
}
|
|
860
2585
|
onKeydown(event) {
|
|
2586
|
+
if (!this.documentShortcuts)
|
|
2587
|
+
return;
|
|
861
2588
|
if (this.isInteractiveTarget(event.target))
|
|
862
2589
|
return;
|
|
863
2590
|
if (event.code === 'Space') {
|
|
864
2591
|
event.preventDefault();
|
|
865
|
-
this.
|
|
2592
|
+
this.requestPlaybackToggle();
|
|
866
2593
|
this.onInteraction();
|
|
867
2594
|
return;
|
|
868
2595
|
}
|
|
@@ -898,6 +2625,80 @@ class SmartPlayerComponent {
|
|
|
898
2625
|
this.onCastClick();
|
|
899
2626
|
}
|
|
900
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;
|
|
2685
|
+
}
|
|
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
|
+
}
|
|
901
2702
|
bindRemotePlaybackState() {
|
|
902
2703
|
const video = this.videoEl?.nativeElement;
|
|
903
2704
|
const remote = video?.remote;
|
|
@@ -927,12 +2728,80 @@ class SmartPlayerComponent {
|
|
|
927
2728
|
this.clearHideControlsTimer();
|
|
928
2729
|
this.controlsVisible = true;
|
|
929
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
|
+
}
|
|
930
2767
|
clearHideControlsTimer() {
|
|
931
2768
|
if (this.hideControlsTimer) {
|
|
932
2769
|
clearTimeout(this.hideControlsTimer);
|
|
933
2770
|
this.hideControlsTimer = null;
|
|
934
2771
|
}
|
|
935
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
|
+
}
|
|
936
2805
|
isInteractiveTarget(target) {
|
|
937
2806
|
const el = target;
|
|
938
2807
|
if (!el)
|
|
@@ -945,15 +2814,20 @@ class SmartPlayerComponent {
|
|
|
945
2814
|
emitPrev() {
|
|
946
2815
|
this.prev.emit();
|
|
947
2816
|
}
|
|
948
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
949
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
950
|
-
<div class="ngxsp-root" [class.ngxsp-skin-
|
|
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">
|
|
951
2820
|
<!-- Surface de présentation : interactions pointeur non essentielles, toutes
|
|
952
2821
|
dupliquées au clavier par @HostListener('document:keydown') (Espace/flèches/F/M/C)
|
|
953
2822
|
et par les contrôles focusables. -->
|
|
954
2823
|
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
|
|
955
2824
|
<div class="ngxsp-video" (dblclick)="onPrimarySurfaceDoubleClick()" (mousemove)="onInteraction()" (mouseenter)="onInteraction()" (mouseleave)="onPointerLeave()" (focusin)="onInteraction()" (touchstart)="onInteraction(true)" (click)="onRootClick($event)">
|
|
956
|
-
<video #video playsinline (timeupdate)="noop()" (loadedmetadata)="onLoadedMetadata()" (ended)="onEnded()"></video>
|
|
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
|
+
}
|
|
957
2831
|
@if (isAudioMode) {
|
|
958
2832
|
<div class="ngxsp-audio-stage" aria-hidden="true">
|
|
959
2833
|
<div class="ngxsp-audio-disc"></div>
|
|
@@ -976,132 +2850,136 @@ class SmartPlayerComponent {
|
|
|
976
2850
|
(copyJson)="copyDebug()"
|
|
977
2851
|
(downloadJson)="downloadDebug()"
|
|
978
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>
|
|
2861
|
+
}
|
|
979
2862
|
|
|
980
|
-
@if (uiSkin==='
|
|
981
|
-
<
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
</button>
|
|
1046
|
-
<button type="button" class="ngxsp-nfx-chip" [class.accent]="repeatMode !== 'off'" (click)="cycleRepeat.emit()" [attr.title]="repeatLabel" [attr.aria-label]="repeatLabel">
|
|
1047
|
-
@if (repeatMode === 'one') {
|
|
1048
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="repeat-one" />
|
|
1049
|
-
} @else {
|
|
1050
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="repeat" />
|
|
1051
|
-
}
|
|
1052
|
-
</button>
|
|
1053
|
-
}
|
|
1054
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="toggleMenu('tracks')" [attr.title]="tracksButtonLabel()" [attr.aria-label]="tracksButtonLabel()">
|
|
1055
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="tracks" />
|
|
1056
|
-
</button>
|
|
1057
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="subtitles.emit()" [attr.title]="captionsLabel()" [attr.aria-label]="captionsLabel()">
|
|
1058
|
-
@if (selectedTextTrackId !== null) {
|
|
1059
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="captions" />
|
|
1060
|
-
} @else {
|
|
1061
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="captions-off" />
|
|
1062
|
-
}
|
|
1063
|
-
</button>
|
|
1064
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="toggleMenu('speed')" [attr.title]="playbackRateLabel()" [attr.aria-label]="playbackRateLabel()">
|
|
1065
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="speed" />
|
|
1066
|
-
</button>
|
|
1067
|
-
@if (showCastButton()) {
|
|
1068
|
-
<button type="button" class="ngxsp-nfx-chip" [class.accent]="isCastActive()" (click)="onCastClick()" [attr.title]="castHintLabel()" [attr.aria-label]="castLabel()">
|
|
1069
|
-
@if (isCastActive()) {
|
|
1070
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="cast-active" />
|
|
1071
|
-
} @else {
|
|
1072
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="cast" />
|
|
1073
|
-
}
|
|
1074
|
-
</button>
|
|
1075
|
-
}
|
|
1076
|
-
@if (!isAudioMode && isPiPSupported()) {
|
|
1077
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="togglePiP()" [attr.title]="pipLabel()" [attr.aria-label]="pipLabel()">
|
|
1078
|
-
@if (isPiPActive()) {
|
|
1079
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="pip-active" />
|
|
1080
|
-
} @else {
|
|
1081
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="pip" />
|
|
1082
|
-
}
|
|
1083
|
-
</button>
|
|
1084
|
-
}
|
|
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]" />
|
|
1085
2928
|
</div>
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
[
|
|
1090
|
-
[
|
|
1091
|
-
[
|
|
1092
|
-
[
|
|
1093
|
-
|
|
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)"
|
|
1094
2946
|
/>
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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
|
+
>
|
|
1105
2983
|
<ngxsp-player-menu
|
|
1106
2984
|
[openMenu]="openMenu"
|
|
1107
2985
|
[isAudioMode]="isAudioMode"
|
|
@@ -1113,114 +2991,79 @@ class SmartPlayerComponent {
|
|
|
1113
2991
|
[selectedVideoTrackId]="selectedVideoTrackId"
|
|
1114
2992
|
[playbackRates]="playbackRates"
|
|
1115
2993
|
[playbackRate]="playbackRate"
|
|
2994
|
+
[labels]="playerMenuLabels"
|
|
2995
|
+
(loadSubtitles)="openSubtitlePicker()"
|
|
1116
2996
|
(selectText)="selectTextTrack($event)"
|
|
1117
2997
|
(selectAudio)="selectAudioTrack($event)"
|
|
1118
2998
|
(selectVideo)="selectVideoTrack($event)"
|
|
1119
2999
|
(selectRate)="setPlaybackRate($event)"
|
|
1120
3000
|
/>
|
|
1121
|
-
</
|
|
3001
|
+
</ngxsp-player-controls-edition>
|
|
1122
3002
|
} @else {
|
|
1123
|
-
<
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
} @else {
|
|
1167
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="cast" />
|
|
1168
|
-
}
|
|
1169
|
-
</button>
|
|
1170
|
-
}
|
|
1171
|
-
@if (!isAudioMode && isPiPSupported()) {
|
|
1172
|
-
<button type="button" class="ngxsp-btn" (click)="togglePiP()" [attr.title]="pipLabel()" [attr.aria-label]="pipLabel()">
|
|
1173
|
-
@if (isPiPActive()) {
|
|
1174
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="pip-active" />
|
|
1175
|
-
} @else {
|
|
1176
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="pip" />
|
|
1177
|
-
}
|
|
1178
|
-
</button>
|
|
1179
|
-
}
|
|
1180
|
-
<button type="button" class="ngxsp-btn" (click)="toggleMute()" [attr.title]="volumeLabel()" [attr.aria-label]="volumeLabel()">
|
|
1181
|
-
@if (volume <= 0.001) {
|
|
1182
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="volume-mute" />
|
|
1183
|
-
} @else if (volume < 0.5) {
|
|
1184
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="volume-low" />
|
|
1185
|
-
} @else {
|
|
1186
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="volume-high" />
|
|
1187
|
-
}
|
|
1188
|
-
</button>
|
|
1189
|
-
|
|
1190
|
-
<ngxsp-time-slider
|
|
1191
|
-
style="flex:1 1 160px; min-width:160px"
|
|
1192
|
-
[currentTime]="currentTime"
|
|
1193
|
-
[duration]="duration"
|
|
1194
|
-
[buffered]="bufferAheadSec"
|
|
1195
|
-
[profile]="sourceProfile"
|
|
1196
|
-
(seek)="onScrub($event)"
|
|
1197
|
-
/>
|
|
1198
|
-
|
|
1199
|
-
<div class="ngxsp-time">{{fmtTime(currentTime)}} / {{fmtTime(duration)}}</div>
|
|
1200
|
-
|
|
1201
|
-
<input class="ngxsp-range" style="max-width:140px" type="range"
|
|
1202
|
-
min="0" max="1" step="0.01"
|
|
1203
|
-
[value]="volume"
|
|
1204
|
-
(input)="onVolume(($any($event.target).value))"
|
|
1205
|
-
aria-label="Volume"
|
|
1206
|
-
/>
|
|
1207
|
-
</div>
|
|
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
|
+
/>
|
|
1208
3046
|
}
|
|
1209
3047
|
|
|
1210
3048
|
</div>
|
|
1211
3049
|
</div>
|
|
1212
|
-
`, isInline: true, styles: [".ngxsp-root{--ngxsp-bg:#07111f;--ngxsp-bg-elev:#0d1a2b;--ngxsp-bg-soft:#102238;--ngxsp-border:rgba(148,163,184,.18);--ngxsp-border-strong:rgba(191,219,254,.24);--ngxsp-text:#e6eef8;--ngxsp-text-dim:rgba(230,238,248,.72);--ngxsp-accent:#7dd3fc;--ngxsp-accent-soft:rgba(125,211,252,.16);position:relative;width:100%;height:100%;background:radial-gradient(circle at top,rgba(56,189,248,.12),transparent 28%),linear-gradient(180deg,#0a1524,#07111f 56%,#050d18);color:var(--ngxsp-text);font-family:Segoe UI,SF Pro Text,Inter,system-ui,sans-serif;letter-spacing:.01em}.ngxsp-video{position:relative;width:100%;height:100%;isolation:isolate}.ngxsp-video video{width:100%;height:100%;display:block;background:#02060b;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:clamp(24px,3.8vw,42px);font-weight:800;letter-spacing:-.03em;color:#f8fbff;text-shadow:0 18px 60px rgba(15,23,42,.6)}.ngxsp-audio-subtitle{font-size:clamp(13px,1.4vw,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-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);color:var(--ngxsp-text);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);color:#d8f3ff}.ngxsp-icon{width:18px;height:18px;display:block;flex:0 0 auto}.ngxsp-icon-lg{width:32px;height:32px}.ngxsp-iconBadge{font-size:11px;font-weight:800;line-height:1}.ngxsp-range{flex:1;min-width:160px}.ngxsp-time{color:var(--ngxsp-text-dim);font-variant-numeric:tabular-nums}.ngxsp-skin-netflix .ngxsp-video{position:relative}.ngxsp-skin-netflix .ngxsp-video:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at center,#0f172a00 30%,#02061747);opacity:.92;pointer-events:none;z-index:0}.ngxsp-skin-netflix .ngxsp-video:after{content:\"\";position:absolute;inset:auto 0 0;height:220px;background:linear-gradient(to top,#040a13f5,#060e1ab8 38%,#0000);opacity:0;transition:opacity .18s ease;pointer-events:none}.ngxsp-skin-netflix.ngxsp-ui-visible .ngxsp-video:after{opacity:1}.ngxsp-mode-audio.ngxsp-skin-netflix .ngxsp-video:before{background:linear-gradient(180deg,#03081259,#0308121f);opacity:1}.ngxsp-nfx-top{position:absolute;left:0;right:0;top:0;display:flex;align-items:flex-start;gap:12px;padding:20px 20px 24px;background:linear-gradient(to bottom,#040a13eb,#040a1300);pointer-events:none;opacity:0;transition:opacity .18s ease;z-index:3}.ngxsp-root.ngxsp-ui-visible .ngxsp-nfx-top{opacity:1}.ngxsp-nfx-titleWrap{display:flex;flex-direction:column;gap:4px}.ngxsp-nfx-eyebrow{font-size:10px;text-transform:uppercase;letter-spacing:.18em;color:#bfdbfe9e}.ngxsp-nfx-title{font-weight:700;letter-spacing:.01em;opacity:.98;pointer-events:none;text-shadow:0 1px 2px rgba(0,0,0,.45);font-size:18px}.ngxsp-nfx-topHint{font-size:12px;color:#bfdbfe85;padding-top:6px}.ngxsp-nfx-status{font-size:10px;text-transform:uppercase;letter-spacing:.14em;padding:7px 10px;border-radius:999px;border:1px solid var(--ngxsp-border);background:#0f172a8a;color:#e2e8f0d6}.ngxsp-nfx-spacer{flex:1}.ngxsp-nfx-centerDock{position:absolute;inset:0;margin:auto;width:max-content;height:max-content;display:flex;align-items:center;gap:14px;z-index:4;opacity:0;transform:translateY(10px) scale(.98);transition:opacity .18s ease,transform .18s ease}.ngxsp-root.ngxsp-ui-visible .ngxsp-nfx-centerDock{opacity:1;transform:translateY(0) scale(1)}.ngxsp-nfx-center{height:92px;width:92px;border-radius:999px;border:1px solid rgba(191,219,254,.2);background:linear-gradient(180deg,#14304af2,#0b1826f5);color:#f8fbff;display:flex;align-items:center;justify-content:center;cursor:pointer;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);box-shadow:0 22px 60px #0206176b,inset 0 1px #ffffff0f}.ngxsp-nfx-side{height:56px;min-width:56px;border-radius:999px;border:1px solid var(--ngxsp-border);background:linear-gradient(180deg,#0f1c2cd1,#0a1421d1);color:var(--ngxsp-text);padding:0 18px;cursor:pointer;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);font-weight:700;font-size:14px;display:inline-flex;align-items:center;justify-content:center;gap:8px;box-shadow:0 14px 34px #0206173d}.ngxsp-nfx-side-wide{min-width:78px}.ngxsp-nfx-bottom{position:absolute;left:14px;right:14px;bottom:14px;padding:14px 16px 16px;background:linear-gradient(180deg,#0c1827eb,#08111df0);border:1px solid var(--ngxsp-border);border-radius:16px;gap:12px;flex-direction:column;align-items:stretch;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);z-index:4;opacity:0;transform:translateY(12px);transition:opacity .18s ease,transform .18s ease;box-shadow:0 24px 54px #02061761}.ngxsp-root.ngxsp-ui-visible .ngxsp-nfx-bottom{opacity:1;transform:translateY(0)}.ngxsp-nfx-metaRow{display:flex;align-items:center;gap:10px;width:100%}.ngxsp-nfx-time{color:#e2e8f0e6;font-variant-numeric:tabular-nums;font-size:12px;font-weight:600}.ngxsp-nfx-range{min-width:100%}.ngxsp-nfx-volumeGroup{display:inline-flex;align-items:center;gap:8px}.ngxsp-nfx-volume{max-width:0;min-width:0;opacity:0;transform:scaleX(.72);transform-origin:left center;pointer-events:none;transition:opacity .18s ease,transform .18s ease,max-width .18s ease}.ngxsp-nfx-volumeGroup:hover .ngxsp-nfx-volume,.ngxsp-nfx-volumeGroup:focus-within .ngxsp-nfx-volume{max-width:160px;min-width:96px;opacity:1;transform:scaleX(1);pointer-events:auto}.ngxsp-nfx-label{font-size:12px;color:#bfdbfe8a}.ngxsp-nfx-chip{background:linear-gradient(180deg,#122236e6,#0b1523e6);border:1px solid var(--ngxsp-border);color:var(--ngxsp-text);border-radius:999px;padding:8px 12px;cursor:pointer;font-size:12px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;gap:8px;box-shadow:inset 0 1px #ffffff0a}.ngxsp-nfx-chip.accent{background:linear-gradient(180deg,#14304af5,#0d2135f5);border-color:var(--ngxsp-border-strong);color:#d8f3ff}.ngxsp-nfx-chip-quiet{background:linear-gradient(180deg,#0c1827e0,#08111de0)}.ngxsp-nfx-center:hover,.ngxsp-nfx-side:hover,.ngxsp-nfx-chip:hover,.ngxsp-btn:hover{border-color:#bfdbfe57;background:linear-gradient(180deg,#182e47f0,#0c1b2cf0)}.ngxsp-nfx-center:disabled,.ngxsp-nfx-side:disabled{opacity:.38;cursor:not-allowed}.ngxsp-range{appearance:none;height:4px;border-radius:999px;background:linear-gradient(to right,#7dd3fcf2,#94a3b833);outline:none}.ngxsp-range::-webkit-slider-thumb{appearance:none;width:14px;height:14px;border-radius:50%;background:#f8fbff;box-shadow:0 0 0 3px #7dd3fc29}.ngxsp-range::-moz-range-thumb{width:14px;height:14px;border:none;border-radius:50%;background:#f8fbff;box-shadow:0 0 0 3px #7dd3fc29}.ngxsp-range::-moz-range-track{height:4px;border:none;border-radius:999px;background:#94a3b83d}.ngxsp-buffer{height:4px;border-radius:999px;background:#94a3b81f;overflow:hidden}.ngxsp-bufferFill{height:100%;border-radius:999px;background:linear-gradient(to right,#7dd3fc4d,#7dd3fceb)}@media(max-width:860px){.ngxsp-nfx-top{padding:14px 14px 18px}.ngxsp-nfx-topHint{display:none}.ngxsp-nfx-centerDock{gap:10px}.ngxsp-nfx-center{height:74px;width:74px}.ngxsp-nfx-side{height:46px;min-width:46px;padding:0 12px}.ngxsp-nfx-side-wide{min-width:64px}.ngxsp-nfx-bottom{left:10px;right:10px;bottom:10px;padding:12px}.ngxsp-nfx-metaRow{flex-wrap:wrap}.ngxsp-nfx-spacer{display:none}.ngxsp-nfx-volumeGroup{order:3;width:100%}.ngxsp-nfx-volumeGroup:focus-within .ngxsp-nfx-volume,.ngxsp-nfx-volumeGroup:hover .ngxsp-nfx-volume{max-width:none;width:100%;min-width:0}.ngxsp-nfx-chip{padding:8px 10px}}@media(max-width:640px){.ngxsp-controls{padding:8px;gap:6px}.ngxsp-btn{padding:7px 9px;border-radius:10px}.ngxsp-icon{width:16px;height:16px}.ngxsp-icon-lg{width:26px;height:26px}.ngxsp-audio-stage{padding:28px 16px 128px;gap:14px}.ngxsp-audio-disc{width:min(48vw,168px)}.ngxsp-nfx-title{font-size:15px}.ngxsp-nfx-status{display:none}.ngxsp-nfx-time,.ngxsp-nfx-label{font-size:11px}.ngxsp-nfx-bottom{border-radius:12px}}@keyframes ngxspSpin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes ngxspBars{0%,to{height:14px;opacity:.45}50%{height:40px;opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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", "step", "bigStep", "ariaLabel", "liveLabel"], outputs: ["seek"] }, { kind: "component", type: PlayerMenuComponent, selector: "ngxsp-player-menu", inputs: ["openMenu", "isAudioMode", "textTracks", "audioTracks", "videoTracks", "selectedTextTrackId", "selectedAudioTrackId", "selectedVideoTrackId", "playbackRates", "playbackRate"], outputs: ["selectText", "selectAudio", "selectVideo", "selectRate"] }, { kind: "component", type: PlayerOverlaysComponent, selector: "ngxsp-player-overlays", inputs: ["error", "debug", "debugJson"], outputs: ["retry", "requestPlay", "copyJson", "downloadJson"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
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 });
|
|
1213
3051
|
}
|
|
1214
|
-
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: [{
|
|
1215
3053
|
type: Component,
|
|
1216
|
-
args: [{ selector: 'ngx-smart-player', standalone: true, imports: [CommonModule,
|
|
1217
|
-
<div class="ngxsp-root" [class.ngxsp-skin-
|
|
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">
|
|
1218
3056
|
<!-- Surface de présentation : interactions pointeur non essentielles, toutes
|
|
1219
3057
|
dupliquées au clavier par @HostListener('document:keydown') (Espace/flèches/F/M/C)
|
|
1220
3058
|
et par les contrôles focusables. -->
|
|
1221
3059
|
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
|
|
1222
3060
|
<div class="ngxsp-video" (dblclick)="onPrimarySurfaceDoubleClick()" (mousemove)="onInteraction()" (mouseenter)="onInteraction()" (mouseleave)="onPointerLeave()" (focusin)="onInteraction()" (touchstart)="onInteraction(true)" (click)="onRootClick($event)">
|
|
1223
|
-
<video #video playsinline (timeupdate)="noop()" (loadedmetadata)="onLoadedMetadata()" (ended)="onEnded()"></video>
|
|
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
|
+
}
|
|
1224
3067
|
@if (isAudioMode) {
|
|
1225
3068
|
<div class="ngxsp-audio-stage" aria-hidden="true">
|
|
1226
3069
|
<div class="ngxsp-audio-disc"></div>
|
|
@@ -1243,132 +3086,136 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1243
3086
|
(copyJson)="copyDebug()"
|
|
1244
3087
|
(downloadJson)="downloadDebug()"
|
|
1245
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>
|
|
3097
|
+
}
|
|
1246
3098
|
|
|
1247
|
-
@if (uiSkin==='
|
|
1248
|
-
<
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
</button>
|
|
1313
|
-
<button type="button" class="ngxsp-nfx-chip" [class.accent]="repeatMode !== 'off'" (click)="cycleRepeat.emit()" [attr.title]="repeatLabel" [attr.aria-label]="repeatLabel">
|
|
1314
|
-
@if (repeatMode === 'one') {
|
|
1315
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="repeat-one" />
|
|
1316
|
-
} @else {
|
|
1317
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="repeat" />
|
|
1318
|
-
}
|
|
1319
|
-
</button>
|
|
1320
|
-
}
|
|
1321
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="toggleMenu('tracks')" [attr.title]="tracksButtonLabel()" [attr.aria-label]="tracksButtonLabel()">
|
|
1322
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="tracks" />
|
|
1323
|
-
</button>
|
|
1324
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="subtitles.emit()" [attr.title]="captionsLabel()" [attr.aria-label]="captionsLabel()">
|
|
1325
|
-
@if (selectedTextTrackId !== null) {
|
|
1326
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="captions" />
|
|
1327
|
-
} @else {
|
|
1328
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="captions-off" />
|
|
1329
|
-
}
|
|
1330
|
-
</button>
|
|
1331
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="toggleMenu('speed')" [attr.title]="playbackRateLabel()" [attr.aria-label]="playbackRateLabel()">
|
|
1332
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="speed" />
|
|
1333
|
-
</button>
|
|
1334
|
-
@if (showCastButton()) {
|
|
1335
|
-
<button type="button" class="ngxsp-nfx-chip" [class.accent]="isCastActive()" (click)="onCastClick()" [attr.title]="castHintLabel()" [attr.aria-label]="castLabel()">
|
|
1336
|
-
@if (isCastActive()) {
|
|
1337
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="cast-active" />
|
|
1338
|
-
} @else {
|
|
1339
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="cast" />
|
|
1340
|
-
}
|
|
1341
|
-
</button>
|
|
1342
|
-
}
|
|
1343
|
-
@if (!isAudioMode && isPiPSupported()) {
|
|
1344
|
-
<button type="button" class="ngxsp-nfx-chip" (click)="togglePiP()" [attr.title]="pipLabel()" [attr.aria-label]="pipLabel()">
|
|
1345
|
-
@if (isPiPActive()) {
|
|
1346
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="pip-active" />
|
|
1347
|
-
} @else {
|
|
1348
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="pip" />
|
|
1349
|
-
}
|
|
1350
|
-
</button>
|
|
1351
|
-
}
|
|
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]" />
|
|
1352
3164
|
</div>
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
[
|
|
1357
|
-
[
|
|
1358
|
-
[
|
|
1359
|
-
[
|
|
1360
|
-
|
|
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)"
|
|
1361
3182
|
/>
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
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
|
+
>
|
|
1372
3219
|
<ngxsp-player-menu
|
|
1373
3220
|
[openMenu]="openMenu"
|
|
1374
3221
|
[isAudioMode]="isAudioMode"
|
|
@@ -1380,103 +3227,63 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1380
3227
|
[selectedVideoTrackId]="selectedVideoTrackId"
|
|
1381
3228
|
[playbackRates]="playbackRates"
|
|
1382
3229
|
[playbackRate]="playbackRate"
|
|
3230
|
+
[labels]="playerMenuLabels"
|
|
3231
|
+
(loadSubtitles)="openSubtitlePicker()"
|
|
1383
3232
|
(selectText)="selectTextTrack($event)"
|
|
1384
3233
|
(selectAudio)="selectAudioTrack($event)"
|
|
1385
3234
|
(selectVideo)="selectVideoTrack($event)"
|
|
1386
3235
|
(selectRate)="setPlaybackRate($event)"
|
|
1387
3236
|
/>
|
|
1388
|
-
</
|
|
3237
|
+
</ngxsp-player-controls-edition>
|
|
1389
3238
|
} @else {
|
|
1390
|
-
<
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
} @else {
|
|
1434
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="cast" />
|
|
1435
|
-
}
|
|
1436
|
-
</button>
|
|
1437
|
-
}
|
|
1438
|
-
@if (!isAudioMode && isPiPSupported()) {
|
|
1439
|
-
<button type="button" class="ngxsp-btn" (click)="togglePiP()" [attr.title]="pipLabel()" [attr.aria-label]="pipLabel()">
|
|
1440
|
-
@if (isPiPActive()) {
|
|
1441
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="pip-active" />
|
|
1442
|
-
} @else {
|
|
1443
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="pip" />
|
|
1444
|
-
}
|
|
1445
|
-
</button>
|
|
1446
|
-
}
|
|
1447
|
-
<button type="button" class="ngxsp-btn" (click)="toggleMute()" [attr.title]="volumeLabel()" [attr.aria-label]="volumeLabel()">
|
|
1448
|
-
@if (volume <= 0.001) {
|
|
1449
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="volume-mute" />
|
|
1450
|
-
} @else if (volume < 0.5) {
|
|
1451
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="volume-low" />
|
|
1452
|
-
} @else {
|
|
1453
|
-
<ngxsp-smart-icon class="ngxsp-icon" name="volume-high" />
|
|
1454
|
-
}
|
|
1455
|
-
</button>
|
|
1456
|
-
|
|
1457
|
-
<ngxsp-time-slider
|
|
1458
|
-
style="flex:1 1 160px; min-width:160px"
|
|
1459
|
-
[currentTime]="currentTime"
|
|
1460
|
-
[duration]="duration"
|
|
1461
|
-
[buffered]="bufferAheadSec"
|
|
1462
|
-
[profile]="sourceProfile"
|
|
1463
|
-
(seek)="onScrub($event)"
|
|
1464
|
-
/>
|
|
1465
|
-
|
|
1466
|
-
<div class="ngxsp-time">{{fmtTime(currentTime)}} / {{fmtTime(duration)}}</div>
|
|
1467
|
-
|
|
1468
|
-
<input class="ngxsp-range" style="max-width:140px" type="range"
|
|
1469
|
-
min="0" max="1" step="0.01"
|
|
1470
|
-
[value]="volume"
|
|
1471
|
-
(input)="onVolume(($any($event.target).value))"
|
|
1472
|
-
aria-label="Volume"
|
|
1473
|
-
/>
|
|
1474
|
-
</div>
|
|
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
|
+
/>
|
|
1475
3282
|
}
|
|
1476
3283
|
|
|
1477
3284
|
</div>
|
|
1478
3285
|
</div>
|
|
1479
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".ngxsp-root{--ngxsp-bg:#
|
|
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"] }]
|
|
1480
3287
|
}], propDecorators: { config: [{
|
|
1481
3288
|
type: Input,
|
|
1482
3289
|
args: [{ required: true }]
|
|
@@ -1490,10 +3297,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1490
3297
|
type: Input
|
|
1491
3298
|
}], startTimeSec: [{
|
|
1492
3299
|
type: Input
|
|
3300
|
+
}], autoplayOnInit: [{
|
|
3301
|
+
type: Input
|
|
3302
|
+
}], playRequest: [{
|
|
3303
|
+
type: Input
|
|
1493
3304
|
}], canPrev: [{
|
|
1494
3305
|
type: Input
|
|
1495
3306
|
}], canNext: [{
|
|
1496
3307
|
type: Input
|
|
3308
|
+
}], previousLabel: [{
|
|
3309
|
+
type: Input
|
|
3310
|
+
}], nextLabel: [{
|
|
3311
|
+
type: Input
|
|
3312
|
+
}], playLabel: [{
|
|
3313
|
+
type: Input
|
|
3314
|
+
}], pauseLabel: [{
|
|
3315
|
+
type: Input
|
|
1497
3316
|
}], shuffleActive: [{
|
|
1498
3317
|
type: Input
|
|
1499
3318
|
}], shuffleLabel: [{
|
|
@@ -1508,6 +3327,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1508
3327
|
type: Input
|
|
1509
3328
|
}], castEnabled: [{
|
|
1510
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
|
|
1511
3368
|
}], ended: [{
|
|
1512
3369
|
type: Output
|
|
1513
3370
|
}], videoSize: [{
|
|
@@ -1524,11 +3381,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1524
3381
|
type: Output
|
|
1525
3382
|
}], subtitles: [{
|
|
1526
3383
|
type: Output
|
|
3384
|
+
}], subtitlesLoaded: [{
|
|
3385
|
+
type: Output
|
|
3386
|
+
}], menuLabels: [{
|
|
3387
|
+
type: Input
|
|
1527
3388
|
}], cast: [{
|
|
1528
3389
|
type: Output
|
|
3390
|
+
}], stateChange: [{
|
|
3391
|
+
type: Output
|
|
3392
|
+
}], playbackToggle: [{
|
|
3393
|
+
type: Output
|
|
3394
|
+
}], outputVolumeChange: [{
|
|
3395
|
+
type: Output
|
|
1529
3396
|
}], videoEl: [{
|
|
1530
3397
|
type: ViewChild,
|
|
1531
3398
|
args: ['video', { static: true }]
|
|
3399
|
+
}], captionsLayer: [{
|
|
3400
|
+
type: ViewChild,
|
|
3401
|
+
args: ['captionsLayer', { static: true }]
|
|
3402
|
+
}], subtitleFileInput: [{
|
|
3403
|
+
type: ViewChild,
|
|
3404
|
+
args: ['subtitleFileInput']
|
|
1532
3405
|
}], onEscape: [{
|
|
1533
3406
|
type: HostListener,
|
|
1534
3407
|
args: ['document:keydown.escape']
|
|
@@ -1560,5 +3433,5 @@ function provideSmartPlayer(cfg) {
|
|
|
1560
3433
|
* Generated bundle index. Do not edit.
|
|
1561
3434
|
*/
|
|
1562
3435
|
|
|
1563
|
-
export { SMART_PLAYER_METADATA_PROVIDERS, SMART_PLAYER_SUBTITLE_PROVIDERS, SmartPlayerComponent, provideSmartPlayer };
|
|
3436
|
+
export { DEFAULT_PLAYER_MENU_LABELS, PlayerControlsComponent, PlayerControlsEditionComponent, PlayerControlsVodComponent, SMART_PLAYER_METADATA_PROVIDERS, SMART_PLAYER_SUBTITLE_PROVIDERS, SmartConfirmDialogComponent, SmartPlayerComponent, SmartPlayerIconComponent, SmartSettingsComponent, provideSmartPlayer };
|
|
1564
3437
|
//# sourceMappingURL=ngxsplayer-ngx-smart-player.mjs.map
|