@edm-webplayer/webplayer-angular 0.2.4 → 0.2.6
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.
|
@@ -84,11 +84,10 @@ class PlyrPlayerComponent {
|
|
|
84
84
|
menuObserver;
|
|
85
85
|
playbackEnded = false;
|
|
86
86
|
overlayBlockAll = false;
|
|
87
|
-
endedPosterOverlay;
|
|
88
|
-
interactionUnmuteCleanup;
|
|
89
|
-
endedLoggedOnPause = false;
|
|
90
87
|
liveInteractionBlocker;
|
|
91
|
-
|
|
88
|
+
controlsSafeZonePxDesktop = 96;
|
|
89
|
+
controlsSafeZonePxTouch = 144;
|
|
90
|
+
overlayInteractionGapPx = 20;
|
|
92
91
|
onResize = () => {
|
|
93
92
|
this.updateRecommendationBlockerSafeZone();
|
|
94
93
|
};
|
|
@@ -124,9 +123,6 @@ class PlyrPlayerComponent {
|
|
|
124
123
|
}
|
|
125
124
|
catch { }
|
|
126
125
|
};
|
|
127
|
-
controlsSafeZonePxDesktop = 96;
|
|
128
|
-
controlsSafeZonePxTouch = 144;
|
|
129
|
-
overlayInteractionGapPx = 20;
|
|
130
126
|
constructor(progressService, document) {
|
|
131
127
|
this.progressService = progressService;
|
|
132
128
|
this.document = document;
|
|
@@ -137,29 +133,22 @@ class PlyrPlayerComponent {
|
|
|
137
133
|
this.applyTheme(this.config.ui.colors);
|
|
138
134
|
}
|
|
139
135
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const tolerance = 0.35;
|
|
146
|
-
if (Math.abs(pending.value - target) > tolerance) {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
const callbacks = pending.queue.slice();
|
|
150
|
-
pending.queue.length = 0;
|
|
151
|
-
if (success) {
|
|
152
|
-
this.pendingStartTime = undefined;
|
|
136
|
+
isTouchDevice() {
|
|
137
|
+
if (typeof window === "undefined") {
|
|
138
|
+
return false;
|
|
153
139
|
}
|
|
154
|
-
|
|
155
|
-
|
|
140
|
+
if (typeof navigator !== "undefined" && navigator.maxTouchPoints > 0) {
|
|
141
|
+
return true;
|
|
156
142
|
}
|
|
157
|
-
|
|
143
|
+
if (window.matchMedia) {
|
|
158
144
|
try {
|
|
159
|
-
|
|
145
|
+
if (window.matchMedia("(pointer: coarse)").matches) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
160
148
|
}
|
|
161
149
|
catch { }
|
|
162
|
-
}
|
|
150
|
+
}
|
|
151
|
+
return "ontouchstart" in window;
|
|
163
152
|
}
|
|
164
153
|
getControlsSafeZone() {
|
|
165
154
|
const controls = (this.plyr?.elements?.controls ??
|
|
@@ -245,45 +234,6 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
245
234
|
}`;
|
|
246
235
|
styleEl.textContent = cssRule;
|
|
247
236
|
}
|
|
248
|
-
normalizeLocaleCandidate(value) {
|
|
249
|
-
if (value === null || value === undefined) {
|
|
250
|
-
return null;
|
|
251
|
-
}
|
|
252
|
-
const candidate = String(value).trim();
|
|
253
|
-
if (!candidate) {
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
return candidate.replace(/_/g, "-");
|
|
257
|
-
}
|
|
258
|
-
resolveLocale() {
|
|
259
|
-
const candidates = [
|
|
260
|
-
this.config?.video_locale,
|
|
261
|
-
this.config?.video_language,
|
|
262
|
-
this.config?.locale,
|
|
263
|
-
this.config?.language,
|
|
264
|
-
this.config?.content_locale,
|
|
265
|
-
this.config?.content_language,
|
|
266
|
-
];
|
|
267
|
-
for (const candidate of candidates) {
|
|
268
|
-
const normalized = this.normalizeLocaleCandidate(candidate);
|
|
269
|
-
if (normalized) {
|
|
270
|
-
return normalized;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return "pt-BR";
|
|
274
|
-
}
|
|
275
|
-
resolveCaptionsLanguage(locale) {
|
|
276
|
-
const explicit = this.normalizeLocaleCandidate(this.config?.captions_locale ??
|
|
277
|
-
this.config?.captions_language ??
|
|
278
|
-
this.config?.subtitle_locale ??
|
|
279
|
-
this.config?.subtitle_language ??
|
|
280
|
-
null);
|
|
281
|
-
const baseLocale = explicit || locale;
|
|
282
|
-
if (!baseLocale)
|
|
283
|
-
return null;
|
|
284
|
-
const code = baseLocale.split("-")[0]?.trim();
|
|
285
|
-
return code ? code.toLowerCase() : null;
|
|
286
|
-
}
|
|
287
237
|
getVideoWrapper() {
|
|
288
238
|
const container = this.plyr?.elements?.container;
|
|
289
239
|
if (!container)
|
|
@@ -404,94 +354,6 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
404
354
|
});
|
|
405
355
|
this.menuObserver = observer;
|
|
406
356
|
}
|
|
407
|
-
isIOSDevice() {
|
|
408
|
-
if (typeof navigator === "undefined")
|
|
409
|
-
return false;
|
|
410
|
-
const ua = navigator.userAgent || "";
|
|
411
|
-
const iOS = /iPad|iPhone|iPod/.test(ua);
|
|
412
|
-
const iPadOS = navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
|
|
413
|
-
return iOS || iPadOS;
|
|
414
|
-
}
|
|
415
|
-
isIOSContext() {
|
|
416
|
-
if (this.isIOSDevice()) {
|
|
417
|
-
return true;
|
|
418
|
-
}
|
|
419
|
-
if (typeof window === "undefined") {
|
|
420
|
-
return false;
|
|
421
|
-
}
|
|
422
|
-
try {
|
|
423
|
-
const params = new URLSearchParams(window.location.search);
|
|
424
|
-
const os = params.get("os");
|
|
425
|
-
if (os && os.toLowerCase() === "ios") {
|
|
426
|
-
return true;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
catch { }
|
|
430
|
-
return false;
|
|
431
|
-
}
|
|
432
|
-
isTouchDevice() {
|
|
433
|
-
if (typeof window === "undefined") {
|
|
434
|
-
return false;
|
|
435
|
-
}
|
|
436
|
-
if (typeof navigator !== "undefined" && navigator.maxTouchPoints > 0) {
|
|
437
|
-
return true;
|
|
438
|
-
}
|
|
439
|
-
if (window.matchMedia) {
|
|
440
|
-
try {
|
|
441
|
-
if (window.matchMedia("(pointer: coarse)").matches) {
|
|
442
|
-
return true;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
catch { }
|
|
446
|
-
}
|
|
447
|
-
return "ontouchstart" in window;
|
|
448
|
-
}
|
|
449
|
-
isIframeProvider() {
|
|
450
|
-
return this.config?.video_host === "youtube";
|
|
451
|
-
}
|
|
452
|
-
shouldDisableAutoplayForIOS() {
|
|
453
|
-
if (this.config?.is_live) {
|
|
454
|
-
return false;
|
|
455
|
-
}
|
|
456
|
-
return this.isIOSDevice();
|
|
457
|
-
}
|
|
458
|
-
shouldResetProgressToBeginning() {
|
|
459
|
-
const host = this.config?.video_host;
|
|
460
|
-
if (host !== "youtube") {
|
|
461
|
-
return false;
|
|
462
|
-
}
|
|
463
|
-
const seenFlag = this.config?.seen;
|
|
464
|
-
if (seenFlag === true || seenFlag === "true" || seenFlag === 1) {
|
|
465
|
-
return true;
|
|
466
|
-
}
|
|
467
|
-
const rawPercent = this.config?.last_percent;
|
|
468
|
-
if (rawPercent === undefined || rawPercent === null) {
|
|
469
|
-
return false;
|
|
470
|
-
}
|
|
471
|
-
let percent = Number(rawPercent);
|
|
472
|
-
if (!Number.isFinite(percent)) {
|
|
473
|
-
return false;
|
|
474
|
-
}
|
|
475
|
-
if (percent > 1) {
|
|
476
|
-
percent = percent / 100;
|
|
477
|
-
}
|
|
478
|
-
return percent >= 0.8;
|
|
479
|
-
}
|
|
480
|
-
shouldEmitEndedOnPause() {
|
|
481
|
-
if (!this.isIOSContext()) {
|
|
482
|
-
return false;
|
|
483
|
-
}
|
|
484
|
-
if (this.endedLoggedOnPause) {
|
|
485
|
-
return false;
|
|
486
|
-
}
|
|
487
|
-
const duration = this.plyr?.duration;
|
|
488
|
-
if (!Number.isFinite(duration) || !duration || duration <= 0) {
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
const current = this.plyr?.currentTime || 0;
|
|
492
|
-
const percent = duration > 0 ? current / duration : 0;
|
|
493
|
-
return percent >= 0.8 && duration - current < 1;
|
|
494
|
-
}
|
|
495
357
|
hasReachedEndThreshold() {
|
|
496
358
|
const duration = this.plyr?.duration;
|
|
497
359
|
if (!Number.isFinite(duration) || !duration || duration <= 0) {
|
|
@@ -542,520 +404,6 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
542
404
|
}
|
|
543
405
|
this.liveInteractionBlocker = null;
|
|
544
406
|
}
|
|
545
|
-
wantsAutoplay() {
|
|
546
|
-
return !!(this.config?.autoplay || this.config?.is_live);
|
|
547
|
-
}
|
|
548
|
-
shouldAutoplay() {
|
|
549
|
-
if (this.config?.is_live) {
|
|
550
|
-
return true;
|
|
551
|
-
}
|
|
552
|
-
if (this.shouldDisableAutoplayForIOS()) {
|
|
553
|
-
return false;
|
|
554
|
-
}
|
|
555
|
-
return this.wantsAutoplay();
|
|
556
|
-
}
|
|
557
|
-
requiresMutedAutoplay() {
|
|
558
|
-
if (this.config?.is_live && this.isIOSDevice()) {
|
|
559
|
-
return true;
|
|
560
|
-
}
|
|
561
|
-
if (!this.isIOSDevice() || this.shouldDisableAutoplayForIOS()) {
|
|
562
|
-
return false;
|
|
563
|
-
}
|
|
564
|
-
return this.wantsAutoplay();
|
|
565
|
-
}
|
|
566
|
-
shouldBlockIframeAutoplay() {
|
|
567
|
-
return this.shouldDisableAutoplayForIOS();
|
|
568
|
-
}
|
|
569
|
-
stopRestrictedIframeAutoplay() {
|
|
570
|
-
if (!this.shouldBlockIframeAutoplay()) {
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
this.clearAutoplayUnlockListener();
|
|
574
|
-
this.setPlayerMutedState(false);
|
|
575
|
-
const media = this.videoRef?.nativeElement ?? null;
|
|
576
|
-
if (media) {
|
|
577
|
-
try {
|
|
578
|
-
media.autoplay = false;
|
|
579
|
-
}
|
|
580
|
-
catch { }
|
|
581
|
-
try {
|
|
582
|
-
media.pause();
|
|
583
|
-
}
|
|
584
|
-
catch { }
|
|
585
|
-
try {
|
|
586
|
-
media.muted = false;
|
|
587
|
-
}
|
|
588
|
-
catch { }
|
|
589
|
-
}
|
|
590
|
-
if (this.plyr) {
|
|
591
|
-
try {
|
|
592
|
-
this.plyr.autoplay = false;
|
|
593
|
-
}
|
|
594
|
-
catch { }
|
|
595
|
-
try {
|
|
596
|
-
if (typeof this.plyr.pause === "function") {
|
|
597
|
-
this.plyr.pause();
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
catch { }
|
|
601
|
-
}
|
|
602
|
-
const embed = this.plyr?.embed;
|
|
603
|
-
if (embed) {
|
|
604
|
-
try {
|
|
605
|
-
if (typeof embed.pause === "function") {
|
|
606
|
-
embed.pause();
|
|
607
|
-
}
|
|
608
|
-
if (typeof embed.setMuted === "function") {
|
|
609
|
-
embed.setMuted(false);
|
|
610
|
-
}
|
|
611
|
-
if (typeof embed.setVolume === "function") {
|
|
612
|
-
embed.setVolume(1);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
catch { }
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
setPlayerMutedState(muted, volume = 1, target) {
|
|
619
|
-
if (this.plyr) {
|
|
620
|
-
try {
|
|
621
|
-
this.plyr.muted = muted;
|
|
622
|
-
}
|
|
623
|
-
catch { }
|
|
624
|
-
if (!muted) {
|
|
625
|
-
try {
|
|
626
|
-
this.plyr.volume = volume;
|
|
627
|
-
}
|
|
628
|
-
catch { }
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
const media = target ?? this.videoRef?.nativeElement ?? null;
|
|
632
|
-
if (media) {
|
|
633
|
-
try {
|
|
634
|
-
media.muted = muted;
|
|
635
|
-
}
|
|
636
|
-
catch { }
|
|
637
|
-
if (!muted && typeof media.volume === "number") {
|
|
638
|
-
try {
|
|
639
|
-
media.volume = volume;
|
|
640
|
-
}
|
|
641
|
-
catch { }
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
temporarilyMuteForSeek(target) {
|
|
646
|
-
const media = target ?? this.videoRef?.nativeElement ?? null;
|
|
647
|
-
const prevVideoMuted = media?.muted ?? false;
|
|
648
|
-
const prevPlyrMuted = !!this.plyr?.muted;
|
|
649
|
-
const shouldForceUnmute = !this.shouldAutoplay();
|
|
650
|
-
try {
|
|
651
|
-
if (media) {
|
|
652
|
-
media.muted = true;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
catch { }
|
|
656
|
-
try {
|
|
657
|
-
if (this.plyr) {
|
|
658
|
-
this.plyr.muted = true;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
catch { }
|
|
662
|
-
return () => {
|
|
663
|
-
try {
|
|
664
|
-
if (media) {
|
|
665
|
-
media.muted = prevVideoMuted;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
catch { }
|
|
669
|
-
try {
|
|
670
|
-
if (this.plyr) {
|
|
671
|
-
this.plyr.muted = prevPlyrMuted;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
catch { }
|
|
675
|
-
if (shouldForceUnmute) {
|
|
676
|
-
this.setPlayerMutedState(false, 1, media);
|
|
677
|
-
}
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
clearAutoplayUnlockListener() {
|
|
681
|
-
if (this.interactionUnmuteCleanup) {
|
|
682
|
-
try {
|
|
683
|
-
this.interactionUnmuteCleanup();
|
|
684
|
-
}
|
|
685
|
-
catch { }
|
|
686
|
-
this.interactionUnmuteCleanup = undefined;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
ensureAutoplayUnlockListener() {
|
|
690
|
-
if (typeof window === "undefined" ||
|
|
691
|
-
this.interactionUnmuteCleanup ||
|
|
692
|
-
!this.shouldAutoplay()) {
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
const capture = true;
|
|
696
|
-
const handler = () => {
|
|
697
|
-
this.setPlayerMutedState(false);
|
|
698
|
-
try {
|
|
699
|
-
this.plyr?.play();
|
|
700
|
-
}
|
|
701
|
-
catch { }
|
|
702
|
-
this.clearAutoplayUnlockListener();
|
|
703
|
-
};
|
|
704
|
-
window.addEventListener("touchend", handler, capture);
|
|
705
|
-
window.addEventListener("pointerup", handler, capture);
|
|
706
|
-
window.addEventListener("click", handler, capture);
|
|
707
|
-
this.interactionUnmuteCleanup = () => {
|
|
708
|
-
window.removeEventListener("touchend", handler, capture);
|
|
709
|
-
window.removeEventListener("pointerup", handler, capture);
|
|
710
|
-
window.removeEventListener("click", handler, capture);
|
|
711
|
-
this.interactionUnmuteCleanup = undefined;
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
ensureIframeAutoplayPermissions() {
|
|
715
|
-
if (!this.isIframeProvider()) {
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
const container = this.plyr?.elements?.container;
|
|
719
|
-
if (!container)
|
|
720
|
-
return;
|
|
721
|
-
const iframe = container.querySelector("iframe") ?? null;
|
|
722
|
-
if (!iframe)
|
|
723
|
-
return;
|
|
724
|
-
const allowRaw = iframe.getAttribute("allow") || "";
|
|
725
|
-
const tokens = new Set(allowRaw
|
|
726
|
-
.split(";")
|
|
727
|
-
.map((token) => token.trim())
|
|
728
|
-
.filter(Boolean));
|
|
729
|
-
[
|
|
730
|
-
"autoplay",
|
|
731
|
-
"fullscreen",
|
|
732
|
-
"encrypted-media",
|
|
733
|
-
"picture-in-picture",
|
|
734
|
-
"gyroscope",
|
|
735
|
-
].forEach((rule) => tokens.add(rule));
|
|
736
|
-
iframe.setAttribute("allow", Array.from(tokens).join("; "));
|
|
737
|
-
if (this.config.video_host === "youtube") {
|
|
738
|
-
iframe.setAttribute("playsinline", "1");
|
|
739
|
-
iframe.setAttribute("webkit-playsinline", "1");
|
|
740
|
-
}
|
|
741
|
-
iframe.setAttribute("allowfullscreen", "true");
|
|
742
|
-
iframe.setAttribute("data-keepplaying", "true");
|
|
743
|
-
}
|
|
744
|
-
scheduleUserInteractionUnmute(video) {
|
|
745
|
-
this.unmuteAfterDelay(video || undefined);
|
|
746
|
-
this.ensureAutoplayUnlockListener();
|
|
747
|
-
}
|
|
748
|
-
requestStartTimeSeek(start, opts = {}) {
|
|
749
|
-
if (!(start > 0)) {
|
|
750
|
-
this.pendingStartTime = undefined;
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
const video = opts.video ?? this.videoRef?.nativeElement ?? null;
|
|
754
|
-
const normalized = Math.max(0, start);
|
|
755
|
-
const tolerance = 0.35;
|
|
756
|
-
const pending = this.pendingStartTime;
|
|
757
|
-
if (pending && Math.abs(pending.value - normalized) <= tolerance) {
|
|
758
|
-
pending.video = video ?? pending.video;
|
|
759
|
-
pending.blockPlayback = true;
|
|
760
|
-
}
|
|
761
|
-
else {
|
|
762
|
-
const carryQueue = this.pendingStartTime?.queue ?? [];
|
|
763
|
-
this.pendingStartTime = {
|
|
764
|
-
value: normalized,
|
|
765
|
-
video,
|
|
766
|
-
queue: carryQueue,
|
|
767
|
-
blockPlayback: true,
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
this.applyStartTimeToCurrentSource(normalized, { video });
|
|
771
|
-
}
|
|
772
|
-
deferUntilStartTimeSettles(callback) {
|
|
773
|
-
if (!this.pendingStartTime || !this.pendingStartTime.blockPlayback)
|
|
774
|
-
return false;
|
|
775
|
-
this.pendingStartTime.queue.push(callback);
|
|
776
|
-
return true;
|
|
777
|
-
}
|
|
778
|
-
retryPendingStartTime() {
|
|
779
|
-
if (!this.pendingStartTime) {
|
|
780
|
-
return;
|
|
781
|
-
}
|
|
782
|
-
const { value, video } = this.pendingStartTime;
|
|
783
|
-
this.applyStartTimeToCurrentSource(value, {
|
|
784
|
-
video: video ?? this.videoRef?.nativeElement ?? null,
|
|
785
|
-
attempts: 3,
|
|
786
|
-
delayMs: 150,
|
|
787
|
-
});
|
|
788
|
-
}
|
|
789
|
-
applyStartTimeToCurrentSource(start, opts = {}) {
|
|
790
|
-
if (!this.plyr || !(start > 0)) {
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
const video = opts.video ?? this.videoRef?.nativeElement ?? null;
|
|
794
|
-
let attempts = Math.max(1, opts.attempts ?? 6);
|
|
795
|
-
const delayMs = opts.delayMs ?? 250;
|
|
796
|
-
const tolerance = 0.35;
|
|
797
|
-
const scheduleRetry = () => {
|
|
798
|
-
attempts -= 1;
|
|
799
|
-
if (attempts > 0) {
|
|
800
|
-
setTimeout(trySeek, delayMs);
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
this.flushPendingStartTime(start, false);
|
|
804
|
-
};
|
|
805
|
-
const verifyAfterAttempt = () => {
|
|
806
|
-
const current = this.plyr?.currentTime || 0;
|
|
807
|
-
if (Math.abs(current - start) <= tolerance) {
|
|
808
|
-
attempts = 0;
|
|
809
|
-
this.flushPendingStartTime(start, true);
|
|
810
|
-
return;
|
|
811
|
-
}
|
|
812
|
-
scheduleRetry();
|
|
813
|
-
};
|
|
814
|
-
const trySeek = () => {
|
|
815
|
-
if (!this.plyr)
|
|
816
|
-
return;
|
|
817
|
-
let maybePromise = null;
|
|
818
|
-
try {
|
|
819
|
-
const result = this.seekUsingHostAPI(start, video);
|
|
820
|
-
if (result && typeof result.then === "function") {
|
|
821
|
-
maybePromise = result;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
catch {
|
|
825
|
-
scheduleRetry();
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
if (maybePromise) {
|
|
829
|
-
maybePromise
|
|
830
|
-
.then(() => verifyAfterAttempt())
|
|
831
|
-
.catch(() => scheduleRetry());
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
verifyAfterAttempt();
|
|
835
|
-
};
|
|
836
|
-
trySeek();
|
|
837
|
-
}
|
|
838
|
-
seekUsingHostAPI(target, video) {
|
|
839
|
-
if (!Number.isFinite(target) || target < 0) {
|
|
840
|
-
return null;
|
|
841
|
-
}
|
|
842
|
-
const restoreMute = this.temporarilyMuteForSeek(video);
|
|
843
|
-
if (video) {
|
|
844
|
-
try {
|
|
845
|
-
video.currentTime = target;
|
|
846
|
-
}
|
|
847
|
-
catch { }
|
|
848
|
-
}
|
|
849
|
-
try {
|
|
850
|
-
this.plyr.currentTime = target;
|
|
851
|
-
}
|
|
852
|
-
catch { }
|
|
853
|
-
if (restoreMute) {
|
|
854
|
-
setTimeout(() => restoreMute(), 300);
|
|
855
|
-
}
|
|
856
|
-
return null;
|
|
857
|
-
}
|
|
858
|
-
startAutoplayFlow(video) {
|
|
859
|
-
if (!this.shouldAutoplay() || !this.plyr)
|
|
860
|
-
return;
|
|
861
|
-
if (this.deferUntilStartTimeSettles(() => this.startAutoplayFlow(video))) {
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
const media = video ?? this.videoRef?.nativeElement ?? null;
|
|
865
|
-
const tryPlay = (muted) => {
|
|
866
|
-
this.setPlayerMutedState(muted, 1, media);
|
|
867
|
-
let playResult;
|
|
868
|
-
try {
|
|
869
|
-
playResult = this.plyr.play();
|
|
870
|
-
this.forceIframeAutoplayFallback();
|
|
871
|
-
}
|
|
872
|
-
catch (error) {
|
|
873
|
-
if (!muted) {
|
|
874
|
-
tryPlay(true);
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
if (media) {
|
|
878
|
-
try {
|
|
879
|
-
const nativePlay = media.play();
|
|
880
|
-
if (nativePlay && typeof nativePlay.catch === "function") {
|
|
881
|
-
nativePlay.catch(() => { });
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
catch { }
|
|
885
|
-
}
|
|
886
|
-
this.scheduleUserInteractionUnmute(media);
|
|
887
|
-
return;
|
|
888
|
-
}
|
|
889
|
-
if (playResult &&
|
|
890
|
-
typeof playResult.catch === "function") {
|
|
891
|
-
playResult.catch((error) => {
|
|
892
|
-
if (!muted) {
|
|
893
|
-
tryPlay(true);
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
if (media) {
|
|
897
|
-
try {
|
|
898
|
-
const nativePlay = media.play();
|
|
899
|
-
if (nativePlay && typeof nativePlay.catch === "function") {
|
|
900
|
-
nativePlay.catch(() => { });
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
catch { }
|
|
904
|
-
}
|
|
905
|
-
this.scheduleUserInteractionUnmute(media);
|
|
906
|
-
});
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
if (muted) {
|
|
910
|
-
this.scheduleUserInteractionUnmute(media);
|
|
911
|
-
}
|
|
912
|
-
else {
|
|
913
|
-
this.clearAutoplayUnlockListener();
|
|
914
|
-
}
|
|
915
|
-
};
|
|
916
|
-
if (this.requiresMutedAutoplay()) {
|
|
917
|
-
tryPlay(true);
|
|
918
|
-
}
|
|
919
|
-
else {
|
|
920
|
-
tryPlay(false);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
forceIframeAutoplayFallback() {
|
|
924
|
-
if (!this.shouldAutoplay() || !this.isIframeProvider()) {
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
const embed = this.plyr?.embed;
|
|
928
|
-
if (!embed)
|
|
929
|
-
return;
|
|
930
|
-
if (this.config.video_host === "youtube") {
|
|
931
|
-
if (typeof embed.playVideo === "function") {
|
|
932
|
-
try {
|
|
933
|
-
embed.playVideo();
|
|
934
|
-
}
|
|
935
|
-
catch { }
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
setupControlsVisibility() {
|
|
940
|
-
const container = this.plyr?.elements?.container;
|
|
941
|
-
if (!container) {
|
|
942
|
-
this.controlsVisibilityCleanup = undefined;
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
if (this.isTouchDevice()) {
|
|
946
|
-
const HIDE_DELAY = 2000;
|
|
947
|
-
let hideTimeout = null;
|
|
948
|
-
const clearHideTimeout = () => {
|
|
949
|
-
if (hideTimeout) {
|
|
950
|
-
clearTimeout(hideTimeout);
|
|
951
|
-
hideTimeout = null;
|
|
952
|
-
}
|
|
953
|
-
};
|
|
954
|
-
const showControls = () => {
|
|
955
|
-
container.classList.remove("plyr--auto-hide-controls");
|
|
956
|
-
};
|
|
957
|
-
const hideControls = () => {
|
|
958
|
-
if (this.plyr?.playing) {
|
|
959
|
-
container.classList.add("plyr--auto-hide-controls");
|
|
960
|
-
}
|
|
961
|
-
};
|
|
962
|
-
const scheduleHide = () => {
|
|
963
|
-
clearHideTimeout();
|
|
964
|
-
if (!this.plyr?.playing)
|
|
965
|
-
return;
|
|
966
|
-
hideTimeout = setTimeout(() => hideControls(), HIDE_DELAY);
|
|
967
|
-
};
|
|
968
|
-
const onPlay = () => {
|
|
969
|
-
showControls();
|
|
970
|
-
this.overlayBlockAll = false;
|
|
971
|
-
};
|
|
972
|
-
const onPause = () => {
|
|
973
|
-
clearHideTimeout();
|
|
974
|
-
showControls();
|
|
975
|
-
this.overlayBlockAll = true;
|
|
976
|
-
this.updateTopClickBlockerSafeZone();
|
|
977
|
-
this.topClickBlocker?.setAttribute("data-state", "paused");
|
|
978
|
-
if (this.shouldEmitEndedOnPause()) {
|
|
979
|
-
this.endedLoggedOnPause = true;
|
|
980
|
-
console.log("event: ended");
|
|
981
|
-
}
|
|
982
|
-
};
|
|
983
|
-
const onEnded = () => {
|
|
984
|
-
clearHideTimeout();
|
|
985
|
-
showControls();
|
|
986
|
-
this.overlayBlockAll = true;
|
|
987
|
-
this.updateTopClickBlockerSafeZone();
|
|
988
|
-
this.topClickBlocker?.setAttribute("data-state", "ended");
|
|
989
|
-
};
|
|
990
|
-
const onUserInteraction = () => {
|
|
991
|
-
showControls();
|
|
992
|
-
scheduleHide();
|
|
993
|
-
};
|
|
994
|
-
this.plyr?.on?.("play", onPlay);
|
|
995
|
-
this.plyr?.on?.("pause", onPause);
|
|
996
|
-
this.plyr?.on?.("ended", onEnded);
|
|
997
|
-
container.addEventListener("touchstart", onUserInteraction);
|
|
998
|
-
container.addEventListener("click", onUserInteraction);
|
|
999
|
-
this.controlsVisibilityCleanup = () => {
|
|
1000
|
-
clearHideTimeout();
|
|
1001
|
-
this.plyr?.off?.("play", onPlay);
|
|
1002
|
-
this.plyr?.off?.("pause", onPause);
|
|
1003
|
-
this.plyr?.off?.("ended", onEnded);
|
|
1004
|
-
container.removeEventListener("touchstart", onUserInteraction);
|
|
1005
|
-
container.removeEventListener("click", onUserInteraction);
|
|
1006
|
-
container.classList.remove("plyr--auto-hide-controls");
|
|
1007
|
-
};
|
|
1008
|
-
onPlay();
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
|
-
let hovering = false;
|
|
1012
|
-
const toggleClass = () => {
|
|
1013
|
-
const playing = !!this.plyr?.playing;
|
|
1014
|
-
if (playing && !hovering) {
|
|
1015
|
-
container.classList.add("plyr--auto-hide-controls");
|
|
1016
|
-
}
|
|
1017
|
-
else {
|
|
1018
|
-
container.classList.remove("plyr--auto-hide-controls");
|
|
1019
|
-
}
|
|
1020
|
-
};
|
|
1021
|
-
const onPlay = () => toggleClass();
|
|
1022
|
-
const onPause = () => toggleClass();
|
|
1023
|
-
const onEnded = () => toggleClass();
|
|
1024
|
-
const onMouseEnter = () => {
|
|
1025
|
-
hovering = true;
|
|
1026
|
-
toggleClass();
|
|
1027
|
-
};
|
|
1028
|
-
const onMouseLeave = () => {
|
|
1029
|
-
hovering = false;
|
|
1030
|
-
toggleClass();
|
|
1031
|
-
};
|
|
1032
|
-
const onFocusIn = () => {
|
|
1033
|
-
hovering = true;
|
|
1034
|
-
toggleClass();
|
|
1035
|
-
};
|
|
1036
|
-
const onFocusOut = () => {
|
|
1037
|
-
hovering = container.matches(":hover");
|
|
1038
|
-
toggleClass();
|
|
1039
|
-
};
|
|
1040
|
-
this.plyr?.on?.("play", onPlay);
|
|
1041
|
-
this.plyr?.on?.("pause", onPause);
|
|
1042
|
-
this.plyr?.on?.("ended", onEnded);
|
|
1043
|
-
container.addEventListener("mouseenter", onMouseEnter);
|
|
1044
|
-
container.addEventListener("mouseleave", onMouseLeave);
|
|
1045
|
-
container.addEventListener("focusin", onFocusIn);
|
|
1046
|
-
container.addEventListener("focusout", onFocusOut);
|
|
1047
|
-
toggleClass();
|
|
1048
|
-
this.controlsVisibilityCleanup = () => {
|
|
1049
|
-
this.plyr?.off?.("play", onPlay);
|
|
1050
|
-
this.plyr?.off?.("pause", onPause);
|
|
1051
|
-
this.plyr?.off?.("ended", onEnded);
|
|
1052
|
-
container.removeEventListener("mouseenter", onMouseEnter);
|
|
1053
|
-
container.removeEventListener("mouseleave", onMouseLeave);
|
|
1054
|
-
container.removeEventListener("focusin", onFocusIn);
|
|
1055
|
-
container.removeEventListener("focusout", onFocusOut);
|
|
1056
|
-
container.classList.remove("plyr--auto-hide-controls");
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
407
|
bindFullscreenBehavior() {
|
|
1060
408
|
const onEnterFullscreen = () => {
|
|
1061
409
|
console.log("event: enterfullscreen");
|
|
@@ -1075,26 +423,10 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1075
423
|
this.controlsVisibilityCleanup?.();
|
|
1076
424
|
this.controlsVisibilityCleanup = undefined;
|
|
1077
425
|
this.plyr?.destroy();
|
|
1078
|
-
this.clearAutoplayUnlockListener();
|
|
1079
426
|
this.recommendationBlocker = null;
|
|
1080
427
|
const speedOptions = [1, 1.25, 1.5, 2];
|
|
1081
|
-
const locale =
|
|
1082
|
-
const captionsLanguage =
|
|
1083
|
-
const shouldAutoplay = this.shouldAutoplay();
|
|
1084
|
-
const startMuted = this.requiresMutedAutoplay();
|
|
1085
|
-
try {
|
|
1086
|
-
this.videoRef.nativeElement.muted = startMuted;
|
|
1087
|
-
}
|
|
1088
|
-
catch { }
|
|
1089
|
-
try {
|
|
1090
|
-
this.videoRef.nativeElement.playsInline =
|
|
1091
|
-
this.config?.video_host === "youtube";
|
|
1092
|
-
}
|
|
1093
|
-
catch { }
|
|
1094
|
-
try {
|
|
1095
|
-
this.videoRef.nativeElement.autoplay = shouldAutoplay;
|
|
1096
|
-
}
|
|
1097
|
-
catch { }
|
|
428
|
+
const locale = 'pt-BR';
|
|
429
|
+
const captionsLanguage = "pt";
|
|
1098
430
|
const isLive = !!this.config?.is_live;
|
|
1099
431
|
const youtubePlayerVars = {
|
|
1100
432
|
rel: 0,
|
|
@@ -1104,8 +436,6 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1104
436
|
youtubePlayerVars.playerVars = {
|
|
1105
437
|
hl: locale,
|
|
1106
438
|
cc_lang_pref: captionsLanguage || locale.split("-")[0]?.toLowerCase(),
|
|
1107
|
-
autoplay: shouldAutoplay ? 1 : 0,
|
|
1108
|
-
mute: startMuted ? 1 : 0,
|
|
1109
439
|
playsinline: 1,
|
|
1110
440
|
};
|
|
1111
441
|
if (captionsLanguage) {
|
|
@@ -1144,8 +474,8 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1144
474
|
clickToPlay: !isLive,
|
|
1145
475
|
hideControls: isLive ? true : false,
|
|
1146
476
|
keyboard: { focused: !isLive, global: false },
|
|
1147
|
-
autoplay:
|
|
1148
|
-
muted:
|
|
477
|
+
autoplay: isLive ? true : false,
|
|
478
|
+
muted: false,
|
|
1149
479
|
};
|
|
1150
480
|
plyrOptions.hl = locale;
|
|
1151
481
|
if (captionsLanguage) {
|
|
@@ -1160,34 +490,14 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1160
490
|
}
|
|
1161
491
|
this.updateLiveInteractionBlocker(isLive);
|
|
1162
492
|
this.bindFullscreenBehavior();
|
|
1163
|
-
this.setupControlsVisibility();
|
|
1164
493
|
this.ensureTopClickBlocker();
|
|
1165
494
|
this.observeMenuChanges();
|
|
1166
495
|
this.playbackEnded = false;
|
|
1167
496
|
this.overlayBlockAll = false;
|
|
1168
|
-
this.endedLoggedOnPause = false;
|
|
1169
497
|
window.removeEventListener("resize", this.onResize);
|
|
1170
498
|
window.addEventListener("resize", this.onResize);
|
|
1171
499
|
this.bindProgressEvents();
|
|
1172
|
-
this.loadSource(this.videoRef.nativeElement).then(() => {
|
|
1173
|
-
this.setupStartTime();
|
|
1174
|
-
});
|
|
1175
|
-
// Dispara o evento ready quando o player estiver pronto
|
|
1176
|
-
this.plyr.on("ready", () => {
|
|
1177
|
-
this.ensureIframeAutoplayPermissions();
|
|
1178
|
-
this.stopRestrictedIframeAutoplay();
|
|
1179
|
-
this.ready.emit();
|
|
1180
|
-
});
|
|
1181
|
-
}
|
|
1182
|
-
setupStartTime() {
|
|
1183
|
-
const apply = () => {
|
|
1184
|
-
const dur = this.plyr.duration || 0;
|
|
1185
|
-
const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, dur);
|
|
1186
|
-
this.plyr?.pause();
|
|
1187
|
-
this.requestStartTimeSeek(start);
|
|
1188
|
-
this.plyr.off("ready", apply);
|
|
1189
|
-
};
|
|
1190
|
-
this.plyr.on("ready", apply);
|
|
500
|
+
this.loadSource(this.videoRef.nativeElement).then(() => { });
|
|
1191
501
|
}
|
|
1192
502
|
bindProgressEvents() {
|
|
1193
503
|
const THROTTLE_MS = 300;
|
|
@@ -1220,7 +530,6 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1220
530
|
});
|
|
1221
531
|
};
|
|
1222
532
|
this.plyr?.on?.("play", () => {
|
|
1223
|
-
this.retryPendingStartTime();
|
|
1224
533
|
this.playbackEnded = false;
|
|
1225
534
|
this.overlayBlockAll = false;
|
|
1226
535
|
this.updateRecommendationBlockerSafeZone();
|
|
@@ -1257,7 +566,6 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1257
566
|
this.playbackEnded = true;
|
|
1258
567
|
this.overlayBlockAll = true;
|
|
1259
568
|
this.updateRecommendationBlockerSafeZone();
|
|
1260
|
-
this.endedLoggedOnPause = true;
|
|
1261
569
|
console.log("event: ended");
|
|
1262
570
|
parent.postMessage("event: ended", "*");
|
|
1263
571
|
this.postCastEvent({
|
|
@@ -1268,48 +576,6 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1268
576
|
this.toggleRecommendationBlocker(true);
|
|
1269
577
|
});
|
|
1270
578
|
}
|
|
1271
|
-
unmuteAfterDelay(video, delayMs = 2000, volume = 1) {
|
|
1272
|
-
if (!this.shouldAutoplay())
|
|
1273
|
-
return;
|
|
1274
|
-
setTimeout(() => {
|
|
1275
|
-
try {
|
|
1276
|
-
if (this.plyr) {
|
|
1277
|
-
this.plyr.muted = false;
|
|
1278
|
-
this.plyr.volume = volume;
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
catch { }
|
|
1282
|
-
try {
|
|
1283
|
-
if (video) {
|
|
1284
|
-
video.muted = false;
|
|
1285
|
-
if (typeof video.volume === "number")
|
|
1286
|
-
video.volume = volume;
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
catch { }
|
|
1290
|
-
}, delayMs);
|
|
1291
|
-
}
|
|
1292
|
-
resetPlaybackRate(video) {
|
|
1293
|
-
try {
|
|
1294
|
-
if (this.plyr && typeof this.plyr.speed !== "undefined") {
|
|
1295
|
-
this.plyr.speed = 1;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
catch { }
|
|
1299
|
-
const media = video ?? this.videoRef?.nativeElement ?? null;
|
|
1300
|
-
if (media) {
|
|
1301
|
-
try {
|
|
1302
|
-
media.playbackRate = 1;
|
|
1303
|
-
}
|
|
1304
|
-
catch { }
|
|
1305
|
-
try {
|
|
1306
|
-
if (media.defaultPlaybackRate !== undefined) {
|
|
1307
|
-
media.defaultPlaybackRate = 1;
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
catch { }
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
579
|
loadSource(video) {
|
|
1314
580
|
return new Promise((resolve) => {
|
|
1315
581
|
if (!this.config) {
|
|
@@ -1319,19 +585,11 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1319
585
|
this.hls.destroy();
|
|
1320
586
|
this.hls = undefined;
|
|
1321
587
|
}
|
|
1322
|
-
this.resetPlaybackRate(video);
|
|
1323
588
|
if (this.config.video_host === "youtube") {
|
|
1324
589
|
const onReady = () => {
|
|
1325
590
|
this.plyr.off("ready", onReady);
|
|
1326
|
-
this.ensureIframeAutoplayPermissions();
|
|
1327
591
|
const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, this.plyr.duration);
|
|
1328
|
-
this.
|
|
1329
|
-
if (this.shouldAutoplay()) {
|
|
1330
|
-
this.startAutoplayFlow();
|
|
1331
|
-
}
|
|
1332
|
-
else if (this.shouldBlockIframeAutoplay()) {
|
|
1333
|
-
this.stopRestrictedIframeAutoplay();
|
|
1334
|
-
}
|
|
592
|
+
this.plyr?.forward(start);
|
|
1335
593
|
parent.postMessage("playerIsReadyToPlay", "*");
|
|
1336
594
|
parent.postMessage("duration:" + this.plyr.duration, "*");
|
|
1337
595
|
console.log("event: current_time " + this.plyr.currentTime);
|
|
@@ -1356,6 +614,7 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1356
614
|
}, 1000);
|
|
1357
615
|
}
|
|
1358
616
|
};
|
|
617
|
+
this.ready.emit();
|
|
1359
618
|
this.plyr.on("ready", onReady);
|
|
1360
619
|
}
|
|
1361
620
|
if (this.config.video_host === "youtube") {
|
|
@@ -1388,8 +647,7 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1388
647
|
const dur = this.plyr.duration || video.duration || 0;
|
|
1389
648
|
const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, dur);
|
|
1390
649
|
console.log(start);
|
|
1391
|
-
this.
|
|
1392
|
-
this.startAutoplayFlow(video);
|
|
650
|
+
this.plyr?.forward(start);
|
|
1393
651
|
video.removeEventListener("loadedmetadata", onLoadedMeta);
|
|
1394
652
|
};
|
|
1395
653
|
video.addEventListener("loadedmetadata", onLoadedMeta);
|
|
@@ -1403,8 +661,7 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1403
661
|
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
1404
662
|
const dur = video.duration || 0;
|
|
1405
663
|
const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, dur);
|
|
1406
|
-
this.
|
|
1407
|
-
this.startAutoplayFlow(video);
|
|
664
|
+
this.plyr?.forward(start);
|
|
1408
665
|
});
|
|
1409
666
|
this.hls.on(Hls.Events.ERROR, (_evt, data) => {
|
|
1410
667
|
if (data?.fatal) {
|
|
@@ -1433,7 +690,7 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1433
690
|
});
|
|
1434
691
|
}
|
|
1435
692
|
resolveStartTime(last_time, last_percent, duration, opts = {}) {
|
|
1436
|
-
if (
|
|
693
|
+
if (!last_time || last_time <= 0) {
|
|
1437
694
|
return 0;
|
|
1438
695
|
}
|
|
1439
696
|
const minStartSec = opts.minStartSec ?? 3;
|
|
@@ -1491,7 +748,6 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1491
748
|
data = JSON.parse(data);
|
|
1492
749
|
}
|
|
1493
750
|
catch {
|
|
1494
|
-
/* ignora */
|
|
1495
751
|
}
|
|
1496
752
|
}
|
|
1497
753
|
if (data?.action === "request-iframe-media-metadata") {
|
|
@@ -1515,7 +771,6 @@ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
|
|
|
1515
771
|
this.hls?.destroy();
|
|
1516
772
|
this.controlsVisibilityCleanup?.();
|
|
1517
773
|
this.controlsVisibilityCleanup = undefined;
|
|
1518
|
-
this.clearAutoplayUnlockListener();
|
|
1519
774
|
this.detachLiveInteractionBlocker();
|
|
1520
775
|
const container = this.plyr?.elements?.container;
|
|
1521
776
|
if (this.recommendationBlocker?.parentElement) {
|