@shival99/z-ui 2.3.10 → 2.3.11
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/fesm2022/shival99-z-ui-components-z-audio-player.mjs +210 -0
- package/fesm2022/shival99-z-ui-components-z-audio-player.mjs.map +1 -0
- package/fesm2022/shival99-z-ui-components-z-autocomplete.mjs +2 -2
- package/fesm2022/shival99-z-ui-components-z-autocomplete.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-gallery.mjs +1177 -1091
- package/fesm2022/shival99-z-ui-components-z-gallery.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-input.mjs +2 -2
- package/fesm2022/shival99-z-ui-components-z-input.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-media-player.mjs +359 -56
- package/fesm2022/shival99-z-ui-components-z-media-player.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-select.mjs +2 -2
- package/fesm2022/shival99-z-ui-components-z-select.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-table.mjs +2 -0
- package/fesm2022/shival99-z-ui-components-z-table.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-tooltip.mjs +1 -1
- package/fesm2022/shival99-z-ui-components-z-tooltip.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-i18n.mjs +4 -2
- package/fesm2022/shival99-z-ui-i18n.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-services.mjs +144 -9
- package/fesm2022/shival99-z-ui-services.mjs.map +1 -1
- package/package.json +21 -1
- package/types/shival99-z-ui-components-z-audio-player.d.ts +46 -0
- package/types/shival99-z-ui-components-z-autocomplete.d.ts +1 -1
- package/types/shival99-z-ui-components-z-calendar.d.ts +4 -4
- package/types/shival99-z-ui-components-z-editor.d.ts +1 -1
- package/types/shival99-z-ui-components-z-gallery.d.ts +67 -36
- package/types/shival99-z-ui-components-z-media-player.d.ts +83 -7
- package/types/shival99-z-ui-components-z-modal.d.ts +1 -1
- package/types/shival99-z-ui-components-z-popover.d.ts +1 -1
- package/types/shival99-z-ui-components-z-select.d.ts +1 -1
- package/types/shival99-z-ui-components-z-table.d.ts +1 -1
- package/types/shival99-z-ui-components-z-toast.d.ts +2 -2
- package/types/shival99-z-ui-services.d.ts +25 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as i1 from '@angular/common';
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { inject, ElementRef, input, output, viewChild, signal, computed, effect, afterNextRender, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
4
|
+
import { inject, ElementRef, NgZone, input, output, viewChild, signal, computed, effect, afterNextRender, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
5
5
|
import * as i2 from '@angular/forms';
|
|
6
6
|
import { FormsModule } from '@angular/forms';
|
|
7
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
7
8
|
import { TranslatePipe } from '@ngx-translate/core';
|
|
8
9
|
import { ZButtonComponent } from '@shival99/z-ui/components/z-button';
|
|
9
10
|
import { ZIconComponent } from '@shival99/z-ui/components/z-icon';
|
|
@@ -11,10 +12,26 @@ import { ZPopoverDirective } from '@shival99/z-ui/components/z-popover';
|
|
|
11
12
|
import { zMergeClasses } from '@shival99/z-ui/utils';
|
|
12
13
|
import { cva } from 'class-variance-authority';
|
|
13
14
|
|
|
15
|
+
/** Matches youtube.com/watch, youtu.be, youtube.com/embed and /shorts links, capturing the 11-char video id. */
|
|
16
|
+
const YOUTUBE_URL_PATTERN = /(?:youtube\.com\/(?:watch\?(?:.*&)?v=|embed\/|shorts\/)|youtu\.be\/)([\w-]{11})/;
|
|
17
|
+
/** Every control group renders by default; pass `zControls` to opt specific groups out. */
|
|
18
|
+
const DEFAULT_MEDIA_PLAYER_CONTROLS = {
|
|
19
|
+
play: true,
|
|
20
|
+
volume: true,
|
|
21
|
+
skip: true,
|
|
22
|
+
time: true,
|
|
23
|
+
timeline: true,
|
|
24
|
+
subtitles: true,
|
|
25
|
+
settings: true,
|
|
26
|
+
pip: true,
|
|
27
|
+
theater: true,
|
|
28
|
+
fullscreen: true,
|
|
29
|
+
};
|
|
30
|
+
|
|
14
31
|
const zMediaPlayerVariants = cva('relative overflow-hidden bg-black select-none transition-all duration-300 w-full flex flex-col justify-center items-center group/player outline-none', {
|
|
15
32
|
variants: {
|
|
16
33
|
zLayout: {
|
|
17
|
-
default: 'aspect-video rounded-
|
|
34
|
+
default: 'aspect-video rounded-sm shadow-sm border border-border max-w-5xl mx-auto',
|
|
18
35
|
theater: 'w-full aspect-[21/9] max-w-full',
|
|
19
36
|
fullscreen: 'fixed inset-0 w-screen h-screen z-[9999] rounded-none max-w-full aspect-none',
|
|
20
37
|
},
|
|
@@ -26,6 +43,8 @@ const zMediaPlayerVariants = cva('relative overflow-hidden bg-black select-none
|
|
|
26
43
|
|
|
27
44
|
class ZMediaPlayerComponent {
|
|
28
45
|
_elementRef = inject((ElementRef));
|
|
46
|
+
_ngZone = inject(NgZone);
|
|
47
|
+
_sanitizer = inject(DomSanitizer);
|
|
29
48
|
// Inputs
|
|
30
49
|
class = input('', /* @ts-ignore */
|
|
31
50
|
...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
|
|
@@ -45,7 +64,15 @@ class ZMediaPlayerComponent {
|
|
|
45
64
|
...(ngDevMode ? [{ debugName: "zVolume" }] : /* istanbul ignore next */ []));
|
|
46
65
|
zSubtitles = input([], /* @ts-ignore */
|
|
47
66
|
...(ngDevMode ? [{ debugName: "zSubtitles" }] : /* istanbul ignore next */ []));
|
|
67
|
+
/** URL to a WebVTT storyboard file powering the YouTube-style timeline hover preview. */
|
|
68
|
+
zThumbnailsVtt = input('', /* @ts-ignore */
|
|
69
|
+
...(ngDevMode ? [{ debugName: "zThumbnailsVtt" }] : /* istanbul ignore next */ []));
|
|
70
|
+
/** Toggles which control bar groups render. Unset groups default to visible. */
|
|
71
|
+
zControls = input({}, /* @ts-ignore */
|
|
72
|
+
...(ngDevMode ? [{ debugName: "zControls" }] : /* istanbul ignore next */ []));
|
|
48
73
|
// Outputs
|
|
74
|
+
zLoaded = output();
|
|
75
|
+
zError = output();
|
|
49
76
|
zPlay = output();
|
|
50
77
|
zTimeUpdate = output();
|
|
51
78
|
zVolumeChange = output();
|
|
@@ -58,6 +85,8 @@ class ZMediaPlayerComponent {
|
|
|
58
85
|
...(ngDevMode ? [{ debugName: "videoElement" }] : /* istanbul ignore next */ []));
|
|
59
86
|
timelineContainer = viewChild('timelineContainer', /* @ts-ignore */
|
|
60
87
|
...(ngDevMode ? [{ debugName: "timelineContainer" }] : /* istanbul ignore next */ []));
|
|
88
|
+
_settingsPopover = viewChild(ZPopoverDirective, /* @ts-ignore */
|
|
89
|
+
...(ngDevMode ? [{ debugName: "_settingsPopover" }] : /* istanbul ignore next */ []));
|
|
61
90
|
// Component Signals trạng thái trình phát
|
|
62
91
|
isPlaying = signal(false, /* @ts-ignore */
|
|
63
92
|
...(ngDevMode ? [{ debugName: "isPlaying" }] : /* istanbul ignore next */ []));
|
|
@@ -73,12 +102,20 @@ class ZMediaPlayerComponent {
|
|
|
73
102
|
...(ngDevMode ? [{ debugName: "playbackSpeed" }] : /* istanbul ignore next */ []));
|
|
74
103
|
activeSourceSrc = signal(null, /* @ts-ignore */
|
|
75
104
|
...(ngDevMode ? [{ debugName: "activeSourceSrc" }] : /* istanbul ignore next */ []));
|
|
105
|
+
_activeQualitySourceLabel = signal(null, /* @ts-ignore */
|
|
106
|
+
...(ngDevMode ? [{ debugName: "_activeQualitySourceLabel" }] : /* istanbul ignore next */ []));
|
|
76
107
|
showControls = signal(true, /* @ts-ignore */
|
|
77
108
|
...(ngDevMode ? [{ debugName: "showControls" }] : /* istanbul ignore next */ []));
|
|
109
|
+
isPointerOver = signal(false, /* @ts-ignore */
|
|
110
|
+
...(ngDevMode ? [{ debugName: "isPointerOver" }] : /* istanbul ignore next */ []));
|
|
78
111
|
isBuffering = signal(false, /* @ts-ignore */
|
|
79
112
|
...(ngDevMode ? [{ debugName: "isBuffering" }] : /* istanbul ignore next */ []));
|
|
80
113
|
captionsEnabled = signal(false, /* @ts-ignore */
|
|
81
114
|
...(ngDevMode ? [{ debugName: "captionsEnabled" }] : /* istanbul ignore next */ []));
|
|
115
|
+
subtitleText = signal('', /* @ts-ignore */
|
|
116
|
+
...(ngDevMode ? [{ debugName: "subtitleText" }] : /* istanbul ignore next */ []));
|
|
117
|
+
hasSubtitles = computed(() => this.zSubtitles().length > 0, /* @ts-ignore */
|
|
118
|
+
...(ngDevMode ? [{ debugName: "hasSubtitles" }] : /* istanbul ignore next */ []));
|
|
82
119
|
isTheaterMode = signal(false, /* @ts-ignore */
|
|
83
120
|
...(ngDevMode ? [{ debugName: "isTheaterMode" }] : /* istanbul ignore next */ []));
|
|
84
121
|
isFullscreen = signal(false, /* @ts-ignore */
|
|
@@ -94,21 +131,21 @@ class ZMediaPlayerComponent {
|
|
|
94
131
|
// Tiến trình đệm và phát
|
|
95
132
|
bufferedProgress = signal(0, /* @ts-ignore */
|
|
96
133
|
...(ngDevMode ? [{ debugName: "bufferedProgress" }] : /* istanbul ignore next */ []));
|
|
97
|
-
progress =
|
|
98
|
-
const dur = this.duration();
|
|
99
|
-
if (dur === 0) {
|
|
100
|
-
return 0;
|
|
101
|
-
}
|
|
102
|
-
return (this.currentTime() / dur) * 100;
|
|
103
|
-
}, /* @ts-ignore */
|
|
134
|
+
progress = signal(0, /* @ts-ignore */
|
|
104
135
|
...(ngDevMode ? [{ debugName: "progress" }] : /* istanbul ignore next */ []));
|
|
105
136
|
// Trạng thái visual hiển thị
|
|
106
137
|
playPauseFlash = signal(null, /* @ts-ignore */
|
|
107
138
|
...(ngDevMode ? [{ debugName: "playPauseFlash" }] : /* istanbul ignore next */ []));
|
|
108
139
|
hoverTimeLabel = signal(null, /* @ts-ignore */
|
|
109
140
|
...(ngDevMode ? [{ debugName: "hoverTimeLabel" }] : /* istanbul ignore next */ []));
|
|
141
|
+
hoverTime = signal(null, /* @ts-ignore */
|
|
142
|
+
...(ngDevMode ? [{ debugName: "hoverTime" }] : /* istanbul ignore next */ []));
|
|
110
143
|
hoverX = signal(0, /* @ts-ignore */
|
|
111
144
|
...(ngDevMode ? [{ debugName: "hoverX" }] : /* istanbul ignore next */ []));
|
|
145
|
+
// Storyboard thumbnails cho preview khi hover timeline (kiểu YouTube)
|
|
146
|
+
_thumbnailCues = signal([], /* @ts-ignore */
|
|
147
|
+
...(ngDevMode ? [{ debugName: "_thumbnailCues" }] : /* istanbul ignore next */ []));
|
|
148
|
+
_positionedSubtitleTracks = new WeakSet();
|
|
112
149
|
// Lớp phủ tua nhanh nhảy số cộng dồn kiểu YouTube
|
|
113
150
|
skipOverlayDirection = signal(null, /* @ts-ignore */
|
|
114
151
|
...(ngDevMode ? [{ debugName: "skipOverlayDirection" }] : /* istanbul ignore next */ []));
|
|
@@ -129,9 +166,10 @@ class ZMediaPlayerComponent {
|
|
|
129
166
|
}
|
|
130
167
|
const video = this.videoElement()?.nativeElement;
|
|
131
168
|
if (video) {
|
|
169
|
+
const supportsPresentationMode = video.webkitSupportsPresentationMode;
|
|
132
170
|
const hasWebkit = 'webkitSupportsPresentationMode' in video &&
|
|
133
|
-
typeof
|
|
134
|
-
|
|
171
|
+
typeof supportsPresentationMode === 'function' &&
|
|
172
|
+
supportsPresentationMode.call(video, 'picture-in-picture');
|
|
135
173
|
return !!hasWebkit;
|
|
136
174
|
}
|
|
137
175
|
const hasWebkitProto = typeof HTMLVideoElement !== 'undefined' &&
|
|
@@ -154,7 +192,11 @@ class ZMediaPlayerComponent {
|
|
|
154
192
|
_controlsTimeout = null;
|
|
155
193
|
_flashTimeout = null;
|
|
156
194
|
_skipOverlayTimeout = null;
|
|
157
|
-
|
|
195
|
+
_bufferAnimationFrame = null;
|
|
196
|
+
_playbackAnimationFrame = null;
|
|
197
|
+
_mouseMoveCleanup = null;
|
|
198
|
+
_lastMouseMoveAt = 0;
|
|
199
|
+
_bufferedTarget = 0;
|
|
158
200
|
_accumulatedSkip = 0;
|
|
159
201
|
// Cập nhật CVA class dựa trên các trạng thái giao diện
|
|
160
202
|
zClasses = computed(() => {
|
|
@@ -173,24 +215,62 @@ class ZMediaPlayerComponent {
|
|
|
173
215
|
...(ngDevMode ? [{ debugName: "currentTimeLabel" }] : /* istanbul ignore next */ []));
|
|
174
216
|
durationLabel = computed(() => this._formatTime(this.duration()), /* @ts-ignore */
|
|
175
217
|
...(ngDevMode ? [{ debugName: "durationLabel" }] : /* istanbul ignore next */ []));
|
|
218
|
+
controls = computed(() => ({
|
|
219
|
+
...DEFAULT_MEDIA_PLAYER_CONTROLS,
|
|
220
|
+
...this.zControls(),
|
|
221
|
+
}), /* @ts-ignore */
|
|
222
|
+
...(ngDevMode ? [{ debugName: "controls" }] : /* istanbul ignore next */ []));
|
|
176
223
|
effectiveSrc = computed(() => this.activeSourceSrc() ?? this.zSrc(), /* @ts-ignore */
|
|
177
224
|
...(ngDevMode ? [{ debugName: "effectiveSrc" }] : /* istanbul ignore next */ []));
|
|
178
225
|
qualitySources = computed(() => this.zSources().filter(source => !!source.src), /* @ts-ignore */
|
|
179
226
|
...(ngDevMode ? [{ debugName: "qualitySources" }] : /* istanbul ignore next */ []));
|
|
180
|
-
activeQualityLabel = computed(() =>
|
|
181
|
-
const activeSrc = this.effectiveSrc();
|
|
182
|
-
return this.qualitySources().find(source => source.src === activeSrc)?.label ?? 'Auto';
|
|
183
|
-
}, /* @ts-ignore */
|
|
227
|
+
activeQualityLabel = computed(() => this._activeQualitySourceLabel() ?? this.qualitySources()[0]?.label ?? 'Auto', /* @ts-ignore */
|
|
184
228
|
...(ngDevMode ? [{ debugName: "activeQualityLabel" }] : /* istanbul ignore next */ []));
|
|
229
|
+
// Nhận diện link YouTube để nhúng iframe gốc thay vì thẻ <video>
|
|
230
|
+
youtubeVideoId = computed(() => YOUTUBE_URL_PATTERN.exec(this.effectiveSrc())?.[1] ?? null, /* @ts-ignore */
|
|
231
|
+
...(ngDevMode ? [{ debugName: "youtubeVideoId" }] : /* istanbul ignore next */ []));
|
|
232
|
+
isYouTube = computed(() => this.youtubeVideoId() !== null, /* @ts-ignore */
|
|
233
|
+
...(ngDevMode ? [{ debugName: "isYouTube" }] : /* istanbul ignore next */ []));
|
|
234
|
+
youtubeEmbedUrl = computed(() => {
|
|
235
|
+
const id = this.youtubeVideoId();
|
|
236
|
+
if (!id) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
const params = new URLSearchParams({
|
|
240
|
+
autoplay: this.zAutoplay() ? '1' : '0',
|
|
241
|
+
mute: this.zMuted() ? '1' : '0',
|
|
242
|
+
loop: this.zLoop() ? '1' : '0',
|
|
243
|
+
playsinline: '1',
|
|
244
|
+
rel: '0',
|
|
245
|
+
modestbranding: '1',
|
|
246
|
+
});
|
|
247
|
+
if (this.zLoop()) {
|
|
248
|
+
params.set('playlist', id);
|
|
249
|
+
}
|
|
250
|
+
return this._sanitizer.bypassSecurityTrustResourceUrl(`https://www.youtube-nocookie.com/embed/${id}?${params.toString()}`);
|
|
251
|
+
}, /* @ts-ignore */
|
|
252
|
+
...(ngDevMode ? [{ debugName: "youtubeEmbedUrl" }] : /* istanbul ignore next */ []));
|
|
253
|
+
// Cue storyboard đang khớp với thời điểm đang hover trên timeline
|
|
254
|
+
hoverThumbnailCue = computed(() => {
|
|
255
|
+
const time = this.hoverTime();
|
|
256
|
+
const cues = this._thumbnailCues();
|
|
257
|
+
if (time === null || cues.length === 0) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
return cues.find(cue => time >= cue.start && time < cue.end) ?? cues[cues.length - 1];
|
|
261
|
+
}, /* @ts-ignore */
|
|
262
|
+
...(ngDevMode ? [{ debugName: "hoverThumbnailCue" }] : /* istanbul ignore next */ []));
|
|
185
263
|
constructor() {
|
|
186
264
|
effect(() => {
|
|
187
265
|
const fallbackSrc = this.zSrc();
|
|
188
266
|
const sources = this.qualitySources();
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
267
|
+
const currentLabel = this._activeQualitySourceLabel();
|
|
268
|
+
if (currentLabel && sources.some(source => source.label === currentLabel)) {
|
|
191
269
|
return;
|
|
192
270
|
}
|
|
193
|
-
|
|
271
|
+
const defaultSource = sources[0] ?? null;
|
|
272
|
+
this.activeSourceSrc.set(defaultSource?.src ?? fallbackSrc);
|
|
273
|
+
this._activeQualitySourceLabel.set(defaultSource?.label ?? null);
|
|
194
274
|
});
|
|
195
275
|
effect(() => {
|
|
196
276
|
const src = this.effectiveSrc();
|
|
@@ -202,11 +282,47 @@ class ZMediaPlayerComponent {
|
|
|
202
282
|
video.load();
|
|
203
283
|
}
|
|
204
284
|
});
|
|
285
|
+
effect(() => {
|
|
286
|
+
const vttUrl = this.zThumbnailsVtt();
|
|
287
|
+
if (!vttUrl) {
|
|
288
|
+
this._thumbnailCues.set([]);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
void this._loadThumbnailVtt(vttUrl);
|
|
292
|
+
});
|
|
293
|
+
effect(() => {
|
|
294
|
+
this.showControls();
|
|
295
|
+
this.isFullscreen();
|
|
296
|
+
const video = this.videoElement()?.nativeElement;
|
|
297
|
+
if (!video || !this.captionsEnabled()) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
let i = 0;
|
|
301
|
+
while (i < video.textTracks.length) {
|
|
302
|
+
this._positionSubtitleCues(video.textTracks[i]);
|
|
303
|
+
i++;
|
|
304
|
+
}
|
|
305
|
+
});
|
|
205
306
|
afterNextRender(() => {
|
|
206
307
|
this._initializeVideoState();
|
|
308
|
+
this._ngZone.runOutsideAngular(() => {
|
|
309
|
+
const host = this._elementRef.nativeElement;
|
|
310
|
+
const onMouseMove = () => {
|
|
311
|
+
this.isPointerOver.set(true);
|
|
312
|
+
const now = performance.now();
|
|
313
|
+
if (this.showControls() && now - this._lastMouseMoveAt < 200) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
this._lastMouseMoveAt = now;
|
|
317
|
+
this._resetControlsTimer();
|
|
318
|
+
};
|
|
319
|
+
host.addEventListener('mousemove', onMouseMove, { passive: true });
|
|
320
|
+
this._mouseMoveCleanup = () => host.removeEventListener('mousemove', onMouseMove);
|
|
321
|
+
});
|
|
207
322
|
});
|
|
208
323
|
}
|
|
209
324
|
ngOnDestroy() {
|
|
325
|
+
this._mouseMoveCleanup?.();
|
|
210
326
|
if (this._controlsTimeout) {
|
|
211
327
|
clearTimeout(this._controlsTimeout);
|
|
212
328
|
}
|
|
@@ -216,8 +332,11 @@ class ZMediaPlayerComponent {
|
|
|
216
332
|
if (this._skipOverlayTimeout) {
|
|
217
333
|
clearTimeout(this._skipOverlayTimeout);
|
|
218
334
|
}
|
|
219
|
-
if (this.
|
|
220
|
-
|
|
335
|
+
if (this._bufferAnimationFrame !== null) {
|
|
336
|
+
cancelAnimationFrame(this._bufferAnimationFrame);
|
|
337
|
+
}
|
|
338
|
+
if (this._playbackAnimationFrame !== null) {
|
|
339
|
+
cancelAnimationFrame(this._playbackAnimationFrame);
|
|
221
340
|
}
|
|
222
341
|
}
|
|
223
342
|
// Khởi tạo trạng thái âm lượng và tắt tiếng khi bắt đầu render
|
|
@@ -299,7 +418,18 @@ class ZMediaPlayerComponent {
|
|
|
299
418
|
onSettingsHidden() {
|
|
300
419
|
this.showSettings.set(false);
|
|
301
420
|
this.closeSettingsPanel();
|
|
302
|
-
this.
|
|
421
|
+
if (this._controlsTimeout) {
|
|
422
|
+
clearTimeout(this._controlsTimeout);
|
|
423
|
+
this._controlsTimeout = null;
|
|
424
|
+
}
|
|
425
|
+
if (!this.isPointerOver()) {
|
|
426
|
+
this.showControls.set(false);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
this._controlsTimeout = setTimeout(() => {
|
|
430
|
+
this.showControls.set(false);
|
|
431
|
+
this._controlsTimeout = null;
|
|
432
|
+
}, 1000);
|
|
303
433
|
}
|
|
304
434
|
toggleInlineSettings() {
|
|
305
435
|
const nextVisible = !this.showSettings();
|
|
@@ -310,7 +440,13 @@ class ZMediaPlayerComponent {
|
|
|
310
440
|
this.onMouseMove();
|
|
311
441
|
}
|
|
312
442
|
setQualitySource(source) {
|
|
443
|
+
if (source.label === this.activeQualityLabel()) {
|
|
444
|
+
this.closeSettingsPanel();
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
313
447
|
if (source.src === this.effectiveSrc()) {
|
|
448
|
+
this._activeQualitySourceLabel.set(source.label);
|
|
449
|
+
this.zQualityChange.emit(source);
|
|
314
450
|
this.closeSettingsPanel();
|
|
315
451
|
return;
|
|
316
452
|
}
|
|
@@ -334,7 +470,8 @@ class ZMediaPlayerComponent {
|
|
|
334
470
|
video.addEventListener('loadeddata', restorePosition, { once: true });
|
|
335
471
|
}
|
|
336
472
|
this.activeSourceSrc.set(source.src);
|
|
337
|
-
this.
|
|
473
|
+
this._activeQualitySourceLabel.set(source.label);
|
|
474
|
+
this._setBufferedTarget(0);
|
|
338
475
|
this.zQualityChange.emit(source);
|
|
339
476
|
this.closeSettingsPanel();
|
|
340
477
|
}
|
|
@@ -346,6 +483,7 @@ class ZMediaPlayerComponent {
|
|
|
346
483
|
}
|
|
347
484
|
video.currentTime = time;
|
|
348
485
|
this.currentTime.set(time);
|
|
486
|
+
this._syncPlaybackProgress(time);
|
|
349
487
|
}
|
|
350
488
|
// Bật/tắt chế độ rạp phim (Theater Mode)
|
|
351
489
|
toggleTheaterMode() {
|
|
@@ -401,6 +539,9 @@ class ZMediaPlayerComponent {
|
|
|
401
539
|
webkitVideo.webkitSupportsPresentationMode('picture-in-picture')) {
|
|
402
540
|
const currentMode = webkitVideo.webkitPresentationMode;
|
|
403
541
|
const targetMode = currentMode === 'picture-in-picture' ? 'inline' : 'picture-in-picture';
|
|
542
|
+
if (typeof webkitVideo.webkitSetPresentationMode !== 'function') {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
404
545
|
webkitVideo.webkitSetPresentationMode(targetMode);
|
|
405
546
|
const isPiP = targetMode === 'picture-in-picture';
|
|
406
547
|
this.isMiniplayer.set(isPiP);
|
|
@@ -439,14 +580,71 @@ class ZMediaPlayerComponent {
|
|
|
439
580
|
this.captionsEnabled.set(targetState);
|
|
440
581
|
let i = 0;
|
|
441
582
|
while (i < tracks.length) {
|
|
442
|
-
tracks[i]
|
|
583
|
+
const track = tracks[i];
|
|
584
|
+
track.mode = targetState ? 'showing' : 'disabled';
|
|
585
|
+
if (targetState) {
|
|
586
|
+
this._positionSubtitleCues(track);
|
|
587
|
+
}
|
|
443
588
|
i++;
|
|
444
589
|
}
|
|
590
|
+
if (!targetState) {
|
|
591
|
+
this.subtitleText.set('');
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
this._updateSubtitleText(tracks);
|
|
595
|
+
}
|
|
596
|
+
_positionSubtitleCues(track) {
|
|
597
|
+
if (!this._positionedSubtitleTracks.has(track) && typeof track.addEventListener === 'function') {
|
|
598
|
+
track.addEventListener('cuechange', () => {
|
|
599
|
+
this._positionSubtitleCues(track);
|
|
600
|
+
this._updateSubtitleText(this.videoElement()?.nativeElement.textTracks);
|
|
601
|
+
});
|
|
602
|
+
this._positionedSubtitleTracks.add(track);
|
|
603
|
+
}
|
|
604
|
+
const cues = track.activeCues;
|
|
605
|
+
if (!cues) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
let i = 0;
|
|
609
|
+
while (i < cues.length) {
|
|
610
|
+
const cue = cues[i];
|
|
611
|
+
if (typeof VTTCue !== 'undefined' && cue instanceof VTTCue) {
|
|
612
|
+
cue.line = 0;
|
|
613
|
+
}
|
|
614
|
+
i++;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
_updateSubtitleText(tracks) {
|
|
618
|
+
if (!tracks) {
|
|
619
|
+
this.subtitleText.set('');
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
let i = 0;
|
|
623
|
+
while (i < tracks.length) {
|
|
624
|
+
const cues = tracks[i].activeCues;
|
|
625
|
+
if (cues && cues.length > 0) {
|
|
626
|
+
const cue = cues[0];
|
|
627
|
+
if ('text' in cue && typeof cue.text === 'string') {
|
|
628
|
+
this.subtitleText.set(cue.text);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
i++;
|
|
633
|
+
}
|
|
634
|
+
this.subtitleText.set('');
|
|
445
635
|
}
|
|
446
636
|
// Cập nhật trạng thái khi video phát/dừng
|
|
447
637
|
onPlayStateChange(playing) {
|
|
448
638
|
this.isPlaying.set(playing);
|
|
449
639
|
this.zPlay.emit(playing);
|
|
640
|
+
if (!playing) {
|
|
641
|
+
const video = this.videoElement()?.nativeElement;
|
|
642
|
+
this._stopPlaybackProgressLoop();
|
|
643
|
+
this._syncPlaybackProgress(video?.currentTime ?? this.currentTime());
|
|
644
|
+
this._resetControlsTimer();
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
this._startPlaybackProgressLoop();
|
|
450
648
|
this._resetControlsTimer();
|
|
451
649
|
}
|
|
452
650
|
// Cập nhật mốc thời gian phát
|
|
@@ -457,6 +655,7 @@ class ZMediaPlayerComponent {
|
|
|
457
655
|
}
|
|
458
656
|
this.currentTime.set(video.currentTime);
|
|
459
657
|
this.zTimeUpdate.emit(video.currentTime);
|
|
658
|
+
this._syncPlaybackProgress(video.currentTime);
|
|
460
659
|
this.updateBufferedProgress();
|
|
461
660
|
}
|
|
462
661
|
onDurationChange() {
|
|
@@ -465,9 +664,24 @@ class ZMediaPlayerComponent {
|
|
|
465
664
|
return;
|
|
466
665
|
}
|
|
467
666
|
this.duration.set(video.duration);
|
|
667
|
+
this._syncPlaybackProgress(video.currentTime);
|
|
668
|
+
this.updateBufferedProgress();
|
|
669
|
+
}
|
|
670
|
+
onLoadedData() {
|
|
671
|
+
if (this.zSubtitles().some(subtitle => subtitle.default)) {
|
|
672
|
+
this.captionsEnabled.set(true);
|
|
673
|
+
}
|
|
674
|
+
this.zLoaded.emit();
|
|
675
|
+
}
|
|
676
|
+
onVideoError() {
|
|
677
|
+
const video = this.videoElement()?.nativeElement;
|
|
678
|
+
this.zError.emit(video?.error ? new Error(video.error.message || 'Media playback error') : null);
|
|
468
679
|
}
|
|
469
680
|
onSeeking(seeking) {
|
|
470
681
|
this.isBuffering.set(seeking);
|
|
682
|
+
if (!seeking) {
|
|
683
|
+
this.updateBufferedProgress();
|
|
684
|
+
}
|
|
471
685
|
}
|
|
472
686
|
updateBufferedProgress() {
|
|
473
687
|
const video = this.videoElement()?.nativeElement;
|
|
@@ -475,7 +689,56 @@ class ZMediaPlayerComponent {
|
|
|
475
689
|
return;
|
|
476
690
|
}
|
|
477
691
|
const end = video.buffered.end(video.buffered.length - 1);
|
|
478
|
-
this.
|
|
692
|
+
this._setBufferedTarget((end / this.duration()) * 100);
|
|
693
|
+
}
|
|
694
|
+
_setBufferedTarget(target) {
|
|
695
|
+
this._bufferedTarget = Math.min(100, Math.max(0, target));
|
|
696
|
+
if (typeof requestAnimationFrame === 'undefined' || this._bufferAnimationFrame !== null) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const animate = () => {
|
|
700
|
+
const current = this.bufferedProgress();
|
|
701
|
+
const distance = this._bufferedTarget - current;
|
|
702
|
+
if (Math.abs(distance) < 0.1) {
|
|
703
|
+
this.bufferedProgress.set(this._bufferedTarget);
|
|
704
|
+
this._bufferAnimationFrame = null;
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
this.bufferedProgress.set(current + distance * 0.08);
|
|
708
|
+
this._bufferAnimationFrame = requestAnimationFrame(animate);
|
|
709
|
+
};
|
|
710
|
+
this._bufferAnimationFrame = requestAnimationFrame(animate);
|
|
711
|
+
}
|
|
712
|
+
_startPlaybackProgressLoop() {
|
|
713
|
+
if (typeof requestAnimationFrame === 'undefined' || this._playbackAnimationFrame !== null) {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const animate = () => {
|
|
717
|
+
const video = this.videoElement()?.nativeElement;
|
|
718
|
+
if (!video || video.paused || video.ended) {
|
|
719
|
+
this._playbackAnimationFrame = null;
|
|
720
|
+
this._syncPlaybackProgress(video?.currentTime ?? this.currentTime());
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
this._syncPlaybackProgress(video.currentTime);
|
|
724
|
+
this._playbackAnimationFrame = requestAnimationFrame(animate);
|
|
725
|
+
};
|
|
726
|
+
this._playbackAnimationFrame = requestAnimationFrame(animate);
|
|
727
|
+
}
|
|
728
|
+
_stopPlaybackProgressLoop() {
|
|
729
|
+
if (this._playbackAnimationFrame === null) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
cancelAnimationFrame(this._playbackAnimationFrame);
|
|
733
|
+
this._playbackAnimationFrame = null;
|
|
734
|
+
}
|
|
735
|
+
_syncPlaybackProgress(time) {
|
|
736
|
+
const dur = this.duration();
|
|
737
|
+
if (!Number.isFinite(dur) || dur <= 0 || !Number.isFinite(time)) {
|
|
738
|
+
this.progress.set(0);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
this.progress.set(Math.min(100, Math.max(0, (time / dur) * 100)));
|
|
479
742
|
}
|
|
480
743
|
// Bắt đầu kéo thanh timeline phát
|
|
481
744
|
onTimelineDragStart(event) {
|
|
@@ -504,11 +767,66 @@ class ZMediaPlayerComponent {
|
|
|
504
767
|
this.hoverX.set(offsetX);
|
|
505
768
|
const ratio = Math.max(0, Math.min(1, offsetX / rect.width));
|
|
506
769
|
const hoverTime = ratio * this.duration();
|
|
770
|
+
this.hoverTime.set(hoverTime);
|
|
507
771
|
this.hoverTimeLabel.set(this._formatTime(hoverTime));
|
|
508
772
|
}
|
|
509
773
|
onTimelineHoverEnd() {
|
|
774
|
+
this.hoverTime.set(null);
|
|
510
775
|
this.hoverTimeLabel.set(null);
|
|
511
776
|
}
|
|
777
|
+
// Tải và phân tích file WebVTT storyboard cho preview ảnh nhỏ khi hover timeline
|
|
778
|
+
async _loadThumbnailVtt(vttUrl) {
|
|
779
|
+
try {
|
|
780
|
+
const response = await fetch(vttUrl);
|
|
781
|
+
if (!response.ok) {
|
|
782
|
+
throw new Error(`Failed to fetch thumbnails VTT: ${response.status}`);
|
|
783
|
+
}
|
|
784
|
+
const text = await response.text();
|
|
785
|
+
// `response.url` is always absolute (resolves relative input against the document, follows redirects),
|
|
786
|
+
// unlike `vttUrl` which callers may pass as a root-relative path.
|
|
787
|
+
this._thumbnailCues.set(this._parseThumbnailVtt(text, response.url));
|
|
788
|
+
}
|
|
789
|
+
catch (err) {
|
|
790
|
+
console.warn('Unable to load media player thumbnails VTT: ', err);
|
|
791
|
+
this._thumbnailCues.set([]);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
// Phân tích nội dung VTT thành danh sách cue { start, end, src, x, y, w, h }
|
|
795
|
+
_parseThumbnailVtt(text, baseUrl) {
|
|
796
|
+
const timeRangePattern = /(\d{2}:\d{2}:\d{2}[.,]\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2}[.,]\d{3})/;
|
|
797
|
+
const lines = text.split(/\r?\n/);
|
|
798
|
+
const cues = [];
|
|
799
|
+
for (let i = 0; i < lines.length; i++) {
|
|
800
|
+
const match = timeRangePattern.exec(lines[i]);
|
|
801
|
+
if (!match) {
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const imageLine = lines[i + 1]?.trim();
|
|
805
|
+
if (!imageLine) {
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
const [imageRef, xywhRef] = imageLine.split('#xywh=');
|
|
809
|
+
const xywh = xywhRef?.split(',').map(Number);
|
|
810
|
+
if (!imageRef || !xywh || xywh.length !== 4 || xywh.some(Number.isNaN)) {
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
cues.push({
|
|
814
|
+
start: this._parseVttTimestamp(match[1]),
|
|
815
|
+
end: this._parseVttTimestamp(match[2]),
|
|
816
|
+
src: new URL(imageRef, baseUrl).href,
|
|
817
|
+
x: xywh[0],
|
|
818
|
+
y: xywh[1],
|
|
819
|
+
w: xywh[2],
|
|
820
|
+
h: xywh[3],
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
return cues;
|
|
824
|
+
}
|
|
825
|
+
_parseVttTimestamp(value) {
|
|
826
|
+
const [hours, minutes, secondsAndMs] = value.split(':');
|
|
827
|
+
const [seconds, milliseconds] = secondsAndMs.split(/[.,]/);
|
|
828
|
+
return Number(hours) * 3600 + Number(minutes) * 60 + Number(seconds) + Number(milliseconds) / 1000;
|
|
829
|
+
}
|
|
512
830
|
_updateTimeFromCoords(event) {
|
|
513
831
|
const container = this.timelineContainer()?.nativeElement;
|
|
514
832
|
if (!container) {
|
|
@@ -536,8 +854,13 @@ class ZMediaPlayerComponent {
|
|
|
536
854
|
this._resetControlsTimer();
|
|
537
855
|
}
|
|
538
856
|
onMouseLeave() {
|
|
857
|
+
this.isPointerOver.set(false);
|
|
539
858
|
const video = this.videoElement()?.nativeElement;
|
|
540
859
|
if (video && !video.paused && !this.isDragging() && !this.showSettings()) {
|
|
860
|
+
if (this._controlsTimeout) {
|
|
861
|
+
clearTimeout(this._controlsTimeout);
|
|
862
|
+
this._controlsTimeout = null;
|
|
863
|
+
}
|
|
541
864
|
this.showControls.set(false);
|
|
542
865
|
}
|
|
543
866
|
}
|
|
@@ -550,37 +873,18 @@ class ZMediaPlayerComponent {
|
|
|
550
873
|
if (video && !video.paused && !this.showSettings() && !this.isDragging()) {
|
|
551
874
|
this._controlsTimeout = setTimeout(() => {
|
|
552
875
|
this.showControls.set(false);
|
|
553
|
-
},
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
// Xử lý sự kiện click phân biệt Single-click và Double-click
|
|
557
|
-
onVideoClick(event) {
|
|
558
|
-
if (this._clickTimeout) {
|
|
559
|
-
clearTimeout(this._clickTimeout);
|
|
560
|
-
this._clickTimeout = null;
|
|
561
|
-
this._handleVideoDoubleClick(event);
|
|
562
|
-
return;
|
|
876
|
+
}, 1000);
|
|
563
877
|
}
|
|
564
|
-
this._clickTimeout = setTimeout(() => {
|
|
565
|
-
this._clickTimeout = null;
|
|
566
|
-
this.togglePlay();
|
|
567
|
-
}, 250);
|
|
568
878
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const clickX = event.clientX - rect.left;
|
|
576
|
-
const isLeftHalf = clickX < rect.width / 2;
|
|
577
|
-
if (isLeftHalf) {
|
|
578
|
-
this.seekTo(Math.max(0, video.currentTime - 10));
|
|
579
|
-
this._triggerSkipOverlay(10, 'left');
|
|
879
|
+
// Click mặt video chỉ điều khiển play/pause; tua dùng các nút skip riêng để tránh click nhanh bị hiểu nhầm.
|
|
880
|
+
onVideoClick() {
|
|
881
|
+
// Nếu menu cài đặt đang mở, click vào video chỉ đóng menu, không toggle play/pause
|
|
882
|
+
if (this.showSettings()) {
|
|
883
|
+
this._settingsPopover()?.hide();
|
|
884
|
+
this.onSettingsHidden();
|
|
580
885
|
return;
|
|
581
886
|
}
|
|
582
|
-
this.
|
|
583
|
-
this._triggerSkipOverlay(10, 'right');
|
|
887
|
+
this.togglePlay();
|
|
584
888
|
}
|
|
585
889
|
// Kích hoạt overlay tua nhanh cộng dồn
|
|
586
890
|
triggerSkip(seconds, direction) {
|
|
@@ -688,7 +992,7 @@ class ZMediaPlayerComponent {
|
|
|
688
992
|
return `${minutes}:${formattedSeconds}`;
|
|
689
993
|
}
|
|
690
994
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ZMediaPlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
691
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: ZMediaPlayerComponent, isStandalone: true, selector: "z-media-player", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, zSrc: { classPropertyName: "zSrc", publicName: "zSrc", isSignal: true, isRequired: true, transformFunction: null }, zSources: { classPropertyName: "zSources", publicName: "zSources", isSignal: true, isRequired: false, transformFunction: null }, zPoster: { classPropertyName: "zPoster", publicName: "zPoster", isSignal: true, isRequired: false, transformFunction: null }, zAutoplay: { classPropertyName: "zAutoplay", publicName: "zAutoplay", isSignal: true, isRequired: false, transformFunction: null }, zLoop: { classPropertyName: "zLoop", publicName: "zLoop", isSignal: true, isRequired: false, transformFunction: null }, zMuted: { classPropertyName: "zMuted", publicName: "zMuted", isSignal: true, isRequired: false, transformFunction: null }, zVolume: { classPropertyName: "zVolume", publicName: "zVolume", isSignal: true, isRequired: false, transformFunction: null }, zSubtitles: { classPropertyName: "zSubtitles", publicName: "zSubtitles", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { zPlay: "zPlay", zTimeUpdate: "zTimeUpdate", zVolumeChange: "zVolumeChange", zFullscreenChange: "zFullscreenChange", zTheaterModeChange: "zTheaterModeChange", zMiniplayerChange: "zMiniplayerChange", zQualityChange: "zQualityChange" }, host: { listeners: { "keydown": "onKeyDown($event)", "mousemove": "onMouseMove()", "mouseleave": "onMouseLeave()", "document:fullscreenchange": "onFullscreenStateChange()" }, properties: { "class": "zClasses()", "attr.tabindex": "0" } }, viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoElement"], descendants: true, isSignal: true }, { propertyName: "timelineContainer", first: true, predicate: ["timelineContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Th\u1EBB video ch\u00EDnh ph\u00E1t ngu\u1ED3n zSrc -->\n<video\n #videoElement\n class=\"h-full w-full cursor-pointer object-contain\"\n [src]=\"effectiveSrc()\"\n [poster]=\"zPoster()\"\n [loop]=\"zLoop()\"\n [autoplay]=\"zAutoplay()\"\n playsinline\n webkit-playsinline\n (click)=\"onVideoClick($event)\"\n (play)=\"onPlayStateChange(true)\"\n (pause)=\"onPlayStateChange(false)\"\n (timeupdate)=\"onTimeUpdate()\"\n (durationchange)=\"onDurationChange()\"\n (seeking)=\"onSeeking(true)\"\n (seeked)=\"onSeeking(false)\"\n (waiting)=\"onSeeking(true)\"\n (playing)=\"onSeeking(false)\"\n (enterpictureinpicture)=\"onEnterPiP()\"\n (leavepictureinpicture)=\"onLeavePiP()\"\n (webkitpresentationmodechanged)=\"onWebkitPresentationModeChanged()\">\n @for (sub of zSubtitles(); track sub.src) {\n <track\n [src]=\"sub.src\"\n kind=\"subtitles\"\n [srclang]=\"sub.srclang\"\n [label]=\"sub.label\"\n [default]=\"sub.default || false\" />\n }\n</video>\n\n<!-- L\u1EDBp ph\u1EE7 v\u00F2ng xoay loading khi video \u0111ang buffer -->\n@if (isBuffering()) {\n <div class=\"pointer-events-none absolute inset-0 z-[10] flex items-center justify-center bg-black/20\">\n <div class=\"size-14 animate-spin rounded-full border-4 border-white/20 border-t-red-600\"></div>\n </div>\n}\n\n<!-- Hi\u1EC7u \u1EE9ng nh\u1EA5p nh\u00E1y Play/Pause l\u1EDBn gi\u1EEFa m\u00E0n h\u00ECnh t\u01B0\u01A1ng t\u1EF1 YouTube -->\n@if (playPauseFlash()) {\n <div class=\"pointer-events-none absolute inset-0 z-[11] flex items-center justify-center\">\n <div\n class=\"z-flash-animation flex size-14 items-center justify-center rounded-full bg-black/60 text-white sm:size-20\">\n <z-icon\n [zType]=\"playPauseFlash() === 'play' ? 'lucidePlay' : 'lucidePause'\"\n zSize=\"24\"\n [zStrokeWidth]=\"2\"\n [class.translate-x-[2px]]=\"playPauseFlash() === 'play'\" />\n </div>\n </div>\n}\n\n<!-- L\u1EDBp ph\u1EE7 tua nhanh nh\u1EA3y s\u1ED1 c\u1ED9ng d\u1ED3n Tr\u00E1i (YouTube style) -->\n@if (skipOverlayValue() > 0 && skipOverlayDirection() === 'left') {\n <div class=\"z-double-tap-ripple-left\" [class.z-skip-pulse-alt]=\"skipOverlayPulse() % 2 === 1\">\n <div class=\"z-skip-overlay-content\">\n <span class=\"z-skip-overlay-number\">- {{ skipOverlayValue() }}</span>\n <span class=\"z-skip-overlay-arrows z-chevron-slide-left\">\n <z-icon zType=\"lucideChevronsLeft\" zSize=\"26\" [zStrokeWidth]=\"2.4\" />\n </span>\n </div>\n </div>\n}\n\n<!-- L\u1EDBp ph\u1EE7 tua nhanh nh\u1EA3y s\u1ED1 c\u1ED9ng d\u1ED3n Ph\u1EA3i (YouTube style) -->\n@if (skipOverlayValue() > 0 && skipOverlayDirection() === 'right') {\n <div class=\"z-double-tap-ripple-right\" [class.z-skip-pulse-alt]=\"skipOverlayPulse() % 2 === 1\">\n <div class=\"z-skip-overlay-content\">\n <span class=\"z-skip-overlay-arrows z-chevron-slide-right\">\n <z-icon zType=\"lucideChevronsRight\" zSize=\"26\" [zStrokeWidth]=\"2.4\" />\n </span>\n <span class=\"z-skip-overlay-number\">+ {{ skipOverlayValue() }}</span>\n </div>\n </div>\n}\n\n<!-- L\u1EDBp ph\u1EE7 ch\u1EE9a thanh \u0111i\u1EC1u khi\u1EC3n (Bottom controls panel) -->\n<div\n class=\"pointer-events-none absolute inset-x-0 bottom-0 z-[12] flex flex-col bg-gradient-to-t from-black/85 via-black/40 to-transparent p-3 pt-12 transition-opacity duration-300 select-none\"\n [class.opacity-0]=\"!showControls()\"\n [class.invisible]=\"!showControls()\">\n <!-- Thanh Timeline (Progress Bar) t\u00F9y ch\u1EC9nh ki\u1EC3u YouTube -->\n <div\n #timelineContainer\n id=\"z_media_player_timeline\"\n class=\"group/timeline pointer-events-auto relative mb-3 flex h-1 w-full cursor-pointer items-center transition-all hover:h-1.5\"\n (mousedown)=\"onTimelineDragStart($event)\"\n (mousemove)=\"onTimelineHover($event)\"\n (mouseleave)=\"onTimelineHoverEnd()\">\n <!-- Thanh n\u1EC1n x\u00E1m c\u1EE7a thanh timeline -->\n <div\n class=\"absolute inset-x-0 h-1 overflow-hidden rounded-full bg-white/25 transition-all group-hover/timeline:h-1.5\">\n <!-- Ti\u1EBFn tr\u00ECnh t\u1EA3i buffer c\u1EE7a video -->\n <div class=\"absolute top-0 left-0 h-full rounded-full bg-white/30\" [style.width.%]=\"bufferedProgress()\"></div>\n <!-- Ti\u1EBFn tr\u00ECnh ph\u00E1t hi\u1EC7n t\u1EA1i c\u1EE7a video -->\n <div class=\"absolute top-0 left-0 h-full rounded-full bg-red-600\" [style.width.%]=\"progress()\"></div>\n </div>\n\n <!-- \u0110\u1EA7u k\u00E9o timeline (Thumb) \u0111\u1ECF ch\u1EC9 hi\u1EC3n th\u1ECB khi hover ho\u1EB7c k\u00E9o -->\n <div\n class=\"bg-red-650 pointer-events-none absolute size-3.5 -translate-x-1/2 scale-0 transform rounded-full opacity-0 transition-opacity duration-150 group-hover/timeline:scale-100 group-hover/timeline:opacity-100\"\n [style.left.%]=\"progress()\"></div>\n\n <!-- Tooltip hi\u1EC3n th\u1ECB m\u1ED1c th\u1EDDi gian khi hover -->\n @if (hoverTimeLabel()) {\n <div\n class=\"pointer-events-none absolute bottom-4 z-[20] -translate-x-1/2 rounded-xs bg-zinc-950/70 px-2 py-1 font-mono text-[11px] leading-none whitespace-nowrap text-zinc-100 shadow-[0_12px_24px_-10px_rgba(0,0,0,0.85)] backdrop-blur-2xl\"\n [style.left.px]=\"hoverX()\">\n {{ hoverTimeLabel() }}\n </div>\n }\n </div>\n\n <!-- H\u00E0ng ch\u1EE9a c\u00E1c n\u00FAt b\u1EA5m \u0111i\u1EC1u khi\u1EC3n ch\u1EE9c n\u0103ng -->\n <div class=\"pointer-events-auto flex items-center justify-between text-sm text-white\">\n <!-- C\u00E1c n\u00FAt \u0111i\u1EC1u khi\u1EC3n ph\u00EDa b\u00EAn tr\u00E1i -->\n <div class=\"flex items-center gap-2\">\n <!-- N\u00FAt Ph\u00E1t / T\u1EA1m d\u1EEBng -->\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_play_btn\"\n class=\"mr-1 flex size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none\"\n [title]=\"(isPlaying() ? 'i18n_z_ui_media_player_pause' : 'i18n_z_ui_media_player_play') | translate\"\n (click)=\"togglePlay()\">\n <z-icon [zType]=\"isPlaying() ? 'lucidePause' : 'lucidePlay'\" zSize=\"20\" [zStrokeWidth]=\"2\" />\n </button>\n\n <!-- N\u00FAt Tua l\u00F9i 5s -->\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_rewind_5_btn\"\n class=\"hidden size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none sm:flex\"\n title=\"Tua l\u00F9i 5 gi\u00E2y (Arrow Left)\"\n (click)=\"triggerSkip(5, 'left')\">\n <div class=\"relative flex items-center justify-center\">\n <z-icon zType=\"lucideRotateCcw\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n <span class=\"pointer-events-none absolute mt-[1px] scale-75 text-[8px] font-bold text-white select-none\">\n 5\n </span>\n </div>\n </button>\n\n <!-- N\u00FAt Tua ti\u1EBFn 5s -->\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_forward_5_btn\"\n class=\"mr-1 hidden size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none sm:flex\"\n title=\"Tua ti\u1EBFn 5 gi\u00E2y (Arrow Right)\"\n (click)=\"triggerSkip(5, 'right')\">\n <div class=\"relative flex items-center justify-center\">\n <z-icon zType=\"lucideRotateCw\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n <span class=\"pointer-events-none absolute mt-[1px] scale-75 text-[8px] font-bold text-white select-none\">\n 5\n </span>\n </div>\n </button>\n\n <!-- T\u1ED5 h\u1EE3p n\u00FAt Mute v\u00E0 Slider \u00E2m l\u01B0\u1EE3ng tr\u01B0\u1EE3t ngang -->\n <div class=\"group/volume hidden items-center gap-1.5 sm:flex\">\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_mute_btn\"\n class=\"flex size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none\"\n [title]=\"(isMuted() ? 'i18n_z_ui_media_player_unmute' : 'i18n_z_ui_media_player_mute') | translate\"\n (click)=\"toggleMute()\">\n <z-icon\n [zType]=\"isMuted() ? 'lucideVolumeX' : volume() < 0.5 ? 'lucideVolume1' : 'lucideVolume2'\"\n zSize=\"20\"\n [zStrokeWidth]=\"2\" />\n </button>\n <!-- Thanh tr\u01B0\u1EE3t \u00E2m l\u01B0\u1EE3ng tr\u01B0\u1EE3t ngang m\u1EDF r\u1ED9ng khi hover -->\n <input\n type=\"range\"\n id=\"z_media_player_volume_slider\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n [ngModel]=\"volume()\"\n (ngModelChange)=\"onVolumeChange($event)\"\n class=\"accent-red-650 h-1 w-0 cursor-pointer rounded-lg bg-white/20 opacity-0 transition-all duration-200 outline-none group-hover/volume:w-16 group-hover/volume:opacity-100 focus-visible:w-16 focus-visible:opacity-100\" />\n </div>\n\n <!-- Hi\u1EC3n th\u1ECB th\u1EDDi gian ph\u00E1t (currentTime / duration) -->\n <div class=\"pl-1 font-mono text-xs text-white/90 select-none\">\n <span>{{ currentTimeLabel() }}</span>\n <span class=\"mx-1 text-white/40\">/</span>\n <span class=\"text-white/60\">{{ durationLabel() }}</span>\n </div>\n </div>\n\n <!-- C\u00E1c n\u00FAt \u0111i\u1EC1u khi\u1EC3n ph\u00EDa b\u00EAn ph\u1EA3i -->\n <div class=\"flex items-center gap-1.5\">\n <!-- N\u00FAt m\u1EDF tr\u00ECnh \u0111\u01A1n C\u00E0i \u0111\u1EB7t b\u1EB1ng ZPopoverDirective -->\n @if (isFullscreen()) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_settings_btn\"\n class=\"flex size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none\"\n [title]=\"'i18n_z_ui_media_player_settings' | translate\"\n (click)=\"toggleInlineSettings()\">\n <z-icon zType=\"lucideSettings\" zSize=\"20\" [zStrokeWidth]=\"2\" />\n </button>\n } @else {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_settings_btn\"\n class=\"flex size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none\"\n [title]=\"'i18n_z_ui_media_player_settings' | translate\"\n z-popover\n [zPopoverContent]=\"speedMenuTemplate\"\n zPosition=\"top-right\"\n zTrigger=\"click\"\n [zOffset]=\"6\"\n zClass=\"z-media-player-popover\"\n (zShow)=\"showSettings.set(true); onMouseMove()\"\n (zHide)=\"onSettingsHidden()\">\n <z-icon zType=\"lucideSettings\" zSize=\"20\" [zStrokeWidth]=\"2\" />\n </button>\n }\n\n <!-- N\u00FAt Miniplayer (Picture-in-Picture) - \u1EA8n ho\u1EB7c disable n\u1EBFu browser ko h\u1ED7 tr\u1EE3 -->\n @if (isPiPSupported()) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_mini_btn\"\n class=\"hidden size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none sm:flex\"\n [title]=\"'i18n_z_ui_media_player_miniplayer' | translate\"\n (click)=\"toggleMiniplayer()\">\n <z-icon zType=\"lucideTv\" zSize=\"20\" [zStrokeWidth]=\"2\" />\n </button>\n }\n\n <!-- N\u00FAt b\u1EADt/t\u1EAFt ch\u1EBF \u0111\u1ED9 r\u1EA1p chi\u1EBFu phim (Theater Mode) -->\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n id=\"z_media_player_theater_btn\"\n class=\"flex hidden size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none md:flex\"\n [title]=\"\n (isTheaterMode() ? 'i18n_z_ui_media_player_default_mode' : 'i18n_z_ui_media_player_theater_mode') | translate\n \"\n (click)=\"toggleTheaterMode()\">\n <z-icon\n [zType]=\"isTheaterMode() ? 'lucideSquare' : 'lucideRectangleHorizontal'\"\n zSize=\"20\"\n [zStrokeWidth]=\"2\" />\n </button>\n\n <!-- N\u00FAt b\u1EADt/t\u1EAFt ch\u1EBF \u0111\u1ED9 to\u00E0n m\u00E0n h\u00ECnh (Fullscreen) -->\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_full_btn\"\n class=\"flex size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none\"\n [title]=\"\n (isFullscreen() ? 'i18n_z_ui_media_player_exit_fullscreen' : 'i18n_z_ui_media_player_fullscreen') | translate\n \"\n (click)=\"toggleFullscreen()\">\n <z-icon [zType]=\"isFullscreen() ? 'lucideMinimize' : 'lucideMaximize'\" zSize=\"20\" [zStrokeWidth]=\"2\" />\n </button>\n </div>\n </div>\n</div>\n\n@if (isFullscreen() && showSettings()) {\n <div class=\"z-media-player-popover z-media-player-inline-settings\">\n <ng-container [ngTemplateOutlet]=\"speedMenuTemplate\" />\n </div>\n}\n\n<!-- Template c\u1EA5u h\u00ECnh Popover c\u00E0i \u0111\u1EB7t s\u1EED d\u1EE5ng ng-template -->\n<ng-template #speedMenuTemplate>\n <div\n class=\"z-media-player-settings flex w-44 flex-col px-1 py-[4px] text-zinc-200\"\n [class.z-main-panel]=\"settingsPanel() === 'main'\"\n [class.z-sub-panel]=\"settingsPanel() !== 'main'\">\n @if (settingsPanel() === 'main') {\n <div class=\"z-media-player-settings-panel flex flex-col\">\n @if (qualitySources().length > 1) {\n <button type=\"button\" class=\"z-media-player-settings-item\" (click)=\"openSettingsPanel('quality')\">\n <span class=\"min-w-0 flex-1 truncate whitespace-nowrap\">\n {{ 'i18n_z_ui_media_player_quality' | translate }}\n </span>\n <span class=\"flex max-w-[5.5rem] min-w-0 items-center gap-1.5 text-zinc-400\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ activeQualityLabel() }}</span>\n <z-icon zType=\"lucideChevronRight\" zSize=\"13\" class=\"shrink-0\" />\n </span>\n </button>\n }\n <button type=\"button\" class=\"z-media-player-settings-item\" (click)=\"openSettingsPanel('speed')\">\n <span class=\"min-w-0 flex-1 truncate whitespace-nowrap\">\n {{ 'i18n_z_ui_media_player_speed' | translate }}\n </span>\n <span class=\"flex max-w-[5.5rem] min-w-0 items-center gap-1.5 text-zinc-400\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">\n {{ playbackSpeed() === 1 ? ('i18n_z_ui_media_player_normal' | translate) : playbackSpeed() + 'x' }}\n </span>\n <z-icon zType=\"lucideChevronRight\" zSize=\"13\" class=\"shrink-0\" />\n </span>\n </button>\n </div>\n }\n\n @if (settingsPanel() === 'quality') {\n <div class=\"z-media-player-settings-panel flex flex-col\">\n <button\n type=\"button\"\n [title]=\"'i18n_z_ui_media_player_back' | translate\"\n class=\"z-media-player-settings-back\"\n (click)=\"closeSettingsPanel()\">\n <z-icon zType=\"lucideChevronLeft\" zSize=\"13\" class=\"shrink-0\" />\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ 'i18n_z_ui_media_player_quality' | translate }}</span>\n </button>\n @for (source of qualitySources(); track source.src) {\n <button\n type=\"button\"\n [id]=\"'z_media_player_quality_btn_' + source.label\"\n class=\"z-media-player-settings-item\"\n [class.z-active]=\"effectiveSrc() === source.src\"\n (click)=\"setQualitySource(source)\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ source.label }}</span>\n </button>\n }\n </div>\n }\n\n @if (settingsPanel() === 'speed') {\n <div class=\"z-media-player-settings-panel flex flex-col\">\n <button\n type=\"button\"\n [title]=\"'i18n_z_ui_media_player_back' | translate\"\n class=\"z-media-player-settings-back\"\n (click)=\"closeSettingsPanel()\">\n <z-icon zType=\"lucideChevronLeft\" zSize=\"13\" class=\"shrink-0\" />\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ 'i18n_z_ui_media_player_speed' | translate }}</span>\n </button>\n @for (speed of playbackSpeeds; track speed.value) {\n <button\n type=\"button\"\n [id]=\"'z_media_player_speed_btn_' + speed.value\"\n class=\"z-media-player-settings-item\"\n [class.z-active]=\"playbackSpeed() === speed.value\"\n (click)=\"setPlaybackSpeed(speed.value)\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ speed.label | translate }}</span>\n </button>\n }\n </div>\n }\n </div>\n</ng-template>\n", styles: ["z-media-player{display:block;width:100%;outline:none}z-media-player:focus,z-media-player:focus-visible{outline:none}z-media-player video::cue{background-color:#080808d9;color:#fff;font-family:inherit;font-size:16px;text-shadow:0 1px 2px rgba(0,0,0,.9);padding:4px 8px;border-radius:4px}z-media-player .z-flash-animation{animation:zUiFlashIcon .72s cubic-bezier(.2,.85,.25,1) forwards;will-change:opacity,transform,filter}z-media-player .z-flash-animation z-icon{width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center}z-media-player .z-flash-animation z-icon ng-icon,z-media-player .z-flash-animation z-icon svg{width:100%!important;height:100%!important}@media(min-width:640px){z-media-player .z-flash-animation z-icon{width:32px!important;height:32px!important}}@keyframes zUiFlashIcon{0%{filter:blur(2px);opacity:0;transform:scale(.72)}18%{filter:blur(0);opacity:.95;transform:scale(1)}58%{opacity:.9;transform:scale(1.04)}to{filter:blur(0);opacity:0;transform:scale(1.18)}}z-media-player .animate-fade-in{animation:zUiFadeIn .2s cubic-bezier(.16,1,.3,1)}@keyframes zUiFadeIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}z-media-player .z-double-tap-ripple-left,z-media-player .z-double-tap-ripple-right{position:absolute;top:0;bottom:0;width:35%;display:flex;align-items:center;z-index:11;pointer-events:none}z-media-player .z-double-tap-ripple-left:before,z-media-player .z-double-tap-ripple-right:before{position:absolute;inset:0;content:\"\";opacity:0;animation-duration:.72s;animation-fill-mode:forwards;animation-timing-function:cubic-bezier(.25,1,.5,1)}z-media-player .z-double-tap-ripple-left{left:0;justify-content:flex-start;padding-left:5%}z-media-player .z-double-tap-ripple-left:before{background:radial-gradient(circle at 0% 50%,#ffffff21,#fff0 70%);border-top-right-radius:100% 50%;border-bottom-right-radius:100% 50%;animation-name:zUiDoubleTapRippleLeft;transform-origin:left center}z-media-player .z-double-tap-ripple-left.z-skip-pulse-alt:before{animation-name:zUiDoubleTapRippleLeftAlt}z-media-player .z-double-tap-ripple-right{right:0;justify-content:flex-end;padding-right:5%}z-media-player .z-double-tap-ripple-right:before{background:radial-gradient(circle at 100% 50%,#ffffff21,#fff0 70%);border-top-left-radius:100% 50%;border-bottom-left-radius:100% 50%;animation-name:zUiDoubleTapRippleRight;transform-origin:right center}z-media-player .z-double-tap-ripple-right.z-skip-pulse-alt:before{animation-name:zUiDoubleTapRippleRightAlt}z-media-player .z-skip-overlay-content{position:relative;display:flex;align-items:center;justify-content:center;gap:6px;min-width:92px;color:#fff;font-family:inherit;font-size:18px;font-weight:700;line-height:1;text-shadow:0 2px 8px rgba(0,0,0,.75);-webkit-user-select:none;user-select:none}z-media-player .z-skip-overlay-number{min-width:42px;animation:zUiSkipNumberBump .22s cubic-bezier(.2,.85,.25,1) forwards;text-align:center;transform-origin:center}z-media-player .z-skip-pulse-alt .z-skip-overlay-number{animation-name:zUiSkipNumberBumpAlt}z-media-player .z-skip-overlay-arrows{display:inline-flex;align-items:center;color:#fffffff2;filter:drop-shadow(0 2px 6px rgba(0,0,0,.7))}@keyframes zUiDoubleTapRippleLeft{0%{opacity:0;transform:scaleX(.5);transform-origin:left center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:left center}}@keyframes zUiDoubleTapRippleLeftAlt{0%{opacity:0;transform:scaleX(.5);transform-origin:left center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:left center}}@keyframes zUiDoubleTapRippleRight{0%{opacity:0;transform:scaleX(.5);transform-origin:right center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:right center}}@keyframes zUiDoubleTapRippleRightAlt{0%{opacity:0;transform:scaleX(.5);transform-origin:right center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:right center}}z-media-player .z-chevron-slide-left{animation:zUiChevronSlideLeft .72s cubic-bezier(.25,1,.5,1) forwards}z-media-player .z-chevron-slide-right{animation:zUiChevronSlideRight .72s cubic-bezier(.25,1,.5,1) forwards}z-media-player .z-skip-pulse-alt .z-chevron-slide-left{animation-name:zUiChevronSlideLeftAlt}z-media-player .z-skip-pulse-alt .z-chevron-slide-right{animation-name:zUiChevronSlideRightAlt}@keyframes zUiSkipNumberBump{0%{opacity:.78;transform:scale(.92)}55%{opacity:1;transform:scale(1.12)}to{opacity:1;transform:scale(1)}}@keyframes zUiSkipNumberBumpAlt{0%{opacity:.78;transform:scale(.92)}55%{opacity:1;transform:scale(1.12)}to{opacity:1;transform:scale(1)}}@keyframes zUiChevronSlideLeft{0%{opacity:0;transform:translate(8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(-12px)}}@keyframes zUiChevronSlideRight{0%{opacity:0;transform:translate(-8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(12px)}}@keyframes zUiChevronSlideLeftAlt{0%{opacity:0;transform:translate(8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(-12px)}}@keyframes zUiChevronSlideRightAlt{0%{opacity:0;transform:translate(-8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(12px)}}z-media-player #z_media_player_volume_slider{-webkit-appearance:none;appearance:none;background:transparent}z-media-player #z_media_player_volume_slider::-webkit-slider-runnable-track{background:#ffffff40;height:3px;border-radius:9999px}z-media-player #z_media_player_volume_slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;background:#fff;height:10px;width:10px;border-radius:9999px;margin-top:-3.5px;transition:transform .15s ease}z-media-player #z_media_player_volume_slider:hover::-webkit-slider-thumb,z-media-player #z_media_player_volume_slider:focus-visible::-webkit-slider-thumb{transform:scale(1.3)}z-media-player #z_media_player_volume_slider::-moz-range-track{background:#ffffff40;height:3px;border-radius:9999px}z-media-player #z_media_player_volume_slider::-moz-range-thumb{background:#fff;height:10px;width:10px;border-radius:9999px;border:none;transition:transform .15s ease}z-media-player #z_media_player_volume_slider:hover::-moz-range-thumb,z-media-player #z_media_player_volume_slider:focus-visible::-moz-range-thumb{transform:scale(1.3)}.z-media-player-popover{backdrop-filter:blur(24px)!important;-webkit-backdrop-filter:blur(24px)!important;background-color:#0f0f0f99!important;border:1px solid rgba(255,255,255,.08)!important;box-shadow:0 15px 30px -10px #000c!important;border-radius:8px!important;padding:0!important;transform-origin:bottom right}.z-media-player-popover[data-state=open]{animation:zUiMediaSettingsPopoverIn .16s cubic-bezier(.2,.85,.25,1) forwards}.z-media-player-popover[data-state=closed]{animation:zUiMediaSettingsPopoverOut .16s cubic-bezier(.4,0,.2,1) forwards!important}.z-media-player-popover .bg-popover{background-color:transparent!important;color:#e4e4e7!important}.z-media-player-popover .z-media-player-settings{overflow:hidden;transform-origin:bottom right;animation:zUiMediaSettingsPanelIn .16s cubic-bezier(.2,.85,.25,1)}.z-media-player-popover .z-media-player-settings-panel{gap:4px;transform-origin:top right;animation:zUiMediaSettingsPanelIn .14s cubic-bezier(.2,.85,.25,1);will-change:opacity,transform}.z-media-player-popover .z-media-player-settings.z-main-panel .z-media-player-settings-panel{animation-name:zUiMediaSettingsPanelFromLeft}.z-media-player-popover .z-media-player-settings.z-sub-panel .z-media-player-settings-panel{animation-name:zUiMediaSettingsPanelFromRight}.z-media-player-popover .z-media-player-settings-item,.z-media-player-popover .z-media-player-settings-back{position:relative;display:flex;width:100%;min-width:0;height:32px;min-height:32px;cursor:pointer;align-items:center;border:0;border-radius:4px;background-color:transparent;padding:0 8px;color:#e4e4e7;font-size:12px;font-weight:500;line-height:1;outline:none;transition:background-color .12s ease,color .12s ease}.z-media-player-popover .z-media-player-settings-item{justify-content:space-between;gap:12px;text-align:left}.z-media-player-popover .z-media-player-settings-item:before{position:absolute;top:7px;bottom:7px;left:0;width:2px;border-radius:9999px;background-color:#fff;content:\"\";opacity:0;transform:scaleY(.5);transition:opacity .12s ease,transform .12s ease}.z-media-player-popover .z-media-player-settings-item.z-active{background-color:#ffffff14;color:#fff;font-weight:600}.z-media-player-popover .z-media-player-settings-item.z-active:before{opacity:1;transform:scaleY(1)}.z-media-player-popover .z-media-player-settings-back{justify-content:flex-start;gap:8px;color:#f4f4f5;font-weight:600}.z-media-player-popover .z-media-player-settings-item:hover,.z-media-player-popover .z-media-player-settings-back:hover,.z-media-player-popover .z-media-player-settings-item:focus-visible,.z-media-player-popover .z-media-player-settings-back:focus-visible{background-color:#ffffff1a}.z-media-player-inline-settings{position:absolute;right:12px;bottom:64px;z-index:30}@keyframes zUiMediaSettingsPopoverIn{0%{opacity:0;transform:translateY(6px) scale(.94)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes zUiMediaSettingsPopoverOut{0%{opacity:1;transform:translateY(0) scale(1)}to{opacity:0;transform:translateY(4px) scale(.96)}}@keyframes zUiMediaSettingsPanelIn{0%{opacity:0;transform:translateY(3px)}to{opacity:1;transform:translateY(0)}}@keyframes zUiMediaSettingsPanelFromRight{0%{opacity:0;transform:translate(8px)}to{opacity:1;transform:translate(0)}}@keyframes zUiMediaSettingsPanelFromLeft{0%{opacity:0;transform:translate(-5px)}to{opacity:1;transform:translate(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.RangeValueAccessor, selector: "input[type=range]:not([ngNoCva])[formControlName],input[type=range]:not([ngNoCva])[formControl],input[type=range]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ZIconComponent, selector: "z-icon, [z-icon]", inputs: ["class", "zType", "zAnimatedType", "zAnimate", "zAnimationTrigger", "zSize", "zStrokeWidth", "zSvg"] }, { kind: "directive", type: ZPopoverDirective, selector: "[z-popover]", inputs: ["zPopoverContent", "zPosition", "zTrigger", "zPopoverTrigger", "zClass", "zShowDelay", "zHideDelay", "zDisabled", "zOffset", "zPopoverWidth", "zTriggerRef", "zManualClose", "zOutsideClickClose", "zScrollClose", "zSticky", "zShowArrow", "zKeyboardSafe"], outputs: ["zShow", "zHide", "zHideStart", "zControl", "zPositionChange", "zOutsideClick"], exportAs: ["zPopover"] }, { kind: "component", type: ZButtonComponent, selector: "z-button, button[z-button], a[z-button]", inputs: ["class", "zType", "zSize", "zShape", "zLabel", "zLoading", "zDisabled", "zTypeIcon", "zAnimatedTypeIcon", "zAnimateIcon", "zAnimationTriggerIcon", "zSizeIcon", "zStrokeWidthIcon", "zWave"], exportAs: ["zButton"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
995
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: ZMediaPlayerComponent, isStandalone: true, selector: "z-media-player", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, zSrc: { classPropertyName: "zSrc", publicName: "zSrc", isSignal: true, isRequired: true, transformFunction: null }, zSources: { classPropertyName: "zSources", publicName: "zSources", isSignal: true, isRequired: false, transformFunction: null }, zPoster: { classPropertyName: "zPoster", publicName: "zPoster", isSignal: true, isRequired: false, transformFunction: null }, zAutoplay: { classPropertyName: "zAutoplay", publicName: "zAutoplay", isSignal: true, isRequired: false, transformFunction: null }, zLoop: { classPropertyName: "zLoop", publicName: "zLoop", isSignal: true, isRequired: false, transformFunction: null }, zMuted: { classPropertyName: "zMuted", publicName: "zMuted", isSignal: true, isRequired: false, transformFunction: null }, zVolume: { classPropertyName: "zVolume", publicName: "zVolume", isSignal: true, isRequired: false, transformFunction: null }, zSubtitles: { classPropertyName: "zSubtitles", publicName: "zSubtitles", isSignal: true, isRequired: false, transformFunction: null }, zThumbnailsVtt: { classPropertyName: "zThumbnailsVtt", publicName: "zThumbnailsVtt", isSignal: true, isRequired: false, transformFunction: null }, zControls: { classPropertyName: "zControls", publicName: "zControls", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { zLoaded: "zLoaded", zError: "zError", zPlay: "zPlay", zTimeUpdate: "zTimeUpdate", zVolumeChange: "zVolumeChange", zFullscreenChange: "zFullscreenChange", zTheaterModeChange: "zTheaterModeChange", zMiniplayerChange: "zMiniplayerChange", zQualityChange: "zQualityChange" }, host: { listeners: { "keydown": "onKeyDown($event)", "mouseleave": "onMouseLeave()", "document:fullscreenchange": "onFullscreenStateChange()" }, properties: { "class": "zClasses()", "attr.tabindex": "0" } }, viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoElement"], descendants: true, isSignal: true }, { propertyName: "timelineContainer", first: true, predicate: ["timelineContainer"], descendants: true, isSignal: true }, { propertyName: "_settingsPopover", first: true, predicate: ZPopoverDirective, descendants: true, isSignal: true }], ngImport: i0, template: "@if (isYouTube()) {\n <!-- Nh\u00FAng iframe YouTube g\u1ED1c, d\u00F9ng control m\u1EB7c \u0111\u1ECBnh c\u1EE7a YouTube -->\n <iframe\n class=\"h-full w-full\"\n [src]=\"youtubeEmbedUrl()\"\n title=\"YouTube video player\"\n frameborder=\"0\"\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n allowfullscreen></iframe>\n} @else {\n <!-- Th\u1EBB video ch\u00EDnh ph\u00E1t ngu\u1ED3n zSrc -->\n <video\n #videoElement\n class=\"h-full w-full cursor-pointer object-cover\"\n [src]=\"effectiveSrc()\"\n [poster]=\"zPoster()\"\n [loop]=\"zLoop()\"\n [autoplay]=\"zAutoplay()\"\n playsinline\n webkit-playsinline\n (click)=\"onVideoClick()\"\n (play)=\"onPlayStateChange(true)\"\n (pause)=\"onPlayStateChange(false)\"\n (loadeddata)=\"onLoadedData()\"\n (error)=\"onVideoError()\"\n (timeupdate)=\"onTimeUpdate()\"\n (durationchange)=\"onDurationChange()\"\n (seeking)=\"onSeeking(true)\"\n (seeked)=\"onSeeking(false)\"\n (waiting)=\"onSeeking(true)\"\n (playing)=\"onSeeking(false)\"\n (enterpictureinpicture)=\"onEnterPiP()\"\n (leavepictureinpicture)=\"onLeavePiP()\"\n (webkitpresentationmodechanged)=\"onWebkitPresentationModeChanged()\">\n @for (sub of zSubtitles(); track sub.src) {\n <track\n [src]=\"sub.src\"\n kind=\"subtitles\"\n [srclang]=\"sub.srclang\"\n [label]=\"sub.label\"\n [default]=\"sub.default || false\" />\n }\n </video>\n\n @if (subtitleText()) {\n <div\n class=\"pointer-events-none absolute inset-x-3 z-[11] mx-auto w-fit max-w-[calc(100%-1.5rem)] rounded-sm bg-black/80 px-4 py-2 text-center text-base leading-6 whitespace-pre-line text-white shadow-sm transition-[bottom] duration-200 ease-out sm:text-lg sm:leading-7\"\n [class.bottom-16]=\"showControls()\"\n [class.bottom-2]=\"!showControls()\">\n {{ subtitleText() }}\n </div>\n }\n\n <!-- V\u00F2ng tr\u00F2n loading gi\u1EEFa m\u00E0n h\u00ECnh khi video \u0111ang buffer -->\n @if (isBuffering()) {\n <div class=\"pointer-events-none absolute inset-0 z-[13] flex items-center justify-center\">\n <span class=\"z-media-player-loading-pulse\"></span>\n </div>\n }\n\n <!-- Hi\u1EC7u \u1EE9ng nh\u1EA5p nh\u00E1y Play/Pause l\u1EDBn gi\u1EEFa m\u00E0n h\u00ECnh t\u01B0\u01A1ng t\u1EF1 YouTube -->\n <div class=\"pointer-events-none absolute inset-0 z-[11] flex items-center justify-center\">\n <div\n class=\"z-flash-animation flex size-10 scale-75 items-center justify-center rounded-full bg-black/60 text-white opacity-0\"\n [class.z-flash-active]=\"!!playPauseFlash()\">\n @if (playPauseFlash(); as flashType) {\n <z-icon\n [zType]=\"flashType === 'play' ? 'tablerPlayerPlayFill' : 'tablerPlayerPauseFill'\"\n zSize=\"28\"\n [zStrokeWidth]=\"2\"\n [class.translate-x-[2px]]=\"flashType === 'play'\" />\n }\n </div>\n </div>\n\n <!-- L\u1EDBp ph\u1EE7 tua nhanh nh\u1EA3y s\u1ED1 c\u1ED9ng d\u1ED3n Tr\u00E1i (YouTube style) -->\n @if (skipOverlayValue() > 0 && skipOverlayDirection() === 'left') {\n <div class=\"z-double-tap-ripple-left\" [class.z-skip-pulse-alt]=\"skipOverlayPulse() % 2 === 1\">\n <div class=\"z-skip-overlay-content\">\n <span class=\"z-skip-overlay-number\">- {{ skipOverlayValue() }}</span>\n <span class=\"z-skip-overlay-arrows z-chevron-slide-left\">\n <z-icon zType=\"tablerChevronsLeft\" zSize=\"26\" [zStrokeWidth]=\"2.4\" />\n </span>\n </div>\n </div>\n }\n\n <!-- L\u1EDBp ph\u1EE7 tua nhanh nh\u1EA3y s\u1ED1 c\u1ED9ng d\u1ED3n Ph\u1EA3i (YouTube style) -->\n @if (skipOverlayValue() > 0 && skipOverlayDirection() === 'right') {\n <div class=\"z-double-tap-ripple-right\" [class.z-skip-pulse-alt]=\"skipOverlayPulse() % 2 === 1\">\n <div class=\"z-skip-overlay-content\">\n <span class=\"z-skip-overlay-arrows z-chevron-slide-right\">\n <z-icon zType=\"tablerChevronsRight\" zSize=\"26\" [zStrokeWidth]=\"2.4\" />\n </span>\n <span class=\"z-skip-overlay-number\">+ {{ skipOverlayValue() }}</span>\n </div>\n </div>\n }\n\n <!-- L\u1EDBp ph\u1EE7 gradient ph\u1EE7 s\u00E1t to\u00E0n b\u1ED9 m\u00E9p d\u01B0\u1EDBi video -->\n <div\n class=\"pointer-events-none absolute inset-x-0 bottom-0 z-[10] h-28 bg-gradient-to-t from-black/55 via-black/25 to-transparent transition-opacity duration-200 ease-out\"\n [class.opacity-0]=\"!showControls()\"></div>\n\n <!-- Thanh \u0111i\u1EC1u khi\u1EC3n d\u1EA1ng rounded-sm n\u1ED5i ph\u00EDa tr\u00EAn \u0111\u00E1y video -->\n <div\n class=\"z-media-player-controls absolute inset-x-2 bottom-2 z-[12] transition-opacity duration-200 ease-out select-none sm:inset-x-3 sm:bottom-3\"\n [class.opacity-0]=\"!showControls()\"\n [class.pointer-events-auto]=\"showControls()\"\n [class.pointer-events-none]=\"!showControls()\">\n <div class=\"z-media-player-controls-surface relative z-[1] rounded-sm px-2 py-1.5 text-sm text-white\">\n <div class=\"flex items-center gap-1.5 sm:gap-2\">\n <!-- N\u00FAt Ph\u00E1t / T\u1EA1m d\u1EEBng -->\n @if (controls().play) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_play_btn\"\n class=\"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90\"\n [title]=\"(isPlaying() ? 'i18n_z_ui_media_player_pause' : 'i18n_z_ui_media_player_play') | translate\"\n (click)=\"togglePlay()\">\n <z-icon\n [zType]=\"isPlaying() ? 'tablerPlayerPauseFill' : 'tablerPlayerPlayFill'\"\n zSize=\"18\"\n [zStrokeWidth]=\"2\" />\n </button>\n }\n\n <!-- N\u00FAt Tua l\u00F9i / Tua ti\u1EBFn 5 gi\u00E2y -->\n @if (controls().skip) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_rewind_5_btn\"\n class=\"hidden size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90 sm:flex\"\n title=\"Tua l\u00F9i 5 gi\u00E2y (Arrow Left)\"\n (click)=\"triggerSkip(5, 'left')\">\n <z-icon zType=\"tablerRewindBackward5\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_forward_5_btn\"\n class=\"hidden size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90 sm:flex\"\n title=\"Tua ti\u1EBFn 5 gi\u00E2y (Arrow Right)\"\n (click)=\"triggerSkip(5, 'right')\">\n <z-icon zType=\"tablerRewindForward5\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n }\n\n <!-- T\u1ED5 h\u1EE3p n\u00FAt Mute v\u00E0 Slider \u00E2m l\u01B0\u1EE3ng tr\u01B0\u1EE3t ngang -->\n @if (controls().volume) {\n <div class=\"group/volume hidden shrink-0 items-center sm:flex\">\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_mute_btn\"\n class=\"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90\"\n [title]=\"(isMuted() ? 'i18n_z_ui_media_player_unmute' : 'i18n_z_ui_media_player_mute') | translate\"\n (click)=\"toggleMute()\">\n <z-icon\n [zType]=\"isMuted() ? 'tablerVolumeOff' : volume() < 0.5 ? 'tablerVolume2' : 'tablerVolume'\"\n zSize=\"18\"\n [zStrokeWidth]=\"2\" />\n </button>\n <!-- Thanh tr\u01B0\u1EE3t \u00E2m l\u01B0\u1EE3ng tr\u01B0\u1EE3t ngang m\u1EDF r\u1ED9ng khi hover -->\n <input\n type=\"range\"\n id=\"z_media_player_volume_slider\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n [ngModel]=\"volume()\"\n (ngModelChange)=\"onVolumeChange($event)\"\n class=\"accent-primary h-1 w-0 cursor-pointer rounded-lg bg-white/20 opacity-0 transition-all duration-200 outline-none group-hover/volume:w-16 group-hover/volume:opacity-100 focus-visible:w-16 focus-visible:opacity-100\" />\n </div>\n }\n\n <!-- Th\u1EDDi gian ph\u00E1t: g\u1ED9p chung \"\u0111\u00E3 ph\u00E1t / t\u1ED5ng th\u1EDDi l\u01B0\u1EE3ng\" th\u00E0nh m\u1ED9t nh\u00F3m duy nh\u1EA5t -->\n @if (controls().time) {\n <span class=\"shrink-0 rounded-full bg-black/30 px-2 py-0.5 font-mono text-xs whitespace-nowrap text-white/90\">\n {{ currentTimeLabel() }}\n <span class=\"text-white/50\">/</span>\n {{ durationLabel() }}\n </span>\n }\n\n <!-- Thanh Timeline (Progress Bar) t\u00F9y ch\u1EC9nh ki\u1EC3u YouTube, n\u1EB1m chung h\u00E0ng v\u1EDBi c\u00E1c n\u00FAt -->\n @if (controls().timeline) {\n <div\n #timelineContainer\n id=\"z_media_player_timeline\"\n class=\"group/timeline relative flex h-1 min-w-0 flex-1 cursor-pointer items-center\"\n (mousedown)=\"onTimelineDragStart($event)\"\n (mousemove)=\"onTimelineHover($event)\"\n (mouseleave)=\"onTimelineHoverEnd()\">\n <!-- Thanh n\u1EC1n x\u00E1m c\u1EE7a thanh timeline -->\n <div\n class=\"absolute inset-x-0 h-1 overflow-hidden rounded-full bg-white/25 transition-all group-hover/timeline:h-1.5\">\n <!-- Ti\u1EBFn tr\u00ECnh t\u1EA3i buffer c\u1EE7a video -->\n <div\n class=\"absolute top-0 left-0 h-full rounded-full bg-white/30\"\n [style.width.%]=\"bufferedProgress()\"></div>\n <!-- Ti\u1EBFn tr\u00ECnh ph\u00E1t hi\u1EC7n t\u1EA1i c\u1EE7a video -->\n <div class=\"bg-primary absolute top-0 left-0 h-full rounded-full\" [style.width.%]=\"progress()\"></div>\n </div>\n\n <!-- \u0110\u1EA7u k\u00E9o timeline (Thumb) \u0111\u1ECF ch\u1EC9 hi\u1EC3n th\u1ECB khi hover ho\u1EB7c k\u00E9o -->\n <div\n class=\"bg-primary pointer-events-none absolute size-3.5 -translate-x-1/2 scale-0 transform rounded-full opacity-0 transition-opacity duration-150 group-hover/timeline:scale-100 group-hover/timeline:opacity-100\"\n [style.left.%]=\"progress()\"></div>\n\n <!-- Preview \u1EA3nh nh\u1ECF storyboard khi hover timeline (ki\u1EC3u YouTube) -->\n @if (hoverThumbnailCue(); as cue) {\n <div\n class=\"pointer-events-none absolute z-[20] -translate-x-1/2 overflow-hidden rounded-md border-0 bg-black shadow-[0_12px_24px_-10px_rgba(0,0,0,0.85)]\"\n [style.left.px]=\"hoverX()\"\n [style.bottom.px]=\"24\"\n [style.width.px]=\"cue.w\"\n [style.height.px]=\"cue.h\">\n <div\n class=\"bg-no-repeat\"\n [style.width.px]=\"cue.w\"\n [style.height.px]=\"cue.h\"\n [style.background-image]=\"'url(' + cue.src + ')'\"\n [style.background-position]=\"'-' + cue.x + 'px -' + cue.y + 'px'\"></div>\n </div>\n }\n\n <!-- Tooltip hi\u1EC3n th\u1ECB m\u1ED1c th\u1EDDi gian khi hover -->\n @if (hoverTimeLabel()) {\n <div\n class=\"pointer-events-none absolute bottom-4 z-[20] -translate-x-1/2 rounded-xs bg-zinc-950/70 px-2 py-1 font-mono text-[11px] leading-none whitespace-nowrap text-zinc-100 shadow-[0_12px_24px_-10px_rgba(0,0,0,0.85)] backdrop-blur-2xl\"\n [style.left.px]=\"hoverX()\">\n {{ hoverTimeLabel() }}\n </div>\n }\n </div>\n }\n\n <!-- N\u00FAt m\u1EDF tr\u00ECnh \u0111\u01A1n C\u00E0i \u0111\u1EB7t b\u1EB1ng ZPopoverDirective -->\n @if (controls().settings) {\n @if (isFullscreen()) {\n <div class=\"relative shrink-0\">\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_settings_btn\"\n class=\"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:border-transparent focus-visible:ring-0 focus-visible:outline-none active:scale-90\"\n [class.bg-white/15]=\"showSettings()\"\n [title]=\"'i18n_z_ui_media_player_settings' | translate\"\n (click)=\"toggleInlineSettings()\">\n <z-icon zType=\"tablerSettings\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n <!-- Popover c\u00E0i \u0111\u1EB7t neo tr\u1EF1c ti\u1EBFp v\u00E0o n\u00FAt, v\u00EC overlay CDK kh\u00F4ng hi\u1EC3n th\u1ECB \u0111\u01B0\u1EE3c trong ch\u1EBF \u0111\u1ED9 Fullscreen g\u1ED1c -->\n @if (showSettings()) {\n <div class=\"z-media-player-popover absolute bottom-full left-1/2 mb-2 -translate-x-1/2\">\n <ng-container [ngTemplateOutlet]=\"speedMenuTemplate\" />\n </div>\n }\n </div>\n } @else {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_settings_btn\"\n class=\"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:border-transparent focus-visible:ring-0 focus-visible:outline-none active:scale-90 aria-expanded:!bg-white/15 aria-expanded:!text-white\"\n [title]=\"'i18n_z_ui_media_player_settings' | translate\"\n z-popover\n [zPopoverContent]=\"speedMenuTemplate\"\n zPosition=\"top\"\n zTrigger=\"click\"\n [zOffset]=\"9\"\n zClass=\"z-media-player-popover\"\n (zShow)=\"showSettings.set(true); onMouseMove()\"\n (zHide)=\"onSettingsHidden()\">\n <z-icon zType=\"tablerSettings\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n }\n }\n\n <!-- N\u00FAt b\u1EADt/t\u1EAFt ph\u1EE5 \u0111\u1EC1 (CC) -->\n @if (controls().subtitles && hasSubtitles()) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"xs\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_subtitles_btn\"\n class=\"flex size-7 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90\"\n [class.bg-white!]=\"captionsEnabled()\"\n [class.text-black!]=\"captionsEnabled()\"\n [attr.aria-pressed]=\"captionsEnabled()\"\n [title]=\"'i18n_z_ui_media_player_subtitles' | translate\"\n (click)=\"toggleSubtitles()\">\n <span class=\"text-[11px] leading-none font-bold tracking-[-0.04em]\">CC</span>\n </button>\n }\n\n <!-- N\u00FAt Miniplayer (Picture-in-Picture) - \u1EA8n ho\u1EB7c disable n\u1EBFu browser ko h\u1ED7 tr\u1EE3 -->\n @if (controls().pip && isPiPSupported()) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_mini_btn\"\n class=\"hidden size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90 sm:flex\"\n [title]=\"'i18n_z_ui_media_player_miniplayer' | translate\"\n (click)=\"toggleMiniplayer()\">\n <z-icon zType=\"tablerPictureInPicture\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n }\n\n <!-- N\u00FAt b\u1EADt/t\u1EAFt ch\u1EBF \u0111\u1ED9 r\u1EA1p chi\u1EBFu phim (Theater Mode) -->\n @if (controls().theater) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n id=\"z_media_player_theater_btn\"\n class=\"hidden size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90 md:flex\"\n [title]=\"\n (isTheaterMode() ? 'i18n_z_ui_media_player_default_mode' : 'i18n_z_ui_media_player_theater_mode')\n | translate\n \"\n (click)=\"toggleTheaterMode()\">\n <z-icon [zType]=\"isTheaterMode() ? 'tablerSquare' : 'tablerRectangle'\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n }\n\n <!-- N\u00FAt b\u1EADt/t\u1EAFt ch\u1EBF \u0111\u1ED9 to\u00E0n m\u00E0n h\u00ECnh (Fullscreen) -->\n @if (controls().fullscreen) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_full_btn\"\n class=\"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90\"\n [title]=\"\n (isFullscreen() ? 'i18n_z_ui_media_player_exit_fullscreen' : 'i18n_z_ui_media_player_fullscreen')\n | translate\n \"\n (click)=\"toggleFullscreen()\">\n <z-icon [zType]=\"isFullscreen() ? 'tablerMinimize' : 'tablerMaximize'\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n }\n </div>\n </div>\n </div>\n\n <!-- Template c\u1EA5u h\u00ECnh Popover c\u00E0i \u0111\u1EB7t s\u1EED d\u1EE5ng ng-template -->\n <ng-template #speedMenuTemplate>\n <div\n class=\"z-media-player-settings flex w-max max-w-48 min-w-28 flex-col px-1 py-[4px] text-zinc-200\"\n [class.z-main-panel]=\"settingsPanel() === 'main'\"\n [class.z-sub-panel]=\"settingsPanel() !== 'main'\">\n @if (settingsPanel() === 'main') {\n <div class=\"z-media-player-settings-panel flex flex-col\">\n @if (qualitySources().length > 1) {\n <button type=\"button\" class=\"z-media-player-settings-item\" (click)=\"openSettingsPanel('quality')\">\n <span class=\"min-w-0 flex-1 truncate whitespace-nowrap\">\n {{ 'i18n_z_ui_media_player_quality' | translate }}\n </span>\n <span class=\"flex max-w-[5.5rem] min-w-0 items-center gap-1.5 text-zinc-400\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ activeQualityLabel() }}</span>\n <z-icon zType=\"tablerChevronRight\" zSize=\"13\" class=\"shrink-0\" />\n </span>\n </button>\n }\n <button type=\"button\" class=\"z-media-player-settings-item\" (click)=\"openSettingsPanel('speed')\">\n <span class=\"min-w-0 flex-1 truncate whitespace-nowrap\">\n {{ 'i18n_z_ui_media_player_speed' | translate }}\n </span>\n <span class=\"flex max-w-[5.5rem] min-w-0 items-center gap-1.5 text-zinc-400\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">\n {{ playbackSpeed() === 1 ? ('i18n_z_ui_media_player_normal' | translate) : playbackSpeed() + 'x' }}\n </span>\n <z-icon zType=\"tablerChevronRight\" zSize=\"13\" class=\"shrink-0\" />\n </span>\n </button>\n </div>\n }\n\n @if (settingsPanel() === 'quality') {\n <div class=\"z-media-player-settings-panel flex flex-col\">\n <button\n type=\"button\"\n [title]=\"'i18n_z_ui_media_player_back' | translate\"\n class=\"z-media-player-settings-back\"\n (click)=\"closeSettingsPanel()\">\n <z-icon zType=\"tablerChevronLeft\" zSize=\"13\" class=\"shrink-0\" />\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ 'i18n_z_ui_media_player_quality' | translate }}</span>\n </button>\n @for (source of qualitySources(); track source.label) {\n <button\n type=\"button\"\n [id]=\"'z_media_player_quality_btn_' + source.label\"\n class=\"z-media-player-settings-item\"\n [class.z-active]=\"activeQualityLabel() === source.label\"\n (click)=\"setQualitySource(source)\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ source.label }}</span>\n </button>\n }\n </div>\n }\n\n @if (settingsPanel() === 'speed') {\n <div class=\"z-media-player-settings-panel flex flex-col\">\n <button\n type=\"button\"\n [title]=\"'i18n_z_ui_media_player_back' | translate\"\n class=\"z-media-player-settings-back\"\n (click)=\"closeSettingsPanel()\">\n <z-icon zType=\"tablerChevronLeft\" zSize=\"13\" class=\"shrink-0\" />\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ 'i18n_z_ui_media_player_speed' | translate }}</span>\n </button>\n @for (speed of playbackSpeeds; track speed.value) {\n <button\n type=\"button\"\n [id]=\"'z_media_player_speed_btn_' + speed.value\"\n class=\"z-media-player-settings-item\"\n [class.z-active]=\"playbackSpeed() === speed.value\"\n (click)=\"setPlaybackSpeed(speed.value)\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ speed.label | translate }}</span>\n </button>\n }\n </div>\n }\n </div>\n </ng-template>\n}\n", styles: ["z-media-player{display:block;width:100%;container-type:inline-size;outline:none}z-media-player:focus,z-media-player:focus-visible{outline:none}z-media-player .z-media-player-controls-surface{position:relative;background-color:#0000008c;box-shadow:0 0 0 1px #0000001a,0 1px 3px #00000026,0 1px 2px -1px #00000026}z-media-player .z-media-player-controls-surface:after{position:absolute;inset:0;z-index:10;border-radius:inherit;box-shadow:inset 0 1px #ffffff1a,inset 0 0 0 1px #ffffff0d;content:\"\";pointer-events:none}z-media-player video::cue{visibility:hidden;background-color:#080808d9;color:#fff;font-family:inherit;font-size:16px;text-shadow:0 1px 2px rgba(0,0,0,.9);padding:4px 8px;border-radius:4px}z-media-player .z-media-player-loading-pulse{position:relative;width:64px;height:64px}z-media-player .z-media-player-loading-pulse:before,z-media-player .z-media-player-loading-pulse:after{position:absolute;inset:0;border-radius:9999px;background-color:#0000008c;content:\"\";animation:zUiMediaPlayerLoadingBreathe 2.2s ease-in-out infinite;will-change:transform}z-media-player .z-media-player-loading-pulse:after{inset:14px;background-color:#000000b3;animation-delay:-1.1s}@keyframes zUiMediaPlayerLoadingBreathe{0%,to{transform:scale(.82)}50%{transform:scale(1.08)}}@media(prefers-reduced-motion:reduce){z-media-player .z-media-player-loading-pulse:before,z-media-player .z-media-player-loading-pulse:after{animation:none;transform:scale(1)}}z-media-player .z-flash-animation{will-change:opacity,transform}z-media-player .z-flash-animation z-icon{width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center}z-media-player .z-flash-animation z-icon ng-icon,z-media-player .z-flash-animation z-icon svg{width:100%!important;height:100%!important}@container (min-width: 480px){z-media-player .z-flash-animation{width:64px;height:64px}z-media-player .z-flash-animation z-icon{width:32px!important;height:32px!important}}@container (min-width: 800px){z-media-player .z-flash-animation{width:96px;height:96px}z-media-player .z-flash-animation z-icon{width:48px!important;height:48px!important}}z-media-player:fullscreen .z-flash-animation{width:112px;height:112px}z-media-player:fullscreen .z-flash-animation z-icon{width:56px!important;height:56px!important}z-media-player .z-flash-animation.z-flash-active{animation:zUiFlashIcon .5s ease-out forwards}@keyframes zUiFlashIcon{0%{opacity:0;transform:scale(.76)}18%{opacity:.95}to{opacity:0;transform:scale(1.18)}}@media(prefers-reduced-motion:reduce){z-media-player .z-flash-animation{animation-duration:.2s}}z-media-player .animate-fade-in{animation:zUiFadeIn .2s cubic-bezier(.16,1,.3,1)}@keyframes zUiFadeIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}z-media-player .z-double-tap-ripple-left,z-media-player .z-double-tap-ripple-right{position:absolute;top:0;bottom:0;width:35%;display:flex;align-items:center;z-index:11;pointer-events:none}z-media-player .z-double-tap-ripple-left:before,z-media-player .z-double-tap-ripple-right:before{position:absolute;inset:0;content:\"\";opacity:0;animation-duration:.72s;animation-fill-mode:forwards;animation-timing-function:cubic-bezier(.25,1,.5,1)}z-media-player .z-double-tap-ripple-left{left:0;justify-content:flex-start;padding-left:5%}z-media-player .z-double-tap-ripple-left:before{background:radial-gradient(circle at 0% 50%,#ffffff21,#fff0 70%);border-top-right-radius:100% 50%;border-bottom-right-radius:100% 50%;animation-name:zUiDoubleTapRippleLeft;transform-origin:left center}z-media-player .z-double-tap-ripple-left.z-skip-pulse-alt:before{animation-name:zUiDoubleTapRippleLeftAlt}z-media-player .z-double-tap-ripple-right{right:0;justify-content:flex-end;padding-right:5%}z-media-player .z-double-tap-ripple-right:before{background:radial-gradient(circle at 100% 50%,#ffffff21,#fff0 70%);border-top-left-radius:100% 50%;border-bottom-left-radius:100% 50%;animation-name:zUiDoubleTapRippleRight;transform-origin:right center}z-media-player .z-double-tap-ripple-right.z-skip-pulse-alt:before{animation-name:zUiDoubleTapRippleRightAlt}z-media-player .z-skip-overlay-content{position:relative;display:flex;align-items:center;justify-content:center;gap:6px;min-width:92px;color:#fff;font-family:inherit;font-size:18px;font-weight:700;line-height:1;text-shadow:0 2px 8px rgba(0,0,0,.75);-webkit-user-select:none;user-select:none}z-media-player .z-skip-overlay-number{min-width:42px;animation:zUiSkipNumberBump .22s cubic-bezier(.2,.85,.25,1) forwards;text-align:center;transform-origin:center}z-media-player .z-skip-pulse-alt .z-skip-overlay-number{animation-name:zUiSkipNumberBumpAlt}z-media-player .z-skip-overlay-arrows{display:inline-flex;align-items:center;color:#fffffff2;filter:drop-shadow(0 2px 6px rgba(0,0,0,.7))}@keyframes zUiDoubleTapRippleLeft{0%{opacity:0;transform:scaleX(.5);transform-origin:left center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:left center}}@keyframes zUiDoubleTapRippleLeftAlt{0%{opacity:0;transform:scaleX(.5);transform-origin:left center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:left center}}@keyframes zUiDoubleTapRippleRight{0%{opacity:0;transform:scaleX(.5);transform-origin:right center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:right center}}@keyframes zUiDoubleTapRippleRightAlt{0%{opacity:0;transform:scaleX(.5);transform-origin:right center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:right center}}z-media-player .z-chevron-slide-left{animation:zUiChevronSlideLeft .72s cubic-bezier(.25,1,.5,1) forwards}z-media-player .z-chevron-slide-right{animation:zUiChevronSlideRight .72s cubic-bezier(.25,1,.5,1) forwards}z-media-player .z-skip-pulse-alt .z-chevron-slide-left{animation-name:zUiChevronSlideLeftAlt}z-media-player .z-skip-pulse-alt .z-chevron-slide-right{animation-name:zUiChevronSlideRightAlt}@keyframes zUiSkipNumberBump{0%{opacity:.78;transform:scale(.92)}55%{opacity:1;transform:scale(1.12)}to{opacity:1;transform:scale(1)}}@keyframes zUiSkipNumberBumpAlt{0%{opacity:.78;transform:scale(.92)}55%{opacity:1;transform:scale(1.12)}to{opacity:1;transform:scale(1)}}@keyframes zUiChevronSlideLeft{0%{opacity:0;transform:translate(8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(-12px)}}@keyframes zUiChevronSlideRight{0%{opacity:0;transform:translate(-8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(12px)}}@keyframes zUiChevronSlideLeftAlt{0%{opacity:0;transform:translate(8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(-12px)}}@keyframes zUiChevronSlideRightAlt{0%{opacity:0;transform:translate(-8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(12px)}}z-media-player #z_media_player_volume_slider{-webkit-appearance:none;appearance:none;background:transparent}z-media-player #z_media_player_volume_slider::-webkit-slider-runnable-track{background:#ffffff40;height:3px;border-radius:9999px}z-media-player #z_media_player_volume_slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;background:#fff;height:10px;width:10px;border-radius:9999px;margin-top:-3.5px;transition:transform .15s ease}z-media-player #z_media_player_volume_slider:hover::-webkit-slider-thumb,z-media-player #z_media_player_volume_slider:focus-visible::-webkit-slider-thumb{transform:scale(1.3)}z-media-player #z_media_player_volume_slider::-moz-range-track{background:#ffffff40;height:3px;border-radius:9999px}z-media-player #z_media_player_volume_slider::-moz-range-thumb{background:#fff;height:10px;width:10px;border-radius:9999px;border:none;transition:transform .15s ease}z-media-player #z_media_player_volume_slider:hover::-moz-range-thumb,z-media-player #z_media_player_volume_slider:focus-visible::-moz-range-thumb{transform:scale(1.3)}.z-media-player-popover{background-color:#0000008c!important;border:none!important;box-shadow:0 0 0 1px #0000001a,0 1px 3px #00000026,0 1px 2px -1px #00000026!important;border-radius:8px!important;padding:0!important;transform-origin:bottom center}.z-media-player-popover:after{position:absolute;inset:0;z-index:10;border-radius:inherit;box-shadow:inset 0 1px #ffffff1a,inset 0 0 0 1px #ffffff0d;content:\"\";pointer-events:none}.z-media-player-popover[data-state=open]{animation:zUiMediaSettingsPopoverIn .16s cubic-bezier(.2,.85,.25,1) forwards}.z-media-player-popover[data-state=closed]{animation:zUiMediaSettingsPopoverOut .16s cubic-bezier(.4,0,.2,1) forwards!important}.z-media-player-popover .bg-popover{background-color:transparent!important;color:#e4e4e7!important}.z-media-player-popover .z-media-player-settings{overflow:hidden;transform-origin:bottom right;animation:zUiMediaSettingsPanelIn .16s cubic-bezier(.2,.85,.25,1)}.z-media-player-popover .z-media-player-settings-panel{gap:4px;transform-origin:top right;animation:zUiMediaSettingsPanelIn .14s cubic-bezier(.2,.85,.25,1);will-change:opacity,transform}.z-media-player-popover .z-media-player-settings.z-main-panel .z-media-player-settings-panel{animation-name:zUiMediaSettingsPanelFromLeft}.z-media-player-popover .z-media-player-settings.z-sub-panel .z-media-player-settings-panel{animation-name:zUiMediaSettingsPanelFromRight}.z-media-player-popover .z-media-player-settings-item,.z-media-player-popover .z-media-player-settings-back{position:relative;display:flex;width:100%;min-width:0;height:32px;min-height:32px;cursor:pointer;align-items:center;border:0;border-radius:4px;background-color:transparent;padding:0 8px;color:#e4e4e7;font-size:12px;font-weight:500;line-height:1;outline:none;transition:background-color .12s ease,color .12s ease}.z-media-player-popover .z-media-player-settings-item{justify-content:space-between;gap:12px;text-align:left}.z-media-player-popover .z-media-player-settings-item:before{position:absolute;top:7px;bottom:7px;left:0;width:2px;border-radius:9999px;background-color:#fff;content:\"\";opacity:0;transform:scaleY(.5);transition:opacity .12s ease,transform .12s ease}.z-media-player-popover .z-media-player-settings-item.z-active{background-color:#ffffff14;color:#fff;font-weight:600}.z-media-player-popover .z-media-player-settings-item.z-active:before{opacity:1;transform:scaleY(1)}.z-media-player-popover .z-media-player-settings-back{justify-content:flex-start;gap:8px;color:#f4f4f5;font-weight:600}.z-media-player-popover .z-media-player-settings-item:hover,.z-media-player-popover .z-media-player-settings-back:hover,.z-media-player-popover .z-media-player-settings-item:focus-visible,.z-media-player-popover .z-media-player-settings-back:focus-visible{background-color:#ffffff1a}@keyframes zUiMediaSettingsPopoverIn{0%{opacity:0;transform:translateY(6px) scale(.94)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes zUiMediaSettingsPopoverOut{0%{opacity:1;transform:translateY(0) scale(1)}to{opacity:0;transform:translateY(4px) scale(.96)}}@keyframes zUiMediaSettingsPanelIn{0%{opacity:0;transform:translateY(3px)}to{opacity:1;transform:translateY(0)}}@keyframes zUiMediaSettingsPanelFromRight{0%{opacity:0;transform:translate(8px)}to{opacity:1;transform:translate(0)}}@keyframes zUiMediaSettingsPanelFromLeft{0%{opacity:0;transform:translate(-5px)}to{opacity:1;transform:translate(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.RangeValueAccessor, selector: "input[type=range]:not([ngNoCva])[formControlName],input[type=range]:not([ngNoCva])[formControl],input[type=range]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ZIconComponent, selector: "z-icon, [z-icon]", inputs: ["class", "zType", "zAnimatedType", "zAnimate", "zAnimationTrigger", "zSize", "zStrokeWidth", "zSvg"] }, { kind: "directive", type: ZPopoverDirective, selector: "[z-popover]", inputs: ["zPopoverContent", "zPosition", "zTrigger", "zPopoverTrigger", "zClass", "zShowDelay", "zHideDelay", "zDisabled", "zOffset", "zPopoverWidth", "zTriggerRef", "zManualClose", "zOutsideClickClose", "zScrollClose", "zSticky", "zShowArrow", "zKeyboardSafe"], outputs: ["zShow", "zHide", "zHideStart", "zControl", "zPositionChange", "zOutsideClick"], exportAs: ["zPopover"] }, { kind: "component", type: ZButtonComponent, selector: "z-button, button[z-button], a[z-button]", inputs: ["class", "zType", "zSize", "zShape", "zLabel", "zLoading", "zDisabled", "zTypeIcon", "zAnimatedTypeIcon", "zAnimateIcon", "zAnimationTriggerIcon", "zSizeIcon", "zStrokeWidthIcon", "zWave"], exportAs: ["zButton"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
692
996
|
}
|
|
693
997
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ZMediaPlayerComponent, decorators: [{
|
|
694
998
|
type: Component,
|
|
@@ -696,11 +1000,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
|
|
|
696
1000
|
'[class]': 'zClasses()',
|
|
697
1001
|
'[attr.tabindex]': '0',
|
|
698
1002
|
'(keydown)': 'onKeyDown($event)',
|
|
699
|
-
'(mousemove)': 'onMouseMove()',
|
|
700
1003
|
'(mouseleave)': 'onMouseLeave()',
|
|
701
1004
|
'(document:fullscreenchange)': 'onFullscreenStateChange()',
|
|
702
|
-
}, template: "<!-- Th\u1EBB video ch\u00EDnh ph\u00E1t ngu\u1ED3n zSrc -->\n<video\n #videoElement\n class=\"h-full w-full cursor-pointer object-contain\"\n [src]=\"effectiveSrc()\"\n [poster]=\"zPoster()\"\n [loop]=\"zLoop()\"\n [autoplay]=\"zAutoplay()\"\n playsinline\n webkit-playsinline\n (click)=\"onVideoClick($event)\"\n (play)=\"onPlayStateChange(true)\"\n (pause)=\"onPlayStateChange(false)\"\n (timeupdate)=\"onTimeUpdate()\"\n (durationchange)=\"onDurationChange()\"\n (seeking)=\"onSeeking(true)\"\n (seeked)=\"onSeeking(false)\"\n (waiting)=\"onSeeking(true)\"\n (playing)=\"onSeeking(false)\"\n (enterpictureinpicture)=\"onEnterPiP()\"\n (leavepictureinpicture)=\"onLeavePiP()\"\n (webkitpresentationmodechanged)=\"onWebkitPresentationModeChanged()\">\n @for (sub of zSubtitles(); track sub.src) {\n <track\n [src]=\"sub.src\"\n kind=\"subtitles\"\n [srclang]=\"sub.srclang\"\n [label]=\"sub.label\"\n [default]=\"sub.default || false\" />\n }\n</video>\n\n<!-- L\u1EDBp ph\u1EE7 v\u00F2ng xoay loading khi video \u0111ang buffer -->\n@if (isBuffering()) {\n <div class=\"pointer-events-none absolute inset-0 z-[10] flex items-center justify-center bg-black/20\">\n <div class=\"size-14 animate-spin rounded-full border-4 border-white/20 border-t-red-600\"></div>\n </div>\n}\n\n<!-- Hi\u1EC7u \u1EE9ng nh\u1EA5p nh\u00E1y Play/Pause l\u1EDBn gi\u1EEFa m\u00E0n h\u00ECnh t\u01B0\u01A1ng t\u1EF1 YouTube -->\n@if (playPauseFlash()) {\n <div class=\"pointer-events-none absolute inset-0 z-[11] flex items-center justify-center\">\n <div\n class=\"z-flash-animation flex size-14 items-center justify-center rounded-full bg-black/60 text-white sm:size-20\">\n <z-icon\n [zType]=\"playPauseFlash() === 'play' ? 'lucidePlay' : 'lucidePause'\"\n zSize=\"24\"\n [zStrokeWidth]=\"2\"\n [class.translate-x-[2px]]=\"playPauseFlash() === 'play'\" />\n </div>\n </div>\n}\n\n<!-- L\u1EDBp ph\u1EE7 tua nhanh nh\u1EA3y s\u1ED1 c\u1ED9ng d\u1ED3n Tr\u00E1i (YouTube style) -->\n@if (skipOverlayValue() > 0 && skipOverlayDirection() === 'left') {\n <div class=\"z-double-tap-ripple-left\" [class.z-skip-pulse-alt]=\"skipOverlayPulse() % 2 === 1\">\n <div class=\"z-skip-overlay-content\">\n <span class=\"z-skip-overlay-number\">- {{ skipOverlayValue() }}</span>\n <span class=\"z-skip-overlay-arrows z-chevron-slide-left\">\n <z-icon zType=\"lucideChevronsLeft\" zSize=\"26\" [zStrokeWidth]=\"2.4\" />\n </span>\n </div>\n </div>\n}\n\n<!-- L\u1EDBp ph\u1EE7 tua nhanh nh\u1EA3y s\u1ED1 c\u1ED9ng d\u1ED3n Ph\u1EA3i (YouTube style) -->\n@if (skipOverlayValue() > 0 && skipOverlayDirection() === 'right') {\n <div class=\"z-double-tap-ripple-right\" [class.z-skip-pulse-alt]=\"skipOverlayPulse() % 2 === 1\">\n <div class=\"z-skip-overlay-content\">\n <span class=\"z-skip-overlay-arrows z-chevron-slide-right\">\n <z-icon zType=\"lucideChevronsRight\" zSize=\"26\" [zStrokeWidth]=\"2.4\" />\n </span>\n <span class=\"z-skip-overlay-number\">+ {{ skipOverlayValue() }}</span>\n </div>\n </div>\n}\n\n<!-- L\u1EDBp ph\u1EE7 ch\u1EE9a thanh \u0111i\u1EC1u khi\u1EC3n (Bottom controls panel) -->\n<div\n class=\"pointer-events-none absolute inset-x-0 bottom-0 z-[12] flex flex-col bg-gradient-to-t from-black/85 via-black/40 to-transparent p-3 pt-12 transition-opacity duration-300 select-none\"\n [class.opacity-0]=\"!showControls()\"\n [class.invisible]=\"!showControls()\">\n <!-- Thanh Timeline (Progress Bar) t\u00F9y ch\u1EC9nh ki\u1EC3u YouTube -->\n <div\n #timelineContainer\n id=\"z_media_player_timeline\"\n class=\"group/timeline pointer-events-auto relative mb-3 flex h-1 w-full cursor-pointer items-center transition-all hover:h-1.5\"\n (mousedown)=\"onTimelineDragStart($event)\"\n (mousemove)=\"onTimelineHover($event)\"\n (mouseleave)=\"onTimelineHoverEnd()\">\n <!-- Thanh n\u1EC1n x\u00E1m c\u1EE7a thanh timeline -->\n <div\n class=\"absolute inset-x-0 h-1 overflow-hidden rounded-full bg-white/25 transition-all group-hover/timeline:h-1.5\">\n <!-- Ti\u1EBFn tr\u00ECnh t\u1EA3i buffer c\u1EE7a video -->\n <div class=\"absolute top-0 left-0 h-full rounded-full bg-white/30\" [style.width.%]=\"bufferedProgress()\"></div>\n <!-- Ti\u1EBFn tr\u00ECnh ph\u00E1t hi\u1EC7n t\u1EA1i c\u1EE7a video -->\n <div class=\"absolute top-0 left-0 h-full rounded-full bg-red-600\" [style.width.%]=\"progress()\"></div>\n </div>\n\n <!-- \u0110\u1EA7u k\u00E9o timeline (Thumb) \u0111\u1ECF ch\u1EC9 hi\u1EC3n th\u1ECB khi hover ho\u1EB7c k\u00E9o -->\n <div\n class=\"bg-red-650 pointer-events-none absolute size-3.5 -translate-x-1/2 scale-0 transform rounded-full opacity-0 transition-opacity duration-150 group-hover/timeline:scale-100 group-hover/timeline:opacity-100\"\n [style.left.%]=\"progress()\"></div>\n\n <!-- Tooltip hi\u1EC3n th\u1ECB m\u1ED1c th\u1EDDi gian khi hover -->\n @if (hoverTimeLabel()) {\n <div\n class=\"pointer-events-none absolute bottom-4 z-[20] -translate-x-1/2 rounded-xs bg-zinc-950/70 px-2 py-1 font-mono text-[11px] leading-none whitespace-nowrap text-zinc-100 shadow-[0_12px_24px_-10px_rgba(0,0,0,0.85)] backdrop-blur-2xl\"\n [style.left.px]=\"hoverX()\">\n {{ hoverTimeLabel() }}\n </div>\n }\n </div>\n\n <!-- H\u00E0ng ch\u1EE9a c\u00E1c n\u00FAt b\u1EA5m \u0111i\u1EC1u khi\u1EC3n ch\u1EE9c n\u0103ng -->\n <div class=\"pointer-events-auto flex items-center justify-between text-sm text-white\">\n <!-- C\u00E1c n\u00FAt \u0111i\u1EC1u khi\u1EC3n ph\u00EDa b\u00EAn tr\u00E1i -->\n <div class=\"flex items-center gap-2\">\n <!-- N\u00FAt Ph\u00E1t / T\u1EA1m d\u1EEBng -->\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_play_btn\"\n class=\"mr-1 flex size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none\"\n [title]=\"(isPlaying() ? 'i18n_z_ui_media_player_pause' : 'i18n_z_ui_media_player_play') | translate\"\n (click)=\"togglePlay()\">\n <z-icon [zType]=\"isPlaying() ? 'lucidePause' : 'lucidePlay'\" zSize=\"20\" [zStrokeWidth]=\"2\" />\n </button>\n\n <!-- N\u00FAt Tua l\u00F9i 5s -->\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_rewind_5_btn\"\n class=\"hidden size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none sm:flex\"\n title=\"Tua l\u00F9i 5 gi\u00E2y (Arrow Left)\"\n (click)=\"triggerSkip(5, 'left')\">\n <div class=\"relative flex items-center justify-center\">\n <z-icon zType=\"lucideRotateCcw\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n <span class=\"pointer-events-none absolute mt-[1px] scale-75 text-[8px] font-bold text-white select-none\">\n 5\n </span>\n </div>\n </button>\n\n <!-- N\u00FAt Tua ti\u1EBFn 5s -->\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_forward_5_btn\"\n class=\"mr-1 hidden size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none sm:flex\"\n title=\"Tua ti\u1EBFn 5 gi\u00E2y (Arrow Right)\"\n (click)=\"triggerSkip(5, 'right')\">\n <div class=\"relative flex items-center justify-center\">\n <z-icon zType=\"lucideRotateCw\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n <span class=\"pointer-events-none absolute mt-[1px] scale-75 text-[8px] font-bold text-white select-none\">\n 5\n </span>\n </div>\n </button>\n\n <!-- T\u1ED5 h\u1EE3p n\u00FAt Mute v\u00E0 Slider \u00E2m l\u01B0\u1EE3ng tr\u01B0\u1EE3t ngang -->\n <div class=\"group/volume hidden items-center gap-1.5 sm:flex\">\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_mute_btn\"\n class=\"flex size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none\"\n [title]=\"(isMuted() ? 'i18n_z_ui_media_player_unmute' : 'i18n_z_ui_media_player_mute') | translate\"\n (click)=\"toggleMute()\">\n <z-icon\n [zType]=\"isMuted() ? 'lucideVolumeX' : volume() < 0.5 ? 'lucideVolume1' : 'lucideVolume2'\"\n zSize=\"20\"\n [zStrokeWidth]=\"2\" />\n </button>\n <!-- Thanh tr\u01B0\u1EE3t \u00E2m l\u01B0\u1EE3ng tr\u01B0\u1EE3t ngang m\u1EDF r\u1ED9ng khi hover -->\n <input\n type=\"range\"\n id=\"z_media_player_volume_slider\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n [ngModel]=\"volume()\"\n (ngModelChange)=\"onVolumeChange($event)\"\n class=\"accent-red-650 h-1 w-0 cursor-pointer rounded-lg bg-white/20 opacity-0 transition-all duration-200 outline-none group-hover/volume:w-16 group-hover/volume:opacity-100 focus-visible:w-16 focus-visible:opacity-100\" />\n </div>\n\n <!-- Hi\u1EC3n th\u1ECB th\u1EDDi gian ph\u00E1t (currentTime / duration) -->\n <div class=\"pl-1 font-mono text-xs text-white/90 select-none\">\n <span>{{ currentTimeLabel() }}</span>\n <span class=\"mx-1 text-white/40\">/</span>\n <span class=\"text-white/60\">{{ durationLabel() }}</span>\n </div>\n </div>\n\n <!-- C\u00E1c n\u00FAt \u0111i\u1EC1u khi\u1EC3n ph\u00EDa b\u00EAn ph\u1EA3i -->\n <div class=\"flex items-center gap-1.5\">\n <!-- N\u00FAt m\u1EDF tr\u00ECnh \u0111\u01A1n C\u00E0i \u0111\u1EB7t b\u1EB1ng ZPopoverDirective -->\n @if (isFullscreen()) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_settings_btn\"\n class=\"flex size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none\"\n [title]=\"'i18n_z_ui_media_player_settings' | translate\"\n (click)=\"toggleInlineSettings()\">\n <z-icon zType=\"lucideSettings\" zSize=\"20\" [zStrokeWidth]=\"2\" />\n </button>\n } @else {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_settings_btn\"\n class=\"flex size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none\"\n [title]=\"'i18n_z_ui_media_player_settings' | translate\"\n z-popover\n [zPopoverContent]=\"speedMenuTemplate\"\n zPosition=\"top-right\"\n zTrigger=\"click\"\n [zOffset]=\"6\"\n zClass=\"z-media-player-popover\"\n (zShow)=\"showSettings.set(true); onMouseMove()\"\n (zHide)=\"onSettingsHidden()\">\n <z-icon zType=\"lucideSettings\" zSize=\"20\" [zStrokeWidth]=\"2\" />\n </button>\n }\n\n <!-- N\u00FAt Miniplayer (Picture-in-Picture) - \u1EA8n ho\u1EB7c disable n\u1EBFu browser ko h\u1ED7 tr\u1EE3 -->\n @if (isPiPSupported()) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_mini_btn\"\n class=\"hidden size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none sm:flex\"\n [title]=\"'i18n_z_ui_media_player_miniplayer' | translate\"\n (click)=\"toggleMiniplayer()\">\n <z-icon zType=\"lucideTv\" zSize=\"20\" [zStrokeWidth]=\"2\" />\n </button>\n }\n\n <!-- N\u00FAt b\u1EADt/t\u1EAFt ch\u1EBF \u0111\u1ED9 r\u1EA1p chi\u1EBFu phim (Theater Mode) -->\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n id=\"z_media_player_theater_btn\"\n class=\"flex hidden size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none md:flex\"\n [title]=\"\n (isTheaterMode() ? 'i18n_z_ui_media_player_default_mode' : 'i18n_z_ui_media_player_theater_mode') | translate\n \"\n (click)=\"toggleTheaterMode()\">\n <z-icon\n [zType]=\"isTheaterMode() ? 'lucideSquare' : 'lucideRectangleHorizontal'\"\n zSize=\"20\"\n [zStrokeWidth]=\"2\" />\n </button>\n\n <!-- N\u00FAt b\u1EADt/t\u1EAFt ch\u1EBF \u0111\u1ED9 to\u00E0n m\u00E0n h\u00ECnh (Fullscreen) -->\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_full_btn\"\n class=\"flex size-8 cursor-pointer items-center justify-center rounded-xs border-0 border-none bg-transparent p-0 text-white shadow-none transition-colors outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none\"\n [title]=\"\n (isFullscreen() ? 'i18n_z_ui_media_player_exit_fullscreen' : 'i18n_z_ui_media_player_fullscreen') | translate\n \"\n (click)=\"toggleFullscreen()\">\n <z-icon [zType]=\"isFullscreen() ? 'lucideMinimize' : 'lucideMaximize'\" zSize=\"20\" [zStrokeWidth]=\"2\" />\n </button>\n </div>\n </div>\n</div>\n\n@if (isFullscreen() && showSettings()) {\n <div class=\"z-media-player-popover z-media-player-inline-settings\">\n <ng-container [ngTemplateOutlet]=\"speedMenuTemplate\" />\n </div>\n}\n\n<!-- Template c\u1EA5u h\u00ECnh Popover c\u00E0i \u0111\u1EB7t s\u1EED d\u1EE5ng ng-template -->\n<ng-template #speedMenuTemplate>\n <div\n class=\"z-media-player-settings flex w-44 flex-col px-1 py-[4px] text-zinc-200\"\n [class.z-main-panel]=\"settingsPanel() === 'main'\"\n [class.z-sub-panel]=\"settingsPanel() !== 'main'\">\n @if (settingsPanel() === 'main') {\n <div class=\"z-media-player-settings-panel flex flex-col\">\n @if (qualitySources().length > 1) {\n <button type=\"button\" class=\"z-media-player-settings-item\" (click)=\"openSettingsPanel('quality')\">\n <span class=\"min-w-0 flex-1 truncate whitespace-nowrap\">\n {{ 'i18n_z_ui_media_player_quality' | translate }}\n </span>\n <span class=\"flex max-w-[5.5rem] min-w-0 items-center gap-1.5 text-zinc-400\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ activeQualityLabel() }}</span>\n <z-icon zType=\"lucideChevronRight\" zSize=\"13\" class=\"shrink-0\" />\n </span>\n </button>\n }\n <button type=\"button\" class=\"z-media-player-settings-item\" (click)=\"openSettingsPanel('speed')\">\n <span class=\"min-w-0 flex-1 truncate whitespace-nowrap\">\n {{ 'i18n_z_ui_media_player_speed' | translate }}\n </span>\n <span class=\"flex max-w-[5.5rem] min-w-0 items-center gap-1.5 text-zinc-400\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">\n {{ playbackSpeed() === 1 ? ('i18n_z_ui_media_player_normal' | translate) : playbackSpeed() + 'x' }}\n </span>\n <z-icon zType=\"lucideChevronRight\" zSize=\"13\" class=\"shrink-0\" />\n </span>\n </button>\n </div>\n }\n\n @if (settingsPanel() === 'quality') {\n <div class=\"z-media-player-settings-panel flex flex-col\">\n <button\n type=\"button\"\n [title]=\"'i18n_z_ui_media_player_back' | translate\"\n class=\"z-media-player-settings-back\"\n (click)=\"closeSettingsPanel()\">\n <z-icon zType=\"lucideChevronLeft\" zSize=\"13\" class=\"shrink-0\" />\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ 'i18n_z_ui_media_player_quality' | translate }}</span>\n </button>\n @for (source of qualitySources(); track source.src) {\n <button\n type=\"button\"\n [id]=\"'z_media_player_quality_btn_' + source.label\"\n class=\"z-media-player-settings-item\"\n [class.z-active]=\"effectiveSrc() === source.src\"\n (click)=\"setQualitySource(source)\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ source.label }}</span>\n </button>\n }\n </div>\n }\n\n @if (settingsPanel() === 'speed') {\n <div class=\"z-media-player-settings-panel flex flex-col\">\n <button\n type=\"button\"\n [title]=\"'i18n_z_ui_media_player_back' | translate\"\n class=\"z-media-player-settings-back\"\n (click)=\"closeSettingsPanel()\">\n <z-icon zType=\"lucideChevronLeft\" zSize=\"13\" class=\"shrink-0\" />\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ 'i18n_z_ui_media_player_speed' | translate }}</span>\n </button>\n @for (speed of playbackSpeeds; track speed.value) {\n <button\n type=\"button\"\n [id]=\"'z_media_player_speed_btn_' + speed.value\"\n class=\"z-media-player-settings-item\"\n [class.z-active]=\"playbackSpeed() === speed.value\"\n (click)=\"setPlaybackSpeed(speed.value)\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ speed.label | translate }}</span>\n </button>\n }\n </div>\n }\n </div>\n</ng-template>\n", styles: ["z-media-player{display:block;width:100%;outline:none}z-media-player:focus,z-media-player:focus-visible{outline:none}z-media-player video::cue{background-color:#080808d9;color:#fff;font-family:inherit;font-size:16px;text-shadow:0 1px 2px rgba(0,0,0,.9);padding:4px 8px;border-radius:4px}z-media-player .z-flash-animation{animation:zUiFlashIcon .72s cubic-bezier(.2,.85,.25,1) forwards;will-change:opacity,transform,filter}z-media-player .z-flash-animation z-icon{width:24px!important;height:24px!important;display:inline-flex!important;align-items:center;justify-content:center}z-media-player .z-flash-animation z-icon ng-icon,z-media-player .z-flash-animation z-icon svg{width:100%!important;height:100%!important}@media(min-width:640px){z-media-player .z-flash-animation z-icon{width:32px!important;height:32px!important}}@keyframes zUiFlashIcon{0%{filter:blur(2px);opacity:0;transform:scale(.72)}18%{filter:blur(0);opacity:.95;transform:scale(1)}58%{opacity:.9;transform:scale(1.04)}to{filter:blur(0);opacity:0;transform:scale(1.18)}}z-media-player .animate-fade-in{animation:zUiFadeIn .2s cubic-bezier(.16,1,.3,1)}@keyframes zUiFadeIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}z-media-player .z-double-tap-ripple-left,z-media-player .z-double-tap-ripple-right{position:absolute;top:0;bottom:0;width:35%;display:flex;align-items:center;z-index:11;pointer-events:none}z-media-player .z-double-tap-ripple-left:before,z-media-player .z-double-tap-ripple-right:before{position:absolute;inset:0;content:\"\";opacity:0;animation-duration:.72s;animation-fill-mode:forwards;animation-timing-function:cubic-bezier(.25,1,.5,1)}z-media-player .z-double-tap-ripple-left{left:0;justify-content:flex-start;padding-left:5%}z-media-player .z-double-tap-ripple-left:before{background:radial-gradient(circle at 0% 50%,#ffffff21,#fff0 70%);border-top-right-radius:100% 50%;border-bottom-right-radius:100% 50%;animation-name:zUiDoubleTapRippleLeft;transform-origin:left center}z-media-player .z-double-tap-ripple-left.z-skip-pulse-alt:before{animation-name:zUiDoubleTapRippleLeftAlt}z-media-player .z-double-tap-ripple-right{right:0;justify-content:flex-end;padding-right:5%}z-media-player .z-double-tap-ripple-right:before{background:radial-gradient(circle at 100% 50%,#ffffff21,#fff0 70%);border-top-left-radius:100% 50%;border-bottom-left-radius:100% 50%;animation-name:zUiDoubleTapRippleRight;transform-origin:right center}z-media-player .z-double-tap-ripple-right.z-skip-pulse-alt:before{animation-name:zUiDoubleTapRippleRightAlt}z-media-player .z-skip-overlay-content{position:relative;display:flex;align-items:center;justify-content:center;gap:6px;min-width:92px;color:#fff;font-family:inherit;font-size:18px;font-weight:700;line-height:1;text-shadow:0 2px 8px rgba(0,0,0,.75);-webkit-user-select:none;user-select:none}z-media-player .z-skip-overlay-number{min-width:42px;animation:zUiSkipNumberBump .22s cubic-bezier(.2,.85,.25,1) forwards;text-align:center;transform-origin:center}z-media-player .z-skip-pulse-alt .z-skip-overlay-number{animation-name:zUiSkipNumberBumpAlt}z-media-player .z-skip-overlay-arrows{display:inline-flex;align-items:center;color:#fffffff2;filter:drop-shadow(0 2px 6px rgba(0,0,0,.7))}@keyframes zUiDoubleTapRippleLeft{0%{opacity:0;transform:scaleX(.5);transform-origin:left center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:left center}}@keyframes zUiDoubleTapRippleLeftAlt{0%{opacity:0;transform:scaleX(.5);transform-origin:left center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:left center}}@keyframes zUiDoubleTapRippleRight{0%{opacity:0;transform:scaleX(.5);transform-origin:right center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:right center}}@keyframes zUiDoubleTapRippleRightAlt{0%{opacity:0;transform:scaleX(.5);transform-origin:right center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:right center}}z-media-player .z-chevron-slide-left{animation:zUiChevronSlideLeft .72s cubic-bezier(.25,1,.5,1) forwards}z-media-player .z-chevron-slide-right{animation:zUiChevronSlideRight .72s cubic-bezier(.25,1,.5,1) forwards}z-media-player .z-skip-pulse-alt .z-chevron-slide-left{animation-name:zUiChevronSlideLeftAlt}z-media-player .z-skip-pulse-alt .z-chevron-slide-right{animation-name:zUiChevronSlideRightAlt}@keyframes zUiSkipNumberBump{0%{opacity:.78;transform:scale(.92)}55%{opacity:1;transform:scale(1.12)}to{opacity:1;transform:scale(1)}}@keyframes zUiSkipNumberBumpAlt{0%{opacity:.78;transform:scale(.92)}55%{opacity:1;transform:scale(1.12)}to{opacity:1;transform:scale(1)}}@keyframes zUiChevronSlideLeft{0%{opacity:0;transform:translate(8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(-12px)}}@keyframes zUiChevronSlideRight{0%{opacity:0;transform:translate(-8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(12px)}}@keyframes zUiChevronSlideLeftAlt{0%{opacity:0;transform:translate(8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(-12px)}}@keyframes zUiChevronSlideRightAlt{0%{opacity:0;transform:translate(-8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(12px)}}z-media-player #z_media_player_volume_slider{-webkit-appearance:none;appearance:none;background:transparent}z-media-player #z_media_player_volume_slider::-webkit-slider-runnable-track{background:#ffffff40;height:3px;border-radius:9999px}z-media-player #z_media_player_volume_slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;background:#fff;height:10px;width:10px;border-radius:9999px;margin-top:-3.5px;transition:transform .15s ease}z-media-player #z_media_player_volume_slider:hover::-webkit-slider-thumb,z-media-player #z_media_player_volume_slider:focus-visible::-webkit-slider-thumb{transform:scale(1.3)}z-media-player #z_media_player_volume_slider::-moz-range-track{background:#ffffff40;height:3px;border-radius:9999px}z-media-player #z_media_player_volume_slider::-moz-range-thumb{background:#fff;height:10px;width:10px;border-radius:9999px;border:none;transition:transform .15s ease}z-media-player #z_media_player_volume_slider:hover::-moz-range-thumb,z-media-player #z_media_player_volume_slider:focus-visible::-moz-range-thumb{transform:scale(1.3)}.z-media-player-popover{backdrop-filter:blur(24px)!important;-webkit-backdrop-filter:blur(24px)!important;background-color:#0f0f0f99!important;border:1px solid rgba(255,255,255,.08)!important;box-shadow:0 15px 30px -10px #000c!important;border-radius:8px!important;padding:0!important;transform-origin:bottom right}.z-media-player-popover[data-state=open]{animation:zUiMediaSettingsPopoverIn .16s cubic-bezier(.2,.85,.25,1) forwards}.z-media-player-popover[data-state=closed]{animation:zUiMediaSettingsPopoverOut .16s cubic-bezier(.4,0,.2,1) forwards!important}.z-media-player-popover .bg-popover{background-color:transparent!important;color:#e4e4e7!important}.z-media-player-popover .z-media-player-settings{overflow:hidden;transform-origin:bottom right;animation:zUiMediaSettingsPanelIn .16s cubic-bezier(.2,.85,.25,1)}.z-media-player-popover .z-media-player-settings-panel{gap:4px;transform-origin:top right;animation:zUiMediaSettingsPanelIn .14s cubic-bezier(.2,.85,.25,1);will-change:opacity,transform}.z-media-player-popover .z-media-player-settings.z-main-panel .z-media-player-settings-panel{animation-name:zUiMediaSettingsPanelFromLeft}.z-media-player-popover .z-media-player-settings.z-sub-panel .z-media-player-settings-panel{animation-name:zUiMediaSettingsPanelFromRight}.z-media-player-popover .z-media-player-settings-item,.z-media-player-popover .z-media-player-settings-back{position:relative;display:flex;width:100%;min-width:0;height:32px;min-height:32px;cursor:pointer;align-items:center;border:0;border-radius:4px;background-color:transparent;padding:0 8px;color:#e4e4e7;font-size:12px;font-weight:500;line-height:1;outline:none;transition:background-color .12s ease,color .12s ease}.z-media-player-popover .z-media-player-settings-item{justify-content:space-between;gap:12px;text-align:left}.z-media-player-popover .z-media-player-settings-item:before{position:absolute;top:7px;bottom:7px;left:0;width:2px;border-radius:9999px;background-color:#fff;content:\"\";opacity:0;transform:scaleY(.5);transition:opacity .12s ease,transform .12s ease}.z-media-player-popover .z-media-player-settings-item.z-active{background-color:#ffffff14;color:#fff;font-weight:600}.z-media-player-popover .z-media-player-settings-item.z-active:before{opacity:1;transform:scaleY(1)}.z-media-player-popover .z-media-player-settings-back{justify-content:flex-start;gap:8px;color:#f4f4f5;font-weight:600}.z-media-player-popover .z-media-player-settings-item:hover,.z-media-player-popover .z-media-player-settings-back:hover,.z-media-player-popover .z-media-player-settings-item:focus-visible,.z-media-player-popover .z-media-player-settings-back:focus-visible{background-color:#ffffff1a}.z-media-player-inline-settings{position:absolute;right:12px;bottom:64px;z-index:30}@keyframes zUiMediaSettingsPopoverIn{0%{opacity:0;transform:translateY(6px) scale(.94)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes zUiMediaSettingsPopoverOut{0%{opacity:1;transform:translateY(0) scale(1)}to{opacity:0;transform:translateY(4px) scale(.96)}}@keyframes zUiMediaSettingsPanelIn{0%{opacity:0;transform:translateY(3px)}to{opacity:1;transform:translateY(0)}}@keyframes zUiMediaSettingsPanelFromRight{0%{opacity:0;transform:translate(8px)}to{opacity:1;transform:translate(0)}}@keyframes zUiMediaSettingsPanelFromLeft{0%{opacity:0;transform:translate(-5px)}to{opacity:1;transform:translate(0)}}\n"] }]
|
|
703
|
-
}], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], zSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSrc", required: true }] }], zSources: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSources", required: false }] }], zPoster: [{ type: i0.Input, args: [{ isSignal: true, alias: "zPoster", required: false }] }], zAutoplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAutoplay", required: false }] }], zLoop: [{ type: i0.Input, args: [{ isSignal: true, alias: "zLoop", required: false }] }], zMuted: [{ type: i0.Input, args: [{ isSignal: true, alias: "zMuted", required: false }] }], zVolume: [{ type: i0.Input, args: [{ isSignal: true, alias: "zVolume", required: false }] }], zSubtitles: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSubtitles", required: false }] }], zPlay: [{ type: i0.Output, args: ["zPlay"] }], zTimeUpdate: [{ type: i0.Output, args: ["zTimeUpdate"] }], zVolumeChange: [{ type: i0.Output, args: ["zVolumeChange"] }], zFullscreenChange: [{ type: i0.Output, args: ["zFullscreenChange"] }], zTheaterModeChange: [{ type: i0.Output, args: ["zTheaterModeChange"] }], zMiniplayerChange: [{ type: i0.Output, args: ["zMiniplayerChange"] }], zQualityChange: [{ type: i0.Output, args: ["zQualityChange"] }], videoElement: [{ type: i0.ViewChild, args: ['videoElement', { isSignal: true }] }], timelineContainer: [{ type: i0.ViewChild, args: ['timelineContainer', { isSignal: true }] }] } });
|
|
1005
|
+
}, template: "@if (isYouTube()) {\n <!-- Nh\u00FAng iframe YouTube g\u1ED1c, d\u00F9ng control m\u1EB7c \u0111\u1ECBnh c\u1EE7a YouTube -->\n <iframe\n class=\"h-full w-full\"\n [src]=\"youtubeEmbedUrl()\"\n title=\"YouTube video player\"\n frameborder=\"0\"\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n allowfullscreen></iframe>\n} @else {\n <!-- Th\u1EBB video ch\u00EDnh ph\u00E1t ngu\u1ED3n zSrc -->\n <video\n #videoElement\n class=\"h-full w-full cursor-pointer object-cover\"\n [src]=\"effectiveSrc()\"\n [poster]=\"zPoster()\"\n [loop]=\"zLoop()\"\n [autoplay]=\"zAutoplay()\"\n playsinline\n webkit-playsinline\n (click)=\"onVideoClick()\"\n (play)=\"onPlayStateChange(true)\"\n (pause)=\"onPlayStateChange(false)\"\n (loadeddata)=\"onLoadedData()\"\n (error)=\"onVideoError()\"\n (timeupdate)=\"onTimeUpdate()\"\n (durationchange)=\"onDurationChange()\"\n (seeking)=\"onSeeking(true)\"\n (seeked)=\"onSeeking(false)\"\n (waiting)=\"onSeeking(true)\"\n (playing)=\"onSeeking(false)\"\n (enterpictureinpicture)=\"onEnterPiP()\"\n (leavepictureinpicture)=\"onLeavePiP()\"\n (webkitpresentationmodechanged)=\"onWebkitPresentationModeChanged()\">\n @for (sub of zSubtitles(); track sub.src) {\n <track\n [src]=\"sub.src\"\n kind=\"subtitles\"\n [srclang]=\"sub.srclang\"\n [label]=\"sub.label\"\n [default]=\"sub.default || false\" />\n }\n </video>\n\n @if (subtitleText()) {\n <div\n class=\"pointer-events-none absolute inset-x-3 z-[11] mx-auto w-fit max-w-[calc(100%-1.5rem)] rounded-sm bg-black/80 px-4 py-2 text-center text-base leading-6 whitespace-pre-line text-white shadow-sm transition-[bottom] duration-200 ease-out sm:text-lg sm:leading-7\"\n [class.bottom-16]=\"showControls()\"\n [class.bottom-2]=\"!showControls()\">\n {{ subtitleText() }}\n </div>\n }\n\n <!-- V\u00F2ng tr\u00F2n loading gi\u1EEFa m\u00E0n h\u00ECnh khi video \u0111ang buffer -->\n @if (isBuffering()) {\n <div class=\"pointer-events-none absolute inset-0 z-[13] flex items-center justify-center\">\n <span class=\"z-media-player-loading-pulse\"></span>\n </div>\n }\n\n <!-- Hi\u1EC7u \u1EE9ng nh\u1EA5p nh\u00E1y Play/Pause l\u1EDBn gi\u1EEFa m\u00E0n h\u00ECnh t\u01B0\u01A1ng t\u1EF1 YouTube -->\n <div class=\"pointer-events-none absolute inset-0 z-[11] flex items-center justify-center\">\n <div\n class=\"z-flash-animation flex size-10 scale-75 items-center justify-center rounded-full bg-black/60 text-white opacity-0\"\n [class.z-flash-active]=\"!!playPauseFlash()\">\n @if (playPauseFlash(); as flashType) {\n <z-icon\n [zType]=\"flashType === 'play' ? 'tablerPlayerPlayFill' : 'tablerPlayerPauseFill'\"\n zSize=\"28\"\n [zStrokeWidth]=\"2\"\n [class.translate-x-[2px]]=\"flashType === 'play'\" />\n }\n </div>\n </div>\n\n <!-- L\u1EDBp ph\u1EE7 tua nhanh nh\u1EA3y s\u1ED1 c\u1ED9ng d\u1ED3n Tr\u00E1i (YouTube style) -->\n @if (skipOverlayValue() > 0 && skipOverlayDirection() === 'left') {\n <div class=\"z-double-tap-ripple-left\" [class.z-skip-pulse-alt]=\"skipOverlayPulse() % 2 === 1\">\n <div class=\"z-skip-overlay-content\">\n <span class=\"z-skip-overlay-number\">- {{ skipOverlayValue() }}</span>\n <span class=\"z-skip-overlay-arrows z-chevron-slide-left\">\n <z-icon zType=\"tablerChevronsLeft\" zSize=\"26\" [zStrokeWidth]=\"2.4\" />\n </span>\n </div>\n </div>\n }\n\n <!-- L\u1EDBp ph\u1EE7 tua nhanh nh\u1EA3y s\u1ED1 c\u1ED9ng d\u1ED3n Ph\u1EA3i (YouTube style) -->\n @if (skipOverlayValue() > 0 && skipOverlayDirection() === 'right') {\n <div class=\"z-double-tap-ripple-right\" [class.z-skip-pulse-alt]=\"skipOverlayPulse() % 2 === 1\">\n <div class=\"z-skip-overlay-content\">\n <span class=\"z-skip-overlay-arrows z-chevron-slide-right\">\n <z-icon zType=\"tablerChevronsRight\" zSize=\"26\" [zStrokeWidth]=\"2.4\" />\n </span>\n <span class=\"z-skip-overlay-number\">+ {{ skipOverlayValue() }}</span>\n </div>\n </div>\n }\n\n <!-- L\u1EDBp ph\u1EE7 gradient ph\u1EE7 s\u00E1t to\u00E0n b\u1ED9 m\u00E9p d\u01B0\u1EDBi video -->\n <div\n class=\"pointer-events-none absolute inset-x-0 bottom-0 z-[10] h-28 bg-gradient-to-t from-black/55 via-black/25 to-transparent transition-opacity duration-200 ease-out\"\n [class.opacity-0]=\"!showControls()\"></div>\n\n <!-- Thanh \u0111i\u1EC1u khi\u1EC3n d\u1EA1ng rounded-sm n\u1ED5i ph\u00EDa tr\u00EAn \u0111\u00E1y video -->\n <div\n class=\"z-media-player-controls absolute inset-x-2 bottom-2 z-[12] transition-opacity duration-200 ease-out select-none sm:inset-x-3 sm:bottom-3\"\n [class.opacity-0]=\"!showControls()\"\n [class.pointer-events-auto]=\"showControls()\"\n [class.pointer-events-none]=\"!showControls()\">\n <div class=\"z-media-player-controls-surface relative z-[1] rounded-sm px-2 py-1.5 text-sm text-white\">\n <div class=\"flex items-center gap-1.5 sm:gap-2\">\n <!-- N\u00FAt Ph\u00E1t / T\u1EA1m d\u1EEBng -->\n @if (controls().play) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_play_btn\"\n class=\"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90\"\n [title]=\"(isPlaying() ? 'i18n_z_ui_media_player_pause' : 'i18n_z_ui_media_player_play') | translate\"\n (click)=\"togglePlay()\">\n <z-icon\n [zType]=\"isPlaying() ? 'tablerPlayerPauseFill' : 'tablerPlayerPlayFill'\"\n zSize=\"18\"\n [zStrokeWidth]=\"2\" />\n </button>\n }\n\n <!-- N\u00FAt Tua l\u00F9i / Tua ti\u1EBFn 5 gi\u00E2y -->\n @if (controls().skip) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_rewind_5_btn\"\n class=\"hidden size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90 sm:flex\"\n title=\"Tua l\u00F9i 5 gi\u00E2y (Arrow Left)\"\n (click)=\"triggerSkip(5, 'left')\">\n <z-icon zType=\"tablerRewindBackward5\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_forward_5_btn\"\n class=\"hidden size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90 sm:flex\"\n title=\"Tua ti\u1EBFn 5 gi\u00E2y (Arrow Right)\"\n (click)=\"triggerSkip(5, 'right')\">\n <z-icon zType=\"tablerRewindForward5\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n }\n\n <!-- T\u1ED5 h\u1EE3p n\u00FAt Mute v\u00E0 Slider \u00E2m l\u01B0\u1EE3ng tr\u01B0\u1EE3t ngang -->\n @if (controls().volume) {\n <div class=\"group/volume hidden shrink-0 items-center sm:flex\">\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_mute_btn\"\n class=\"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90\"\n [title]=\"(isMuted() ? 'i18n_z_ui_media_player_unmute' : 'i18n_z_ui_media_player_mute') | translate\"\n (click)=\"toggleMute()\">\n <z-icon\n [zType]=\"isMuted() ? 'tablerVolumeOff' : volume() < 0.5 ? 'tablerVolume2' : 'tablerVolume'\"\n zSize=\"18\"\n [zStrokeWidth]=\"2\" />\n </button>\n <!-- Thanh tr\u01B0\u1EE3t \u00E2m l\u01B0\u1EE3ng tr\u01B0\u1EE3t ngang m\u1EDF r\u1ED9ng khi hover -->\n <input\n type=\"range\"\n id=\"z_media_player_volume_slider\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n [ngModel]=\"volume()\"\n (ngModelChange)=\"onVolumeChange($event)\"\n class=\"accent-primary h-1 w-0 cursor-pointer rounded-lg bg-white/20 opacity-0 transition-all duration-200 outline-none group-hover/volume:w-16 group-hover/volume:opacity-100 focus-visible:w-16 focus-visible:opacity-100\" />\n </div>\n }\n\n <!-- Th\u1EDDi gian ph\u00E1t: g\u1ED9p chung \"\u0111\u00E3 ph\u00E1t / t\u1ED5ng th\u1EDDi l\u01B0\u1EE3ng\" th\u00E0nh m\u1ED9t nh\u00F3m duy nh\u1EA5t -->\n @if (controls().time) {\n <span class=\"shrink-0 rounded-full bg-black/30 px-2 py-0.5 font-mono text-xs whitespace-nowrap text-white/90\">\n {{ currentTimeLabel() }}\n <span class=\"text-white/50\">/</span>\n {{ durationLabel() }}\n </span>\n }\n\n <!-- Thanh Timeline (Progress Bar) t\u00F9y ch\u1EC9nh ki\u1EC3u YouTube, n\u1EB1m chung h\u00E0ng v\u1EDBi c\u00E1c n\u00FAt -->\n @if (controls().timeline) {\n <div\n #timelineContainer\n id=\"z_media_player_timeline\"\n class=\"group/timeline relative flex h-1 min-w-0 flex-1 cursor-pointer items-center\"\n (mousedown)=\"onTimelineDragStart($event)\"\n (mousemove)=\"onTimelineHover($event)\"\n (mouseleave)=\"onTimelineHoverEnd()\">\n <!-- Thanh n\u1EC1n x\u00E1m c\u1EE7a thanh timeline -->\n <div\n class=\"absolute inset-x-0 h-1 overflow-hidden rounded-full bg-white/25 transition-all group-hover/timeline:h-1.5\">\n <!-- Ti\u1EBFn tr\u00ECnh t\u1EA3i buffer c\u1EE7a video -->\n <div\n class=\"absolute top-0 left-0 h-full rounded-full bg-white/30\"\n [style.width.%]=\"bufferedProgress()\"></div>\n <!-- Ti\u1EBFn tr\u00ECnh ph\u00E1t hi\u1EC7n t\u1EA1i c\u1EE7a video -->\n <div class=\"bg-primary absolute top-0 left-0 h-full rounded-full\" [style.width.%]=\"progress()\"></div>\n </div>\n\n <!-- \u0110\u1EA7u k\u00E9o timeline (Thumb) \u0111\u1ECF ch\u1EC9 hi\u1EC3n th\u1ECB khi hover ho\u1EB7c k\u00E9o -->\n <div\n class=\"bg-primary pointer-events-none absolute size-3.5 -translate-x-1/2 scale-0 transform rounded-full opacity-0 transition-opacity duration-150 group-hover/timeline:scale-100 group-hover/timeline:opacity-100\"\n [style.left.%]=\"progress()\"></div>\n\n <!-- Preview \u1EA3nh nh\u1ECF storyboard khi hover timeline (ki\u1EC3u YouTube) -->\n @if (hoverThumbnailCue(); as cue) {\n <div\n class=\"pointer-events-none absolute z-[20] -translate-x-1/2 overflow-hidden rounded-md border-0 bg-black shadow-[0_12px_24px_-10px_rgba(0,0,0,0.85)]\"\n [style.left.px]=\"hoverX()\"\n [style.bottom.px]=\"24\"\n [style.width.px]=\"cue.w\"\n [style.height.px]=\"cue.h\">\n <div\n class=\"bg-no-repeat\"\n [style.width.px]=\"cue.w\"\n [style.height.px]=\"cue.h\"\n [style.background-image]=\"'url(' + cue.src + ')'\"\n [style.background-position]=\"'-' + cue.x + 'px -' + cue.y + 'px'\"></div>\n </div>\n }\n\n <!-- Tooltip hi\u1EC3n th\u1ECB m\u1ED1c th\u1EDDi gian khi hover -->\n @if (hoverTimeLabel()) {\n <div\n class=\"pointer-events-none absolute bottom-4 z-[20] -translate-x-1/2 rounded-xs bg-zinc-950/70 px-2 py-1 font-mono text-[11px] leading-none whitespace-nowrap text-zinc-100 shadow-[0_12px_24px_-10px_rgba(0,0,0,0.85)] backdrop-blur-2xl\"\n [style.left.px]=\"hoverX()\">\n {{ hoverTimeLabel() }}\n </div>\n }\n </div>\n }\n\n <!-- N\u00FAt m\u1EDF tr\u00ECnh \u0111\u01A1n C\u00E0i \u0111\u1EB7t b\u1EB1ng ZPopoverDirective -->\n @if (controls().settings) {\n @if (isFullscreen()) {\n <div class=\"relative shrink-0\">\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_settings_btn\"\n class=\"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:border-transparent focus-visible:ring-0 focus-visible:outline-none active:scale-90\"\n [class.bg-white/15]=\"showSettings()\"\n [title]=\"'i18n_z_ui_media_player_settings' | translate\"\n (click)=\"toggleInlineSettings()\">\n <z-icon zType=\"tablerSettings\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n <!-- Popover c\u00E0i \u0111\u1EB7t neo tr\u1EF1c ti\u1EBFp v\u00E0o n\u00FAt, v\u00EC overlay CDK kh\u00F4ng hi\u1EC3n th\u1ECB \u0111\u01B0\u1EE3c trong ch\u1EBF \u0111\u1ED9 Fullscreen g\u1ED1c -->\n @if (showSettings()) {\n <div class=\"z-media-player-popover absolute bottom-full left-1/2 mb-2 -translate-x-1/2\">\n <ng-container [ngTemplateOutlet]=\"speedMenuTemplate\" />\n </div>\n }\n </div>\n } @else {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_settings_btn\"\n class=\"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:border-transparent focus-visible:ring-0 focus-visible:outline-none active:scale-90 aria-expanded:!bg-white/15 aria-expanded:!text-white\"\n [title]=\"'i18n_z_ui_media_player_settings' | translate\"\n z-popover\n [zPopoverContent]=\"speedMenuTemplate\"\n zPosition=\"top\"\n zTrigger=\"click\"\n [zOffset]=\"9\"\n zClass=\"z-media-player-popover\"\n (zShow)=\"showSettings.set(true); onMouseMove()\"\n (zHide)=\"onSettingsHidden()\">\n <z-icon zType=\"tablerSettings\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n }\n }\n\n <!-- N\u00FAt b\u1EADt/t\u1EAFt ph\u1EE5 \u0111\u1EC1 (CC) -->\n @if (controls().subtitles && hasSubtitles()) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"xs\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_subtitles_btn\"\n class=\"flex size-7 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90\"\n [class.bg-white!]=\"captionsEnabled()\"\n [class.text-black!]=\"captionsEnabled()\"\n [attr.aria-pressed]=\"captionsEnabled()\"\n [title]=\"'i18n_z_ui_media_player_subtitles' | translate\"\n (click)=\"toggleSubtitles()\">\n <span class=\"text-[11px] leading-none font-bold tracking-[-0.04em]\">CC</span>\n </button>\n }\n\n <!-- N\u00FAt Miniplayer (Picture-in-Picture) - \u1EA8n ho\u1EB7c disable n\u1EBFu browser ko h\u1ED7 tr\u1EE3 -->\n @if (controls().pip && isPiPSupported()) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_mini_btn\"\n class=\"hidden size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90 sm:flex\"\n [title]=\"'i18n_z_ui_media_player_miniplayer' | translate\"\n (click)=\"toggleMiniplayer()\">\n <z-icon zType=\"tablerPictureInPicture\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n }\n\n <!-- N\u00FAt b\u1EADt/t\u1EAFt ch\u1EBF \u0111\u1ED9 r\u1EA1p chi\u1EBFu phim (Theater Mode) -->\n @if (controls().theater) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n id=\"z_media_player_theater_btn\"\n class=\"hidden size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90 md:flex\"\n [title]=\"\n (isTheaterMode() ? 'i18n_z_ui_media_player_default_mode' : 'i18n_z_ui_media_player_theater_mode')\n | translate\n \"\n (click)=\"toggleTheaterMode()\">\n <z-icon [zType]=\"isTheaterMode() ? 'tablerSquare' : 'tablerRectangle'\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n }\n\n <!-- N\u00FAt b\u1EADt/t\u1EAFt ch\u1EBF \u0111\u1ED9 to\u00E0n m\u00E0n h\u00ECnh (Fullscreen) -->\n @if (controls().fullscreen) {\n <button\n z-button\n zType=\"ghost\"\n zSize=\"sm\"\n zShape=\"default\"\n [zWave]=\"false\"\n type=\"button\"\n id=\"z_media_player_full_btn\"\n class=\"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 border-none bg-transparent p-0 text-white shadow-none transition-[background-color,color,transform] outline-none hover:bg-white/15 hover:text-white focus:outline-none focus-visible:outline-none active:scale-90\"\n [title]=\"\n (isFullscreen() ? 'i18n_z_ui_media_player_exit_fullscreen' : 'i18n_z_ui_media_player_fullscreen')\n | translate\n \"\n (click)=\"toggleFullscreen()\">\n <z-icon [zType]=\"isFullscreen() ? 'tablerMinimize' : 'tablerMaximize'\" zSize=\"18\" [zStrokeWidth]=\"2\" />\n </button>\n }\n </div>\n </div>\n </div>\n\n <!-- Template c\u1EA5u h\u00ECnh Popover c\u00E0i \u0111\u1EB7t s\u1EED d\u1EE5ng ng-template -->\n <ng-template #speedMenuTemplate>\n <div\n class=\"z-media-player-settings flex w-max max-w-48 min-w-28 flex-col px-1 py-[4px] text-zinc-200\"\n [class.z-main-panel]=\"settingsPanel() === 'main'\"\n [class.z-sub-panel]=\"settingsPanel() !== 'main'\">\n @if (settingsPanel() === 'main') {\n <div class=\"z-media-player-settings-panel flex flex-col\">\n @if (qualitySources().length > 1) {\n <button type=\"button\" class=\"z-media-player-settings-item\" (click)=\"openSettingsPanel('quality')\">\n <span class=\"min-w-0 flex-1 truncate whitespace-nowrap\">\n {{ 'i18n_z_ui_media_player_quality' | translate }}\n </span>\n <span class=\"flex max-w-[5.5rem] min-w-0 items-center gap-1.5 text-zinc-400\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ activeQualityLabel() }}</span>\n <z-icon zType=\"tablerChevronRight\" zSize=\"13\" class=\"shrink-0\" />\n </span>\n </button>\n }\n <button type=\"button\" class=\"z-media-player-settings-item\" (click)=\"openSettingsPanel('speed')\">\n <span class=\"min-w-0 flex-1 truncate whitespace-nowrap\">\n {{ 'i18n_z_ui_media_player_speed' | translate }}\n </span>\n <span class=\"flex max-w-[5.5rem] min-w-0 items-center gap-1.5 text-zinc-400\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">\n {{ playbackSpeed() === 1 ? ('i18n_z_ui_media_player_normal' | translate) : playbackSpeed() + 'x' }}\n </span>\n <z-icon zType=\"tablerChevronRight\" zSize=\"13\" class=\"shrink-0\" />\n </span>\n </button>\n </div>\n }\n\n @if (settingsPanel() === 'quality') {\n <div class=\"z-media-player-settings-panel flex flex-col\">\n <button\n type=\"button\"\n [title]=\"'i18n_z_ui_media_player_back' | translate\"\n class=\"z-media-player-settings-back\"\n (click)=\"closeSettingsPanel()\">\n <z-icon zType=\"tablerChevronLeft\" zSize=\"13\" class=\"shrink-0\" />\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ 'i18n_z_ui_media_player_quality' | translate }}</span>\n </button>\n @for (source of qualitySources(); track source.label) {\n <button\n type=\"button\"\n [id]=\"'z_media_player_quality_btn_' + source.label\"\n class=\"z-media-player-settings-item\"\n [class.z-active]=\"activeQualityLabel() === source.label\"\n (click)=\"setQualitySource(source)\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ source.label }}</span>\n </button>\n }\n </div>\n }\n\n @if (settingsPanel() === 'speed') {\n <div class=\"z-media-player-settings-panel flex flex-col\">\n <button\n type=\"button\"\n [title]=\"'i18n_z_ui_media_player_back' | translate\"\n class=\"z-media-player-settings-back\"\n (click)=\"closeSettingsPanel()\">\n <z-icon zType=\"tablerChevronLeft\" zSize=\"13\" class=\"shrink-0\" />\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ 'i18n_z_ui_media_player_speed' | translate }}</span>\n </button>\n @for (speed of playbackSpeeds; track speed.value) {\n <button\n type=\"button\"\n [id]=\"'z_media_player_speed_btn_' + speed.value\"\n class=\"z-media-player-settings-item\"\n [class.z-active]=\"playbackSpeed() === speed.value\"\n (click)=\"setPlaybackSpeed(speed.value)\">\n <span class=\"min-w-0 truncate whitespace-nowrap\">{{ speed.label | translate }}</span>\n </button>\n }\n </div>\n }\n </div>\n </ng-template>\n}\n", styles: ["z-media-player{display:block;width:100%;container-type:inline-size;outline:none}z-media-player:focus,z-media-player:focus-visible{outline:none}z-media-player .z-media-player-controls-surface{position:relative;background-color:#0000008c;box-shadow:0 0 0 1px #0000001a,0 1px 3px #00000026,0 1px 2px -1px #00000026}z-media-player .z-media-player-controls-surface:after{position:absolute;inset:0;z-index:10;border-radius:inherit;box-shadow:inset 0 1px #ffffff1a,inset 0 0 0 1px #ffffff0d;content:\"\";pointer-events:none}z-media-player video::cue{visibility:hidden;background-color:#080808d9;color:#fff;font-family:inherit;font-size:16px;text-shadow:0 1px 2px rgba(0,0,0,.9);padding:4px 8px;border-radius:4px}z-media-player .z-media-player-loading-pulse{position:relative;width:64px;height:64px}z-media-player .z-media-player-loading-pulse:before,z-media-player .z-media-player-loading-pulse:after{position:absolute;inset:0;border-radius:9999px;background-color:#0000008c;content:\"\";animation:zUiMediaPlayerLoadingBreathe 2.2s ease-in-out infinite;will-change:transform}z-media-player .z-media-player-loading-pulse:after{inset:14px;background-color:#000000b3;animation-delay:-1.1s}@keyframes zUiMediaPlayerLoadingBreathe{0%,to{transform:scale(.82)}50%{transform:scale(1.08)}}@media(prefers-reduced-motion:reduce){z-media-player .z-media-player-loading-pulse:before,z-media-player .z-media-player-loading-pulse:after{animation:none;transform:scale(1)}}z-media-player .z-flash-animation{will-change:opacity,transform}z-media-player .z-flash-animation z-icon{width:20px!important;height:20px!important;display:inline-flex!important;align-items:center;justify-content:center}z-media-player .z-flash-animation z-icon ng-icon,z-media-player .z-flash-animation z-icon svg{width:100%!important;height:100%!important}@container (min-width: 480px){z-media-player .z-flash-animation{width:64px;height:64px}z-media-player .z-flash-animation z-icon{width:32px!important;height:32px!important}}@container (min-width: 800px){z-media-player .z-flash-animation{width:96px;height:96px}z-media-player .z-flash-animation z-icon{width:48px!important;height:48px!important}}z-media-player:fullscreen .z-flash-animation{width:112px;height:112px}z-media-player:fullscreen .z-flash-animation z-icon{width:56px!important;height:56px!important}z-media-player .z-flash-animation.z-flash-active{animation:zUiFlashIcon .5s ease-out forwards}@keyframes zUiFlashIcon{0%{opacity:0;transform:scale(.76)}18%{opacity:.95}to{opacity:0;transform:scale(1.18)}}@media(prefers-reduced-motion:reduce){z-media-player .z-flash-animation{animation-duration:.2s}}z-media-player .animate-fade-in{animation:zUiFadeIn .2s cubic-bezier(.16,1,.3,1)}@keyframes zUiFadeIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}z-media-player .z-double-tap-ripple-left,z-media-player .z-double-tap-ripple-right{position:absolute;top:0;bottom:0;width:35%;display:flex;align-items:center;z-index:11;pointer-events:none}z-media-player .z-double-tap-ripple-left:before,z-media-player .z-double-tap-ripple-right:before{position:absolute;inset:0;content:\"\";opacity:0;animation-duration:.72s;animation-fill-mode:forwards;animation-timing-function:cubic-bezier(.25,1,.5,1)}z-media-player .z-double-tap-ripple-left{left:0;justify-content:flex-start;padding-left:5%}z-media-player .z-double-tap-ripple-left:before{background:radial-gradient(circle at 0% 50%,#ffffff21,#fff0 70%);border-top-right-radius:100% 50%;border-bottom-right-radius:100% 50%;animation-name:zUiDoubleTapRippleLeft;transform-origin:left center}z-media-player .z-double-tap-ripple-left.z-skip-pulse-alt:before{animation-name:zUiDoubleTapRippleLeftAlt}z-media-player .z-double-tap-ripple-right{right:0;justify-content:flex-end;padding-right:5%}z-media-player .z-double-tap-ripple-right:before{background:radial-gradient(circle at 100% 50%,#ffffff21,#fff0 70%);border-top-left-radius:100% 50%;border-bottom-left-radius:100% 50%;animation-name:zUiDoubleTapRippleRight;transform-origin:right center}z-media-player .z-double-tap-ripple-right.z-skip-pulse-alt:before{animation-name:zUiDoubleTapRippleRightAlt}z-media-player .z-skip-overlay-content{position:relative;display:flex;align-items:center;justify-content:center;gap:6px;min-width:92px;color:#fff;font-family:inherit;font-size:18px;font-weight:700;line-height:1;text-shadow:0 2px 8px rgba(0,0,0,.75);-webkit-user-select:none;user-select:none}z-media-player .z-skip-overlay-number{min-width:42px;animation:zUiSkipNumberBump .22s cubic-bezier(.2,.85,.25,1) forwards;text-align:center;transform-origin:center}z-media-player .z-skip-pulse-alt .z-skip-overlay-number{animation-name:zUiSkipNumberBumpAlt}z-media-player .z-skip-overlay-arrows{display:inline-flex;align-items:center;color:#fffffff2;filter:drop-shadow(0 2px 6px rgba(0,0,0,.7))}@keyframes zUiDoubleTapRippleLeft{0%{opacity:0;transform:scaleX(.5);transform-origin:left center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:left center}}@keyframes zUiDoubleTapRippleLeftAlt{0%{opacity:0;transform:scaleX(.5);transform-origin:left center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:left center}}@keyframes zUiDoubleTapRippleRight{0%{opacity:0;transform:scaleX(.5);transform-origin:right center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:right center}}@keyframes zUiDoubleTapRippleRightAlt{0%{opacity:0;transform:scaleX(.5);transform-origin:right center}15%{opacity:1}80%{opacity:1}to{opacity:0;transform:scaleX(1);transform-origin:right center}}z-media-player .z-chevron-slide-left{animation:zUiChevronSlideLeft .72s cubic-bezier(.25,1,.5,1) forwards}z-media-player .z-chevron-slide-right{animation:zUiChevronSlideRight .72s cubic-bezier(.25,1,.5,1) forwards}z-media-player .z-skip-pulse-alt .z-chevron-slide-left{animation-name:zUiChevronSlideLeftAlt}z-media-player .z-skip-pulse-alt .z-chevron-slide-right{animation-name:zUiChevronSlideRightAlt}@keyframes zUiSkipNumberBump{0%{opacity:.78;transform:scale(.92)}55%{opacity:1;transform:scale(1.12)}to{opacity:1;transform:scale(1)}}@keyframes zUiSkipNumberBumpAlt{0%{opacity:.78;transform:scale(.92)}55%{opacity:1;transform:scale(1.12)}to{opacity:1;transform:scale(1)}}@keyframes zUiChevronSlideLeft{0%{opacity:0;transform:translate(8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(-12px)}}@keyframes zUiChevronSlideRight{0%{opacity:0;transform:translate(-8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(12px)}}@keyframes zUiChevronSlideLeftAlt{0%{opacity:0;transform:translate(8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(-12px)}}@keyframes zUiChevronSlideRightAlt{0%{opacity:0;transform:translate(-8px)}28%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(12px)}}z-media-player #z_media_player_volume_slider{-webkit-appearance:none;appearance:none;background:transparent}z-media-player #z_media_player_volume_slider::-webkit-slider-runnable-track{background:#ffffff40;height:3px;border-radius:9999px}z-media-player #z_media_player_volume_slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;background:#fff;height:10px;width:10px;border-radius:9999px;margin-top:-3.5px;transition:transform .15s ease}z-media-player #z_media_player_volume_slider:hover::-webkit-slider-thumb,z-media-player #z_media_player_volume_slider:focus-visible::-webkit-slider-thumb{transform:scale(1.3)}z-media-player #z_media_player_volume_slider::-moz-range-track{background:#ffffff40;height:3px;border-radius:9999px}z-media-player #z_media_player_volume_slider::-moz-range-thumb{background:#fff;height:10px;width:10px;border-radius:9999px;border:none;transition:transform .15s ease}z-media-player #z_media_player_volume_slider:hover::-moz-range-thumb,z-media-player #z_media_player_volume_slider:focus-visible::-moz-range-thumb{transform:scale(1.3)}.z-media-player-popover{background-color:#0000008c!important;border:none!important;box-shadow:0 0 0 1px #0000001a,0 1px 3px #00000026,0 1px 2px -1px #00000026!important;border-radius:8px!important;padding:0!important;transform-origin:bottom center}.z-media-player-popover:after{position:absolute;inset:0;z-index:10;border-radius:inherit;box-shadow:inset 0 1px #ffffff1a,inset 0 0 0 1px #ffffff0d;content:\"\";pointer-events:none}.z-media-player-popover[data-state=open]{animation:zUiMediaSettingsPopoverIn .16s cubic-bezier(.2,.85,.25,1) forwards}.z-media-player-popover[data-state=closed]{animation:zUiMediaSettingsPopoverOut .16s cubic-bezier(.4,0,.2,1) forwards!important}.z-media-player-popover .bg-popover{background-color:transparent!important;color:#e4e4e7!important}.z-media-player-popover .z-media-player-settings{overflow:hidden;transform-origin:bottom right;animation:zUiMediaSettingsPanelIn .16s cubic-bezier(.2,.85,.25,1)}.z-media-player-popover .z-media-player-settings-panel{gap:4px;transform-origin:top right;animation:zUiMediaSettingsPanelIn .14s cubic-bezier(.2,.85,.25,1);will-change:opacity,transform}.z-media-player-popover .z-media-player-settings.z-main-panel .z-media-player-settings-panel{animation-name:zUiMediaSettingsPanelFromLeft}.z-media-player-popover .z-media-player-settings.z-sub-panel .z-media-player-settings-panel{animation-name:zUiMediaSettingsPanelFromRight}.z-media-player-popover .z-media-player-settings-item,.z-media-player-popover .z-media-player-settings-back{position:relative;display:flex;width:100%;min-width:0;height:32px;min-height:32px;cursor:pointer;align-items:center;border:0;border-radius:4px;background-color:transparent;padding:0 8px;color:#e4e4e7;font-size:12px;font-weight:500;line-height:1;outline:none;transition:background-color .12s ease,color .12s ease}.z-media-player-popover .z-media-player-settings-item{justify-content:space-between;gap:12px;text-align:left}.z-media-player-popover .z-media-player-settings-item:before{position:absolute;top:7px;bottom:7px;left:0;width:2px;border-radius:9999px;background-color:#fff;content:\"\";opacity:0;transform:scaleY(.5);transition:opacity .12s ease,transform .12s ease}.z-media-player-popover .z-media-player-settings-item.z-active{background-color:#ffffff14;color:#fff;font-weight:600}.z-media-player-popover .z-media-player-settings-item.z-active:before{opacity:1;transform:scaleY(1)}.z-media-player-popover .z-media-player-settings-back{justify-content:flex-start;gap:8px;color:#f4f4f5;font-weight:600}.z-media-player-popover .z-media-player-settings-item:hover,.z-media-player-popover .z-media-player-settings-back:hover,.z-media-player-popover .z-media-player-settings-item:focus-visible,.z-media-player-popover .z-media-player-settings-back:focus-visible{background-color:#ffffff1a}@keyframes zUiMediaSettingsPopoverIn{0%{opacity:0;transform:translateY(6px) scale(.94)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes zUiMediaSettingsPopoverOut{0%{opacity:1;transform:translateY(0) scale(1)}to{opacity:0;transform:translateY(4px) scale(.96)}}@keyframes zUiMediaSettingsPanelIn{0%{opacity:0;transform:translateY(3px)}to{opacity:1;transform:translateY(0)}}@keyframes zUiMediaSettingsPanelFromRight{0%{opacity:0;transform:translate(8px)}to{opacity:1;transform:translate(0)}}@keyframes zUiMediaSettingsPanelFromLeft{0%{opacity:0;transform:translate(-5px)}to{opacity:1;transform:translate(0)}}\n"] }]
|
|
1006
|
+
}], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], zSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSrc", required: true }] }], zSources: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSources", required: false }] }], zPoster: [{ type: i0.Input, args: [{ isSignal: true, alias: "zPoster", required: false }] }], zAutoplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAutoplay", required: false }] }], zLoop: [{ type: i0.Input, args: [{ isSignal: true, alias: "zLoop", required: false }] }], zMuted: [{ type: i0.Input, args: [{ isSignal: true, alias: "zMuted", required: false }] }], zVolume: [{ type: i0.Input, args: [{ isSignal: true, alias: "zVolume", required: false }] }], zSubtitles: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSubtitles", required: false }] }], zThumbnailsVtt: [{ type: i0.Input, args: [{ isSignal: true, alias: "zThumbnailsVtt", required: false }] }], zControls: [{ type: i0.Input, args: [{ isSignal: true, alias: "zControls", required: false }] }], zLoaded: [{ type: i0.Output, args: ["zLoaded"] }], zError: [{ type: i0.Output, args: ["zError"] }], zPlay: [{ type: i0.Output, args: ["zPlay"] }], zTimeUpdate: [{ type: i0.Output, args: ["zTimeUpdate"] }], zVolumeChange: [{ type: i0.Output, args: ["zVolumeChange"] }], zFullscreenChange: [{ type: i0.Output, args: ["zFullscreenChange"] }], zTheaterModeChange: [{ type: i0.Output, args: ["zTheaterModeChange"] }], zMiniplayerChange: [{ type: i0.Output, args: ["zMiniplayerChange"] }], zQualityChange: [{ type: i0.Output, args: ["zQualityChange"] }], videoElement: [{ type: i0.ViewChild, args: ['videoElement', { isSignal: true }] }], timelineContainer: [{ type: i0.ViewChild, args: ['timelineContainer', { isSignal: true }] }], _settingsPopover: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ZPopoverDirective), { isSignal: true }] }] } });
|
|
704
1007
|
|
|
705
1008
|
/**
|
|
706
1009
|
* Generated bundle index. Do not edit.
|