@edm-webplayer/webplayer-angular 0.2.0 → 0.2.1

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,68 @@ 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
+ iosFullscreenCleanup;
92
+ pendingStartTime;
93
+ onResize = () => {
94
+ this.updateRecommendationBlockerSafeZone();
95
+ };
96
+ onRecommendationBlockerClick = (event) => {
97
+ if (event) {
98
+ try {
99
+ event.preventDefault();
100
+ }
101
+ catch { }
102
+ try {
103
+ event.stopPropagation();
104
+ }
105
+ catch { }
106
+ }
107
+ };
108
+ onTopBlockerPointerDown = (event) => {
109
+ try {
110
+ event.preventDefault();
111
+ }
112
+ catch { }
113
+ try {
114
+ event.stopPropagation();
115
+ }
116
+ catch { }
117
+ };
118
+ onTopBlockerClick = (event) => {
119
+ try {
120
+ event.preventDefault();
121
+ }
122
+ catch { }
123
+ try {
124
+ event.stopPropagation();
125
+ }
126
+ catch { }
127
+ };
128
+ controlsSafeZonePxDesktop = 96;
129
+ controlsSafeZonePxTouch = 144;
130
+ overlayInteractionGapPx = 20;
77
131
  constructor(progressService, document) {
78
132
  this.progressService = progressService;
79
133
  this.document = document;
@@ -84,8 +138,82 @@ class PlyrPlayerComponent {
84
138
  this.applyTheme(this.config.ui.colors);
85
139
  }
86
140
  }
141
+ flushPendingStartTime(target, success) {
142
+ const pending = this.pendingStartTime;
143
+ if (!pending) {
144
+ return;
145
+ }
146
+ const tolerance = 0.35;
147
+ if (Math.abs(pending.value - target) > tolerance) {
148
+ return;
149
+ }
150
+ const callbacks = pending.queue.slice();
151
+ pending.queue.length = 0;
152
+ if (success) {
153
+ this.pendingStartTime = undefined;
154
+ }
155
+ else {
156
+ pending.blockPlayback = false;
157
+ }
158
+ callbacks.forEach((cb) => {
159
+ try {
160
+ cb();
161
+ }
162
+ catch { }
163
+ });
164
+ }
165
+ getControlsSafeZone() {
166
+ const controls = (this.plyr?.elements?.controls ??
167
+ null);
168
+ const menuHeight = this.getOpenMenuHeight();
169
+ const baseSafeZone = this.isTouchDevice()
170
+ ? this.controlsSafeZonePxTouch
171
+ : this.controlsSafeZonePxDesktop;
172
+ if (!controls) {
173
+ return Math.max(baseSafeZone, menuHeight + 5);
174
+ }
175
+ let controlsHeight = 0;
176
+ try {
177
+ const rect = controls.getBoundingClientRect?.();
178
+ if (rect && rect.height) {
179
+ controlsHeight = rect.height;
180
+ }
181
+ }
182
+ catch { }
183
+ if (!controlsHeight) {
184
+ controlsHeight = controls.offsetHeight || controls.clientHeight || 0;
185
+ }
186
+ return Math.max(baseSafeZone, Math.ceil(controlsHeight + menuHeight + 24));
187
+ }
188
+ getOverlaySafeZone() {
189
+ const host = this.config?.video_host;
190
+ if (this.overlayBlockAll && host === "youtube") {
191
+ if (this.playbackEnded) {
192
+ return 0;
193
+ }
194
+ return this.overlayInteractionGapPx;
195
+ }
196
+ return Math.max(this.overlayInteractionGapPx, this.getControlsSafeZone());
197
+ }
198
+ getOpenMenuHeight() {
199
+ const container = this.plyr?.elements?.container;
200
+ if (!container)
201
+ return 0;
202
+ const menu = container.querySelector('.plyr__menu__container[aria-hidden="false"], .plyr__menu__container:not([aria-hidden])');
203
+ if (!menu)
204
+ return 0;
205
+ try {
206
+ const rect = menu.getBoundingClientRect?.();
207
+ if (rect && rect.height)
208
+ return Math.ceil(rect.height);
209
+ }
210
+ catch { }
211
+ const fallback = menu.offsetHeight || menu.clientHeight || 0;
212
+ return fallback;
213
+ }
87
214
  ngAfterViewInit() {
88
215
  this.setupPlayer();
216
+ window.addEventListener("message", this.onCastMessage);
89
217
  }
90
218
  ngOnChanges(changes) {
91
219
  if (changes["config"] && this.plyr) {
@@ -93,10 +221,10 @@ class PlyrPlayerComponent {
93
221
  }
94
222
  }
95
223
  applyTheme(colors) {
96
- const STYLE_ID = 'webplayer-theme-vars';
224
+ const STYLE_ID = "webplayer-theme-vars";
97
225
  let styleEl = this.document.getElementById(STYLE_ID);
98
226
  if (!styleEl) {
99
- styleEl = this.document.createElement('style');
227
+ styleEl = this.document.createElement("style");
100
228
  styleEl.id = STYLE_ID;
101
229
  this.document.head.appendChild(styleEl);
102
230
  }
@@ -104,7 +232,7 @@ class PlyrPlayerComponent {
104
232
  const allPairs = [];
105
233
  for (const key in colors) {
106
234
  if (Object.prototype.hasOwnProperty.call(colors, key)) {
107
- const cssVarName = `--theme-${key.replace('progresss-', 'progress-')}`;
235
+ const cssVarName = `--theme-${key.replace("progresss-", "progress-")}`;
108
236
  const cssVarValue = String(colors[key]);
109
237
  allPairs.push([cssVarName, cssVarValue]);
110
238
  try {
@@ -114,316 +242,1990 @@ class PlyrPlayerComponent {
114
242
  }
115
243
  }
116
244
  const cssRule = `:root{
117
- ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join('\n')}
245
+ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join("\n")}
118
246
  }`;
119
247
  styleEl.textContent = cssRule;
120
248
  }
121
- setupPlayer() {
122
- if (!this.videoRef)
249
+ normalizeLocaleCandidate(value) {
250
+ if (value === null || value === undefined) {
251
+ return null;
252
+ }
253
+ const candidate = String(value).trim();
254
+ if (!candidate) {
255
+ return null;
256
+ }
257
+ return candidate.replace(/_/g, "-");
258
+ }
259
+ resolveLocale() {
260
+ const candidates = [
261
+ this.config?.video_locale,
262
+ this.config?.video_language,
263
+ this.config?.locale,
264
+ this.config?.language,
265
+ this.config?.content_locale,
266
+ this.config?.content_language,
267
+ ];
268
+ for (const candidate of candidates) {
269
+ const normalized = this.normalizeLocaleCandidate(candidate);
270
+ if (normalized) {
271
+ return normalized;
272
+ }
273
+ }
274
+ return "pt-BR";
275
+ }
276
+ resolveCaptionsLanguage(locale) {
277
+ const explicit = this.normalizeLocaleCandidate(this.config?.captions_locale ??
278
+ this.config?.captions_language ??
279
+ this.config?.subtitle_locale ??
280
+ this.config?.subtitle_language ??
281
+ null);
282
+ const baseLocale = explicit || locale;
283
+ if (!baseLocale)
284
+ return null;
285
+ const code = baseLocale.split("-")[0]?.trim();
286
+ return code ? code.toLowerCase() : null;
287
+ }
288
+ getVideoWrapper() {
289
+ const container = this.plyr?.elements?.container;
290
+ if (!container)
291
+ return null;
292
+ const wrapper = this.plyr?.elements?.wrapper ||
293
+ container.querySelector(".plyr__video-wrapper");
294
+ return wrapper || null;
295
+ }
296
+ ensureRecommendationBlocker() {
297
+ if (this.recommendationBlocker && this.recommendationBlocker.isConnected) {
298
+ return this.recommendationBlocker;
299
+ }
300
+ const wrapper = this.getVideoWrapper();
301
+ if (!wrapper)
302
+ return null;
303
+ const blocker = this.document.createElement("div");
304
+ blocker.className = "plyr-recommendation-blocker";
305
+ blocker.style.position = "absolute";
306
+ blocker.style.top = "0";
307
+ blocker.style.left = "0";
308
+ blocker.style.width = "100%";
309
+ blocker.style.height = "100%";
310
+ blocker.style.zIndex = "9";
311
+ blocker.style.pointerEvents = "none";
312
+ blocker.style.display = "none";
313
+ blocker.style.background = "rgba(0,0,0,0)";
314
+ blocker.style.cursor = "pointer";
315
+ const activeZone = this.document.createElement("div");
316
+ activeZone.className = "plyr-recommendation-blocker__zone";
317
+ activeZone.style.position = "absolute";
318
+ activeZone.style.top = "0";
319
+ activeZone.style.left = "0";
320
+ activeZone.style.width = "100%";
321
+ activeZone.style.bottom = `20px`;
322
+ activeZone.style.pointerEvents = "auto";
323
+ activeZone.style.background = "rgba(0,0,0,0)";
324
+ activeZone.style.cursor = "pointer";
325
+ activeZone.addEventListener("pointerdown", this.onTopBlockerPointerDown);
326
+ activeZone.addEventListener("click", this.onRecommendationBlockerClick);
327
+ blocker.appendChild(activeZone);
328
+ wrapper.appendChild(blocker);
329
+ this.recommendationBlocker = blocker;
330
+ this.recommendationBlockerActiveZone = activeZone;
331
+ this.updateRecommendationBlockerSafeZone();
332
+ return blocker;
333
+ }
334
+ toggleRecommendationBlocker(active) {
335
+ if (this.config.video_host !== "youtube") {
123
336
  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 },
337
+ }
338
+ const blocker = this.ensureRecommendationBlocker();
339
+ if (!blocker)
340
+ return;
341
+ blocker.style.display = active ? "block" : "none";
342
+ blocker.style.pointerEvents = "none";
343
+ this.updateRecommendationBlockerSafeZone();
344
+ }
345
+ updateRecommendationBlockerSafeZone() {
346
+ if (this.recommendationBlockerActiveZone) {
347
+ this.recommendationBlockerActiveZone.style.pointerEvents = this.overlayBlockAll
348
+ ? "auto"
349
+ : "none";
350
+ }
351
+ this.updateTopClickBlockerSafeZone();
352
+ }
353
+ ensureTopClickBlocker() {
354
+ if (this.config.video_host !== "youtube") {
355
+ return;
356
+ }
357
+ const wrapper = this.getVideoWrapper();
358
+ if (!wrapper)
359
+ return;
360
+ if (this.topClickBlocker && this.topClickBlocker.isConnected) {
361
+ this.updateTopClickBlockerSafeZone();
362
+ return;
363
+ }
364
+ const blocker = this.document.createElement("div");
365
+ blocker.className = "plyr-top-click-blocker";
366
+ blocker.style.position = "absolute";
367
+ blocker.style.top = "0";
368
+ blocker.style.left = "0";
369
+ blocker.style.right = "0";
370
+ blocker.style.width = "100%";
371
+ blocker.style.zIndex = "12";
372
+ blocker.style.pointerEvents = "auto";
373
+ blocker.style.background = "rgba(0,0,0,0)";
374
+ blocker.style.touchAction = "none";
375
+ blocker.style.cursor = "default";
376
+ blocker.addEventListener("pointerdown", this.onTopBlockerPointerDown);
377
+ blocker.addEventListener("click", this.onTopBlockerClick);
378
+ wrapper.appendChild(blocker);
379
+ this.topClickBlocker = blocker;
380
+ this.updateTopClickBlockerSafeZone();
381
+ }
382
+ updateTopClickBlockerSafeZone() {
383
+ if (!this.topClickBlocker)
384
+ return;
385
+ const height = Math.max(48, Math.ceil(this.getOverlaySafeZone()));
386
+ this.topClickBlocker.style.height = `${height}px`;
387
+ this.topClickBlocker.style.pointerEvents = this.overlayBlockAll
388
+ ? "auto"
389
+ : "none";
390
+ }
391
+ observeMenuChanges() {
392
+ this.menuObserver?.disconnect();
393
+ const container = this.plyr?.elements?.container;
394
+ if (!container) {
395
+ this.menuObserver = undefined;
396
+ return;
397
+ }
398
+ const observer = new MutationObserver(() => {
399
+ this.updateRecommendationBlockerSafeZone();
143
400
  });
144
- this.bindProgressEvents();
145
- this.loadSource(this.videoRef.nativeElement).then(() => this.setupStartTime());
401
+ observer.observe(container, {
402
+ subtree: true,
403
+ attributes: true,
404
+ attributeFilter: ["aria-hidden", "class", "style"],
405
+ });
406
+ this.menuObserver = observer;
146
407
  }
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);
408
+ isIOSDevice() {
409
+ if (typeof navigator === "undefined")
410
+ return false;
411
+ const ua = navigator.userAgent || "";
412
+ const iOS = /iPad|iPhone|iPod/.test(ua);
413
+ const iPadOS = navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
414
+ return iOS || iPadOS;
156
415
  }
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();
416
+ isIOSContext() {
417
+ if (this.isIOSDevice()) {
418
+ return true;
419
+ }
420
+ if (typeof window === "undefined") {
421
+ return false;
422
+ }
423
+ try {
424
+ const params = new URLSearchParams(window.location.search);
425
+ const os = params.get("os");
426
+ if (os && os.toLowerCase() === "ios") {
427
+ return true;
428
+ }
429
+ }
430
+ catch { }
431
+ return false;
432
+ }
433
+ isTouchDevice() {
434
+ if (typeof window === "undefined") {
435
+ return false;
436
+ }
437
+ if (typeof navigator !== "undefined" && navigator.maxTouchPoints > 0) {
438
+ return true;
439
+ }
440
+ if (window.matchMedia) {
441
+ try {
442
+ if (window.matchMedia("(pointer: coarse)").matches) {
443
+ return true;
444
+ }
445
+ }
446
+ catch { }
447
+ }
448
+ return "ontouchstart" in window;
449
+ }
450
+ applyInlineAttributes(videoElement, isIOS) {
451
+ try {
452
+ videoElement.setAttribute("playsinline", "true");
453
+ videoElement.setAttribute("webkit-playsinline", "true");
454
+ if (isIOS) {
455
+ videoElement.setAttribute("x5-playsinline", "true");
456
+ videoElement.setAttribute("x5-video-player-type", "h5");
457
+ }
458
+ }
459
+ catch { }
460
+ }
461
+ bindIOSFullscreenLogging(videoElement, isIOS) {
462
+ this.removeIOSFullscreenLogging();
463
+ if (!isIOS)
464
+ return;
465
+ const logEvent = (name) => {
466
+ console.log(`event: ${name}`);
467
+ try {
468
+ parent.postMessage(`event: ${name}`, "*");
171
469
  }
470
+ catch { }
172
471
  };
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
- });
472
+ const onEnter = () => logEvent("webkit_enterfullscreen");
473
+ const onExit = () => logEvent("webkit_exitfullscreen");
474
+ try {
475
+ videoElement.addEventListener("webkitbeginfullscreen", onEnter);
476
+ videoElement.addEventListener("webkitendfullscreen", onExit);
477
+ this.iosFullscreenCleanup = () => {
478
+ try {
479
+ videoElement.removeEventListener("webkitbeginfullscreen", onEnter);
480
+ videoElement.removeEventListener("webkitendfullscreen", onExit);
481
+ }
482
+ catch { }
483
+ this.iosFullscreenCleanup = undefined;
484
+ };
485
+ }
486
+ catch { }
179
487
  }
180
- unmuteAfterDelay(video, delayMs = 2000, volume = 1) {
181
- if (!this.config.autoPlay)
488
+ removeIOSFullscreenLogging() {
489
+ try {
490
+ this.iosFullscreenCleanup?.();
491
+ }
492
+ catch { }
493
+ this.iosFullscreenCleanup = undefined;
494
+ }
495
+ isIframeProvider() {
496
+ return this.config?.video_host === "youtube";
497
+ }
498
+ shouldDisableAutoplayForIOS() {
499
+ if (this.config?.is_live) {
500
+ return false;
501
+ }
502
+ return this.isIOSDevice();
503
+ }
504
+ shouldResetProgressToBeginning() {
505
+ const host = this.config?.video_host;
506
+ if (host !== "youtube") {
507
+ return false;
508
+ }
509
+ const seenFlag = this.config?.seen;
510
+ if (seenFlag === true || seenFlag === "true" || seenFlag === 1) {
511
+ return true;
512
+ }
513
+ const rawPercent = this.config?.last_percent;
514
+ if (rawPercent === undefined || rawPercent === null) {
515
+ return false;
516
+ }
517
+ let percent = Number(rawPercent);
518
+ if (!Number.isFinite(percent)) {
519
+ return false;
520
+ }
521
+ if (percent > 1) {
522
+ percent = percent / 100;
523
+ }
524
+ return percent >= 0.8;
525
+ }
526
+ shouldEmitEndedOnPause() {
527
+ if (!this.isIOSContext()) {
528
+ return false;
529
+ }
530
+ if (this.endedLoggedOnPause) {
531
+ return false;
532
+ }
533
+ const duration = this.plyr?.duration;
534
+ if (!Number.isFinite(duration) || !duration || duration <= 0) {
535
+ return false;
536
+ }
537
+ const current = this.plyr?.currentTime || 0;
538
+ const percent = duration > 0 ? current / duration : 0;
539
+ return percent >= 0.8 && duration - current < 1;
540
+ }
541
+ hasReachedEndThreshold() {
542
+ const duration = this.plyr?.duration;
543
+ if (!Number.isFinite(duration) || !duration || duration <= 0) {
544
+ return false;
545
+ }
546
+ const current = this.plyr?.currentTime || 0;
547
+ const remaining = duration - current;
548
+ const percent = duration > 0 ? current / duration : 0;
549
+ if (this.config?.video_host === "youtube") {
550
+ return percent >= 0.95 || remaining <= 1.5;
551
+ }
552
+ return percent >= 0.98 || remaining <= 0.75;
553
+ }
554
+ forceLivePlaybackResume() {
555
+ try {
556
+ const maybePromise = this.plyr?.play?.();
557
+ if (maybePromise && typeof maybePromise.then === "function") {
558
+ maybePromise.catch(() => { });
559
+ }
560
+ }
561
+ catch { }
562
+ }
563
+ updateLiveInteractionBlocker(isLive) {
564
+ if (!isLive) {
565
+ this.detachLiveInteractionBlocker();
182
566
  return;
183
- setTimeout(() => {
567
+ }
568
+ const container = (this.plyr?.elements?.container ??
569
+ null);
570
+ if (!container) {
571
+ return;
572
+ }
573
+ if (this.liveInteractionBlocker?.isConnected) {
574
+ return;
575
+ }
576
+ const blocker = this.document.createElement("div");
577
+ blocker.className = "plyr__live-interaction-blocker";
578
+ blocker.setAttribute("aria-hidden", "true");
579
+ container.appendChild(blocker);
580
+ this.liveInteractionBlocker = blocker;
581
+ }
582
+ detachLiveInteractionBlocker() {
583
+ if (this.liveInteractionBlocker?.parentNode) {
184
584
  try {
185
- if (this.plyr) {
186
- this.plyr.muted = false;
187
- this.plyr.volume = volume;
585
+ this.liveInteractionBlocker.parentNode.removeChild(this.liveInteractionBlocker);
586
+ }
587
+ catch { }
588
+ }
589
+ this.liveInteractionBlocker = null;
590
+ }
591
+ wantsAutoplay() {
592
+ return !!(this.config?.autoplay || this.config?.is_live);
593
+ }
594
+ shouldAutoplay() {
595
+ if (this.config?.is_live) {
596
+ return true;
597
+ }
598
+ if (this.shouldDisableAutoplayForIOS()) {
599
+ return false;
600
+ }
601
+ return this.wantsAutoplay();
602
+ }
603
+ requiresMutedAutoplay() {
604
+ if (this.config?.is_live && this.isIOSDevice()) {
605
+ return true;
606
+ }
607
+ if (!this.isIOSDevice() || this.shouldDisableAutoplayForIOS()) {
608
+ return false;
609
+ }
610
+ return this.wantsAutoplay();
611
+ }
612
+ shouldBlockIframeAutoplay() {
613
+ return this.shouldDisableAutoplayForIOS();
614
+ }
615
+ stopRestrictedIframeAutoplay() {
616
+ if (!this.shouldBlockIframeAutoplay()) {
617
+ return;
618
+ }
619
+ this.clearAutoplayUnlockListener();
620
+ this.setPlayerMutedState(false);
621
+ const media = this.videoRef?.nativeElement ?? null;
622
+ if (media) {
623
+ try {
624
+ media.autoplay = false;
625
+ }
626
+ catch { }
627
+ try {
628
+ media.pause();
629
+ }
630
+ catch { }
631
+ try {
632
+ media.muted = false;
633
+ }
634
+ catch { }
635
+ }
636
+ if (this.plyr) {
637
+ try {
638
+ this.plyr.autoplay = false;
639
+ }
640
+ catch { }
641
+ try {
642
+ if (typeof this.plyr.pause === "function") {
643
+ this.plyr.pause();
188
644
  }
189
645
  }
190
646
  catch { }
647
+ }
648
+ const embed = this.plyr?.embed;
649
+ if (embed) {
191
650
  try {
192
- if (video) {
193
- video.muted = false;
194
- if (typeof video.volume === "number")
195
- video.volume = volume;
651
+ if (typeof embed.pause === "function") {
652
+ embed.pause();
653
+ }
654
+ if (typeof embed.setMuted === "function") {
655
+ embed.setMuted(false);
656
+ }
657
+ if (typeof embed.setVolume === "function") {
658
+ embed.setVolume(1);
196
659
  }
197
660
  }
198
661
  catch { }
199
- }, delayMs);
662
+ }
200
663
  }
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);
664
+ setPlayerMutedState(muted, volume = 1, target) {
665
+ if (this.plyr) {
666
+ try {
667
+ this.plyr.muted = muted;
668
+ }
669
+ catch { }
670
+ if (!muted) {
671
+ try {
672
+ this.plyr.volume = volume;
673
+ }
674
+ catch { }
675
+ }
676
+ }
677
+ const media = target ?? this.videoRef?.nativeElement ?? null;
678
+ if (media) {
679
+ try {
680
+ media.muted = muted;
681
+ }
682
+ catch { }
683
+ if (!muted && typeof media.volume === "number") {
684
+ try {
685
+ media.volume = volume;
686
+ }
687
+ catch { }
688
+ }
689
+ }
690
+ }
691
+ temporarilyMuteForSeek(target) {
692
+ const media = target ?? this.videoRef?.nativeElement ?? null;
693
+ const prevVideoMuted = media?.muted ?? false;
694
+ const prevPlyrMuted = !!this.plyr?.muted;
695
+ const shouldForceUnmute = !this.shouldAutoplay();
696
+ try {
697
+ if (media) {
698
+ media.muted = true;
699
+ }
700
+ }
701
+ catch { }
702
+ try {
703
+ if (this.plyr) {
704
+ this.plyr.muted = true;
705
+ }
706
+ }
707
+ catch { }
708
+ return () => {
709
+ try {
710
+ if (media) {
711
+ media.muted = prevVideoMuted;
712
+ }
713
+ }
714
+ catch { }
715
+ try {
716
+ if (this.plyr) {
717
+ this.plyr.muted = prevPlyrMuted;
718
+ }
719
+ }
720
+ catch { }
721
+ if (shouldForceUnmute) {
722
+ this.setPlayerMutedState(false, 1, media);
723
+ }
228
724
  };
229
- return new Promise((resolve) => {
230
- if (!this.config) {
231
- return resolve();
725
+ }
726
+ clearAutoplayUnlockListener() {
727
+ if (this.interactionUnmuteCleanup) {
728
+ try {
729
+ this.interactionUnmuteCleanup();
730
+ }
731
+ catch { }
732
+ this.interactionUnmuteCleanup = undefined;
733
+ }
734
+ }
735
+ ensureAutoplayUnlockListener() {
736
+ if (typeof window === "undefined" ||
737
+ this.interactionUnmuteCleanup ||
738
+ !this.shouldAutoplay()) {
739
+ return;
740
+ }
741
+ const capture = true;
742
+ const handler = () => {
743
+ this.setPlayerMutedState(false);
744
+ try {
745
+ this.plyr?.play();
746
+ }
747
+ catch { }
748
+ this.clearAutoplayUnlockListener();
749
+ };
750
+ window.addEventListener("touchend", handler, capture);
751
+ window.addEventListener("pointerup", handler, capture);
752
+ window.addEventListener("click", handler, capture);
753
+ this.interactionUnmuteCleanup = () => {
754
+ window.removeEventListener("touchend", handler, capture);
755
+ window.removeEventListener("pointerup", handler, capture);
756
+ window.removeEventListener("click", handler, capture);
757
+ this.interactionUnmuteCleanup = undefined;
758
+ };
759
+ }
760
+ ensureIframeAutoplayPermissions() {
761
+ if (!this.isIframeProvider()) {
762
+ return;
763
+ }
764
+ const container = this.plyr?.elements?.container;
765
+ if (!container)
766
+ return;
767
+ const iframe = container.querySelector("iframe") ?? null;
768
+ if (!iframe)
769
+ return;
770
+ const allowRaw = iframe.getAttribute("allow") || "";
771
+ const tokens = new Set(allowRaw
772
+ .split(";")
773
+ .map((token) => token.trim())
774
+ .filter(Boolean));
775
+ [
776
+ "autoplay",
777
+ "fullscreen",
778
+ "encrypted-media",
779
+ "picture-in-picture",
780
+ "gyroscope",
781
+ ].forEach((rule) => tokens.add(rule));
782
+ iframe.setAttribute("allow", Array.from(tokens).join("; "));
783
+ if (this.config.video_host === "youtube") {
784
+ iframe.setAttribute("playsinline", "1");
785
+ iframe.setAttribute("webkit-playsinline", "1");
786
+ }
787
+ iframe.setAttribute("allowfullscreen", "true");
788
+ iframe.setAttribute("data-keepplaying", "true");
789
+ }
790
+ scheduleUserInteractionUnmute(video) {
791
+ this.unmuteAfterDelay(video || undefined);
792
+ this.ensureAutoplayUnlockListener();
793
+ }
794
+ requestStartTimeSeek(start, opts = {}) {
795
+ if (!(start > 0)) {
796
+ this.pendingStartTime = undefined;
797
+ return;
798
+ }
799
+ const video = opts.video ?? this.videoRef?.nativeElement ?? null;
800
+ const normalized = Math.max(0, start);
801
+ const tolerance = 0.35;
802
+ const pending = this.pendingStartTime;
803
+ if (pending && Math.abs(pending.value - normalized) <= tolerance) {
804
+ pending.video = video ?? pending.video;
805
+ pending.blockPlayback = true;
806
+ }
807
+ else {
808
+ const carryQueue = this.pendingStartTime?.queue ?? [];
809
+ this.pendingStartTime = {
810
+ value: normalized,
811
+ video,
812
+ queue: carryQueue,
813
+ blockPlayback: true,
814
+ };
815
+ }
816
+ this.applyStartTimeToCurrentSource(normalized, { video });
817
+ }
818
+ deferUntilStartTimeSettles(callback) {
819
+ if (!this.pendingStartTime || !this.pendingStartTime.blockPlayback)
820
+ return false;
821
+ this.pendingStartTime.queue.push(callback);
822
+ return true;
823
+ }
824
+ retryPendingStartTime() {
825
+ if (!this.pendingStartTime) {
826
+ return;
827
+ }
828
+ const { value, video } = this.pendingStartTime;
829
+ this.applyStartTimeToCurrentSource(value, {
830
+ video: video ?? this.videoRef?.nativeElement ?? null,
831
+ attempts: 3,
832
+ delayMs: 150,
833
+ });
834
+ }
835
+ applyStartTimeToCurrentSource(start, opts = {}) {
836
+ if (!this.plyr || !(start > 0)) {
837
+ return;
838
+ }
839
+ const video = opts.video ?? this.videoRef?.nativeElement ?? null;
840
+ let attempts = Math.max(1, opts.attempts ?? 6);
841
+ const delayMs = opts.delayMs ?? 250;
842
+ const tolerance = 0.35;
843
+ const scheduleRetry = () => {
844
+ attempts -= 1;
845
+ if (attempts > 0) {
846
+ setTimeout(trySeek, delayMs);
847
+ return;
848
+ }
849
+ this.flushPendingStartTime(start, false);
850
+ };
851
+ const verifyAfterAttempt = () => {
852
+ const current = this.plyr?.currentTime || 0;
853
+ if (Math.abs(current - start) <= tolerance) {
854
+ attempts = 0;
855
+ this.flushPendingStartTime(start, true);
856
+ return;
857
+ }
858
+ scheduleRetry();
859
+ };
860
+ const trySeek = () => {
861
+ if (!this.plyr)
862
+ return;
863
+ let maybePromise = null;
864
+ try {
865
+ const result = this.seekUsingHostAPI(start, video);
866
+ if (result && typeof result.then === "function") {
867
+ maybePromise = result;
868
+ }
869
+ }
870
+ catch {
871
+ scheduleRetry();
872
+ return;
873
+ }
874
+ if (maybePromise) {
875
+ maybePromise
876
+ .then(() => verifyAfterAttempt())
877
+ .catch(() => scheduleRetry());
878
+ return;
879
+ }
880
+ verifyAfterAttempt();
881
+ };
882
+ trySeek();
883
+ }
884
+ seekUsingHostAPI(target, video) {
885
+ if (!Number.isFinite(target) || target < 0) {
886
+ return null;
887
+ }
888
+ const restoreMute = this.temporarilyMuteForSeek(video);
889
+ if (video) {
890
+ try {
891
+ video.currentTime = target;
892
+ }
893
+ catch { }
894
+ }
895
+ try {
896
+ this.plyr.currentTime = target;
897
+ }
898
+ catch { }
899
+ if (restoreMute) {
900
+ setTimeout(() => restoreMute(), 300);
901
+ }
902
+ return null;
903
+ }
904
+ startAutoplayFlow(video) {
905
+ if (!this.shouldAutoplay() || !this.plyr)
906
+ return;
907
+ if (this.deferUntilStartTimeSettles(() => this.startAutoplayFlow(video))) {
908
+ return;
909
+ }
910
+ const media = video ?? this.videoRef?.nativeElement ?? null;
911
+ const tryPlay = (muted) => {
912
+ this.setPlayerMutedState(muted, 1, media);
913
+ let playResult;
914
+ try {
915
+ playResult = this.plyr.play();
916
+ this.forceIframeAutoplayFallback();
917
+ }
918
+ catch (error) {
919
+ if (!muted) {
920
+ tryPlay(true);
921
+ return;
922
+ }
923
+ if (media) {
924
+ try {
925
+ const nativePlay = media.play();
926
+ if (nativePlay && typeof nativePlay.catch === "function") {
927
+ nativePlay.catch(() => { });
928
+ }
929
+ }
930
+ catch { }
931
+ }
932
+ this.scheduleUserInteractionUnmute(media);
933
+ return;
934
+ }
935
+ if (playResult &&
936
+ typeof playResult.catch === "function") {
937
+ playResult.catch((error) => {
938
+ if (!muted) {
939
+ tryPlay(true);
940
+ return;
941
+ }
942
+ if (media) {
943
+ try {
944
+ const nativePlay = media.play();
945
+ if (nativePlay && typeof nativePlay.catch === "function") {
946
+ nativePlay.catch(() => { });
947
+ }
948
+ }
949
+ catch { }
950
+ }
951
+ this.scheduleUserInteractionUnmute(media);
952
+ });
953
+ return;
954
+ }
955
+ if (muted) {
956
+ this.scheduleUserInteractionUnmute(media);
957
+ }
958
+ else {
959
+ this.clearAutoplayUnlockListener();
960
+ }
961
+ };
962
+ if (this.requiresMutedAutoplay()) {
963
+ tryPlay(true);
964
+ }
965
+ else {
966
+ tryPlay(false);
967
+ }
968
+ }
969
+ forceIframeAutoplayFallback() {
970
+ if (!this.shouldAutoplay() || !this.isIframeProvider()) {
971
+ return;
972
+ }
973
+ const embed = this.plyr?.embed;
974
+ if (!embed)
975
+ return;
976
+ if (this.config.video_host === "youtube") {
977
+ if (typeof embed.playVideo === "function") {
978
+ try {
979
+ embed.playVideo();
980
+ }
981
+ catch { }
982
+ }
983
+ }
984
+ }
985
+ setupControlsVisibility() {
986
+ const container = this.plyr?.elements?.container;
987
+ if (!container) {
988
+ this.controlsVisibilityCleanup = undefined;
989
+ return;
990
+ }
991
+ if (this.isTouchDevice()) {
992
+ const HIDE_DELAY = 2000;
993
+ let hideTimeout = null;
994
+ const clearHideTimeout = () => {
995
+ if (hideTimeout) {
996
+ clearTimeout(hideTimeout);
997
+ hideTimeout = null;
998
+ }
999
+ };
1000
+ const showControls = () => {
1001
+ container.classList.remove("plyr--auto-hide-controls");
1002
+ };
1003
+ const hideControls = () => {
1004
+ if (this.plyr?.playing) {
1005
+ container.classList.add("plyr--auto-hide-controls");
1006
+ }
1007
+ };
1008
+ const scheduleHide = () => {
1009
+ clearHideTimeout();
1010
+ if (!this.plyr?.playing)
1011
+ return;
1012
+ hideTimeout = setTimeout(() => hideControls(), HIDE_DELAY);
1013
+ };
1014
+ const onPlay = () => {
1015
+ showControls();
1016
+ this.overlayBlockAll = false;
1017
+ };
1018
+ const onPause = () => {
1019
+ clearHideTimeout();
1020
+ showControls();
1021
+ this.overlayBlockAll = true;
1022
+ this.updateTopClickBlockerSafeZone();
1023
+ this.topClickBlocker?.setAttribute("data-state", "paused");
1024
+ if (this.shouldEmitEndedOnPause()) {
1025
+ this.endedLoggedOnPause = true;
1026
+ console.log("event: ended");
1027
+ }
1028
+ };
1029
+ const onEnded = () => {
1030
+ clearHideTimeout();
1031
+ showControls();
1032
+ this.overlayBlockAll = true;
1033
+ this.updateTopClickBlockerSafeZone();
1034
+ this.topClickBlocker?.setAttribute("data-state", "ended");
1035
+ };
1036
+ const onUserInteraction = () => {
1037
+ showControls();
1038
+ scheduleHide();
1039
+ };
1040
+ this.plyr?.on?.("play", onPlay);
1041
+ this.plyr?.on?.("pause", onPause);
1042
+ this.plyr?.on?.("ended", onEnded);
1043
+ container.addEventListener("touchstart", onUserInteraction);
1044
+ container.addEventListener("click", onUserInteraction);
1045
+ this.controlsVisibilityCleanup = () => {
1046
+ clearHideTimeout();
1047
+ this.plyr?.off?.("play", onPlay);
1048
+ this.plyr?.off?.("pause", onPause);
1049
+ this.plyr?.off?.("ended", onEnded);
1050
+ container.removeEventListener("touchstart", onUserInteraction);
1051
+ container.removeEventListener("click", onUserInteraction);
1052
+ container.classList.remove("plyr--auto-hide-controls");
1053
+ };
1054
+ onPlay();
1055
+ return;
1056
+ }
1057
+ let hovering = false;
1058
+ const toggleClass = () => {
1059
+ const playing = !!this.plyr?.playing;
1060
+ if (playing && !hovering) {
1061
+ container.classList.add("plyr--auto-hide-controls");
1062
+ }
1063
+ else {
1064
+ container.classList.remove("plyr--auto-hide-controls");
1065
+ }
1066
+ };
1067
+ const onPlay = () => toggleClass();
1068
+ const onPause = () => toggleClass();
1069
+ const onEnded = () => toggleClass();
1070
+ const onMouseEnter = () => {
1071
+ hovering = true;
1072
+ toggleClass();
1073
+ };
1074
+ const onMouseLeave = () => {
1075
+ hovering = false;
1076
+ toggleClass();
1077
+ };
1078
+ const onFocusIn = () => {
1079
+ hovering = true;
1080
+ toggleClass();
1081
+ };
1082
+ const onFocusOut = () => {
1083
+ hovering = container.matches(":hover");
1084
+ toggleClass();
1085
+ };
1086
+ this.plyr?.on?.("play", onPlay);
1087
+ this.plyr?.on?.("pause", onPause);
1088
+ this.plyr?.on?.("ended", onEnded);
1089
+ container.addEventListener("mouseenter", onMouseEnter);
1090
+ container.addEventListener("mouseleave", onMouseLeave);
1091
+ container.addEventListener("focusin", onFocusIn);
1092
+ container.addEventListener("focusout", onFocusOut);
1093
+ toggleClass();
1094
+ this.controlsVisibilityCleanup = () => {
1095
+ this.plyr?.off?.("play", onPlay);
1096
+ this.plyr?.off?.("pause", onPause);
1097
+ this.plyr?.off?.("ended", onEnded);
1098
+ container.removeEventListener("mouseenter", onMouseEnter);
1099
+ container.removeEventListener("mouseleave", onMouseLeave);
1100
+ container.removeEventListener("focusin", onFocusIn);
1101
+ container.removeEventListener("focusout", onFocusOut);
1102
+ container.classList.remove("plyr--auto-hide-controls");
1103
+ };
1104
+ }
1105
+ bindFullscreenBehavior() {
1106
+ const onEnterFullscreen = () => {
1107
+ console.log("event: enterfullscreen");
1108
+ parent.postMessage("event: fullscreen_enter', '*'");
1109
+ };
1110
+ const onExitFullscreen = () => {
1111
+ console.log("event: exitfullscreen");
1112
+ parent.postMessage("event: fullscreen_exit', '*'");
1113
+ };
1114
+ this.plyr.on("enterfullscreen", onEnterFullscreen);
1115
+ this.plyr.on("exitfullscreen", onExitFullscreen);
1116
+ }
1117
+ setupPlayer() {
1118
+ if (!this.videoRef)
1119
+ return;
1120
+ const videoEl = this.videoRef.nativeElement;
1121
+ const isIOS = this.isIOSContext();
1122
+ this.applyInlineAttributes(videoEl, isIOS);
1123
+ this.removeIOSFullscreenLogging();
1124
+ this.hls?.destroy();
1125
+ this.controlsVisibilityCleanup?.();
1126
+ this.controlsVisibilityCleanup = undefined;
1127
+ this.plyr?.destroy();
1128
+ this.clearAutoplayUnlockListener();
1129
+ this.recommendationBlocker = null;
1130
+ const speedOptions = [1, 1.25, 1.5, 2];
1131
+ const locale = this.resolveLocale();
1132
+ const captionsLanguage = this.resolveCaptionsLanguage(locale) ?? "pt";
1133
+ const shouldAutoplay = this.shouldAutoplay();
1134
+ const startMuted = this.requiresMutedAutoplay();
1135
+ try {
1136
+ this.videoRef.nativeElement.muted = startMuted;
1137
+ }
1138
+ catch { }
1139
+ try {
1140
+ this.videoRef.nativeElement.playsInline =
1141
+ this.config?.video_host === "youtube";
1142
+ }
1143
+ catch { }
1144
+ try {
1145
+ this.videoRef.nativeElement.autoplay = shouldAutoplay;
1146
+ }
1147
+ catch { }
1148
+ const isLive = !!this.config?.is_live;
1149
+ const youtubePlayerVars = {
1150
+ rel: 0,
1151
+ modestbranding: 1,
1152
+ noCookie: true,
1153
+ };
1154
+ youtubePlayerVars.playerVars = {
1155
+ hl: locale,
1156
+ cc_lang_pref: captionsLanguage || locale.split("-")[0]?.toLowerCase(),
1157
+ autoplay: shouldAutoplay ? 1 : 0,
1158
+ mute: startMuted ? 1 : 0,
1159
+ playsinline: 1,
1160
+ };
1161
+ if (captionsLanguage) {
1162
+ youtubePlayerVars.playerVars.cc_load_policy = 1;
1163
+ }
1164
+ const defaultControls = [
1165
+ "play-large",
1166
+ "play",
1167
+ "progress",
1168
+ "current-time",
1169
+ "duration",
1170
+ "mute",
1171
+ "volume",
1172
+ "captions",
1173
+ "settings",
1174
+ "pip",
1175
+ "airplay",
1176
+ "fullscreen",
1177
+ ];
1178
+ const controls = isLive || !isIOS
1179
+ ? defaultControls
1180
+ : defaultControls.filter((c) => c !== "fullscreen");
1181
+ const liveControls = [];
1182
+ const plyrOptions = {
1183
+ controls: isLive ? liveControls : controls,
1184
+ fullscreen: {
1185
+ enabled: !isIOS,
1186
+ fallback: !isIOS,
1187
+ iosNative: true,
1188
+ container: null,
1189
+ },
1190
+ i18n: {
1191
+ speed: "Velocidade",
1192
+ },
1193
+ speed: {
1194
+ selected: 1,
1195
+ options: speedOptions,
1196
+ },
1197
+ clickToPlay: !isLive,
1198
+ hideControls: isLive ? true : false,
1199
+ keyboard: { focused: !isLive, global: false },
1200
+ autoplay: shouldAutoplay,
1201
+ muted: startMuted,
1202
+ };
1203
+ plyrOptions.hl = locale;
1204
+ if (captionsLanguage) {
1205
+ plyrOptions.captions = { language: captionsLanguage };
1206
+ }
1207
+ if (this.config.video_host === "youtube") {
1208
+ plyrOptions.youtube = youtubePlayerVars;
1209
+ }
1210
+ this.plyr = new Plyr.default(this.videoRef.nativeElement, plyrOptions);
1211
+ if (typeof this.plyr?.toggleControls === "function") {
1212
+ this.plyr.toggleControls(!isLive);
1213
+ }
1214
+ this.updateLiveInteractionBlocker(isLive);
1215
+ this.bindFullscreenBehavior();
1216
+ this.bindIOSFullscreenLogging(videoEl, isIOS);
1217
+ this.setupControlsVisibility();
1218
+ this.ensureTopClickBlocker();
1219
+ this.observeMenuChanges();
1220
+ this.playbackEnded = false;
1221
+ this.overlayBlockAll = false;
1222
+ this.endedLoggedOnPause = false;
1223
+ window.removeEventListener("resize", this.onResize);
1224
+ window.addEventListener("resize", this.onResize);
1225
+ this.bindProgressEvents();
1226
+ this.loadSource(this.videoRef.nativeElement).then(() => {
1227
+ this.setupStartTime();
1228
+ });
1229
+ // Dispara o evento ready quando o player estiver pronto
1230
+ this.plyr.on("ready", () => {
1231
+ this.ensureIframeAutoplayPermissions();
1232
+ this.stopRestrictedIframeAutoplay();
1233
+ this.ready.emit();
1234
+ });
1235
+ }
1236
+ setupStartTime() {
1237
+ const apply = () => {
1238
+ const dur = this.plyr.duration || 0;
1239
+ const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, dur);
1240
+ this.requestStartTimeSeek(start);
1241
+ this.plyr.off("ready", apply);
1242
+ };
1243
+ this.plyr.on("ready", apply);
1244
+ }
1245
+ bindProgressEvents() {
1246
+ const THROTTLE_MS = 300;
1247
+ let lastEmit = 0;
1248
+ const emitProgress = () => {
1249
+ const t = this.plyr.currentTime || 0;
1250
+ const d = this.plyr.duration || 0;
1251
+ const p = d > 0 ? +((t / d) * 100).toFixed(2) : 0;
1252
+ this.progressService.progress$.next({
1253
+ currentTime: t,
1254
+ duration: d,
1255
+ percent: p,
1256
+ });
1257
+ };
1258
+ const onTimeUpdate = () => {
1259
+ const now = performance.now();
1260
+ if (now - lastEmit >= THROTTLE_MS) {
1261
+ lastEmit = now;
1262
+ emitProgress();
1263
+ }
1264
+ const current = Math.floor(this.plyr.currentTime || 0);
1265
+ const duration = Math.floor(this.plyr.duration || 0);
1266
+ parent.postMessage("currentTime:" + current, "*");
1267
+ console.log("event: current_time " + current);
1268
+ console.log("event: duration " + duration);
1269
+ this.postCastEvent({
1270
+ event: "current_time",
1271
+ currentTime: current,
1272
+ duration,
1273
+ });
1274
+ };
1275
+ this.plyr?.on?.("play", () => {
1276
+ this.retryPendingStartTime();
1277
+ this.playbackEnded = false;
1278
+ this.overlayBlockAll = false;
1279
+ this.updateRecommendationBlockerSafeZone();
1280
+ this.toggleRecommendationBlocker(false);
1281
+ parent.postMessage("playing_video", "*");
1282
+ this.postCastEvent({
1283
+ event: "play",
1284
+ currentTime: this.plyr.currentTime || 0,
1285
+ });
1286
+ });
1287
+ this.plyr?.on?.("timeupdate", onTimeUpdate);
1288
+ this.plyr?.on?.("pause", () => {
1289
+ if (this.config?.is_live) {
1290
+ this.forceLivePlaybackResume();
1291
+ return;
1292
+ }
1293
+ emitProgress();
1294
+ console.log("event: pause");
1295
+ parent.postMessage("paused_video", "*");
1296
+ this.postCastEvent({
1297
+ event: "pause",
1298
+ currentTime: this.plyr.currentTime || 0,
1299
+ });
1300
+ this.overlayBlockAll = true;
1301
+ this.updateRecommendationBlockerSafeZone();
1302
+ this.toggleRecommendationBlocker(true);
1303
+ });
1304
+ this.plyr?.on?.("ended", () => {
1305
+ emitProgress();
1306
+ if (!this.hasReachedEndThreshold()) {
1307
+ console.warn("Ignoring premature ended event", this.plyr?.currentTime, this.plyr?.duration);
1308
+ return;
1309
+ }
1310
+ this.playbackEnded = true;
1311
+ this.overlayBlockAll = true;
1312
+ this.updateRecommendationBlockerSafeZone();
1313
+ this.endedLoggedOnPause = true;
1314
+ console.log("event: ended");
1315
+ parent.postMessage("event: ended", "*");
1316
+ this.postCastEvent({
1317
+ event: "ended",
1318
+ currentTime: this.plyr.currentTime || 0,
1319
+ });
1320
+ this.progressService.ended$.next();
1321
+ this.toggleRecommendationBlocker(true);
1322
+ });
1323
+ }
1324
+ unmuteAfterDelay(video, delayMs = 2000, volume = 1) {
1325
+ if (!this.shouldAutoplay())
1326
+ return;
1327
+ setTimeout(() => {
1328
+ try {
1329
+ if (this.plyr) {
1330
+ this.plyr.muted = false;
1331
+ this.plyr.volume = volume;
1332
+ }
1333
+ }
1334
+ catch { }
1335
+ try {
1336
+ if (video) {
1337
+ video.muted = false;
1338
+ if (typeof video.volume === "number")
1339
+ video.volume = volume;
1340
+ }
1341
+ }
1342
+ catch { }
1343
+ }, delayMs);
1344
+ }
1345
+ resetPlaybackRate(video) {
1346
+ try {
1347
+ if (this.plyr && typeof this.plyr.speed !== "undefined") {
1348
+ this.plyr.speed = 1;
1349
+ }
1350
+ }
1351
+ catch { }
1352
+ const media = video ?? this.videoRef?.nativeElement ?? null;
1353
+ if (media) {
1354
+ try {
1355
+ media.playbackRate = 1;
1356
+ }
1357
+ catch { }
1358
+ try {
1359
+ if (media.defaultPlaybackRate !== undefined) {
1360
+ media.defaultPlaybackRate = 1;
1361
+ }
1362
+ }
1363
+ catch { }
1364
+ }
1365
+ }
1366
+ loadSource(video) {
1367
+ return new Promise((resolve) => {
1368
+ if (!this.config) {
1369
+ return resolve();
1370
+ }
1371
+ if (this.hls) {
1372
+ this.hls.destroy();
1373
+ this.hls = undefined;
1374
+ }
1375
+ this.resetPlaybackRate(video);
1376
+ if (this.config.video_host === "youtube") {
1377
+ const onReady = () => {
1378
+ this.plyr.off("ready", onReady);
1379
+ this.ensureIframeAutoplayPermissions();
1380
+ const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, this.plyr.duration);
1381
+ this.requestStartTimeSeek(start);
1382
+ if (this.shouldAutoplay()) {
1383
+ this.startAutoplayFlow();
1384
+ }
1385
+ else if (this.shouldBlockIframeAutoplay()) {
1386
+ this.stopRestrictedIframeAutoplay();
1387
+ }
1388
+ parent.postMessage("playerIsReadyToPlay", "*");
1389
+ parent.postMessage("duration:" + this.plyr.duration, "*");
1390
+ console.log("event: current_time " + this.plyr.currentTime);
1391
+ console.log("event: duration " + this.plyr.duration);
1392
+ console.log("event: ready");
1393
+ this.postCastEvent({
1394
+ event: "ready",
1395
+ duration: this.plyr.duration || 0,
1396
+ currentTime: this.plyr.currentTime || 0,
1397
+ });
1398
+ if (this.config.is_live) {
1399
+ this.plyr?.on("playing", function () {
1400
+ console.log("event: playing_live");
1401
+ parent.postMessage("event: playing_live", "*");
1402
+ });
1403
+ }
1404
+ if (this.config.mirror) {
1405
+ setTimeout(() => {
1406
+ document
1407
+ .querySelector(`.plyr__video-embed iframe`)
1408
+ ?.classList.add("mirror");
1409
+ }, 1000);
1410
+ }
1411
+ };
1412
+ this.plyr.on("ready", onReady);
1413
+ }
1414
+ if (this.config.video_host === "youtube") {
1415
+ this.plyr.source = {
1416
+ type: "video",
1417
+ sources: [{ src: this.config.video_id, provider: "youtube" }],
1418
+ };
1419
+ }
1420
+ else if (this.config.video_host === "mp4") {
1421
+ this.plyr.source = {
1422
+ type: "video",
1423
+ sources: [{ src: this.config.video_external_id, type: "video/mp4" }],
1424
+ };
1425
+ }
1426
+ else if (this.config.video_host === "hls") {
1427
+ let sourceX = "";
1428
+ try {
1429
+ sourceX = atob(this.config.video_id || "");
1430
+ }
1431
+ catch (e) {
1432
+ sourceX = this.config.video_id || "";
1433
+ }
1434
+ const canNative = video.canPlayType("application/vnd.apple.mpegurl");
1435
+ if (canNative) {
1436
+ this.plyr.source = {
1437
+ type: "video",
1438
+ sources: [{ src: sourceX, type: "application/vnd.apple.mpegurl" }],
1439
+ };
1440
+ const onLoadedMeta = () => {
1441
+ const dur = this.plyr.duration || video.duration || 0;
1442
+ const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, dur);
1443
+ console.log(start);
1444
+ this.requestStartTimeSeek(start, { video });
1445
+ this.startAutoplayFlow(video);
1446
+ video.removeEventListener("loadedmetadata", onLoadedMeta);
1447
+ };
1448
+ video.addEventListener("loadedmetadata", onLoadedMeta);
1449
+ }
1450
+ else if (Hls.isSupported()) {
1451
+ this.hls = new Hls({ enableWorker: true, lowLatencyMode: true });
1452
+ this.hls.attachMedia(video);
1453
+ this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
1454
+ this.hls.loadSource(sourceX);
1455
+ });
1456
+ this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
1457
+ const dur = video.duration || 0;
1458
+ const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, dur);
1459
+ this.requestStartTimeSeek(start, { video });
1460
+ this.startAutoplayFlow(video);
1461
+ });
1462
+ this.hls.on(Hls.Events.ERROR, (_evt, data) => {
1463
+ if (data?.fatal) {
1464
+ switch (data.type) {
1465
+ case Hls.ErrorTypes.NETWORK_ERROR:
1466
+ this.hls.startLoad();
1467
+ break;
1468
+ case Hls.ErrorTypes.MEDIA_ERROR:
1469
+ this.hls.recoverMediaError();
1470
+ break;
1471
+ default:
1472
+ try {
1473
+ this.hls.destroy();
1474
+ }
1475
+ catch { }
1476
+ break;
1477
+ }
1478
+ }
1479
+ });
1480
+ }
1481
+ else {
1482
+ this.plyr.source = { type: "video", sources: [{ src: sourceX }] };
1483
+ }
1484
+ }
1485
+ resolve();
1486
+ });
1487
+ }
1488
+ resolveStartTime(last_time, last_percent, duration, opts = {}) {
1489
+ if (this.shouldResetProgressToBeginning()) {
1490
+ return 0;
1491
+ }
1492
+ const minStartSec = opts.minStartSec ?? 3;
1493
+ const endGuardSec = opts.endGuardSec ?? 2;
1494
+ const dur = Number(duration);
1495
+ const hasValidDuration = Number.isFinite(dur) && dur > 0;
1496
+ const hasTime = last_time !== undefined && last_time !== null && last_time !== "";
1497
+ const clamp = (value) => {
1498
+ let t = Number(value);
1499
+ if (!isFinite(t) || t < 0)
1500
+ t = 0;
1501
+ if (t < minStartSec)
1502
+ return 0;
1503
+ if (hasValidDuration) {
1504
+ if (t >= dur - endGuardSec) {
1505
+ return 0;
1506
+ }
1507
+ const maxStart = Math.max(0, dur - endGuardSec);
1508
+ t = Math.min(maxStart, t);
1509
+ }
1510
+ return t;
1511
+ };
1512
+ if (hasTime) {
1513
+ return clamp(last_time);
1514
+ }
1515
+ const hasPercent = last_percent !== undefined &&
1516
+ last_percent !== null &&
1517
+ last_percent !== "";
1518
+ if (hasPercent && hasValidDuration) {
1519
+ let p = Number(last_percent);
1520
+ if (!isFinite(p) || p < 0)
1521
+ p = 0;
1522
+ if (p >= 1)
1523
+ return 0;
1524
+ if (p > 1)
1525
+ p = p / 100;
1526
+ if (p >= 0.99)
1527
+ return 0;
1528
+ let t = dur * p;
1529
+ if (t < minStartSec)
1530
+ return 0;
1531
+ if (t >= dur - endGuardSec)
1532
+ return 0;
1533
+ return Math.min(dur - endGuardSec, t);
1534
+ }
1535
+ return 0;
1536
+ }
1537
+ postCastEvent(payload) {
1538
+ parent.postMessage(JSON.stringify(payload), "*");
1539
+ }
1540
+ onCastMessage = (evt) => {
1541
+ let data = evt.data;
1542
+ if (typeof data === "string") {
1543
+ try {
1544
+ data = JSON.parse(data);
1545
+ }
1546
+ catch {
1547
+ /* ignora */
1548
+ }
1549
+ }
1550
+ if (data?.action === "request-iframe-media-metadata") {
1551
+ this.postCastEvent({
1552
+ event: "metadata",
1553
+ duration: this.plyr?.duration || 0,
1554
+ currentTime: this.plyr?.currentTime || 0,
1555
+ });
1556
+ }
1557
+ if (data?.action === "play") {
1558
+ this.plyr?.play();
1559
+ }
1560
+ if (data?.action === "pause") {
1561
+ this.plyr?.pause();
1562
+ }
1563
+ if (data?.action === "seek" && typeof data.time === "number") {
1564
+ this.plyr && (this.plyr.currentTime = data.time);
1565
+ }
1566
+ };
1567
+ ngOnDestroy() {
1568
+ this.hls?.destroy();
1569
+ this.controlsVisibilityCleanup?.();
1570
+ this.controlsVisibilityCleanup = undefined;
1571
+ this.clearAutoplayUnlockListener();
1572
+ this.detachLiveInteractionBlocker();
1573
+ const container = this.plyr?.elements?.container;
1574
+ if (this.recommendationBlocker?.parentElement) {
1575
+ try {
1576
+ this.recommendationBlocker.parentElement.removeChild(this.recommendationBlocker);
1577
+ }
1578
+ catch { }
1579
+ }
1580
+ if (this.recommendationBlockerActiveZone) {
1581
+ try {
1582
+ this.recommendationBlockerActiveZone.removeEventListener("pointerdown", this.onTopBlockerPointerDown);
1583
+ this.recommendationBlockerActiveZone.removeEventListener("click", this.onRecommendationBlockerClick);
1584
+ }
1585
+ catch { }
1586
+ }
1587
+ this.recommendationBlocker = null;
1588
+ this.recommendationBlockerActiveZone = null;
1589
+ if (this.topClickBlocker?.parentElement) {
1590
+ try {
1591
+ this.topClickBlocker.removeEventListener("pointerdown", this.onTopBlockerPointerDown);
1592
+ this.topClickBlocker.removeEventListener("click", this.onTopBlockerClick);
1593
+ this.topClickBlocker.parentElement.removeChild(this.topClickBlocker);
1594
+ }
1595
+ catch { }
1596
+ }
1597
+ this.topClickBlocker = null;
1598
+ this.menuObserver?.disconnect();
1599
+ this.menuObserver = undefined;
1600
+ window.removeEventListener("resize", this.onResize);
1601
+ this.removeIOSFullscreenLogging();
1602
+ this.plyr?.destroy();
1603
+ window.removeEventListener("message", this.onCastMessage);
1604
+ }
1605
+ 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 });
1606
+ 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 }] });
1607
+ }
1608
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlyrPlayerComponent, decorators: [{
1609
+ type: Component,
1610
+ 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"] }]
1611
+ }], ctorParameters: () => [{ type: WebplayerProgressService }, { type: Document, decorators: [{
1612
+ type: Inject,
1613
+ args: [DOCUMENT]
1614
+ }] }], propDecorators: { isLiveMode: [{
1615
+ type: HostBinding,
1616
+ args: ["class.live-mode"]
1617
+ }], videoRef: [{
1618
+ type: ViewChild,
1619
+ args: ["video", { static: true }]
1620
+ }], config: [{
1621
+ type: Input
1622
+ }], ready: [{
1623
+ type: Output
1624
+ }] } });
1625
+
1626
+ class VimeoPlayerComponent {
1627
+ progressService;
1628
+ document;
1629
+ config;
1630
+ ready = new EventEmitter();
1631
+ videoRef;
1632
+ plyr;
1633
+ startTimeApplied = false;
1634
+ pendingStartTime;
1635
+ endedLoggedOnPause = false;
1636
+ iosFullscreenCleanup;
1637
+ castListenerAttached = false;
1638
+ unmuteForCast() {
1639
+ try {
1640
+ const videoEl = this.videoRef?.nativeElement;
1641
+ if (videoEl) {
1642
+ videoEl.muted = false;
1643
+ videoEl.volume = 1;
1644
+ }
1645
+ if (this.plyr) {
1646
+ this.plyr.muted = false;
1647
+ this.plyr.volume = 1;
1648
+ }
1649
+ }
1650
+ catch { }
1651
+ }
1652
+ onCastMessage = (event) => {
1653
+ let data = event.data;
1654
+ if (typeof data === 'string') {
1655
+ try {
1656
+ data = JSON.parse(data);
1657
+ }
1658
+ catch { }
1659
+ }
1660
+ if (data?.action === 'request-iframe-media-metadata') {
1661
+ console.log('Received request-iframe-media-metadata message');
1662
+ setTimeout(() => {
1663
+ this.postCastEvent({
1664
+ event: 'metadata',
1665
+ duration: this.plyr?.duration || 0,
1666
+ currentTime: this.plyr?.currentTime || 0,
1667
+ });
1668
+ }, 100);
1669
+ }
1670
+ if (data?.action === 'play') {
1671
+ this.unmuteForCast();
1672
+ this.plyr?.play?.();
1673
+ }
1674
+ if (data?.action === 'pause') {
1675
+ this.plyr?.pause?.();
1676
+ }
1677
+ if (data?.action === 'seek' && typeof data.time === 'number') {
1678
+ this.plyr && (this.plyr.currentTime = data.time);
1679
+ }
1680
+ };
1681
+ endedOverlayVisible = false;
1682
+ constructor(progressService, document) {
1683
+ this.progressService = progressService;
1684
+ this.document = document;
1685
+ }
1686
+ ngOnInit() {
1687
+ this.applyTheme(this.config?.ui?.colors);
1688
+ }
1689
+ ngAfterViewInit() {
1690
+ this.ensureCastMessageListener();
1691
+ this.setupPlayer();
1692
+ }
1693
+ ngOnChanges(changes) {
1694
+ if (changes['config'] && this.plyr) {
1695
+ this.ensureCastMessageListener();
1696
+ this.setupPlayer();
1697
+ }
1698
+ }
1699
+ setupPlayer() {
1700
+ if (!this.videoRef) {
1701
+ return;
1702
+ }
1703
+ const videoElement = this.videoRef.nativeElement;
1704
+ const isIOS = this.isIOSContext();
1705
+ this.applyInlineAttributes(videoElement, isIOS);
1706
+ this.plyr?.destroy();
1707
+ this.startTimeApplied = false;
1708
+ this.pendingStartTime = undefined;
1709
+ this.endedLoggedOnPause = false;
1710
+ this.endedOverlayVisible = false;
1711
+ const shouldAutoplay = false;
1712
+ const startMuted = shouldAutoplay;
1713
+ const speedOptions = [1, 1.25, 1.5, 2];
1714
+ const baseControls = [
1715
+ 'play-large',
1716
+ 'play',
1717
+ 'progress',
1718
+ 'current-time',
1719
+ 'duration',
1720
+ 'mute',
1721
+ 'volume',
1722
+ 'captions',
1723
+ 'settings',
1724
+ 'pip',
1725
+ 'airplay',
1726
+ 'fullscreen',
1727
+ ];
1728
+ const options = {
1729
+ controls: isIOS ? baseControls.filter((c) => c !== 'fullscreen') : baseControls,
1730
+ clickToPlay: true,
1731
+ autoplay: shouldAutoplay,
1732
+ muted: startMuted,
1733
+ keyboard: { focused: true, global: false },
1734
+ hideControls: true,
1735
+ i18n: {
1736
+ speed: "Velocidade",
1737
+ },
1738
+ speed: {
1739
+ selected: 1,
1740
+ options: speedOptions,
1741
+ },
1742
+ fullscreen: {
1743
+ enabled: !isIOS,
1744
+ fallback: !isIOS,
1745
+ iosNative: true,
1746
+ container: null,
1747
+ },
1748
+ };
1749
+ options.vimeo = {
1750
+ autopause: true,
1751
+ autoplay: shouldAutoplay,
1752
+ muted: startMuted,
1753
+ playsinline: true,
1754
+ byline: false,
1755
+ portrait: false,
1756
+ title: false,
1757
+ pip: false,
1758
+ controls: false,
1759
+ };
1760
+ this.plyr = new Plyr.default(videoElement, options);
1761
+ this.plyr.source = {
1762
+ type: 'video',
1763
+ sources: [{ src: this.config.video_id, provider: 'vimeo' }],
1764
+ };
1765
+ this.bindIOSFullscreenLogging(videoElement, isIOS);
1766
+ this.bindEvents();
1767
+ }
1768
+ bindEvents() {
1769
+ if (!this.plyr)
1770
+ return;
1771
+ let durationPosted = false;
1772
+ const onTimeUpdate = () => {
1773
+ setTimeout(() => {
1774
+ const currentReal = this.plyr.currentTime || 0;
1775
+ const durationReal = this.plyr.duration || 0;
1776
+ const current = Math.floor(currentReal);
1777
+ const duration = Math.floor(this.plyr.duration);
1778
+ if (!durationPosted && duration > 0) {
1779
+ durationPosted = true;
1780
+ console.log('event: duration ' + duration);
1781
+ parent.postMessage('duration:' + duration, '*');
1782
+ }
1783
+ const percent = duration > 0 ? +((current / duration) * 100).toFixed(2) : 0;
1784
+ this.progressService.progress$.next({ currentTime: currentReal, duration: durationReal, percent });
1785
+ parent.postMessage('currentTime:' + current, '*');
1786
+ console.log('event: current_time ' + current);
1787
+ console.log('event: duration ' + duration);
1788
+ this.postCastEvent({
1789
+ event: 'current_time',
1790
+ currentTime: current,
1791
+ duration,
1792
+ });
1793
+ this.tryApplyPendingStartTime();
1794
+ }, 0);
1795
+ };
1796
+ this.plyr.on('ready', () => {
1797
+ this.applySavedStartTime();
1798
+ const duration = this.plyr.duration || 0;
1799
+ console.log('event: ready');
1800
+ if (duration > 0) {
1801
+ parent.postMessage('duration:' + duration, '*');
1802
+ }
1803
+ parent.postMessage('playerIsReadyToPlay', '*');
1804
+ this.postCastEvent({
1805
+ event: 'ready',
1806
+ duration,
1807
+ currentTime: this.plyr.currentTime || 0,
1808
+ });
1809
+ this.ready.emit();
1810
+ });
1811
+ const reapplyStartTime = () => {
1812
+ if (typeof this.pendingStartTime === 'number' || !this.startTimeApplied) {
1813
+ this.applySavedStartTime(true);
1814
+ }
1815
+ if (this.startTimeApplied) {
1816
+ this.plyr?.off?.('loadedmetadata', reapplyStartTime);
1817
+ this.plyr?.off?.('durationchange', reapplyStartTime);
1818
+ }
1819
+ };
1820
+ this.plyr.on('timeupdate', onTimeUpdate);
1821
+ this.plyr.on('loadedmetadata', reapplyStartTime);
1822
+ this.plyr.on('durationchange', reapplyStartTime);
1823
+ this.plyr.on('play', () => {
1824
+ this.endedLoggedOnPause = false;
1825
+ this.tryApplyPendingStartTime();
1826
+ //this.jumpToPendingStartTime();
1827
+ this.endedOverlayVisible = false;
1828
+ this.progressService.ended$.next();
1829
+ console.log('event: playing_video');
1830
+ if (this.config?.is_live) {
1831
+ console.log('event: playing_live');
1832
+ parent.postMessage('event: playing_live', '*');
1833
+ }
1834
+ parent.postMessage('playing_video', '*');
1835
+ this.postCastEvent({
1836
+ event: 'play',
1837
+ currentTime: this.plyr.currentTime || 0,
1838
+ });
1839
+ onTimeUpdate();
1840
+ });
1841
+ this.plyr.on('pause', () => {
1842
+ this.endedOverlayVisible = false;
1843
+ console.log('event: pause');
1844
+ parent.postMessage('paused_video', '*');
1845
+ this.postCastEvent({
1846
+ event: 'pause',
1847
+ currentTime: this.plyr.currentTime || 0,
1848
+ });
1849
+ onTimeUpdate();
1850
+ if (this.shouldEmitEndedOnPause()) {
1851
+ this.handleEnded(true);
1852
+ }
1853
+ this.unlockPlayerInteraction();
1854
+ });
1855
+ this.plyr.on('ended', () => {
1856
+ onTimeUpdate();
1857
+ this.handleEnded();
1858
+ });
1859
+ this.plyr.on('enterfullscreen', () => {
1860
+ console.log('event: enterfullscreen');
1861
+ parent.postMessage('event: fullscreen_enter', '*');
1862
+ this.postCastEvent({ event: 'enterfullscreen' });
1863
+ });
1864
+ this.plyr.on('exitfullscreen', () => {
1865
+ console.log('event: exitfullscreen');
1866
+ parent.postMessage('event: fullscreen_exit', '*');
1867
+ this.postCastEvent({ event: 'exitfullscreen' });
1868
+ });
1869
+ }
1870
+ resolveStartTime(last_time, last_percent, duration) {
1871
+ if (this.shouldResetProgressToBeginning()) {
1872
+ return 0;
1873
+ }
1874
+ const dur = Number(duration) || 0;
1875
+ if (dur <= 0) {
1876
+ if (last_time && Number(last_time) > 0) {
1877
+ return Number(last_time);
1878
+ }
1879
+ return 0;
1880
+ }
1881
+ if (last_time && Number(last_time) > 0) {
1882
+ return Math.min(Number(last_time), dur - 2);
1883
+ }
1884
+ if (last_percent && Number(last_percent) > 0) {
1885
+ let percent = Number(last_percent);
1886
+ if (percent > 1) {
1887
+ percent = percent / 100;
1888
+ }
1889
+ return Math.min(percent * dur, dur - 2);
1890
+ }
1891
+ return 0;
1892
+ }
1893
+ shouldResetProgressToBeginning() {
1894
+ const seenFlag = this.config?.seen;
1895
+ if (seenFlag === true || seenFlag === 'true' || seenFlag === 1) {
1896
+ return true;
1897
+ }
1898
+ const rawPercent = this.config?.last_percent;
1899
+ if (rawPercent === undefined || rawPercent === null || rawPercent === 0) {
1900
+ return false;
1901
+ }
1902
+ let percent = Number(rawPercent);
1903
+ if (!Number.isFinite(percent)) {
1904
+ return false;
1905
+ }
1906
+ if (percent > 1) {
1907
+ percent = percent / 100;
1908
+ }
1909
+ return percent >= 0.8;
1910
+ }
1911
+ isIOSDevice() {
1912
+ if (typeof navigator === 'undefined')
1913
+ return false;
1914
+ const ua = navigator.userAgent || '';
1915
+ const iOS = /iPad|iPhone|iPod/.test(ua);
1916
+ const iPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;
1917
+ return iOS || iPadOS;
1918
+ }
1919
+ applySavedStartTime(force = false) {
1920
+ if (!this.plyr)
1921
+ return;
1922
+ if (this.startTimeApplied && !force)
1923
+ return;
1924
+ const duration = this.plyr.duration || 0;
1925
+ const start = this.resolveStartTime(this.config?.last_time, this.config?.last_percent, duration);
1926
+ if (!(start > 0)) {
1927
+ this.pendingStartTime = undefined;
1928
+ return;
1929
+ }
1930
+ if (duration > 0) {
1931
+ this.pendingStartTime = start;
1932
+ this.startTimeApplied = false;
1933
+ this.jumpToPendingStartTime();
1934
+ this.resumeAfterPendingSeek();
1935
+ }
1936
+ else {
1937
+ this.pendingStartTime = start;
1938
+ this.startTimeApplied = false;
1939
+ }
1940
+ }
1941
+ ngOnDestroy() {
1942
+ this.plyr?.destroy();
1943
+ this.removeIOSFullscreenLogging();
1944
+ if (typeof window !== 'undefined') {
1945
+ if (this.castListenerAttached) {
1946
+ window.removeEventListener('message', this.onCastMessage);
1947
+ this.castListenerAttached = false;
1948
+ }
1949
+ window.removeEventListener('message', this.onCastMessage);
1950
+ }
1951
+ }
1952
+ postCastEvent(payload) {
1953
+ parent.postMessage(JSON.stringify(payload), '*');
1954
+ }
1955
+ applyTheme(colors) {
1956
+ if (!colors)
1957
+ return;
1958
+ const STYLE_ID = 'webplayer-theme-vars';
1959
+ let styleEl = this.document.getElementById(STYLE_ID);
1960
+ if (!styleEl) {
1961
+ styleEl = this.document.createElement('style');
1962
+ styleEl.id = STYLE_ID;
1963
+ this.document.head.appendChild(styleEl);
1964
+ }
1965
+ const rootStyle = this.document.documentElement.style;
1966
+ const allPairs = [];
1967
+ for (const key in colors) {
1968
+ if (Object.prototype.hasOwnProperty.call(colors, key)) {
1969
+ const cssVarName = `--theme-${key.replace('progresss-', 'progress-')}`;
1970
+ const cssVarValue = String(colors[key]);
1971
+ allPairs.push([cssVarName, cssVarValue]);
1972
+ try {
1973
+ rootStyle.setProperty(cssVarName, cssVarValue);
1974
+ }
1975
+ catch { }
1976
+ }
1977
+ }
1978
+ const cssRule = `:root{
1979
+ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join('\n')}
1980
+ }`;
1981
+ styleEl.textContent = cssRule;
1982
+ }
1983
+ seekTo(time) {
1984
+ if (!this.plyr)
1985
+ return;
1986
+ const target = Math.max(0, Number(time) || 0);
1987
+ try {
1988
+ this.plyr.currentTime = target;
1989
+ }
1990
+ catch { }
1991
+ }
1992
+ tryApplyPendingStartTime() {
1993
+ if (this.startTimeApplied)
1994
+ return;
1995
+ if (!this.plyr)
1996
+ return;
1997
+ const pending = this.pendingStartTime;
1998
+ if (typeof pending !== 'number' || !(pending > 0)) {
1999
+ return;
2000
+ }
2001
+ const duration = this.plyr.duration || 0;
2002
+ if (!(duration > 0)) {
2003
+ return;
2004
+ }
2005
+ const current = this.plyr.currentTime || 0;
2006
+ const tolerance = 0.35;
2007
+ const nearingTarget = Math.abs(current - pending) <= tolerance;
2008
+ if (nearingTarget && duration > 0) {
2009
+ this.startTimeApplied = true;
2010
+ this.pendingStartTime = undefined;
2011
+ return;
2012
+ }
2013
+ this.jumpToPendingStartTime();
2014
+ }
2015
+ jumpToPendingStartTime() {
2016
+ if (this.startTimeApplied)
2017
+ return;
2018
+ if (!this.plyr)
2019
+ return;
2020
+ const pending = this.pendingStartTime;
2021
+ if (typeof pending !== 'number' || !(pending > 0)) {
2022
+ return;
2023
+ }
2024
+ const restoreMute = this.temporarilyMuteForSeek();
2025
+ this.seekTo(pending);
2026
+ if (restoreMute) {
2027
+ setTimeout(() => restoreMute(), 300);
2028
+ }
2029
+ this.pendingStartTime = undefined;
2030
+ this.startTimeApplied = true;
2031
+ }
2032
+ resumeAfterPendingSeek() {
2033
+ if (!this.plyr)
2034
+ return;
2035
+ const resume = () => {
2036
+ this.plyr?.off?.('seeked', resume);
2037
+ try {
2038
+ const maybePromise = this.plyr?.play?.();
2039
+ if (maybePromise && typeof maybePromise.then === 'function') {
2040
+ maybePromise.catch(() => { });
2041
+ }
2042
+ }
2043
+ catch { }
2044
+ };
2045
+ try {
2046
+ this.plyr?.pause?.();
2047
+ }
2048
+ catch { }
2049
+ this.plyr?.off?.('seeked', resume);
2050
+ this.plyr?.on?.('seeked', resume);
2051
+ }
2052
+ shouldEmitEndedOnPause() {
2053
+ if (!this.isIOSContext()) {
2054
+ return false;
2055
+ }
2056
+ if (this.endedLoggedOnPause) {
2057
+ return false;
2058
+ }
2059
+ const duration = this.plyr?.duration;
2060
+ if (!Number.isFinite(duration) || !duration || duration <= 0) {
2061
+ return false;
2062
+ }
2063
+ const current = this.plyr?.currentTime || 0;
2064
+ return duration - current < 1;
2065
+ }
2066
+ handleEnded(triggeredByPause = false) {
2067
+ if (triggeredByPause && this.endedLoggedOnPause) {
2068
+ return;
2069
+ }
2070
+ if (!triggeredByPause) {
2071
+ this.endedOverlayVisible = true;
2072
+ }
2073
+ console.log('event: ended');
2074
+ parent.postMessage('event: ended', '*');
2075
+ this.postCastEvent({
2076
+ event: 'ended',
2077
+ currentTime: this.plyr?.currentTime || 0,
2078
+ });
2079
+ this.progressService.ended$.next();
2080
+ this.endedLoggedOnPause = true;
2081
+ }
2082
+ isIOSContext() {
2083
+ if (this.isIOSDevice()) {
2084
+ return true;
2085
+ }
2086
+ if (typeof window === 'undefined') {
2087
+ return false;
2088
+ }
2089
+ try {
2090
+ const params = new URLSearchParams(window.location.search);
2091
+ const os = params.get('os');
2092
+ if (os && os.toLowerCase() === 'ios') {
2093
+ return true;
2094
+ }
2095
+ }
2096
+ catch { }
2097
+ return false;
2098
+ }
2099
+ temporarilyMuteForSeek() {
2100
+ const video = this.videoRef?.nativeElement ?? null;
2101
+ const prevVideoMuted = video?.muted ?? false;
2102
+ const prevPlyrMuted = !!this.plyr?.muted;
2103
+ try {
2104
+ if (video) {
2105
+ video.muted = true;
232
2106
  }
233
- if (this.hls) {
234
- this.hls.destroy();
235
- this.hls = undefined;
2107
+ if (this.plyr) {
2108
+ this.plyr.muted = true;
236
2109
  }
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);
2110
+ }
2111
+ catch { }
2112
+ return () => {
2113
+ try {
2114
+ if (video) {
2115
+ video.muted = prevVideoMuted;
2116
+ }
258
2117
  }
259
- if (this.config.video_host === "youtube") {
260
- this.plyr.source = {
261
- type: "video",
262
- sources: [{ src: this.config.video_id, provider: "youtube" }],
263
- };
2118
+ catch { }
2119
+ try {
2120
+ if (this.plyr) {
2121
+ this.plyr.muted = prevPlyrMuted;
2122
+ }
264
2123
  }
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
- };
2124
+ catch { }
2125
+ this.unlockPlayerInteraction();
2126
+ };
2127
+ }
2128
+ applyInlineAttributes(videoElement, isIOS) {
2129
+ try {
2130
+ videoElement.setAttribute('playsinline', 'true');
2131
+ videoElement.setAttribute('webkit-playsinline', 'true');
2132
+ if (isIOS) {
2133
+ videoElement.setAttribute('x5-playsinline', 'true');
2134
+ videoElement.setAttribute('x5-video-player-type', 'h5');
270
2135
  }
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
- };
2136
+ }
2137
+ catch { }
2138
+ }
2139
+ bindIOSFullscreenLogging(videoElement, isIOS) {
2140
+ this.removeIOSFullscreenLogging();
2141
+ if (!isIOS)
2142
+ return;
2143
+ const logEvent = (name) => {
2144
+ console.log(`event: ${name}`);
2145
+ try {
2146
+ parent.postMessage(`event: ${name}`, '*');
276
2147
  }
277
- else if (this.config.video_host === "hls") {
278
- let sourceX = "";
2148
+ catch { }
2149
+ };
2150
+ const onEnter = () => logEvent('webkit_enterfullscreen');
2151
+ const onExit = () => logEvent('webkit_exitfullscreen');
2152
+ try {
2153
+ videoElement.addEventListener('webkitbeginfullscreen', onEnter);
2154
+ videoElement.addEventListener('webkitendfullscreen', onExit);
2155
+ this.iosFullscreenCleanup = () => {
279
2156
  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
- });
2157
+ videoElement.removeEventListener('webkitbeginfullscreen', onEnter);
2158
+ videoElement.removeEventListener('webkitendfullscreen', onExit);
366
2159
  }
367
- else {
368
- this.plyr.source = { type: "video", sources: [{ src: sourceX }] };
369
- }
370
- }
371
- resolve();
372
- });
2160
+ catch { }
2161
+ this.iosFullscreenCleanup = undefined;
2162
+ };
2163
+ }
2164
+ catch { }
373
2165
  }
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);
2166
+ removeIOSFullscreenLogging() {
2167
+ try {
2168
+ this.iosFullscreenCleanup?.();
388
2169
  }
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);
2170
+ catch { }
2171
+ this.iosFullscreenCleanup = undefined;
2172
+ }
2173
+ ensureCastMessageListener() {
2174
+ if (typeof window === 'undefined') {
2175
+ return;
400
2176
  }
401
- return 0;
2177
+ if (this.castListenerAttached) {
2178
+ return;
2179
+ }
2180
+ window.addEventListener('message', this.onCastMessage);
2181
+ this.castListenerAttached = true;
402
2182
  }
403
- ngOnDestroy() {
404
- this.hls?.destroy();
405
- this.plyr?.destroy();
2183
+ unlockPlayerInteraction() {
2184
+ try {
2185
+ const controls = this.plyr?.elements?.controls;
2186
+ if (controls) {
2187
+ controls.style.pointerEvents = 'auto';
2188
+ }
2189
+ const overlay = this.plyr?.elements?.buttons?.play;
2190
+ if (overlay) {
2191
+ overlay.style.pointerEvents = 'auto';
2192
+ }
2193
+ }
2194
+ catch { }
406
2195
  }
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 }] });
2196
+ 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 });
2197
+ 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: `
2198
+ <div class="vimeo-wrapper">
2199
+ <video #video playsinline></video>
2200
+ </div>
2201
+ `, 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
2202
  }
410
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlyrPlayerComponent, decorators: [{
2203
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: VimeoPlayerComponent, decorators: [{
411
2204
  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"] }]
2205
+ args: [{ selector: 'app-vimeo-player', standalone: true, imports: [CommonModule], host: {
2206
+ '[class.overlay-visible]': 'endedOverlayVisible',
2207
+ }, template: `
2208
+ <div class="vimeo-wrapper">
2209
+ <video #video playsinline></video>
2210
+ </div>
2211
+ `, 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
2212
  }], ctorParameters: () => [{ type: WebplayerProgressService }, { type: Document, decorators: [{
414
2213
  type: Inject,
415
2214
  args: [DOCUMENT]
416
- }] }], propDecorators: { videoRef: [{
417
- type: ViewChild,
418
- args: ["video", { static: true }]
419
- }], config: [{
2215
+ }] }], propDecorators: { config: [{
420
2216
  type: Input
2217
+ }], ready: [{
2218
+ type: Output
2219
+ }], videoRef: [{
2220
+ type: ViewChild,
2221
+ args: ['video', { static: true }]
421
2222
  }] } });
422
2223
 
423
2224
  class PandaPlayerComponent {
424
2225
  elementRef;
425
2226
  progressService;
426
2227
  config;
2228
+ ready = new EventEmitter();
427
2229
  pandaPlayer;
428
2230
  pandaContainer;
429
2231
  pandaProgressThrottle = 0;
@@ -457,6 +2259,7 @@ class PandaPlayerComponent {
457
2259
  }
458
2260
  this.ensurePandaScript().then(() => {
459
2261
  const host = this.elementRef.nativeElement;
2262
+ console.log('Panda Player Config:', this.config, host);
460
2263
  if (!this.pandaContainer) {
461
2264
  this.pandaContainer = document.createElement("div");
462
2265
  this.pandaContainer.style.width = "100%";
@@ -468,8 +2271,8 @@ class PandaPlayerComponent {
468
2271
  this.pandaPlayer?.destroy?.();
469
2272
  }
470
2273
  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);
2274
+ const videoId = this.config.video_external_id || (this.config.embed ? this.extractPandaVideoId(this.config.embed) : undefined);
2275
+ const libraryId = (this.config.embed ? this.extractPandaLibraryId(this.config.embed) : undefined);
473
2276
  window.pandascripttag = window.pandascripttag || [];
474
2277
  window.pandascripttag.push(() => {
475
2278
  this.pandaPlayer = new window.PandaPlayer(this.pandaContainer.id, {
@@ -496,16 +2299,17 @@ class PandaPlayerComponent {
496
2299
  }
497
2300
  catch { }
498
2301
  }
499
- if (this.config.autoPlay) {
2302
+ if (this.config.autoplay) {
500
2303
  try {
501
2304
  this.pandaPlayer.play();
502
2305
  }
503
2306
  catch { }
504
2307
  }
2308
+ this.ready.emit();
505
2309
  },
506
2310
  playerConfigs: {
507
- autoplay: !!this.config.autoPlay,
508
- smartAutoplay: !!this.config.autoPlay,
2311
+ autoplay: !!this.config.autoplay,
2312
+ smartAutoplay: !!this.config.autoplay,
509
2313
  },
510
2314
  });
511
2315
  this.pandaPlayer.onEvent((event) => {
@@ -604,6 +2408,13 @@ class PandaPlayerComponent {
604
2408
  catch { }
605
2409
  }
606
2410
  }
2411
+ if (type === "panda_play") {
2412
+ const snapshot = this.safeProgressSnapshot();
2413
+ if (snapshot) {
2414
+ this.progressService.progress$.next(snapshot);
2415
+ }
2416
+ this.notifyParentPlaybackEvent('play', snapshot ?? {});
2417
+ }
607
2418
  if (type === "panda_pause") {
608
2419
  try {
609
2420
  const t = this.pandaPlayer?.getCurrentTime?.() || 0;
@@ -612,16 +2423,15 @@ class PandaPlayerComponent {
612
2423
  this.progressService.progress$.next({ currentTime: t, duration: d, percent: p });
613
2424
  }
614
2425
  catch { }
2426
+ this.notifyParentPlaybackEvent('pause', this.safeProgressSnapshot());
615
2427
  }
616
2428
  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 });
2429
+ const snapshot = this.safeProgressSnapshot();
2430
+ if (snapshot) {
2431
+ this.progressService.progress$.next(snapshot);
622
2432
  }
623
- catch { }
624
2433
  this.progressService.ended$.next();
2434
+ this.notifyParentPlaybackEvent('ended', snapshot ?? {});
625
2435
  }
626
2436
  }
627
2437
  resolveStartTime(last_time, last_percent, duration, opts = {}) {
@@ -655,23 +2465,56 @@ class PandaPlayerComponent {
655
2465
  }
656
2466
  return 0;
657
2467
  }
2468
+ safeProgressSnapshot() {
2469
+ try {
2470
+ const currentTime = this.pandaPlayer?.getCurrentTime?.() || 0;
2471
+ const duration = this.pandaPlayer?.getDuration?.() || 0;
2472
+ const percent = duration > 0 ? +((currentTime / duration) * 100).toFixed(2) : 0;
2473
+ return { currentTime, duration, percent };
2474
+ }
2475
+ catch {
2476
+ return null;
2477
+ }
2478
+ }
2479
+ notifyParentPlaybackEvent(event, extra = {}) {
2480
+ if (typeof window === 'undefined')
2481
+ return;
2482
+ const target = typeof parent !== 'undefined' ? parent : null;
2483
+ if (!target || target === window)
2484
+ return;
2485
+ const payload = { event, ...extra };
2486
+ try {
2487
+ target.postMessage(`event: ${event}`, '*');
2488
+ }
2489
+ catch { }
2490
+ try {
2491
+ target.postMessage(JSON.stringify(payload), '*');
2492
+ }
2493
+ catch { }
2494
+ }
658
2495
  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 }] });
2496
+ 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
2497
  }
661
2498
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PandaPlayerComponent, decorators: [{
662
2499
  type: Component,
663
2500
  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
2501
  }], ctorParameters: () => [{ type: i0.ElementRef }, { type: WebplayerProgressService }], propDecorators: { config: [{
665
2502
  type: Input
2503
+ }], ready: [{
2504
+ type: Output
666
2505
  }] } });
667
2506
 
668
2507
  class ScaleupPlayerComponent {
669
2508
  elementRef;
670
2509
  progressService;
671
2510
  config;
2511
+ ready = new EventEmitter();
672
2512
  scaleupIframe;
673
2513
  scaleupMessageHandler;
674
2514
  scaleupProgressThrottle = 0;
2515
+ castMessageHandler;
2516
+ lastDuration = 0;
2517
+ lastCurrentTime = 0;
675
2518
  constructor(elementRef, progressService) {
676
2519
  this.elementRef = elementRef;
677
2520
  this.progressService = progressService;
@@ -690,16 +2533,25 @@ class ScaleupPlayerComponent {
690
2533
  loadScaleupPlayer() {
691
2534
  const videoId = this.config.video_id;
692
2535
  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}`;
2536
+ const eventsList = this.resolveEventsList();
2537
+ const params = new URLSearchParams({
2538
+ seekTo: String(lastTime),
2539
+ events: eventsList,
2540
+ autoplay: String(!!this.config.autoplay),
2541
+ });
2542
+ if (this.isIOSContext()) {
2543
+ params.set("fullscreen", "0");
2544
+ }
2545
+ const scaleupSrc = `https://player.scaleup.com.br/embed/${videoId}?${params.toString()}`;
695
2546
  const host = this.elementRef.nativeElement;
696
2547
  if (this.scaleupIframe) {
697
2548
  this.scaleupIframe.remove();
698
2549
  }
699
2550
  const iframe = document.createElement("iframe");
700
2551
  iframe.src = scaleupSrc;
701
- iframe.allow = "autoplay; fullscreen; encrypted-media; picture-in-picture";
2552
+ iframe.allow = "autoplay; encrypted-media; picture-in-picture; web-share";
702
2553
  iframe.referrerPolicy = "origin-when-cross-origin";
2554
+ iframe.allowFullscreen = false;
703
2555
  iframe.frameBorder = "0";
704
2556
  iframe.style.width = "100%";
705
2557
  iframe.style.height = "100%";
@@ -708,6 +2560,11 @@ class ScaleupPlayerComponent {
708
2560
  iframe.style.left = "0";
709
2561
  host.appendChild(iframe);
710
2562
  this.scaleupIframe = iframe;
2563
+ this.ensureCastMessageHandler();
2564
+ // Emite evento ready quando o iframe carregar
2565
+ iframe.onload = () => {
2566
+ this.ready.emit();
2567
+ };
711
2568
  if (this.scaleupMessageHandler) {
712
2569
  window.removeEventListener("message", this.scaleupMessageHandler);
713
2570
  }
@@ -715,6 +2572,8 @@ class ScaleupPlayerComponent {
715
2572
  try {
716
2573
  const originOk = typeof e.origin === "string" &&
717
2574
  e.origin.includes("scaleup.com.br");
2575
+ console.log('Scaleup message received:', e);
2576
+ console.log('Origin OK:', originOk);
718
2577
  if (!originOk)
719
2578
  return;
720
2579
  const data = typeof e.data === "string"
@@ -727,6 +2586,7 @@ class ScaleupPlayerComponent {
727
2586
  }
728
2587
  })()
729
2588
  : e.data;
2589
+ console.log('Parsed data:', data);
730
2590
  if (!data || typeof data !== "object")
731
2591
  return;
732
2592
  const type = (data.event ||
@@ -736,11 +2596,21 @@ class ScaleupPlayerComponent {
736
2596
  "")
737
2597
  .toString()
738
2598
  .toLowerCase();
2599
+ console.log('Event type:', type);
739
2600
  if (!type)
740
2601
  return;
2602
+ if (type.includes("fullscreen") || type.includes("fullwindow")) {
2603
+ try {
2604
+ console.log(`scaleup event: ${type}`);
2605
+ }
2606
+ catch { }
2607
+ }
741
2608
  const t = Number((data.currentTime ?? data.time ?? data.position) || 0);
742
2609
  const d = Number((data.duration ?? data.totalDuration) || 0);
743
2610
  const p = d > 0 ? +((t / d) * 100).toFixed(2) : 0;
2611
+ this.lastCurrentTime = t;
2612
+ this.lastDuration = d;
2613
+ this.postCastProgress(t, d);
744
2614
  const THROTTLE_MS = 300;
745
2615
  const now = performance.now();
746
2616
  if (type.includes("timeupdate") ||
@@ -772,94 +2642,299 @@ class ScaleupPlayerComponent {
772
2642
  window.removeEventListener("message", this.scaleupMessageHandler);
773
2643
  this.scaleupMessageHandler = undefined;
774
2644
  }
2645
+ if (this.castMessageHandler) {
2646
+ window.removeEventListener("message", this.castMessageHandler);
2647
+ this.castMessageHandler = undefined;
2648
+ }
2649
+ }
2650
+ postCastProgress(currentTime, duration) {
2651
+ try {
2652
+ console.log("Scaleup -> Cast postProgress", { currentTime, duration });
2653
+ parent.postMessage({
2654
+ currentTime,
2655
+ duration,
2656
+ }, "*");
2657
+ }
2658
+ catch { }
2659
+ }
2660
+ sendScaleupCommand(cmd) {
2661
+ try {
2662
+ console.log("Scaleup -> iframe command", cmd);
2663
+ const target = this.scaleupIframe?.contentWindow;
2664
+ if (!target)
2665
+ return;
2666
+ target.postMessage(cmd, "*");
2667
+ if (cmd?.action && typeof cmd.action === "string") {
2668
+ target.postMessage({ type: cmd.action }, "*");
2669
+ target.postMessage(cmd.action, "*");
2670
+ }
2671
+ }
2672
+ catch { }
2673
+ }
2674
+ ensureCastMessageHandler() {
2675
+ if (this.castMessageHandler)
2676
+ return;
2677
+ this.castMessageHandler = (event) => {
2678
+ console.log("Cast message received (Scaleup)", event.data);
2679
+ const data = event?.data;
2680
+ if (!data)
2681
+ return;
2682
+ let payload = data;
2683
+ if (typeof data === "string") {
2684
+ try {
2685
+ payload = JSON.parse(data);
2686
+ }
2687
+ catch {
2688
+ payload = { action: data };
2689
+ }
2690
+ }
2691
+ const actionRaw = payload?.action ?? payload?.type ?? "";
2692
+ const action = typeof actionRaw === "string"
2693
+ ? actionRaw.toLowerCase()
2694
+ : "";
2695
+ console.log("Cast normalized action", action);
2696
+ if (action === "request-iframe-media-metadata") {
2697
+ const duration = this.lastDuration || 0;
2698
+ const currentTime = this.lastCurrentTime || 0;
2699
+ this.postCastProgress(currentTime, duration);
2700
+ return;
2701
+ }
2702
+ if (action === "play") {
2703
+ this.sendScaleupCommand({ action: "play" });
2704
+ return;
2705
+ }
2706
+ if (action === "pause") {
2707
+ this.sendScaleupCommand({ action: "pause" });
2708
+ return;
2709
+ }
2710
+ if (action === "seek" && typeof payload.time === "number") {
2711
+ this.sendScaleupCommand({ action: "seek", time: payload.time });
2712
+ return;
2713
+ }
2714
+ };
2715
+ window.addEventListener("message", this.castMessageHandler);
2716
+ }
2717
+ resolveEventsList() {
2718
+ const baseEvents = [
2719
+ "controlsdisabled",
2720
+ "controlsenabled",
2721
+ "dispose",
2722
+ "durationchange",
2723
+ "ended",
2724
+ "enterpictureinpicture",
2725
+ "error",
2726
+ "firstplay",
2727
+ "leavepictureinpicture",
2728
+ "loadedmetadata",
2729
+ "loadstart",
2730
+ "pause",
2731
+ "play",
2732
+ "playerreset",
2733
+ "playerresize",
2734
+ "playing",
2735
+ "posterchange",
2736
+ "progress",
2737
+ "ratechange",
2738
+ "ready",
2739
+ "resize",
2740
+ "seeked",
2741
+ "seeking",
2742
+ "textdata",
2743
+ "timeupdate",
2744
+ "useractive",
2745
+ "userinactive",
2746
+ "volumechange",
2747
+ "waiting",
2748
+ ];
2749
+ if (!this.isIOSContext()) {
2750
+ return baseEvents.join(",");
2751
+ }
2752
+ return baseEvents
2753
+ .filter((name) => !name.toLowerCase().includes("fullscreen") &&
2754
+ !name.toLowerCase().includes("fullwindow") &&
2755
+ !name.toLowerCase().includes("airplay"))
2756
+ .join(",");
2757
+ }
2758
+ isIOSDevice() {
2759
+ if (typeof navigator === "undefined")
2760
+ return false;
2761
+ const ua = navigator.userAgent || "";
2762
+ const iOS = /iPad|iPhone|iPod/.test(ua);
2763
+ const iPadOS = navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
2764
+ return iOS || iPadOS;
2765
+ }
2766
+ isIOSContext() {
2767
+ if (this.isIOSDevice()) {
2768
+ return true;
2769
+ }
2770
+ return false;
775
2771
  }
776
2772
  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 }] });
2773
+ 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
2774
  }
779
2775
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ScaleupPlayerComponent, decorators: [{
780
2776
  type: Component,
781
2777
  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
2778
  }], ctorParameters: () => [{ type: i0.ElementRef }, { type: WebplayerProgressService }], propDecorators: { config: [{
783
2779
  type: Input
2780
+ }], ready: [{
2781
+ type: Output
784
2782
  }] } });
785
2783
 
2784
+ class GlobalLoadingService {
2785
+ document;
2786
+ loadingElement;
2787
+ constructor(document) {
2788
+ this.document = document;
2789
+ this.loadingElement = this.document.getElementById('global-loading');
2790
+ }
2791
+ hide() {
2792
+ if (this.loadingElement) {
2793
+ this.loadingElement.style.opacity = '0';
2794
+ setTimeout(() => {
2795
+ if (this.loadingElement) {
2796
+ this.loadingElement.style.display = 'none';
2797
+ }
2798
+ }, 500); // Duração da transição do CSS
2799
+ }
2800
+ }
2801
+ show() {
2802
+ if (this.loadingElement) {
2803
+ this.loadingElement.style.display = 'flex';
2804
+ this.loadingElement.style.opacity = '1';
2805
+ }
2806
+ }
2807
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GlobalLoadingService, deps: [{ token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
2808
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GlobalLoadingService, providedIn: 'root' });
2809
+ }
2810
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GlobalLoadingService, decorators: [{
2811
+ type: Injectable,
2812
+ args: [{
2813
+ providedIn: 'root'
2814
+ }]
2815
+ }], ctorParameters: () => [{ type: Document, decorators: [{
2816
+ type: Inject,
2817
+ args: [DOCUMENT]
2818
+ }] }] });
2819
+
786
2820
  class PlayerLoaderComponent {
2821
+ defaultConfig;
2822
+ globalLoading;
787
2823
  config;
788
- ngOnInit() {
789
- console.log(this.config);
2824
+ playerType = null;
2825
+ constructor(defaultConfig, globalLoading) {
2826
+ this.defaultConfig = defaultConfig;
2827
+ this.globalLoading = globalLoading;
2828
+ this.config = { ...this.defaultConfig, ...this.config };
2829
+ this.playerType = this.getPlayerType(this.config.videoUrl);
2830
+ }
2831
+ getPlayerType(videoUrl) {
2832
+ if (videoUrl?.includes("panda"))
2833
+ return "panda";
2834
+ if (videoUrl?.includes("scaleup"))
2835
+ return "scaleup";
2836
+ if (videoUrl?.includes("vimeo") ||
2837
+ videoUrl?.includes("youtube") ||
2838
+ videoUrl?.includes(".m3u8") ||
2839
+ videoUrl?.includes(".mp4"))
2840
+ return "plyr";
2841
+ return "plyr";
790
2842
  }
791
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlayerLoaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2843
+ onPlayerReady() {
2844
+ this.globalLoading.hide();
2845
+ }
2846
+ 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
2847
  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>
2848
+ <div style="width: 100%; height: 100%;">
2849
+ <div [ngSwitch]="config?.video_host" style="width: 100%; height: 100%;">
2850
+ <app-plyr-player
2851
+ *ngSwitchCase="'hls'"
2852
+ [config]="config"
2853
+ (ready)="onPlayerReady()"
2854
+ ></app-plyr-player>
2855
+ <app-plyr-player
2856
+ *ngSwitchCase="'mp4'"
2857
+ [config]="config"
2858
+ (ready)="onPlayerReady()"
2859
+ ></app-plyr-player>
2860
+ <app-plyr-player
2861
+ *ngSwitchCase="'youtube'"
2862
+ [config]="config"
2863
+ (ready)="onPlayerReady()"
2864
+ ></app-plyr-player>
2865
+ <app-vimeo-player
2866
+ *ngSwitchCase="'vimeo'"
2867
+ [config]="config"
2868
+ (ready)="onPlayerReady()"
2869
+ ></app-vimeo-player>
2870
+ <app-panda-player
2871
+ *ngSwitchCase="'panda'"
2872
+ [config]="config"
2873
+ (ready)="onPlayerReady()"
2874
+ ></app-panda-player>
2875
+ <app-scaleup-player
2876
+ *ngSwitchCase="'scaleup'"
2877
+ [config]="config"
2878
+ (ready)="onPlayerReady()"
2879
+ ></app-scaleup-player>
2880
+ <div *ngSwitchDefault>
2881
+ <p>Player não suportado ou configuração não fornecida.</p>
2882
+ </div>
820
2883
  </div>
821
2884
  </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"] }] });
2885
+ `, 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
2886
  }
824
2887
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlayerLoaderComponent, decorators: [{
825
2888
  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>
2889
+ args: [{ selector: "app-player-loader", template: `
2890
+ <div style="width: 100%; height: 100%;">
2891
+ <div [ngSwitch]="config?.video_host" style="width: 100%; height: 100%;">
2892
+ <app-plyr-player
2893
+ *ngSwitchCase="'hls'"
2894
+ [config]="config"
2895
+ (ready)="onPlayerReady()"
2896
+ ></app-plyr-player>
2897
+ <app-plyr-player
2898
+ *ngSwitchCase="'mp4'"
2899
+ [config]="config"
2900
+ (ready)="onPlayerReady()"
2901
+ ></app-plyr-player>
2902
+ <app-plyr-player
2903
+ *ngSwitchCase="'youtube'"
2904
+ [config]="config"
2905
+ (ready)="onPlayerReady()"
2906
+ ></app-plyr-player>
2907
+ <app-vimeo-player
2908
+ *ngSwitchCase="'vimeo'"
2909
+ [config]="config"
2910
+ (ready)="onPlayerReady()"
2911
+ ></app-vimeo-player>
2912
+ <app-panda-player
2913
+ *ngSwitchCase="'panda'"
2914
+ [config]="config"
2915
+ (ready)="onPlayerReady()"
2916
+ ></app-panda-player>
2917
+ <app-scaleup-player
2918
+ *ngSwitchCase="'scaleup'"
2919
+ [config]="config"
2920
+ (ready)="onPlayerReady()"
2921
+ ></app-scaleup-player>
2922
+ <div *ngSwitchDefault>
2923
+ <p>Player não suportado ou configuração não fornecida.</p>
2924
+ </div>
854
2925
  </div>
855
2926
  </div>
856
2927
  `, standalone: true, imports: [
857
2928
  CommonModule,
858
2929
  PlyrPlayerComponent,
2930
+ VimeoPlayerComponent,
859
2931
  PandaPlayerComponent,
860
2932
  ScaleupPlayerComponent,
861
2933
  ], styles: [":host{display:block;width:100%;height:100%}\n"] }]
862
- }], propDecorators: { config: [{
2934
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
2935
+ type: Inject,
2936
+ args: [WEPLAYER_CONFIG]
2937
+ }] }, { type: GlobalLoadingService }], propDecorators: { config: [{
863
2938
  type: Input
864
2939
  }] } });
865
2940
 
@@ -887,7 +2962,7 @@ class WebplayerComponent {
887
2962
  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
2963
  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
2964
  <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"] }] });
2965
+ `, 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
2966
  }
892
2967
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WebplayerComponent, decorators: [{
893
2968
  type: Component,