@edm-webplayer/webplayer-angular 0.2.0 → 0.2.2

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.
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Optional, Inject, Injectable, Input, ViewChild, Component, EventEmitter, Output, Pipe, NgModule } from '@angular/core';
3
- import * as i1 from '@angular/common';
2
+ import { InjectionToken, Optional, Inject, Injectable, EventEmitter, Output, Input, ViewChild, HostBinding, Component, Pipe, NgModule } from '@angular/core';
3
+ import * as i2 from '@angular/common';
4
4
  import { DOCUMENT, CommonModule } from '@angular/common';
5
5
  import { BehaviorSubject, Subject, Subscription } from 'rxjs';
6
6
  import * as Plyr from 'plyr';
@@ -66,14 +66,67 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
66
66
  args: [{ providedIn: 'root' }]
67
67
  }] });
68
68
 
69
- const PlyrPlayer = Plyr;
70
69
  class PlyrPlayerComponent {
71
70
  progressService;
72
71
  document;
72
+ get isLiveMode() {
73
+ return !!this.config?.is_live;
74
+ }
73
75
  videoRef;
74
76
  config;
77
+ ready = new EventEmitter();
75
78
  plyr;
76
79
  hls;
80
+ controlsVisibilityCleanup;
81
+ recommendationBlocker;
82
+ recommendationBlockerActiveZone;
83
+ topClickBlocker;
84
+ menuObserver;
85
+ playbackEnded = false;
86
+ overlayBlockAll = false;
87
+ endedPosterOverlay;
88
+ interactionUnmuteCleanup;
89
+ endedLoggedOnPause = false;
90
+ liveInteractionBlocker;
91
+ pendingStartTime;
92
+ onResize = () => {
93
+ this.updateRecommendationBlockerSafeZone();
94
+ };
95
+ onRecommendationBlockerClick = (event) => {
96
+ if (event) {
97
+ try {
98
+ event.preventDefault();
99
+ }
100
+ catch { }
101
+ try {
102
+ event.stopPropagation();
103
+ }
104
+ catch { }
105
+ }
106
+ };
107
+ onTopBlockerPointerDown = (event) => {
108
+ try {
109
+ event.preventDefault();
110
+ }
111
+ catch { }
112
+ try {
113
+ event.stopPropagation();
114
+ }
115
+ catch { }
116
+ };
117
+ onTopBlockerClick = (event) => {
118
+ try {
119
+ event.preventDefault();
120
+ }
121
+ catch { }
122
+ try {
123
+ event.stopPropagation();
124
+ }
125
+ catch { }
126
+ };
127
+ controlsSafeZonePxDesktop = 96;
128
+ controlsSafeZonePxTouch = 144;
129
+ overlayInteractionGapPx = 20;
77
130
  constructor(progressService, document) {
78
131
  this.progressService = progressService;
79
132
  this.document = document;
@@ -84,8 +137,82 @@ class PlyrPlayerComponent {
84
137
  this.applyTheme(this.config.ui.colors);
85
138
  }
86
139
  }
140
+ flushPendingStartTime(target, success) {
141
+ const pending = this.pendingStartTime;
142
+ if (!pending) {
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;
153
+ }
154
+ else {
155
+ pending.blockPlayback = false;
156
+ }
157
+ callbacks.forEach((cb) => {
158
+ try {
159
+ cb();
160
+ }
161
+ catch { }
162
+ });
163
+ }
164
+ getControlsSafeZone() {
165
+ const controls = (this.plyr?.elements?.controls ??
166
+ null);
167
+ const menuHeight = this.getOpenMenuHeight();
168
+ const baseSafeZone = this.isTouchDevice()
169
+ ? this.controlsSafeZonePxTouch
170
+ : this.controlsSafeZonePxDesktop;
171
+ if (!controls) {
172
+ return Math.max(baseSafeZone, menuHeight + 5);
173
+ }
174
+ let controlsHeight = 0;
175
+ try {
176
+ const rect = controls.getBoundingClientRect?.();
177
+ if (rect && rect.height) {
178
+ controlsHeight = rect.height;
179
+ }
180
+ }
181
+ catch { }
182
+ if (!controlsHeight) {
183
+ controlsHeight = controls.offsetHeight || controls.clientHeight || 0;
184
+ }
185
+ return Math.max(baseSafeZone, Math.ceil(controlsHeight + menuHeight + 24));
186
+ }
187
+ getOverlaySafeZone() {
188
+ const host = this.config?.video_host;
189
+ if (this.overlayBlockAll && host === "youtube") {
190
+ if (this.playbackEnded) {
191
+ return 0;
192
+ }
193
+ return this.overlayInteractionGapPx;
194
+ }
195
+ return Math.max(this.overlayInteractionGapPx, this.getControlsSafeZone());
196
+ }
197
+ getOpenMenuHeight() {
198
+ const container = this.plyr?.elements?.container;
199
+ if (!container)
200
+ return 0;
201
+ const menu = container.querySelector('.plyr__menu__container[aria-hidden="false"], .plyr__menu__container:not([aria-hidden])');
202
+ if (!menu)
203
+ return 0;
204
+ try {
205
+ const rect = menu.getBoundingClientRect?.();
206
+ if (rect && rect.height)
207
+ return Math.ceil(rect.height);
208
+ }
209
+ catch { }
210
+ const fallback = menu.offsetHeight || menu.clientHeight || 0;
211
+ return fallback;
212
+ }
87
213
  ngAfterViewInit() {
88
214
  this.setupPlayer();
215
+ window.addEventListener("message", this.onCastMessage);
89
216
  }
90
217
  ngOnChanges(changes) {
91
218
  if (changes["config"] && this.plyr) {
@@ -93,10 +220,10 @@ class PlyrPlayerComponent {
93
220
  }
94
221
  }
95
222
  applyTheme(colors) {
96
- const STYLE_ID = 'webplayer-theme-vars';
223
+ const STYLE_ID = "webplayer-theme-vars";
97
224
  let styleEl = this.document.getElementById(STYLE_ID);
98
225
  if (!styleEl) {
99
- styleEl = this.document.createElement('style');
226
+ styleEl = this.document.createElement("style");
100
227
  styleEl.id = STYLE_ID;
101
228
  this.document.head.appendChild(styleEl);
102
229
  }
@@ -104,7 +231,7 @@ class PlyrPlayerComponent {
104
231
  const allPairs = [];
105
232
  for (const key in colors) {
106
233
  if (Object.prototype.hasOwnProperty.call(colors, key)) {
107
- const cssVarName = `--theme-${key.replace('progresss-', 'progress-')}`;
234
+ const cssVarName = `--theme-${key.replace("progresss-", "progress-")}`;
108
235
  const cssVarValue = String(colors[key]);
109
236
  allPairs.push([cssVarName, cssVarValue]);
110
237
  try {
@@ -114,316 +241,1936 @@ class PlyrPlayerComponent {
114
241
  }
115
242
  }
116
243
  const cssRule = `:root{
117
- ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join('\n')}
244
+ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
118
245
  }`;
119
246
  styleEl.textContent = cssRule;
120
247
  }
121
- setupPlayer() {
122
- if (!this.videoRef)
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
+ getVideoWrapper() {
288
+ const container = this.plyr?.elements?.container;
289
+ if (!container)
290
+ return null;
291
+ const wrapper = this.plyr?.elements?.wrapper ||
292
+ container.querySelector(".plyr__video-wrapper");
293
+ return wrapper || null;
294
+ }
295
+ ensureRecommendationBlocker() {
296
+ if (this.recommendationBlocker && this.recommendationBlocker.isConnected) {
297
+ return this.recommendationBlocker;
298
+ }
299
+ const wrapper = this.getVideoWrapper();
300
+ if (!wrapper)
301
+ return null;
302
+ const blocker = this.document.createElement("div");
303
+ blocker.className = "plyr-recommendation-blocker";
304
+ blocker.style.position = "absolute";
305
+ blocker.style.top = "0";
306
+ blocker.style.left = "0";
307
+ blocker.style.width = "100%";
308
+ blocker.style.height = "100%";
309
+ blocker.style.zIndex = "9";
310
+ blocker.style.pointerEvents = "none";
311
+ blocker.style.display = "none";
312
+ blocker.style.background = "rgba(0,0,0,0)";
313
+ blocker.style.cursor = "pointer";
314
+ const activeZone = this.document.createElement("div");
315
+ activeZone.className = "plyr-recommendation-blocker__zone";
316
+ activeZone.style.position = "absolute";
317
+ activeZone.style.top = "0";
318
+ activeZone.style.left = "0";
319
+ activeZone.style.width = "100%";
320
+ activeZone.style.bottom = `20px`;
321
+ activeZone.style.pointerEvents = "auto";
322
+ activeZone.style.background = "rgba(0,0,0,0)";
323
+ activeZone.style.cursor = "pointer";
324
+ activeZone.addEventListener("pointerdown", this.onTopBlockerPointerDown);
325
+ activeZone.addEventListener("click", this.onRecommendationBlockerClick);
326
+ blocker.appendChild(activeZone);
327
+ wrapper.appendChild(blocker);
328
+ this.recommendationBlocker = blocker;
329
+ this.recommendationBlockerActiveZone = activeZone;
330
+ this.updateRecommendationBlockerSafeZone();
331
+ return blocker;
332
+ }
333
+ toggleRecommendationBlocker(active) {
334
+ if (this.config.video_host !== "youtube") {
123
335
  return;
124
- this.hls?.destroy();
125
- this.plyr?.destroy();
126
- this.plyr = new PlyrPlayer(this.videoRef.nativeElement, {
127
- controls: [
128
- "play-large",
129
- "play",
130
- "progress",
131
- "current-time",
132
- "duration",
133
- "mute",
134
- "volume",
135
- "captions",
136
- "settings",
137
- "pip",
138
- "airplay",
139
- "fullscreen",
140
- ],
141
- youtube: { rel: 0, modestbranding: 1, noCookie: true },
142
- vimeo: { byline: false, portrait: false, title: false },
336
+ }
337
+ const blocker = this.ensureRecommendationBlocker();
338
+ if (!blocker)
339
+ return;
340
+ blocker.style.display = active ? "block" : "none";
341
+ blocker.style.pointerEvents = "none";
342
+ this.updateRecommendationBlockerSafeZone();
343
+ }
344
+ updateRecommendationBlockerSafeZone() {
345
+ if (this.recommendationBlockerActiveZone) {
346
+ this.recommendationBlockerActiveZone.style.pointerEvents = this.overlayBlockAll
347
+ ? "auto"
348
+ : "none";
349
+ }
350
+ this.updateTopClickBlockerSafeZone();
351
+ }
352
+ ensureTopClickBlocker() {
353
+ if (this.config.video_host !== "youtube") {
354
+ return;
355
+ }
356
+ const wrapper = this.getVideoWrapper();
357
+ if (!wrapper)
358
+ return;
359
+ if (this.topClickBlocker && this.topClickBlocker.isConnected) {
360
+ this.updateTopClickBlockerSafeZone();
361
+ return;
362
+ }
363
+ const blocker = this.document.createElement("div");
364
+ blocker.className = "plyr-top-click-blocker";
365
+ blocker.style.position = "absolute";
366
+ blocker.style.top = "0";
367
+ blocker.style.left = "0";
368
+ blocker.style.right = "0";
369
+ blocker.style.width = "100%";
370
+ blocker.style.zIndex = "12";
371
+ blocker.style.pointerEvents = "auto";
372
+ blocker.style.background = "rgba(0,0,0,0)";
373
+ blocker.style.touchAction = "none";
374
+ blocker.style.cursor = "default";
375
+ blocker.addEventListener("pointerdown", this.onTopBlockerPointerDown);
376
+ blocker.addEventListener("click", this.onTopBlockerClick);
377
+ wrapper.appendChild(blocker);
378
+ this.topClickBlocker = blocker;
379
+ this.updateTopClickBlockerSafeZone();
380
+ }
381
+ updateTopClickBlockerSafeZone() {
382
+ if (!this.topClickBlocker)
383
+ return;
384
+ const height = Math.max(48, Math.ceil(this.getOverlaySafeZone()));
385
+ this.topClickBlocker.style.height = `${height}px`;
386
+ this.topClickBlocker.style.pointerEvents = this.overlayBlockAll
387
+ ? "auto"
388
+ : "none";
389
+ }
390
+ observeMenuChanges() {
391
+ this.menuObserver?.disconnect();
392
+ const container = this.plyr?.elements?.container;
393
+ if (!container) {
394
+ this.menuObserver = undefined;
395
+ return;
396
+ }
397
+ const observer = new MutationObserver(() => {
398
+ this.updateRecommendationBlockerSafeZone();
143
399
  });
144
- this.bindProgressEvents();
145
- this.loadSource(this.videoRef.nativeElement).then(() => this.setupStartTime());
400
+ observer.observe(container, {
401
+ subtree: true,
402
+ attributes: true,
403
+ attributeFilter: ["aria-hidden", "class", "style"],
404
+ });
405
+ this.menuObserver = observer;
146
406
  }
147
- setupStartTime() {
148
- const apply = () => {
149
- const dur = this.plyr.duration || 0;
150
- const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, dur);
151
- if (start > 0)
152
- this.plyr.currentTime = start;
153
- this.plyr.off("ready", apply);
154
- };
155
- this.plyr.on("ready", apply);
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;
156
414
  }
157
- bindProgressEvents() {
158
- const THROTTLE_MS = 300;
159
- let lastEmit = 0;
160
- const emitProgress = () => {
161
- const t = this.plyr.currentTime || 0;
162
- const d = this.plyr.duration || 0;
163
- const p = d > 0 ? +((t / d) * 100).toFixed(2) : 0;
164
- this.progressService.progress$.next({ currentTime: t, duration: d, percent: p });
165
- };
166
- const onTimeUpdate = () => {
167
- const now = performance.now();
168
- if (now - lastEmit >= THROTTLE_MS) {
169
- lastEmit = now;
170
- emitProgress();
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;
171
427
  }
172
- };
173
- this.plyr?.on?.("timeupdate", onTimeUpdate);
174
- this.plyr?.on?.("pause", emitProgress);
175
- this.plyr?.on?.("ended", () => {
176
- emitProgress();
177
- this.progressService.ended$.next();
178
- });
428
+ }
429
+ catch { }
430
+ return false;
179
431
  }
180
- unmuteAfterDelay(video, delayMs = 2000, volume = 1) {
181
- if (!this.config.autoPlay)
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
+ hasReachedEndThreshold() {
496
+ const duration = this.plyr?.duration;
497
+ if (!Number.isFinite(duration) || !duration || duration <= 0) {
498
+ return false;
499
+ }
500
+ const current = this.plyr?.currentTime || 0;
501
+ const remaining = duration - current;
502
+ const percent = duration > 0 ? current / duration : 0;
503
+ if (this.config?.video_host === "youtube") {
504
+ return percent >= 0.95 || remaining <= 1.5;
505
+ }
506
+ return percent >= 0.98 || remaining <= 0.75;
507
+ }
508
+ forceLivePlaybackResume() {
509
+ try {
510
+ const maybePromise = this.plyr?.play?.();
511
+ if (maybePromise && typeof maybePromise.then === "function") {
512
+ maybePromise.catch(() => { });
513
+ }
514
+ }
515
+ catch { }
516
+ }
517
+ updateLiveInteractionBlocker(isLive) {
518
+ if (!isLive) {
519
+ this.detachLiveInteractionBlocker();
182
520
  return;
183
- setTimeout(() => {
521
+ }
522
+ const container = (this.plyr?.elements?.container ??
523
+ null);
524
+ if (!container) {
525
+ return;
526
+ }
527
+ if (this.liveInteractionBlocker?.isConnected) {
528
+ return;
529
+ }
530
+ const blocker = this.document.createElement("div");
531
+ blocker.className = "plyr__live-interaction-blocker";
532
+ blocker.setAttribute("aria-hidden", "true");
533
+ container.appendChild(blocker);
534
+ this.liveInteractionBlocker = blocker;
535
+ }
536
+ detachLiveInteractionBlocker() {
537
+ if (this.liveInteractionBlocker?.parentNode) {
184
538
  try {
185
- if (this.plyr) {
186
- this.plyr.muted = false;
539
+ this.liveInteractionBlocker.parentNode.removeChild(this.liveInteractionBlocker);
540
+ }
541
+ catch { }
542
+ }
543
+ this.liveInteractionBlocker = null;
544
+ }
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 {
187
626
  this.plyr.volume = volume;
188
627
  }
628
+ catch { }
629
+ }
630
+ }
631
+ const media = target ?? this.videoRef?.nativeElement ?? null;
632
+ if (media) {
633
+ try {
634
+ media.muted = muted;
189
635
  }
190
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 () => {
191
663
  try {
192
- if (video) {
193
- video.muted = false;
194
- if (typeof video.volume === "number")
195
- video.volume = volume;
664
+ if (media) {
665
+ media.muted = prevVideoMuted;
196
666
  }
197
667
  }
198
668
  catch { }
199
- }, delayMs);
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
+ };
200
679
  }
201
- loadSource(video) {
202
- const setDisabledTopPageClick = () => {
203
- setTimeout(() => {
204
- const wrapper = document.querySelector(".plyr__video-wrapper");
205
- if (wrapper) {
206
- const blocker = document.createElement("div");
207
- blocker.style.position = "absolute";
208
- blocker.style.top = "0";
209
- blocker.style.left = "0";
210
- blocker.style.width = "100%";
211
- blocker.style.height = "200px";
212
- blocker.style.zIndex = "5";
213
- blocker.style.background = "transparent";
214
- blocker.style.pointerEvents = "auto";
215
- wrapper.appendChild(blocker);
216
- const blocker2 = document.createElement("div");
217
- blocker2.style.position = "absolute";
218
- blocker2.style.bottom = "80px";
219
- blocker2.style.left = "0";
220
- blocker2.style.width = "100%";
221
- blocker2.style.height = "100px";
222
- blocker2.style.zIndex = "5";
223
- blocker2.style.background = "transparent";
224
- blocker2.style.pointerEvents = "auto";
225
- wrapper.appendChild(blocker2);
226
- }
227
- }, 2500);
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();
228
703
  };
229
- return new Promise((resolve) => {
230
- if (!this.config) {
231
- return resolve();
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
+ bindFullscreenBehavior() {
1060
+ const onEnterFullscreen = () => {
1061
+ console.log("event: enterfullscreen");
1062
+ parent.postMessage("event: fullscreen_enter', '*'");
1063
+ };
1064
+ const onExitFullscreen = () => {
1065
+ console.log("event: exitfullscreen");
1066
+ parent.postMessage("event: fullscreen_exit', '*'");
1067
+ };
1068
+ this.plyr.on("enterfullscreen", onEnterFullscreen);
1069
+ this.plyr.on("exitfullscreen", onExitFullscreen);
1070
+ }
1071
+ setupPlayer() {
1072
+ if (!this.videoRef)
1073
+ return;
1074
+ this.hls?.destroy();
1075
+ this.controlsVisibilityCleanup?.();
1076
+ this.controlsVisibilityCleanup = undefined;
1077
+ this.plyr?.destroy();
1078
+ this.clearAutoplayUnlockListener();
1079
+ this.recommendationBlocker = null;
1080
+ const speedOptions = [1, 1.25, 1.5, 2];
1081
+ const locale = this.resolveLocale();
1082
+ const captionsLanguage = this.resolveCaptionsLanguage(locale) ?? "pt";
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 { }
1098
+ const isLive = !!this.config?.is_live;
1099
+ const youtubePlayerVars = {
1100
+ rel: 0,
1101
+ modestbranding: 1,
1102
+ noCookie: true,
1103
+ };
1104
+ youtubePlayerVars.playerVars = {
1105
+ hl: locale,
1106
+ cc_lang_pref: captionsLanguage || locale.split("-")[0]?.toLowerCase(),
1107
+ autoplay: shouldAutoplay ? 1 : 0,
1108
+ mute: startMuted ? 1 : 0,
1109
+ playsinline: 1,
1110
+ };
1111
+ if (captionsLanguage) {
1112
+ youtubePlayerVars.playerVars.cc_load_policy = 1;
1113
+ }
1114
+ const defaultControls = [
1115
+ "play-large",
1116
+ "play",
1117
+ "progress",
1118
+ "current-time",
1119
+ "duration",
1120
+ "mute",
1121
+ "volume",
1122
+ "captions",
1123
+ "settings",
1124
+ "pip",
1125
+ "airplay",
1126
+ "fullscreen",
1127
+ ];
1128
+ const liveControls = [];
1129
+ const plyrOptions = {
1130
+ controls: isLive ? liveControls : defaultControls,
1131
+ fullscreen: {
1132
+ enabled: true,
1133
+ fallback: true,
1134
+ iosNative: !isLive,
1135
+ container: null,
1136
+ },
1137
+ i18n: {
1138
+ speed: "Velocidade",
1139
+ },
1140
+ speed: {
1141
+ selected: 1,
1142
+ options: speedOptions,
1143
+ },
1144
+ clickToPlay: !isLive,
1145
+ hideControls: isLive ? true : false,
1146
+ keyboard: { focused: !isLive, global: false },
1147
+ autoplay: shouldAutoplay,
1148
+ muted: startMuted,
1149
+ };
1150
+ plyrOptions.hl = locale;
1151
+ if (captionsLanguage) {
1152
+ plyrOptions.captions = { language: captionsLanguage };
1153
+ }
1154
+ if (this.config.video_host === "youtube") {
1155
+ plyrOptions.youtube = youtubePlayerVars;
1156
+ }
1157
+ this.plyr = new Plyr.default(this.videoRef.nativeElement, plyrOptions);
1158
+ if (typeof this.plyr?.toggleControls === "function") {
1159
+ this.plyr.toggleControls(!isLive);
1160
+ }
1161
+ this.updateLiveInteractionBlocker(isLive);
1162
+ this.bindFullscreenBehavior();
1163
+ this.setupControlsVisibility();
1164
+ this.ensureTopClickBlocker();
1165
+ this.observeMenuChanges();
1166
+ this.playbackEnded = false;
1167
+ this.overlayBlockAll = false;
1168
+ this.endedLoggedOnPause = false;
1169
+ window.removeEventListener("resize", this.onResize);
1170
+ window.addEventListener("resize", this.onResize);
1171
+ 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.requestStartTimeSeek(start);
1187
+ this.plyr.off("ready", apply);
1188
+ };
1189
+ this.plyr.on("ready", apply);
1190
+ }
1191
+ bindProgressEvents() {
1192
+ const THROTTLE_MS = 300;
1193
+ let lastEmit = 0;
1194
+ const emitProgress = () => {
1195
+ const t = this.plyr.currentTime || 0;
1196
+ const d = this.plyr.duration || 0;
1197
+ const p = d > 0 ? +((t / d) * 100).toFixed(2) : 0;
1198
+ this.progressService.progress$.next({
1199
+ currentTime: t,
1200
+ duration: d,
1201
+ percent: p,
1202
+ });
1203
+ };
1204
+ const onTimeUpdate = () => {
1205
+ const now = performance.now();
1206
+ if (now - lastEmit >= THROTTLE_MS) {
1207
+ lastEmit = now;
1208
+ emitProgress();
1209
+ }
1210
+ const current = Math.floor(this.plyr.currentTime || 0);
1211
+ const duration = Math.floor(this.plyr.duration || 0);
1212
+ parent.postMessage("currentTime:" + current, "*");
1213
+ console.log("event: current_time " + current);
1214
+ console.log("event: duration " + duration);
1215
+ this.postCastEvent({
1216
+ event: "current_time",
1217
+ currentTime: current,
1218
+ duration,
1219
+ });
1220
+ };
1221
+ this.plyr?.on?.("play", () => {
1222
+ this.retryPendingStartTime();
1223
+ this.playbackEnded = false;
1224
+ this.overlayBlockAll = false;
1225
+ this.updateRecommendationBlockerSafeZone();
1226
+ this.toggleRecommendationBlocker(false);
1227
+ parent.postMessage("playing_video", "*");
1228
+ this.postCastEvent({
1229
+ event: "play",
1230
+ currentTime: this.plyr.currentTime || 0,
1231
+ });
1232
+ });
1233
+ this.plyr?.on?.("timeupdate", onTimeUpdate);
1234
+ this.plyr?.on?.("pause", () => {
1235
+ if (this.config?.is_live) {
1236
+ this.forceLivePlaybackResume();
1237
+ return;
1238
+ }
1239
+ emitProgress();
1240
+ console.log("event: pause");
1241
+ parent.postMessage("paused_video", "*");
1242
+ this.postCastEvent({
1243
+ event: "pause",
1244
+ currentTime: this.plyr.currentTime || 0,
1245
+ });
1246
+ this.overlayBlockAll = true;
1247
+ this.updateRecommendationBlockerSafeZone();
1248
+ this.toggleRecommendationBlocker(true);
1249
+ });
1250
+ this.plyr?.on?.("ended", () => {
1251
+ emitProgress();
1252
+ if (!this.hasReachedEndThreshold()) {
1253
+ console.warn("Ignoring premature ended event", this.plyr?.currentTime, this.plyr?.duration);
1254
+ return;
1255
+ }
1256
+ this.playbackEnded = true;
1257
+ this.overlayBlockAll = true;
1258
+ this.updateRecommendationBlockerSafeZone();
1259
+ this.endedLoggedOnPause = true;
1260
+ console.log("event: ended");
1261
+ parent.postMessage("event: ended", "*");
1262
+ this.postCastEvent({
1263
+ event: "ended",
1264
+ currentTime: this.plyr.currentTime || 0,
1265
+ });
1266
+ this.progressService.ended$.next();
1267
+ this.toggleRecommendationBlocker(true);
1268
+ });
1269
+ }
1270
+ unmuteAfterDelay(video, delayMs = 2000, volume = 1) {
1271
+ if (!this.shouldAutoplay())
1272
+ return;
1273
+ setTimeout(() => {
1274
+ try {
1275
+ if (this.plyr) {
1276
+ this.plyr.muted = false;
1277
+ this.plyr.volume = volume;
1278
+ }
1279
+ }
1280
+ catch { }
1281
+ try {
1282
+ if (video) {
1283
+ video.muted = false;
1284
+ if (typeof video.volume === "number")
1285
+ video.volume = volume;
1286
+ }
1287
+ }
1288
+ catch { }
1289
+ }, delayMs);
1290
+ }
1291
+ resetPlaybackRate(video) {
1292
+ try {
1293
+ if (this.plyr && typeof this.plyr.speed !== "undefined") {
1294
+ this.plyr.speed = 1;
1295
+ }
1296
+ }
1297
+ catch { }
1298
+ const media = video ?? this.videoRef?.nativeElement ?? null;
1299
+ if (media) {
1300
+ try {
1301
+ media.playbackRate = 1;
1302
+ }
1303
+ catch { }
1304
+ try {
1305
+ if (media.defaultPlaybackRate !== undefined) {
1306
+ media.defaultPlaybackRate = 1;
1307
+ }
1308
+ }
1309
+ catch { }
1310
+ }
1311
+ }
1312
+ loadSource(video) {
1313
+ return new Promise((resolve) => {
1314
+ if (!this.config) {
1315
+ return resolve();
1316
+ }
1317
+ if (this.hls) {
1318
+ this.hls.destroy();
1319
+ this.hls = undefined;
1320
+ }
1321
+ this.resetPlaybackRate(video);
1322
+ if (this.config.video_host === "youtube") {
1323
+ const onReady = () => {
1324
+ this.plyr.off("ready", onReady);
1325
+ this.ensureIframeAutoplayPermissions();
1326
+ const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, this.plyr.duration);
1327
+ this.requestStartTimeSeek(start);
1328
+ if (this.shouldAutoplay()) {
1329
+ this.startAutoplayFlow();
1330
+ }
1331
+ else if (this.shouldBlockIframeAutoplay()) {
1332
+ this.stopRestrictedIframeAutoplay();
1333
+ }
1334
+ parent.postMessage("playerIsReadyToPlay", "*");
1335
+ parent.postMessage("duration:" + this.plyr.duration, "*");
1336
+ console.log("event: current_time " + this.plyr.currentTime);
1337
+ console.log("event: duration " + this.plyr.duration);
1338
+ console.log("event: ready");
1339
+ this.postCastEvent({
1340
+ event: "ready",
1341
+ duration: this.plyr.duration || 0,
1342
+ currentTime: this.plyr.currentTime || 0,
1343
+ });
1344
+ if (this.config.is_live) {
1345
+ this.plyr?.on("playing", function () {
1346
+ console.log("event: playing_live");
1347
+ parent.postMessage("event: playing_live", "*");
1348
+ });
1349
+ }
1350
+ if (this.config.mirror) {
1351
+ setTimeout(() => {
1352
+ document
1353
+ .querySelector(`.plyr__video-embed iframe`)
1354
+ ?.classList.add("mirror");
1355
+ }, 1000);
1356
+ }
1357
+ };
1358
+ this.plyr.on("ready", onReady);
1359
+ }
1360
+ if (this.config.video_host === "youtube") {
1361
+ this.plyr.source = {
1362
+ type: "video",
1363
+ sources: [{ src: this.config.video_id, provider: "youtube" }],
1364
+ };
1365
+ }
1366
+ else if (this.config.video_host === "mp4") {
1367
+ this.plyr.source = {
1368
+ type: "video",
1369
+ sources: [{ src: this.config.video_external_id, type: "video/mp4" }],
1370
+ };
1371
+ }
1372
+ else if (this.config.video_host === "hls") {
1373
+ let sourceX = "";
1374
+ try {
1375
+ sourceX = atob(this.config.video_id || "");
1376
+ }
1377
+ catch (e) {
1378
+ sourceX = this.config.video_id || "";
1379
+ }
1380
+ const canNative = video.canPlayType("application/vnd.apple.mpegurl");
1381
+ if (canNative) {
1382
+ this.plyr.source = {
1383
+ type: "video",
1384
+ sources: [{ src: sourceX, type: "application/vnd.apple.mpegurl" }],
1385
+ };
1386
+ const onLoadedMeta = () => {
1387
+ const dur = this.plyr.duration || video.duration || 0;
1388
+ const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, dur);
1389
+ console.log(start);
1390
+ this.requestStartTimeSeek(start, { video });
1391
+ this.startAutoplayFlow(video);
1392
+ video.removeEventListener("loadedmetadata", onLoadedMeta);
1393
+ };
1394
+ video.addEventListener("loadedmetadata", onLoadedMeta);
1395
+ }
1396
+ else if (Hls.isSupported()) {
1397
+ this.hls = new Hls({ enableWorker: true, lowLatencyMode: true });
1398
+ this.hls.attachMedia(video);
1399
+ this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
1400
+ this.hls.loadSource(sourceX);
1401
+ });
1402
+ this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
1403
+ const dur = video.duration || 0;
1404
+ const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, dur);
1405
+ this.requestStartTimeSeek(start, { video });
1406
+ this.startAutoplayFlow(video);
1407
+ });
1408
+ this.hls.on(Hls.Events.ERROR, (_evt, data) => {
1409
+ if (data?.fatal) {
1410
+ switch (data.type) {
1411
+ case Hls.ErrorTypes.NETWORK_ERROR:
1412
+ this.hls.startLoad();
1413
+ break;
1414
+ case Hls.ErrorTypes.MEDIA_ERROR:
1415
+ this.hls.recoverMediaError();
1416
+ break;
1417
+ default:
1418
+ try {
1419
+ this.hls.destroy();
1420
+ }
1421
+ catch { }
1422
+ break;
1423
+ }
1424
+ }
1425
+ });
1426
+ }
1427
+ else {
1428
+ this.plyr.source = { type: "video", sources: [{ src: sourceX }] };
1429
+ }
1430
+ }
1431
+ resolve();
1432
+ });
1433
+ }
1434
+ resolveStartTime(last_time, last_percent, duration, opts = {}) {
1435
+ if (this.shouldResetProgressToBeginning()) {
1436
+ return 0;
1437
+ }
1438
+ const minStartSec = opts.minStartSec ?? 3;
1439
+ const endGuardSec = opts.endGuardSec ?? 2;
1440
+ const dur = Number(duration);
1441
+ const hasValidDuration = Number.isFinite(dur) && dur > 0;
1442
+ const hasTime = last_time !== undefined && last_time !== null && last_time !== "";
1443
+ const clamp = (value) => {
1444
+ let t = Number(value);
1445
+ if (!isFinite(t) || t < 0)
1446
+ t = 0;
1447
+ if (t < minStartSec)
1448
+ return 0;
1449
+ if (hasValidDuration) {
1450
+ if (t >= dur - endGuardSec) {
1451
+ return 0;
1452
+ }
1453
+ const maxStart = Math.max(0, dur - endGuardSec);
1454
+ t = Math.min(maxStart, t);
1455
+ }
1456
+ return t;
1457
+ };
1458
+ if (hasTime) {
1459
+ return clamp(last_time);
1460
+ }
1461
+ const hasPercent = last_percent !== undefined &&
1462
+ last_percent !== null &&
1463
+ last_percent !== "";
1464
+ if (hasPercent && hasValidDuration) {
1465
+ let p = Number(last_percent);
1466
+ if (!isFinite(p) || p < 0)
1467
+ p = 0;
1468
+ if (p >= 1)
1469
+ return 0;
1470
+ if (p > 1)
1471
+ p = p / 100;
1472
+ if (p >= 0.99)
1473
+ return 0;
1474
+ let t = dur * p;
1475
+ if (t < minStartSec)
1476
+ return 0;
1477
+ if (t >= dur - endGuardSec)
1478
+ return 0;
1479
+ return Math.min(dur - endGuardSec, t);
1480
+ }
1481
+ return 0;
1482
+ }
1483
+ postCastEvent(payload) {
1484
+ parent.postMessage(JSON.stringify(payload), "*");
1485
+ }
1486
+ onCastMessage = (evt) => {
1487
+ let data = evt.data;
1488
+ if (typeof data === "string") {
1489
+ try {
1490
+ data = JSON.parse(data);
1491
+ }
1492
+ catch {
1493
+ /* ignora */
1494
+ }
1495
+ }
1496
+ if (data?.action === "request-iframe-media-metadata") {
1497
+ this.postCastEvent({
1498
+ event: "metadata",
1499
+ duration: this.plyr?.duration || 0,
1500
+ currentTime: this.plyr?.currentTime || 0,
1501
+ });
1502
+ }
1503
+ if (data?.action === "play") {
1504
+ this.plyr?.play();
1505
+ }
1506
+ if (data?.action === "pause") {
1507
+ this.plyr?.pause();
1508
+ }
1509
+ if (data?.action === "seek" && typeof data.time === "number") {
1510
+ this.plyr && (this.plyr.currentTime = data.time);
1511
+ }
1512
+ };
1513
+ ngOnDestroy() {
1514
+ this.hls?.destroy();
1515
+ this.controlsVisibilityCleanup?.();
1516
+ this.controlsVisibilityCleanup = undefined;
1517
+ this.clearAutoplayUnlockListener();
1518
+ this.detachLiveInteractionBlocker();
1519
+ const container = this.plyr?.elements?.container;
1520
+ if (this.recommendationBlocker?.parentElement) {
1521
+ try {
1522
+ this.recommendationBlocker.parentElement.removeChild(this.recommendationBlocker);
1523
+ }
1524
+ catch { }
1525
+ }
1526
+ if (this.recommendationBlockerActiveZone) {
1527
+ try {
1528
+ this.recommendationBlockerActiveZone.removeEventListener("pointerdown", this.onTopBlockerPointerDown);
1529
+ this.recommendationBlockerActiveZone.removeEventListener("click", this.onRecommendationBlockerClick);
1530
+ }
1531
+ catch { }
1532
+ }
1533
+ this.recommendationBlocker = null;
1534
+ this.recommendationBlockerActiveZone = null;
1535
+ if (this.topClickBlocker?.parentElement) {
1536
+ try {
1537
+ this.topClickBlocker.removeEventListener("pointerdown", this.onTopBlockerPointerDown);
1538
+ this.topClickBlocker.removeEventListener("click", this.onTopBlockerClick);
1539
+ this.topClickBlocker.parentElement.removeChild(this.topClickBlocker);
1540
+ }
1541
+ catch { }
1542
+ }
1543
+ this.topClickBlocker = null;
1544
+ this.menuObserver?.disconnect();
1545
+ this.menuObserver = undefined;
1546
+ window.removeEventListener("resize", this.onResize);
1547
+ this.plyr?.destroy();
1548
+ window.removeEventListener("message", this.onCastMessage);
1549
+ }
1550
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlyrPlayerComponent, deps: [{ token: WebplayerProgressService }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component });
1551
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PlyrPlayerComponent, isStandalone: true, selector: "app-plyr-player", inputs: { config: "config" }, outputs: { ready: "ready" }, host: { properties: { "class.live-mode": "this.isLiveMode" } }, viewQueries: [{ propertyName: "videoRef", first: true, predicate: ["video"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `<video #video playsinline></video>`, isInline: true, styles: [":host{display:block;width:100%;height:100%;position:relative;--plyr-color-main: var(--theme-alert, #19a6d9);--plyr-color-hover: var(--theme-alert, #2dbbff);--plyr-progress-buffer-background: var( --theme-progress-view-off, rgba(255, 255, 255, .25) );--plyr-control-background: var(--theme-bg, rgba(0, 0, 0, .8));--plyr-control-color: var(--theme-text-color, #fff);--plyr-control-button-color: var(--theme-font-bt, #fff)}:host ::ng-deep .plyr__controls{color:var(--plyr-control-color)!important;transition:opacity .2s ease,transform .2s ease;z-index:10!important}:host ::ng-deep .plyr__controls button{color:var(--plyr-control-color)!important}:host ::ng-deep .plyr__progress__buffer{background-color:var(--plyr-progress-buffer-background)!important}:host ::ng-deep .plyr,:host ::ng-deep .plyr__video-wrapper,:host ::ng-deep .plyr__video-embed,:host ::ng-deep .plyr__video-embed>iframe,:host ::ng-deep video{width:100%!important;height:100%!important;aspect-ratio:auto!important;top:0!important;left:0!important}:host ::ng-deep .mirror{transform:scaleX(-1)!important;-webkit-transform:scaleX(-1)!important}:host ::ng-deep .plyr--video .plyr__control.plyr__tab-focus,:host ::ng-deep .plyr--video .plyr__control:hover,:host ::ng-deep .plyr--video .plyr__control[aria-expanded=true]{background:var(--plyr-color-hover)!important;color:var(--plyr-control-button-color)!important}:host ::ng-deep .plyr__control{z-index:10!important}:host ::ng-deep .plyr--full-ui input[type=range]{color:var(--plyr-color-main)!important}:host ::ng-deep .plyr__menu__container .plyr__control[role=menuitemradio][aria-checked=true]:before{background:var(--plyr-color-main)!important}:host ::ng-deep .plyr__menu__container{background:var(--plyr-control-background)!important;z-index:7!important}:host ::ng-deep .plyr--auto-hide-controls .plyr__controls{opacity:0!important;pointer-events:none!important;transform:translateY(12px)}:host ::ng-deep .ytp-more-videos-view .ytp-suggestions{pointer-events:none!important}:host(.live-mode) ::ng-deep .plyr__controls,:host(.live-mode) ::ng-deep .plyr__control,:host(.live-mode) ::ng-deep .plyr__menu,:host(.live-mode) ::ng-deep .plyr__menu__container,:host(.live-mode) ::ng-deep .plyr__control--overlaid,:host(.live-mode) ::ng-deep .plyr__control--overlaid+.plyr__control{display:none!important}:host(.live-mode) ::ng-deep .plyr__video-wrapper,:host(.live-mode) ::ng-deep video,:host(.live-mode) ::ng-deep .plyr__video-embed{pointer-events:none!important}:host ::ng-deep .plyr__live-interaction-blocker{position:absolute;inset:0;z-index:12;pointer-events:auto}:host ::ng-deep .plyr__control--overlaid,:host ::ng-deep .plyr--video .plyr__control.plyr__control--overlaid:hover{background:#fff!important;color:#000!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
1552
+ }
1553
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlyrPlayerComponent, decorators: [{
1554
+ type: Component,
1555
+ args: [{ selector: "app-plyr-player", template: `<video #video playsinline></video>`, standalone: true, imports: [CommonModule], styles: [":host{display:block;width:100%;height:100%;position:relative;--plyr-color-main: var(--theme-alert, #19a6d9);--plyr-color-hover: var(--theme-alert, #2dbbff);--plyr-progress-buffer-background: var( --theme-progress-view-off, rgba(255, 255, 255, .25) );--plyr-control-background: var(--theme-bg, rgba(0, 0, 0, .8));--plyr-control-color: var(--theme-text-color, #fff);--plyr-control-button-color: var(--theme-font-bt, #fff)}:host ::ng-deep .plyr__controls{color:var(--plyr-control-color)!important;transition:opacity .2s ease,transform .2s ease;z-index:10!important}:host ::ng-deep .plyr__controls button{color:var(--plyr-control-color)!important}:host ::ng-deep .plyr__progress__buffer{background-color:var(--plyr-progress-buffer-background)!important}:host ::ng-deep .plyr,:host ::ng-deep .plyr__video-wrapper,:host ::ng-deep .plyr__video-embed,:host ::ng-deep .plyr__video-embed>iframe,:host ::ng-deep video{width:100%!important;height:100%!important;aspect-ratio:auto!important;top:0!important;left:0!important}:host ::ng-deep .mirror{transform:scaleX(-1)!important;-webkit-transform:scaleX(-1)!important}:host ::ng-deep .plyr--video .plyr__control.plyr__tab-focus,:host ::ng-deep .plyr--video .plyr__control:hover,:host ::ng-deep .plyr--video .plyr__control[aria-expanded=true]{background:var(--plyr-color-hover)!important;color:var(--plyr-control-button-color)!important}:host ::ng-deep .plyr__control{z-index:10!important}:host ::ng-deep .plyr--full-ui input[type=range]{color:var(--plyr-color-main)!important}:host ::ng-deep .plyr__menu__container .plyr__control[role=menuitemradio][aria-checked=true]:before{background:var(--plyr-color-main)!important}:host ::ng-deep .plyr__menu__container{background:var(--plyr-control-background)!important;z-index:7!important}:host ::ng-deep .plyr--auto-hide-controls .plyr__controls{opacity:0!important;pointer-events:none!important;transform:translateY(12px)}:host ::ng-deep .ytp-more-videos-view .ytp-suggestions{pointer-events:none!important}:host(.live-mode) ::ng-deep .plyr__controls,:host(.live-mode) ::ng-deep .plyr__control,:host(.live-mode) ::ng-deep .plyr__menu,:host(.live-mode) ::ng-deep .plyr__menu__container,:host(.live-mode) ::ng-deep .plyr__control--overlaid,:host(.live-mode) ::ng-deep .plyr__control--overlaid+.plyr__control{display:none!important}:host(.live-mode) ::ng-deep .plyr__video-wrapper,:host(.live-mode) ::ng-deep video,:host(.live-mode) ::ng-deep .plyr__video-embed{pointer-events:none!important}:host ::ng-deep .plyr__live-interaction-blocker{position:absolute;inset:0;z-index:12;pointer-events:auto}:host ::ng-deep .plyr__control--overlaid,:host ::ng-deep .plyr--video .plyr__control.plyr__control--overlaid:hover{background:#fff!important;color:#000!important}\n"] }]
1556
+ }], ctorParameters: () => [{ type: WebplayerProgressService }, { type: Document, decorators: [{
1557
+ type: Inject,
1558
+ args: [DOCUMENT]
1559
+ }] }], propDecorators: { isLiveMode: [{
1560
+ type: HostBinding,
1561
+ args: ["class.live-mode"]
1562
+ }], videoRef: [{
1563
+ type: ViewChild,
1564
+ args: ["video", { static: true }]
1565
+ }], config: [{
1566
+ type: Input
1567
+ }], ready: [{
1568
+ type: Output
1569
+ }] } });
1570
+
1571
+ class VimeoPlayerComponent {
1572
+ progressService;
1573
+ document;
1574
+ config;
1575
+ ready = new EventEmitter();
1576
+ videoRef;
1577
+ plyr;
1578
+ startTimeApplied = false;
1579
+ pendingStartTime;
1580
+ endedLoggedOnPause = false;
1581
+ iosFullscreenCleanup;
1582
+ castListenerAttached = false;
1583
+ unmuteForCast() {
1584
+ try {
1585
+ const videoEl = this.videoRef?.nativeElement;
1586
+ if (videoEl) {
1587
+ videoEl.muted = false;
1588
+ videoEl.volume = 1;
1589
+ }
1590
+ if (this.plyr) {
1591
+ this.plyr.muted = false;
1592
+ this.plyr.volume = 1;
1593
+ }
1594
+ }
1595
+ catch { }
1596
+ }
1597
+ onCastMessage = (event) => {
1598
+ let data = event.data;
1599
+ if (typeof data === 'string') {
1600
+ try {
1601
+ data = JSON.parse(data);
1602
+ }
1603
+ catch { }
1604
+ }
1605
+ if (data?.action === 'request-iframe-media-metadata') {
1606
+ console.log('Received request-iframe-media-metadata message');
1607
+ setTimeout(() => {
1608
+ this.postCastEvent({
1609
+ event: 'metadata',
1610
+ duration: this.plyr?.duration || 0,
1611
+ currentTime: this.plyr?.currentTime || 0,
1612
+ });
1613
+ }, 100);
1614
+ }
1615
+ if (data?.action === 'play') {
1616
+ this.unmuteForCast();
1617
+ this.plyr?.play?.();
1618
+ }
1619
+ if (data?.action === 'pause') {
1620
+ this.plyr?.pause?.();
1621
+ }
1622
+ if (data?.action === 'seek' && typeof data.time === 'number') {
1623
+ this.plyr && (this.plyr.currentTime = data.time);
1624
+ }
1625
+ };
1626
+ endedOverlayVisible = false;
1627
+ constructor(progressService, document) {
1628
+ this.progressService = progressService;
1629
+ this.document = document;
1630
+ }
1631
+ ngOnInit() {
1632
+ this.applyTheme(this.config?.ui?.colors);
1633
+ }
1634
+ ngAfterViewInit() {
1635
+ this.ensureCastMessageListener();
1636
+ this.setupPlayer();
1637
+ }
1638
+ ngOnChanges(changes) {
1639
+ if (changes['config'] && this.plyr) {
1640
+ this.ensureCastMessageListener();
1641
+ this.setupPlayer();
1642
+ }
1643
+ }
1644
+ setupPlayer() {
1645
+ if (!this.videoRef) {
1646
+ return;
1647
+ }
1648
+ const videoElement = this.videoRef.nativeElement;
1649
+ const isIOS = this.isIOSContext();
1650
+ this.applyInlineAttributes(videoElement, isIOS);
1651
+ this.plyr?.destroy();
1652
+ this.startTimeApplied = false;
1653
+ this.pendingStartTime = undefined;
1654
+ this.endedLoggedOnPause = false;
1655
+ this.endedOverlayVisible = false;
1656
+ const shouldAutoplay = false;
1657
+ const startMuted = shouldAutoplay;
1658
+ const speedOptions = [1, 1.25, 1.5, 2];
1659
+ const baseControls = [
1660
+ 'play-large',
1661
+ 'play',
1662
+ 'progress',
1663
+ 'current-time',
1664
+ 'duration',
1665
+ 'mute',
1666
+ 'volume',
1667
+ 'captions',
1668
+ 'settings',
1669
+ 'pip',
1670
+ 'airplay',
1671
+ 'fullscreen',
1672
+ ];
1673
+ const options = {
1674
+ controls: isIOS ? baseControls.filter((c) => c !== 'fullscreen') : baseControls,
1675
+ clickToPlay: true,
1676
+ autoplay: shouldAutoplay,
1677
+ muted: startMuted,
1678
+ keyboard: { focused: true, global: false },
1679
+ hideControls: true,
1680
+ i18n: {
1681
+ speed: "Velocidade",
1682
+ },
1683
+ speed: {
1684
+ selected: 1,
1685
+ options: speedOptions,
1686
+ },
1687
+ fullscreen: {
1688
+ enabled: !isIOS,
1689
+ fallback: !isIOS,
1690
+ iosNative: true,
1691
+ container: null,
1692
+ },
1693
+ };
1694
+ options.vimeo = {
1695
+ autopause: true,
1696
+ autoplay: shouldAutoplay,
1697
+ muted: startMuted,
1698
+ playsinline: true,
1699
+ byline: false,
1700
+ portrait: false,
1701
+ title: false,
1702
+ pip: false,
1703
+ controls: false,
1704
+ };
1705
+ this.plyr = new Plyr.default(videoElement, options);
1706
+ this.plyr.source = {
1707
+ type: 'video',
1708
+ sources: [{ src: this.config.video_id, provider: 'vimeo' }],
1709
+ };
1710
+ this.bindIOSFullscreenLogging(videoElement, isIOS);
1711
+ this.bindEvents();
1712
+ }
1713
+ bindEvents() {
1714
+ if (!this.plyr)
1715
+ return;
1716
+ let durationPosted = false;
1717
+ const onTimeUpdate = () => {
1718
+ setTimeout(() => {
1719
+ const currentReal = this.plyr.currentTime || 0;
1720
+ const durationReal = this.plyr.duration || 0;
1721
+ const current = Math.floor(currentReal);
1722
+ const duration = Math.floor(this.plyr.duration);
1723
+ if (!durationPosted && duration > 0) {
1724
+ durationPosted = true;
1725
+ console.log('event: duration ' + duration);
1726
+ parent.postMessage('duration:' + duration, '*');
1727
+ }
1728
+ const percent = duration > 0 ? +((current / duration) * 100).toFixed(2) : 0;
1729
+ this.progressService.progress$.next({ currentTime: currentReal, duration: durationReal, percent });
1730
+ parent.postMessage('currentTime:' + current, '*');
1731
+ console.log('event: current_time ' + current);
1732
+ console.log('event: duration ' + duration);
1733
+ this.postCastEvent({
1734
+ event: 'current_time',
1735
+ currentTime: current,
1736
+ duration,
1737
+ });
1738
+ this.tryApplyPendingStartTime();
1739
+ }, 0);
1740
+ };
1741
+ this.plyr.on('ready', () => {
1742
+ this.applySavedStartTime();
1743
+ const duration = this.plyr.duration || 0;
1744
+ console.log('event: ready');
1745
+ if (duration > 0) {
1746
+ parent.postMessage('duration:' + duration, '*');
1747
+ }
1748
+ parent.postMessage('playerIsReadyToPlay', '*');
1749
+ this.postCastEvent({
1750
+ event: 'ready',
1751
+ duration,
1752
+ currentTime: this.plyr.currentTime || 0,
1753
+ });
1754
+ this.ready.emit();
1755
+ });
1756
+ const reapplyStartTime = () => {
1757
+ if (typeof this.pendingStartTime === 'number' || !this.startTimeApplied) {
1758
+ this.applySavedStartTime(true);
1759
+ }
1760
+ if (this.startTimeApplied) {
1761
+ this.plyr?.off?.('loadedmetadata', reapplyStartTime);
1762
+ this.plyr?.off?.('durationchange', reapplyStartTime);
1763
+ }
1764
+ };
1765
+ this.plyr.on('timeupdate', onTimeUpdate);
1766
+ this.plyr.on('loadedmetadata', reapplyStartTime);
1767
+ this.plyr.on('durationchange', reapplyStartTime);
1768
+ this.plyr.on('play', () => {
1769
+ this.endedLoggedOnPause = false;
1770
+ this.tryApplyPendingStartTime();
1771
+ //this.jumpToPendingStartTime();
1772
+ this.endedOverlayVisible = false;
1773
+ this.progressService.ended$.next();
1774
+ console.log('event: playing_video');
1775
+ if (this.config?.is_live) {
1776
+ console.log('event: playing_live');
1777
+ parent.postMessage('event: playing_live', '*');
1778
+ }
1779
+ parent.postMessage('playing_video', '*');
1780
+ this.postCastEvent({
1781
+ event: 'play',
1782
+ currentTime: this.plyr.currentTime || 0,
1783
+ });
1784
+ onTimeUpdate();
1785
+ });
1786
+ this.plyr.on('pause', () => {
1787
+ this.endedOverlayVisible = false;
1788
+ console.log('event: pause');
1789
+ parent.postMessage('paused_video', '*');
1790
+ this.postCastEvent({
1791
+ event: 'pause',
1792
+ currentTime: this.plyr.currentTime || 0,
1793
+ });
1794
+ onTimeUpdate();
1795
+ if (this.shouldEmitEndedOnPause()) {
1796
+ this.handleEnded(true);
1797
+ }
1798
+ this.unlockPlayerInteraction();
1799
+ });
1800
+ this.plyr.on('ended', () => {
1801
+ onTimeUpdate();
1802
+ this.handleEnded();
1803
+ });
1804
+ this.plyr.on('enterfullscreen', () => {
1805
+ console.log('event: enterfullscreen');
1806
+ parent.postMessage('event: fullscreen_enter', '*');
1807
+ this.postCastEvent({ event: 'enterfullscreen' });
1808
+ });
1809
+ this.plyr.on('exitfullscreen', () => {
1810
+ console.log('event: exitfullscreen');
1811
+ parent.postMessage('event: fullscreen_exit', '*');
1812
+ this.postCastEvent({ event: 'exitfullscreen' });
1813
+ });
1814
+ }
1815
+ resolveStartTime(last_time, last_percent, duration) {
1816
+ if (this.shouldResetProgressToBeginning()) {
1817
+ return 0;
1818
+ }
1819
+ const dur = Number(duration) || 0;
1820
+ if (dur <= 0) {
1821
+ if (last_time && Number(last_time) > 0) {
1822
+ return Number(last_time);
1823
+ }
1824
+ return 0;
1825
+ }
1826
+ if (last_time && Number(last_time) > 0) {
1827
+ return Math.min(Number(last_time), dur - 2);
1828
+ }
1829
+ if (last_percent && Number(last_percent) > 0) {
1830
+ let percent = Number(last_percent);
1831
+ if (percent > 1) {
1832
+ percent = percent / 100;
1833
+ }
1834
+ return Math.min(percent * dur, dur - 2);
1835
+ }
1836
+ return 0;
1837
+ }
1838
+ shouldResetProgressToBeginning() {
1839
+ const seenFlag = this.config?.seen;
1840
+ if (seenFlag === true || seenFlag === 'true' || seenFlag === 1) {
1841
+ return true;
1842
+ }
1843
+ const rawPercent = this.config?.last_percent;
1844
+ if (rawPercent === undefined || rawPercent === null || rawPercent === 0) {
1845
+ return false;
1846
+ }
1847
+ let percent = Number(rawPercent);
1848
+ if (!Number.isFinite(percent)) {
1849
+ return false;
1850
+ }
1851
+ if (percent > 1) {
1852
+ percent = percent / 100;
1853
+ }
1854
+ return percent >= 0.8;
1855
+ }
1856
+ isIOSDevice() {
1857
+ if (typeof navigator === 'undefined')
1858
+ return false;
1859
+ const ua = navigator.userAgent || '';
1860
+ const iOS = /iPad|iPhone|iPod/.test(ua);
1861
+ const iPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;
1862
+ return iOS || iPadOS;
1863
+ }
1864
+ applySavedStartTime(force = false) {
1865
+ if (!this.plyr)
1866
+ return;
1867
+ if (this.startTimeApplied && !force)
1868
+ return;
1869
+ const duration = this.plyr.duration || 0;
1870
+ const start = this.resolveStartTime(this.config?.last_time, this.config?.last_percent, duration);
1871
+ if (!(start > 0)) {
1872
+ this.pendingStartTime = undefined;
1873
+ return;
1874
+ }
1875
+ if (duration > 0) {
1876
+ this.pendingStartTime = start;
1877
+ this.startTimeApplied = false;
1878
+ this.jumpToPendingStartTime();
1879
+ this.resumeAfterPendingSeek();
1880
+ }
1881
+ else {
1882
+ this.pendingStartTime = start;
1883
+ this.startTimeApplied = false;
1884
+ }
1885
+ }
1886
+ ngOnDestroy() {
1887
+ this.plyr?.destroy();
1888
+ this.removeIOSFullscreenLogging();
1889
+ if (typeof window !== 'undefined') {
1890
+ if (this.castListenerAttached) {
1891
+ window.removeEventListener('message', this.onCastMessage);
1892
+ this.castListenerAttached = false;
1893
+ }
1894
+ window.removeEventListener('message', this.onCastMessage);
1895
+ }
1896
+ }
1897
+ postCastEvent(payload) {
1898
+ parent.postMessage(JSON.stringify(payload), '*');
1899
+ }
1900
+ applyTheme(colors) {
1901
+ if (!colors)
1902
+ return;
1903
+ const STYLE_ID = 'webplayer-theme-vars';
1904
+ let styleEl = this.document.getElementById(STYLE_ID);
1905
+ if (!styleEl) {
1906
+ styleEl = this.document.createElement('style');
1907
+ styleEl.id = STYLE_ID;
1908
+ this.document.head.appendChild(styleEl);
1909
+ }
1910
+ const rootStyle = this.document.documentElement.style;
1911
+ const allPairs = [];
1912
+ for (const key in colors) {
1913
+ if (Object.prototype.hasOwnProperty.call(colors, key)) {
1914
+ const cssVarName = `--theme-${key.replace('progresss-', 'progress-')}`;
1915
+ const cssVarValue = String(colors[key]);
1916
+ allPairs.push([cssVarName, cssVarValue]);
1917
+ try {
1918
+ rootStyle.setProperty(cssVarName, cssVarValue);
1919
+ }
1920
+ catch { }
1921
+ }
1922
+ }
1923
+ const cssRule = `:root{
1924
+ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join('\n')}
1925
+ }`;
1926
+ styleEl.textContent = cssRule;
1927
+ }
1928
+ seekTo(time) {
1929
+ if (!this.plyr)
1930
+ return;
1931
+ const target = Math.max(0, Number(time) || 0);
1932
+ try {
1933
+ this.plyr.currentTime = target;
1934
+ }
1935
+ catch { }
1936
+ }
1937
+ tryApplyPendingStartTime() {
1938
+ if (this.startTimeApplied)
1939
+ return;
1940
+ if (!this.plyr)
1941
+ return;
1942
+ const pending = this.pendingStartTime;
1943
+ if (typeof pending !== 'number' || !(pending > 0)) {
1944
+ return;
1945
+ }
1946
+ const duration = this.plyr.duration || 0;
1947
+ if (!(duration > 0)) {
1948
+ return;
1949
+ }
1950
+ const current = this.plyr.currentTime || 0;
1951
+ const tolerance = 0.35;
1952
+ const nearingTarget = Math.abs(current - pending) <= tolerance;
1953
+ if (nearingTarget && duration > 0) {
1954
+ this.startTimeApplied = true;
1955
+ this.pendingStartTime = undefined;
1956
+ return;
1957
+ }
1958
+ this.jumpToPendingStartTime();
1959
+ }
1960
+ jumpToPendingStartTime() {
1961
+ if (this.startTimeApplied)
1962
+ return;
1963
+ if (!this.plyr)
1964
+ return;
1965
+ const pending = this.pendingStartTime;
1966
+ if (typeof pending !== 'number' || !(pending > 0)) {
1967
+ return;
1968
+ }
1969
+ const restoreMute = this.temporarilyMuteForSeek();
1970
+ this.seekTo(pending);
1971
+ if (restoreMute) {
1972
+ setTimeout(() => restoreMute(), 300);
1973
+ }
1974
+ this.pendingStartTime = undefined;
1975
+ this.startTimeApplied = true;
1976
+ }
1977
+ resumeAfterPendingSeek() {
1978
+ if (!this.plyr)
1979
+ return;
1980
+ const resume = () => {
1981
+ this.plyr?.off?.('seeked', resume);
1982
+ try {
1983
+ const maybePromise = this.plyr?.play?.();
1984
+ if (maybePromise && typeof maybePromise.then === 'function') {
1985
+ maybePromise.catch(() => { });
1986
+ }
1987
+ }
1988
+ catch { }
1989
+ };
1990
+ try {
1991
+ this.plyr?.pause?.();
1992
+ }
1993
+ catch { }
1994
+ this.plyr?.off?.('seeked', resume);
1995
+ this.plyr?.on?.('seeked', resume);
1996
+ }
1997
+ shouldEmitEndedOnPause() {
1998
+ if (!this.isIOSContext()) {
1999
+ return false;
2000
+ }
2001
+ if (this.endedLoggedOnPause) {
2002
+ return false;
2003
+ }
2004
+ const duration = this.plyr?.duration;
2005
+ if (!Number.isFinite(duration) || !duration || duration <= 0) {
2006
+ return false;
2007
+ }
2008
+ const current = this.plyr?.currentTime || 0;
2009
+ return duration - current < 1;
2010
+ }
2011
+ handleEnded(triggeredByPause = false) {
2012
+ if (triggeredByPause && this.endedLoggedOnPause) {
2013
+ return;
2014
+ }
2015
+ if (!triggeredByPause) {
2016
+ this.endedOverlayVisible = true;
2017
+ }
2018
+ console.log('event: ended');
2019
+ parent.postMessage('event: ended', '*');
2020
+ this.postCastEvent({
2021
+ event: 'ended',
2022
+ currentTime: this.plyr?.currentTime || 0,
2023
+ });
2024
+ this.progressService.ended$.next();
2025
+ this.endedLoggedOnPause = true;
2026
+ }
2027
+ isIOSContext() {
2028
+ if (this.isIOSDevice()) {
2029
+ return true;
2030
+ }
2031
+ if (typeof window === 'undefined') {
2032
+ return false;
2033
+ }
2034
+ try {
2035
+ const params = new URLSearchParams(window.location.search);
2036
+ const os = params.get('os');
2037
+ if (os && os.toLowerCase() === 'ios') {
2038
+ return true;
2039
+ }
2040
+ }
2041
+ catch { }
2042
+ return false;
2043
+ }
2044
+ temporarilyMuteForSeek() {
2045
+ const video = this.videoRef?.nativeElement ?? null;
2046
+ const prevVideoMuted = video?.muted ?? false;
2047
+ const prevPlyrMuted = !!this.plyr?.muted;
2048
+ try {
2049
+ if (video) {
2050
+ video.muted = true;
232
2051
  }
233
- if (this.hls) {
234
- this.hls.destroy();
235
- this.hls = undefined;
2052
+ if (this.plyr) {
2053
+ this.plyr.muted = true;
236
2054
  }
237
- setDisabledTopPageClick();
238
- if (this.config.video_host === "youtube" || this.config.video_host === "vimeo") {
239
- const onReady = () => {
240
- this.plyr.off("ready", onReady);
241
- const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, this.plyr.duration);
242
- if (start > 0)
243
- this.plyr.currentTime = start;
244
- if (this.config.autoPlay) {
245
- setTimeout(() => {
246
- this.plyr.play();
247
- }, 1000);
248
- }
249
- if (this.config.mirror) {
250
- setTimeout(() => {
251
- document
252
- .querySelector(`.plyr__video-embed iframe`) // eslint-disable-line
253
- ?.classList.add("mirror");
254
- }, 1000);
255
- }
256
- };
257
- this.plyr.on("ready", onReady);
2055
+ }
2056
+ catch { }
2057
+ return () => {
2058
+ try {
2059
+ if (video) {
2060
+ video.muted = prevVideoMuted;
2061
+ }
258
2062
  }
259
- if (this.config.video_host === "youtube") {
260
- this.plyr.source = {
261
- type: "video",
262
- sources: [{ src: this.config.video_id, provider: "youtube" }],
263
- };
2063
+ catch { }
2064
+ try {
2065
+ if (this.plyr) {
2066
+ this.plyr.muted = prevPlyrMuted;
2067
+ }
264
2068
  }
265
- else if (this.config.video_host === "vimeo") {
266
- this.plyr.source = {
267
- type: "video",
268
- sources: [{ src: this.config.video_id, provider: "vimeo" }],
269
- };
2069
+ catch { }
2070
+ this.unlockPlayerInteraction();
2071
+ };
2072
+ }
2073
+ applyInlineAttributes(videoElement, isIOS) {
2074
+ try {
2075
+ videoElement.setAttribute('playsinline', 'true');
2076
+ videoElement.setAttribute('webkit-playsinline', 'true');
2077
+ if (isIOS) {
2078
+ videoElement.setAttribute('x5-playsinline', 'true');
2079
+ videoElement.setAttribute('x5-video-player-type', 'h5');
270
2080
  }
271
- else if (this.config.video_host === "mp4") {
272
- this.plyr.source = {
273
- type: "video",
274
- sources: [{ src: this.config.video_external_id, type: "video/mp4" }],
275
- };
2081
+ }
2082
+ catch { }
2083
+ }
2084
+ bindIOSFullscreenLogging(videoElement, isIOS) {
2085
+ this.removeIOSFullscreenLogging();
2086
+ if (!isIOS)
2087
+ return;
2088
+ const logEvent = (name) => {
2089
+ console.log(`event: ${name}`);
2090
+ try {
2091
+ parent.postMessage(`event: ${name}`, '*');
276
2092
  }
277
- else if (this.config.video_host === "hls") {
278
- let sourceX = "";
2093
+ catch { }
2094
+ };
2095
+ const onEnter = () => logEvent('webkit_enterfullscreen');
2096
+ const onExit = () => logEvent('webkit_exitfullscreen');
2097
+ try {
2098
+ videoElement.addEventListener('webkitbeginfullscreen', onEnter);
2099
+ videoElement.addEventListener('webkitendfullscreen', onExit);
2100
+ this.iosFullscreenCleanup = () => {
279
2101
  try {
280
- sourceX = atob(this.config.video_id);
281
- }
282
- catch (e) {
283
- sourceX = this.config.video_id;
284
- }
285
- const canNative = video.canPlayType("application/vnd.apple.mpegurl");
286
- if (canNative) {
287
- this.plyr.source = {
288
- type: "video",
289
- sources: [{ src: sourceX, type: "application/vnd.apple.mpegurl" }],
290
- };
291
- const onLoadedMeta = () => {
292
- const dur = this.plyr.duration || video.duration || 0;
293
- const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, dur);
294
- if (start > 0) {
295
- try {
296
- this.plyr.currentTime = start;
297
- }
298
- catch {
299
- try {
300
- video.currentTime = start;
301
- }
302
- catch { }
303
- }
304
- }
305
- if (this.config.autoPlay) {
306
- try {
307
- video.muted = true;
308
- }
309
- catch { }
310
- setTimeout(() => {
311
- try {
312
- this.plyr.play();
313
- }
314
- catch {
315
- try {
316
- video.play();
317
- }
318
- catch { }
319
- }
320
- this.unmuteAfterDelay(video);
321
- }, 250);
322
- }
323
- video.removeEventListener("loadedmetadata", onLoadedMeta);
324
- };
325
- video.addEventListener("loadedmetadata", onLoadedMeta);
326
- }
327
- else if (Hls.isSupported()) {
328
- this.hls = new Hls({ enableWorker: true, lowLatencyMode: true });
329
- this.hls.attachMedia(video);
330
- this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
331
- this.hls.loadSource(sourceX);
332
- });
333
- this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
334
- const dur = video.duration || 0;
335
- const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, dur);
336
- if (start > 0) {
337
- try {
338
- video.currentTime = start;
339
- }
340
- catch { }
341
- }
342
- if (this.config.autoPlay) {
343
- video.muted = true;
344
- video.play().catch(() => { });
345
- this.unmuteAfterDelay(video);
346
- }
347
- });
348
- this.hls.on(Hls.Events.ERROR, (_evt, data) => {
349
- if (data?.fatal) {
350
- switch (data.type) {
351
- case Hls.ErrorTypes.NETWORK_ERROR:
352
- this.hls.startLoad();
353
- break;
354
- case Hls.ErrorTypes.MEDIA_ERROR:
355
- this.hls.recoverMediaError();
356
- break;
357
- default:
358
- try {
359
- this.hls.destroy();
360
- }
361
- catch { }
362
- break;
363
- }
364
- }
365
- });
2102
+ videoElement.removeEventListener('webkitbeginfullscreen', onEnter);
2103
+ videoElement.removeEventListener('webkitendfullscreen', onExit);
366
2104
  }
367
- else {
368
- this.plyr.source = { type: "video", sources: [{ src: sourceX }] };
369
- }
370
- }
371
- resolve();
372
- });
2105
+ catch { }
2106
+ this.iosFullscreenCleanup = undefined;
2107
+ };
2108
+ }
2109
+ catch { }
373
2110
  }
374
- resolveStartTime(last_time, last_percent, duration, opts = {}) {
375
- const minStartSec = opts.minStartSec ?? 3;
376
- const endGuardSec = opts.endGuardSec ?? 2;
377
- const dur = Number(duration) || 0;
378
- if (dur <= 0)
379
- return 0;
380
- const hasTime = last_time !== undefined && last_time !== null && last_time !== "";
381
- if (hasTime) {
382
- let t = Number(last_time);
383
- if (!isFinite(t) || t < 0)
384
- t = 0;
385
- if (t < minStartSec)
386
- return 0;
387
- return Math.min(dur - endGuardSec, t);
2111
+ removeIOSFullscreenLogging() {
2112
+ try {
2113
+ this.iosFullscreenCleanup?.();
388
2114
  }
389
- const hasPercent = last_percent !== undefined && last_percent !== null && last_percent !== "";
390
- if (hasPercent) {
391
- let p = Number(last_percent);
392
- if (!isFinite(p) || p < 0)
393
- p = 0;
394
- if (p > 1)
395
- p = p / 100;
396
- let t = dur * p;
397
- if (t < minStartSec)
398
- return 0;
399
- return Math.min(dur - endGuardSec, t);
2115
+ catch { }
2116
+ this.iosFullscreenCleanup = undefined;
2117
+ }
2118
+ ensureCastMessageListener() {
2119
+ if (typeof window === 'undefined') {
2120
+ return;
400
2121
  }
401
- return 0;
2122
+ if (this.castListenerAttached) {
2123
+ return;
2124
+ }
2125
+ window.addEventListener('message', this.onCastMessage);
2126
+ this.castListenerAttached = true;
402
2127
  }
403
- ngOnDestroy() {
404
- this.hls?.destroy();
405
- this.plyr?.destroy();
2128
+ unlockPlayerInteraction() {
2129
+ try {
2130
+ const controls = this.plyr?.elements?.controls;
2131
+ if (controls) {
2132
+ controls.style.pointerEvents = 'auto';
2133
+ }
2134
+ const overlay = this.plyr?.elements?.buttons?.play;
2135
+ if (overlay) {
2136
+ overlay.style.pointerEvents = 'auto';
2137
+ }
2138
+ }
2139
+ catch { }
406
2140
  }
407
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlyrPlayerComponent, deps: [{ token: WebplayerProgressService }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component });
408
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PlyrPlayerComponent, isStandalone: true, selector: "app-plyr-player", inputs: { config: "config" }, viewQueries: [{ propertyName: "videoRef", first: true, predicate: ["video"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `<video #video playsinline></video>`, isInline: true, styles: [":host{display:block;width:100%;height:100%;position:relative;--plyr-color-main: var(--theme-alert, #19a6d9);--plyr-color-hover: var(--theme-alert, #2dbbff);--plyr-progress-buffer-background: var(--theme-progress-view-off, rgba(255, 255, 255, .25));--plyr-control-background: var(--theme-bg, rgba(0, 0, 0, .8));--plyr-control-color: var(--theme-text-color, #fff);--plyr-control-button-color: var(--theme-font-bt, #fff)}:host ::ng-deep .plyr__controls{background:var(--plyr-control-background)!important;color:var(--plyr-control-color)!important}:host ::ng-deep .plyr__controls button{color:var(--plyr-control-color)!important}:host ::ng-deep .plyr__progress__buffer{background-color:var(--plyr-progress-buffer-background)!important}:host ::ng-deep .plyr,:host ::ng-deep .plyr__video-wrapper,:host ::ng-deep .plyr__video-embed,:host ::ng-deep .plyr__video-embed>iframe,:host ::ng-deep video{width:100%!important;height:100%!important;aspect-ratio:auto!important;top:0!important;left:0!important}:host ::ng-deep .mirror{transform:scaleX(-1)!important;-webkit-transform:scaleX(-1)!important}:host ::ng-deep .plyr--video .plyr__control.plyr__tab-focus,:host ::ng-deep .plyr--video .plyr__control:hover,:host ::ng-deep .plyr--video .plyr__control[aria-expanded=true]{background:var(--plyr-color-hover)!important;color:var(--plyr-control-button-color)!important}:host ::ng-deep .plyr--full-ui input[type=range]{color:var(--plyr-color-main)!important}:host ::ng-deep .plyr__menu__container .plyr__control[role=menuitemradio][aria-checked=true]:before{background:var(--plyr-color-main)!important}:host ::ng-deep .plyr__menu__container{background:var(--plyr-control-background)!important}:host ::ng-deep .ytp-more-videos-view .ytp-suggestions{pointer-events:none!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2141
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: VimeoPlayerComponent, deps: [{ token: WebplayerProgressService }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component });
2142
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: VimeoPlayerComponent, isStandalone: true, selector: "app-vimeo-player", inputs: { config: "config" }, outputs: { ready: "ready" }, host: { properties: { "class.overlay-visible": "endedOverlayVisible" } }, viewQueries: [{ propertyName: "videoRef", first: true, predicate: ["video"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
2143
+ <div class="vimeo-wrapper">
2144
+ <video #video playsinline></video>
2145
+ </div>
2146
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100%;position:relative;--plyr-color-main: var(--theme-alert, #19a6d9);--plyr-color-hover: var(--theme-alert, #2dbbff);--plyr-progress-buffer-background: var( --theme-progress-view-off, rgba(255, 255, 255, .25) );--plyr-control-background: var(--theme-bg, rgba(0, 0, 0, .8));--plyr-control-color: var(--theme-text-color, #fff);--plyr-control-button-color: var(--theme-font-bt, #fff)}:host ::ng-deep .plyr__controls{color:var(--plyr-control-color)!important;transition:opacity .2s ease,transform .2s ease;z-index:5}:host ::ng-deep .plyr__controls button{color:var(--plyr-control-color)!important}:host ::ng-deep .plyr__progress__buffer{background-color:var(--plyr-progress-buffer-background)!important}.vimeo-wrapper{width:100%;height:100%;position:relative}:host ::ng-deep .plyr,:host ::ng-deep .plyr__video-wrapper,:host ::ng-deep .plyr__video-embed,:host ::ng-deep .plyr__video-embed>iframe{width:100%!important;height:100%!important;top:0!important;left:0!important;background:#000!important}:host ::ng-deep .plyr__video-embed>iframe,:host ::ng-deep video{max-height:100%!important;object-fit:contain!important}:host ::ng-deep .plyr__video-wrapper{position:relative;z-index:1}:host(.overlay-visible) ::ng-deep .plyr__video-wrapper:after{content:\"\";position:absolute;inset:0 0 40px;pointer-events:none;background:radial-gradient(circle at 30% 20%,rgba(255,255,255,.12),transparent 45%),radial-gradient(circle at 70% 30%,rgba(255,255,255,.09),transparent 45%),#000000eb;z-index:2}:host ::ng-deep .plyr--video .plyr__control.plyr__tab-focus,:host ::ng-deep .plyr--video .plyr__control:hover,:host ::ng-deep .plyr--video .plyr__control[aria-expanded=true]{background:var(--plyr-color-hover)!important;color:var(--plyr-control-button-color)!important}:host ::ng-deep .plyr--full-ui input[type=range]{color:var(--plyr-color-main)!important}:host ::ng-deep .plyr__menu__container .plyr__control[role=menuitemradio][aria-checked=true]:before{background:var(--plyr-color-main)!important}:host ::ng-deep .plyr__menu__container{background:var(--plyr-control-background)!important;z-index:7!important}:host ::ng-deep .plyr__control--overlaid{position:fixed!important;z-index:4!important}:host ::ng-deep .plyr--auto-hide-controls .plyr__controls{opacity:0!important;pointer-events:none!important;transform:translateY(12px)}:host ::ng-deep .ytp-more-videos-view .ytp-suggestions{pointer-events:none!important}:host ::ng-deep .plyr--vimeo .plyr__video-wrapper.plyr__video-embed{aspect-ratio:16 / 9!important;width:100%!important;height:auto!important}:host ::ng-deep .plyr__control--overlaid,:host ::ng-deep .plyr--video .plyr__control.plyr__control--overlaid:hover{background:#fff!important;color:#000!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
409
2147
  }
410
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlyrPlayerComponent, decorators: [{
2148
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: VimeoPlayerComponent, decorators: [{
411
2149
  type: Component,
412
- args: [{ selector: "app-plyr-player", template: `<video #video playsinline></video>`, standalone: true, imports: [CommonModule], styles: [":host{display:block;width:100%;height:100%;position:relative;--plyr-color-main: var(--theme-alert, #19a6d9);--plyr-color-hover: var(--theme-alert, #2dbbff);--plyr-progress-buffer-background: var(--theme-progress-view-off, rgba(255, 255, 255, .25));--plyr-control-background: var(--theme-bg, rgba(0, 0, 0, .8));--plyr-control-color: var(--theme-text-color, #fff);--plyr-control-button-color: var(--theme-font-bt, #fff)}:host ::ng-deep .plyr__controls{background:var(--plyr-control-background)!important;color:var(--plyr-control-color)!important}:host ::ng-deep .plyr__controls button{color:var(--plyr-control-color)!important}:host ::ng-deep .plyr__progress__buffer{background-color:var(--plyr-progress-buffer-background)!important}:host ::ng-deep .plyr,:host ::ng-deep .plyr__video-wrapper,:host ::ng-deep .plyr__video-embed,:host ::ng-deep .plyr__video-embed>iframe,:host ::ng-deep video{width:100%!important;height:100%!important;aspect-ratio:auto!important;top:0!important;left:0!important}:host ::ng-deep .mirror{transform:scaleX(-1)!important;-webkit-transform:scaleX(-1)!important}:host ::ng-deep .plyr--video .plyr__control.plyr__tab-focus,:host ::ng-deep .plyr--video .plyr__control:hover,:host ::ng-deep .plyr--video .plyr__control[aria-expanded=true]{background:var(--plyr-color-hover)!important;color:var(--plyr-control-button-color)!important}:host ::ng-deep .plyr--full-ui input[type=range]{color:var(--plyr-color-main)!important}:host ::ng-deep .plyr__menu__container .plyr__control[role=menuitemradio][aria-checked=true]:before{background:var(--plyr-color-main)!important}:host ::ng-deep .plyr__menu__container{background:var(--plyr-control-background)!important}:host ::ng-deep .ytp-more-videos-view .ytp-suggestions{pointer-events:none!important}\n"] }]
2150
+ args: [{ selector: 'app-vimeo-player', standalone: true, imports: [CommonModule], host: {
2151
+ '[class.overlay-visible]': 'endedOverlayVisible',
2152
+ }, template: `
2153
+ <div class="vimeo-wrapper">
2154
+ <video #video playsinline></video>
2155
+ </div>
2156
+ `, styles: [":host{display:block;width:100%;height:100%;position:relative;--plyr-color-main: var(--theme-alert, #19a6d9);--plyr-color-hover: var(--theme-alert, #2dbbff);--plyr-progress-buffer-background: var( --theme-progress-view-off, rgba(255, 255, 255, .25) );--plyr-control-background: var(--theme-bg, rgba(0, 0, 0, .8));--plyr-control-color: var(--theme-text-color, #fff);--plyr-control-button-color: var(--theme-font-bt, #fff)}:host ::ng-deep .plyr__controls{color:var(--plyr-control-color)!important;transition:opacity .2s ease,transform .2s ease;z-index:5}:host ::ng-deep .plyr__controls button{color:var(--plyr-control-color)!important}:host ::ng-deep .plyr__progress__buffer{background-color:var(--plyr-progress-buffer-background)!important}.vimeo-wrapper{width:100%;height:100%;position:relative}:host ::ng-deep .plyr,:host ::ng-deep .plyr__video-wrapper,:host ::ng-deep .plyr__video-embed,:host ::ng-deep .plyr__video-embed>iframe{width:100%!important;height:100%!important;top:0!important;left:0!important;background:#000!important}:host ::ng-deep .plyr__video-embed>iframe,:host ::ng-deep video{max-height:100%!important;object-fit:contain!important}:host ::ng-deep .plyr__video-wrapper{position:relative;z-index:1}:host(.overlay-visible) ::ng-deep .plyr__video-wrapper:after{content:\"\";position:absolute;inset:0 0 40px;pointer-events:none;background:radial-gradient(circle at 30% 20%,rgba(255,255,255,.12),transparent 45%),radial-gradient(circle at 70% 30%,rgba(255,255,255,.09),transparent 45%),#000000eb;z-index:2}:host ::ng-deep .plyr--video .plyr__control.plyr__tab-focus,:host ::ng-deep .plyr--video .plyr__control:hover,:host ::ng-deep .plyr--video .plyr__control[aria-expanded=true]{background:var(--plyr-color-hover)!important;color:var(--plyr-control-button-color)!important}:host ::ng-deep .plyr--full-ui input[type=range]{color:var(--plyr-color-main)!important}:host ::ng-deep .plyr__menu__container .plyr__control[role=menuitemradio][aria-checked=true]:before{background:var(--plyr-color-main)!important}:host ::ng-deep .plyr__menu__container{background:var(--plyr-control-background)!important;z-index:7!important}:host ::ng-deep .plyr__control--overlaid{position:fixed!important;z-index:4!important}:host ::ng-deep .plyr--auto-hide-controls .plyr__controls{opacity:0!important;pointer-events:none!important;transform:translateY(12px)}:host ::ng-deep .ytp-more-videos-view .ytp-suggestions{pointer-events:none!important}:host ::ng-deep .plyr--vimeo .plyr__video-wrapper.plyr__video-embed{aspect-ratio:16 / 9!important;width:100%!important;height:auto!important}:host ::ng-deep .plyr__control--overlaid,:host ::ng-deep .plyr--video .plyr__control.plyr__control--overlaid:hover{background:#fff!important;color:#000!important}\n"] }]
413
2157
  }], ctorParameters: () => [{ type: WebplayerProgressService }, { type: Document, decorators: [{
414
2158
  type: Inject,
415
2159
  args: [DOCUMENT]
416
- }] }], propDecorators: { videoRef: [{
417
- type: ViewChild,
418
- args: ["video", { static: true }]
419
- }], config: [{
2160
+ }] }], propDecorators: { config: [{
420
2161
  type: Input
2162
+ }], ready: [{
2163
+ type: Output
2164
+ }], videoRef: [{
2165
+ type: ViewChild,
2166
+ args: ['video', { static: true }]
421
2167
  }] } });
422
2168
 
423
2169
  class PandaPlayerComponent {
424
2170
  elementRef;
425
2171
  progressService;
426
2172
  config;
2173
+ ready = new EventEmitter();
427
2174
  pandaPlayer;
428
2175
  pandaContainer;
429
2176
  pandaProgressThrottle = 0;
@@ -457,6 +2204,7 @@ class PandaPlayerComponent {
457
2204
  }
458
2205
  this.ensurePandaScript().then(() => {
459
2206
  const host = this.elementRef.nativeElement;
2207
+ console.log('Panda Player Config:', this.config, host);
460
2208
  if (!this.pandaContainer) {
461
2209
  this.pandaContainer = document.createElement("div");
462
2210
  this.pandaContainer.style.width = "100%";
@@ -468,8 +2216,8 @@ class PandaPlayerComponent {
468
2216
  this.pandaPlayer?.destroy?.();
469
2217
  }
470
2218
  catch { }
471
- const videoId = this.config.video_id || (this.config.embed ? this.extractPandaVideoId(this.config.embed) : undefined);
472
- const libraryId = this.config.library_id || (this.config.embed ? this.extractPandaLibraryId(this.config.embed) : undefined);
2219
+ const videoId = this.config.video_external_id || (this.config.embed ? this.extractPandaVideoId(this.config.embed) : undefined);
2220
+ const libraryId = (this.config.embed ? this.extractPandaLibraryId(this.config.embed) : undefined);
473
2221
  window.pandascripttag = window.pandascripttag || [];
474
2222
  window.pandascripttag.push(() => {
475
2223
  this.pandaPlayer = new window.PandaPlayer(this.pandaContainer.id, {
@@ -496,16 +2244,17 @@ class PandaPlayerComponent {
496
2244
  }
497
2245
  catch { }
498
2246
  }
499
- if (this.config.autoPlay) {
2247
+ if (this.config.autoplay) {
500
2248
  try {
501
2249
  this.pandaPlayer.play();
502
2250
  }
503
2251
  catch { }
504
2252
  }
2253
+ this.ready.emit();
505
2254
  },
506
2255
  playerConfigs: {
507
- autoplay: !!this.config.autoPlay,
508
- smartAutoplay: !!this.config.autoPlay,
2256
+ autoplay: !!this.config.autoplay,
2257
+ smartAutoplay: !!this.config.autoplay,
509
2258
  },
510
2259
  });
511
2260
  this.pandaPlayer.onEvent((event) => {
@@ -604,6 +2353,13 @@ class PandaPlayerComponent {
604
2353
  catch { }
605
2354
  }
606
2355
  }
2356
+ if (type === "panda_play") {
2357
+ const snapshot = this.safeProgressSnapshot();
2358
+ if (snapshot) {
2359
+ this.progressService.progress$.next(snapshot);
2360
+ }
2361
+ this.notifyParentPlaybackEvent('play', snapshot ?? {});
2362
+ }
607
2363
  if (type === "panda_pause") {
608
2364
  try {
609
2365
  const t = this.pandaPlayer?.getCurrentTime?.() || 0;
@@ -612,16 +2368,15 @@ class PandaPlayerComponent {
612
2368
  this.progressService.progress$.next({ currentTime: t, duration: d, percent: p });
613
2369
  }
614
2370
  catch { }
2371
+ this.notifyParentPlaybackEvent('pause', this.safeProgressSnapshot());
615
2372
  }
616
2373
  if (type === "panda_ended") {
617
- try {
618
- const t = this.pandaPlayer?.getCurrentTime?.() || 0;
619
- const d = this.pandaPlayer?.getDuration?.() || 0;
620
- const p = d > 0 ? +((t / d) * 100).toFixed(2) : 0;
621
- this.progressService.progress$.next({ currentTime: t, duration: d, percent: p });
2374
+ const snapshot = this.safeProgressSnapshot();
2375
+ if (snapshot) {
2376
+ this.progressService.progress$.next(snapshot);
622
2377
  }
623
- catch { }
624
2378
  this.progressService.ended$.next();
2379
+ this.notifyParentPlaybackEvent('ended', snapshot ?? {});
625
2380
  }
626
2381
  }
627
2382
  resolveStartTime(last_time, last_percent, duration, opts = {}) {
@@ -655,23 +2410,56 @@ class PandaPlayerComponent {
655
2410
  }
656
2411
  return 0;
657
2412
  }
2413
+ safeProgressSnapshot() {
2414
+ try {
2415
+ const currentTime = this.pandaPlayer?.getCurrentTime?.() || 0;
2416
+ const duration = this.pandaPlayer?.getDuration?.() || 0;
2417
+ const percent = duration > 0 ? +((currentTime / duration) * 100).toFixed(2) : 0;
2418
+ return { currentTime, duration, percent };
2419
+ }
2420
+ catch {
2421
+ return null;
2422
+ }
2423
+ }
2424
+ notifyParentPlaybackEvent(event, extra = {}) {
2425
+ if (typeof window === 'undefined')
2426
+ return;
2427
+ const target = typeof parent !== 'undefined' ? parent : null;
2428
+ if (!target || target === window)
2429
+ return;
2430
+ const payload = { event, ...extra };
2431
+ try {
2432
+ target.postMessage(`event: ${event}`, '*');
2433
+ }
2434
+ catch { }
2435
+ try {
2436
+ target.postMessage(JSON.stringify(payload), '*');
2437
+ }
2438
+ catch { }
2439
+ }
658
2440
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PandaPlayerComponent, deps: [{ token: i0.ElementRef }, { token: WebplayerProgressService }], target: i0.ɵɵFactoryTarget.Component });
659
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PandaPlayerComponent, isStandalone: true, selector: "app-panda-player", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true, styles: [":host{display:block;width:100%;height:100%;position:relative}:host ::ng-deep .panda-player,:host ::ng-deep iframe[src*=\"pandavideo.com.br\"]{position:absolute!important;top:0!important;left:0!important;width:100%!important;height:100%!important;border:none!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2441
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PandaPlayerComponent, isStandalone: true, selector: "app-panda-player", inputs: { config: "config" }, outputs: { ready: "ready" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true, styles: [":host{display:block;width:100%;height:100%;position:relative}:host ::ng-deep .panda-player,:host ::ng-deep iframe[src*=\"pandavideo.com.br\"]{position:absolute!important;top:0!important;left:0!important;width:100%!important;height:100%!important;border:none!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
660
2442
  }
661
2443
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PandaPlayerComponent, decorators: [{
662
2444
  type: Component,
663
2445
  args: [{ selector: 'app-panda-player', template: '', standalone: true, imports: [CommonModule], styles: [":host{display:block;width:100%;height:100%;position:relative}:host ::ng-deep .panda-player,:host ::ng-deep iframe[src*=\"pandavideo.com.br\"]{position:absolute!important;top:0!important;left:0!important;width:100%!important;height:100%!important;border:none!important}\n"] }]
664
2446
  }], ctorParameters: () => [{ type: i0.ElementRef }, { type: WebplayerProgressService }], propDecorators: { config: [{
665
2447
  type: Input
2448
+ }], ready: [{
2449
+ type: Output
666
2450
  }] } });
667
2451
 
668
2452
  class ScaleupPlayerComponent {
669
2453
  elementRef;
670
2454
  progressService;
671
2455
  config;
2456
+ ready = new EventEmitter();
672
2457
  scaleupIframe;
673
2458
  scaleupMessageHandler;
674
2459
  scaleupProgressThrottle = 0;
2460
+ castMessageHandler;
2461
+ lastDuration = 0;
2462
+ lastCurrentTime = 0;
675
2463
  constructor(elementRef, progressService) {
676
2464
  this.elementRef = elementRef;
677
2465
  this.progressService = progressService;
@@ -690,16 +2478,25 @@ class ScaleupPlayerComponent {
690
2478
  loadScaleupPlayer() {
691
2479
  const videoId = this.config.video_id;
692
2480
  const lastTime = Math.max(0, Math.floor(Number(this.config.last_time) || 0));
693
- const eventsList = "controlsdisabled,controlsenabled,dispose,durationchange,ended,enterFullWindow,enterpictureinpicture,error,exitFullWindow,firstplay,fullscreenchange,leavepictureinpicture,loadedmetadata,loadstart,pause,play,playerreset,playerresize,playing,posterchange,progress,ratechange,ready,resize,seeked,seeking,textdata,timeupdate,useractive,userinactive,volumechange,waiting";
694
- const scaleupSrc = `https://player.scaleup.com.br/embed/${videoId}?seekTo=${lastTime}&events=${eventsList}&autoplay=${this.config.autoPlay}`;
2481
+ const eventsList = this.resolveEventsList();
2482
+ const params = new URLSearchParams({
2483
+ seekTo: String(lastTime),
2484
+ events: eventsList,
2485
+ autoplay: String(!!this.config.autoplay),
2486
+ });
2487
+ if (this.isIOSContext()) {
2488
+ params.set("fullscreen", "0");
2489
+ }
2490
+ const scaleupSrc = `https://player.scaleup.com.br/embed/${videoId}?${params.toString()}`;
695
2491
  const host = this.elementRef.nativeElement;
696
2492
  if (this.scaleupIframe) {
697
2493
  this.scaleupIframe.remove();
698
2494
  }
699
2495
  const iframe = document.createElement("iframe");
700
2496
  iframe.src = scaleupSrc;
701
- iframe.allow = "autoplay; fullscreen; encrypted-media; picture-in-picture";
2497
+ iframe.allow = "autoplay; encrypted-media; picture-in-picture; web-share";
702
2498
  iframe.referrerPolicy = "origin-when-cross-origin";
2499
+ iframe.allowFullscreen = false;
703
2500
  iframe.frameBorder = "0";
704
2501
  iframe.style.width = "100%";
705
2502
  iframe.style.height = "100%";
@@ -708,6 +2505,11 @@ class ScaleupPlayerComponent {
708
2505
  iframe.style.left = "0";
709
2506
  host.appendChild(iframe);
710
2507
  this.scaleupIframe = iframe;
2508
+ this.ensureCastMessageHandler();
2509
+ // Emite evento ready quando o iframe carregar
2510
+ iframe.onload = () => {
2511
+ this.ready.emit();
2512
+ };
711
2513
  if (this.scaleupMessageHandler) {
712
2514
  window.removeEventListener("message", this.scaleupMessageHandler);
713
2515
  }
@@ -715,6 +2517,8 @@ class ScaleupPlayerComponent {
715
2517
  try {
716
2518
  const originOk = typeof e.origin === "string" &&
717
2519
  e.origin.includes("scaleup.com.br");
2520
+ console.log('Scaleup message received:', e);
2521
+ console.log('Origin OK:', originOk);
718
2522
  if (!originOk)
719
2523
  return;
720
2524
  const data = typeof e.data === "string"
@@ -727,6 +2531,7 @@ class ScaleupPlayerComponent {
727
2531
  }
728
2532
  })()
729
2533
  : e.data;
2534
+ console.log('Parsed data:', data);
730
2535
  if (!data || typeof data !== "object")
731
2536
  return;
732
2537
  const type = (data.event ||
@@ -736,11 +2541,21 @@ class ScaleupPlayerComponent {
736
2541
  "")
737
2542
  .toString()
738
2543
  .toLowerCase();
2544
+ console.log('Event type:', type);
739
2545
  if (!type)
740
2546
  return;
2547
+ if (type.includes("fullscreen") || type.includes("fullwindow")) {
2548
+ try {
2549
+ console.log(`scaleup event: ${type}`);
2550
+ }
2551
+ catch { }
2552
+ }
741
2553
  const t = Number((data.currentTime ?? data.time ?? data.position) || 0);
742
2554
  const d = Number((data.duration ?? data.totalDuration) || 0);
743
2555
  const p = d > 0 ? +((t / d) * 100).toFixed(2) : 0;
2556
+ this.lastCurrentTime = t;
2557
+ this.lastDuration = d;
2558
+ this.postCastProgress(t, d);
744
2559
  const THROTTLE_MS = 300;
745
2560
  const now = performance.now();
746
2561
  if (type.includes("timeupdate") ||
@@ -772,94 +2587,299 @@ class ScaleupPlayerComponent {
772
2587
  window.removeEventListener("message", this.scaleupMessageHandler);
773
2588
  this.scaleupMessageHandler = undefined;
774
2589
  }
2590
+ if (this.castMessageHandler) {
2591
+ window.removeEventListener("message", this.castMessageHandler);
2592
+ this.castMessageHandler = undefined;
2593
+ }
2594
+ }
2595
+ postCastProgress(currentTime, duration) {
2596
+ try {
2597
+ console.log("Scaleup -> Cast postProgress", { currentTime, duration });
2598
+ parent.postMessage({
2599
+ currentTime,
2600
+ duration,
2601
+ }, "*");
2602
+ }
2603
+ catch { }
2604
+ }
2605
+ sendScaleupCommand(cmd) {
2606
+ try {
2607
+ console.log("Scaleup -> iframe command", cmd);
2608
+ const target = this.scaleupIframe?.contentWindow;
2609
+ if (!target)
2610
+ return;
2611
+ target.postMessage(cmd, "*");
2612
+ if (cmd?.action && typeof cmd.action === "string") {
2613
+ target.postMessage({ type: cmd.action }, "*");
2614
+ target.postMessage(cmd.action, "*");
2615
+ }
2616
+ }
2617
+ catch { }
2618
+ }
2619
+ ensureCastMessageHandler() {
2620
+ if (this.castMessageHandler)
2621
+ return;
2622
+ this.castMessageHandler = (event) => {
2623
+ console.log("Cast message received (Scaleup)", event.data);
2624
+ const data = event?.data;
2625
+ if (!data)
2626
+ return;
2627
+ let payload = data;
2628
+ if (typeof data === "string") {
2629
+ try {
2630
+ payload = JSON.parse(data);
2631
+ }
2632
+ catch {
2633
+ payload = { action: data };
2634
+ }
2635
+ }
2636
+ const actionRaw = payload?.action ?? payload?.type ?? "";
2637
+ const action = typeof actionRaw === "string"
2638
+ ? actionRaw.toLowerCase()
2639
+ : "";
2640
+ console.log("Cast normalized action", action);
2641
+ if (action === "request-iframe-media-metadata") {
2642
+ const duration = this.lastDuration || 0;
2643
+ const currentTime = this.lastCurrentTime || 0;
2644
+ this.postCastProgress(currentTime, duration);
2645
+ return;
2646
+ }
2647
+ if (action === "play") {
2648
+ this.sendScaleupCommand({ action: "play" });
2649
+ return;
2650
+ }
2651
+ if (action === "pause") {
2652
+ this.sendScaleupCommand({ action: "pause" });
2653
+ return;
2654
+ }
2655
+ if (action === "seek" && typeof payload.time === "number") {
2656
+ this.sendScaleupCommand({ action: "seek", time: payload.time });
2657
+ return;
2658
+ }
2659
+ };
2660
+ window.addEventListener("message", this.castMessageHandler);
2661
+ }
2662
+ resolveEventsList() {
2663
+ const baseEvents = [
2664
+ "controlsdisabled",
2665
+ "controlsenabled",
2666
+ "dispose",
2667
+ "durationchange",
2668
+ "ended",
2669
+ "enterpictureinpicture",
2670
+ "error",
2671
+ "firstplay",
2672
+ "leavepictureinpicture",
2673
+ "loadedmetadata",
2674
+ "loadstart",
2675
+ "pause",
2676
+ "play",
2677
+ "playerreset",
2678
+ "playerresize",
2679
+ "playing",
2680
+ "posterchange",
2681
+ "progress",
2682
+ "ratechange",
2683
+ "ready",
2684
+ "resize",
2685
+ "seeked",
2686
+ "seeking",
2687
+ "textdata",
2688
+ "timeupdate",
2689
+ "useractive",
2690
+ "userinactive",
2691
+ "volumechange",
2692
+ "waiting",
2693
+ ];
2694
+ if (!this.isIOSContext()) {
2695
+ return baseEvents.join(",");
2696
+ }
2697
+ return baseEvents
2698
+ .filter((name) => !name.toLowerCase().includes("fullscreen") &&
2699
+ !name.toLowerCase().includes("fullwindow") &&
2700
+ !name.toLowerCase().includes("airplay"))
2701
+ .join(",");
2702
+ }
2703
+ isIOSDevice() {
2704
+ if (typeof navigator === "undefined")
2705
+ return false;
2706
+ const ua = navigator.userAgent || "";
2707
+ const iOS = /iPad|iPhone|iPod/.test(ua);
2708
+ const iPadOS = navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
2709
+ return iOS || iPadOS;
2710
+ }
2711
+ isIOSContext() {
2712
+ if (this.isIOSDevice()) {
2713
+ return true;
2714
+ }
2715
+ return false;
775
2716
  }
776
2717
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ScaleupPlayerComponent, deps: [{ token: i0.ElementRef }, { token: WebplayerProgressService }], target: i0.ɵɵFactoryTarget.Component });
777
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: ScaleupPlayerComponent, isStandalone: true, selector: "app-scaleup-player", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true, styles: [":host{display:block;width:100%;height:100%;position:relative}:host ::ng-deep iframe[src*=\"scaleup.com.br\"]{position:absolute!important;top:0!important;left:0!important;width:100%!important;height:100%!important;border:none!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2718
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: ScaleupPlayerComponent, isStandalone: true, selector: "app-scaleup-player", inputs: { config: "config" }, outputs: { ready: "ready" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true, styles: [":host{display:block;width:100%;height:100%;position:relative}:host ::ng-deep iframe[src*=\"scaleup.com.br\"]{position:absolute!important;top:0!important;left:0!important;width:100%!important;height:100%!important;border:none!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
778
2719
  }
779
2720
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ScaleupPlayerComponent, decorators: [{
780
2721
  type: Component,
781
2722
  args: [{ selector: 'app-scaleup-player', template: '', standalone: true, imports: [CommonModule], styles: [":host{display:block;width:100%;height:100%;position:relative}:host ::ng-deep iframe[src*=\"scaleup.com.br\"]{position:absolute!important;top:0!important;left:0!important;width:100%!important;height:100%!important;border:none!important}\n"] }]
782
2723
  }], ctorParameters: () => [{ type: i0.ElementRef }, { type: WebplayerProgressService }], propDecorators: { config: [{
783
2724
  type: Input
2725
+ }], ready: [{
2726
+ type: Output
784
2727
  }] } });
785
2728
 
2729
+ class GlobalLoadingService {
2730
+ document;
2731
+ loadingElement;
2732
+ constructor(document) {
2733
+ this.document = document;
2734
+ this.loadingElement = this.document.getElementById('global-loading');
2735
+ }
2736
+ hide() {
2737
+ if (this.loadingElement) {
2738
+ this.loadingElement.style.opacity = '0';
2739
+ setTimeout(() => {
2740
+ if (this.loadingElement) {
2741
+ this.loadingElement.style.display = 'none';
2742
+ }
2743
+ }, 500); // Duração da transição do CSS
2744
+ }
2745
+ }
2746
+ show() {
2747
+ if (this.loadingElement) {
2748
+ this.loadingElement.style.display = 'flex';
2749
+ this.loadingElement.style.opacity = '1';
2750
+ }
2751
+ }
2752
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GlobalLoadingService, deps: [{ token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
2753
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GlobalLoadingService, providedIn: 'root' });
2754
+ }
2755
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GlobalLoadingService, decorators: [{
2756
+ type: Injectable,
2757
+ args: [{
2758
+ providedIn: 'root'
2759
+ }]
2760
+ }], ctorParameters: () => [{ type: Document, decorators: [{
2761
+ type: Inject,
2762
+ args: [DOCUMENT]
2763
+ }] }] });
2764
+
786
2765
  class PlayerLoaderComponent {
2766
+ defaultConfig;
2767
+ globalLoading;
787
2768
  config;
788
- ngOnInit() {
789
- console.log(this.config);
2769
+ playerType = null;
2770
+ constructor(defaultConfig, globalLoading) {
2771
+ this.defaultConfig = defaultConfig;
2772
+ this.globalLoading = globalLoading;
2773
+ this.config = { ...this.defaultConfig, ...this.config };
2774
+ this.playerType = this.getPlayerType(this.config.videoUrl);
2775
+ }
2776
+ getPlayerType(videoUrl) {
2777
+ if (videoUrl?.includes("panda"))
2778
+ return "panda";
2779
+ if (videoUrl?.includes("scaleup"))
2780
+ return "scaleup";
2781
+ if (videoUrl?.includes("vimeo") ||
2782
+ videoUrl?.includes("youtube") ||
2783
+ videoUrl?.includes(".m3u8") ||
2784
+ videoUrl?.includes(".mp4"))
2785
+ return "plyr";
2786
+ return "plyr";
790
2787
  }
791
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlayerLoaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2788
+ onPlayerReady() {
2789
+ this.globalLoading.hide();
2790
+ }
2791
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlayerLoaderComponent, deps: [{ token: WEPLAYER_CONFIG }, { token: GlobalLoadingService }], target: i0.ɵɵFactoryTarget.Component });
792
2792
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PlayerLoaderComponent, isStandalone: true, selector: "app-player-loader", inputs: { config: "config" }, ngImport: i0, template: `
793
- <div [ngSwitch]="config?.video_host" style="width: 100%; height: 100%;">
794
- <app-plyr-player
795
- *ngSwitchCase="'hls'"
796
- [config]="config"
797
- ></app-plyr-player>
798
- <app-plyr-player
799
- *ngSwitchCase="'mp4'"
800
- [config]="config"
801
- ></app-plyr-player>
802
- <app-plyr-player
803
- *ngSwitchCase="'youtube'"
804
- [config]="config"
805
- ></app-plyr-player>
806
- <app-plyr-player
807
- *ngSwitchCase="'vimeo'"
808
- [config]="config"
809
- ></app-plyr-player>
810
- <app-panda-player
811
- *ngSwitchCase="'panda'"
812
- [config]="config"
813
- ></app-panda-player>
814
- <app-scaleup-player
815
- *ngSwitchCase="'scaleup'"
816
- [config]="config"
817
- ></app-scaleup-player>
818
- <div *ngSwitchDefault>
819
- <p>Player not supported or config not provided.</p>
2793
+ <div style="width: 100%; height: 100%;">
2794
+ <div [ngSwitch]="config?.video_host" style="width: 100%; height: 100%;">
2795
+ <app-plyr-player
2796
+ *ngSwitchCase="'hls'"
2797
+ [config]="config"
2798
+ (ready)="onPlayerReady()"
2799
+ ></app-plyr-player>
2800
+ <app-plyr-player
2801
+ *ngSwitchCase="'mp4'"
2802
+ [config]="config"
2803
+ (ready)="onPlayerReady()"
2804
+ ></app-plyr-player>
2805
+ <app-plyr-player
2806
+ *ngSwitchCase="'youtube'"
2807
+ [config]="config"
2808
+ (ready)="onPlayerReady()"
2809
+ ></app-plyr-player>
2810
+ <app-vimeo-player
2811
+ *ngSwitchCase="'vimeo'"
2812
+ [config]="config"
2813
+ (ready)="onPlayerReady()"
2814
+ ></app-vimeo-player>
2815
+ <app-panda-player
2816
+ *ngSwitchCase="'panda'"
2817
+ [config]="config"
2818
+ (ready)="onPlayerReady()"
2819
+ ></app-panda-player>
2820
+ <app-scaleup-player
2821
+ *ngSwitchCase="'scaleup'"
2822
+ [config]="config"
2823
+ (ready)="onPlayerReady()"
2824
+ ></app-scaleup-player>
2825
+ <div *ngSwitchDefault>
2826
+ <p>Player não suportado ou configuração não fornecida.</p>
2827
+ </div>
820
2828
  </div>
821
2829
  </div>
822
- `, isInline: true, styles: [":host{display:block;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "component", type: PlyrPlayerComponent, selector: "app-plyr-player", inputs: ["config"] }, { kind: "component", type: PandaPlayerComponent, selector: "app-panda-player", inputs: ["config"] }, { kind: "component", type: ScaleupPlayerComponent, selector: "app-scaleup-player", inputs: ["config"] }] });
2830
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i2.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "component", type: PlyrPlayerComponent, selector: "app-plyr-player", inputs: ["config"], outputs: ["ready"] }, { kind: "component", type: VimeoPlayerComponent, selector: "app-vimeo-player", inputs: ["config"], outputs: ["ready"] }, { kind: "component", type: PandaPlayerComponent, selector: "app-panda-player", inputs: ["config"], outputs: ["ready"] }, { kind: "component", type: ScaleupPlayerComponent, selector: "app-scaleup-player", inputs: ["config"], outputs: ["ready"] }] });
823
2831
  }
824
2832
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlayerLoaderComponent, decorators: [{
825
2833
  type: Component,
826
- args: [{ selector: 'app-player-loader', template: `
827
- <div [ngSwitch]="config?.video_host" style="width: 100%; height: 100%;">
828
- <app-plyr-player
829
- *ngSwitchCase="'hls'"
830
- [config]="config"
831
- ></app-plyr-player>
832
- <app-plyr-player
833
- *ngSwitchCase="'mp4'"
834
- [config]="config"
835
- ></app-plyr-player>
836
- <app-plyr-player
837
- *ngSwitchCase="'youtube'"
838
- [config]="config"
839
- ></app-plyr-player>
840
- <app-plyr-player
841
- *ngSwitchCase="'vimeo'"
842
- [config]="config"
843
- ></app-plyr-player>
844
- <app-panda-player
845
- *ngSwitchCase="'panda'"
846
- [config]="config"
847
- ></app-panda-player>
848
- <app-scaleup-player
849
- *ngSwitchCase="'scaleup'"
850
- [config]="config"
851
- ></app-scaleup-player>
852
- <div *ngSwitchDefault>
853
- <p>Player not supported or config not provided.</p>
2834
+ args: [{ selector: "app-player-loader", template: `
2835
+ <div style="width: 100%; height: 100%;">
2836
+ <div [ngSwitch]="config?.video_host" style="width: 100%; height: 100%;">
2837
+ <app-plyr-player
2838
+ *ngSwitchCase="'hls'"
2839
+ [config]="config"
2840
+ (ready)="onPlayerReady()"
2841
+ ></app-plyr-player>
2842
+ <app-plyr-player
2843
+ *ngSwitchCase="'mp4'"
2844
+ [config]="config"
2845
+ (ready)="onPlayerReady()"
2846
+ ></app-plyr-player>
2847
+ <app-plyr-player
2848
+ *ngSwitchCase="'youtube'"
2849
+ [config]="config"
2850
+ (ready)="onPlayerReady()"
2851
+ ></app-plyr-player>
2852
+ <app-vimeo-player
2853
+ *ngSwitchCase="'vimeo'"
2854
+ [config]="config"
2855
+ (ready)="onPlayerReady()"
2856
+ ></app-vimeo-player>
2857
+ <app-panda-player
2858
+ *ngSwitchCase="'panda'"
2859
+ [config]="config"
2860
+ (ready)="onPlayerReady()"
2861
+ ></app-panda-player>
2862
+ <app-scaleup-player
2863
+ *ngSwitchCase="'scaleup'"
2864
+ [config]="config"
2865
+ (ready)="onPlayerReady()"
2866
+ ></app-scaleup-player>
2867
+ <div *ngSwitchDefault>
2868
+ <p>Player não suportado ou configuração não fornecida.</p>
2869
+ </div>
854
2870
  </div>
855
2871
  </div>
856
2872
  `, standalone: true, imports: [
857
2873
  CommonModule,
858
2874
  PlyrPlayerComponent,
2875
+ VimeoPlayerComponent,
859
2876
  PandaPlayerComponent,
860
2877
  ScaleupPlayerComponent,
861
2878
  ], styles: [":host{display:block;width:100%;height:100%}\n"] }]
862
- }], propDecorators: { config: [{
2879
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
2880
+ type: Inject,
2881
+ args: [WEPLAYER_CONFIG]
2882
+ }] }, { type: GlobalLoadingService }], propDecorators: { config: [{
863
2883
  type: Input
864
2884
  }] } });
865
2885
 
@@ -887,7 +2907,7 @@ class WebplayerComponent {
887
2907
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WebplayerComponent, deps: [{ token: WebplayerConfigService }, { token: WebplayerProgressService }], target: i0.ɵɵFactoryTarget.Component });
888
2908
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: WebplayerComponent, isStandalone: true, selector: "edm-webplayer", outputs: { progress: "playProgress", ended: "playEnded" }, ngImport: i0, template: `
889
2909
  <app-player-loader [config]="config" *ngIf="config"></app-player-loader>
890
- `, isInline: true, styles: [":host{display:block;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: PlayerLoaderComponent, selector: "app-player-loader", inputs: ["config"] }] });
2910
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: PlayerLoaderComponent, selector: "app-player-loader", inputs: ["config"] }] });
891
2911
  }
892
2912
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WebplayerComponent, decorators: [{
893
2913
  type: Component,