@edm-webplayer/webplayer-angular 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,974 @@
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';
4
+ import { DOCUMENT, CommonModule } from '@angular/common';
5
+ import { BehaviorSubject, Subject, Subscription } from 'rxjs';
6
+ import Plyr from 'plyr';
7
+ import 'plyr/dist/plyr.css';
8
+ import Hls from 'hls.js';
9
+
10
+ const WEPLAYER_CONFIG = new InjectionToken('webplayer.config');
11
+
12
+ class WebplayerConfigService {
13
+ configSubject;
14
+ get config$() {
15
+ return this.configSubject.asObservable();
16
+ }
17
+ constructor(providedConfig) {
18
+ this.configSubject = new BehaviorSubject(providedConfig);
19
+ }
20
+ setConfig(config) {
21
+ const currentConfig = this.configSubject.getValue();
22
+ const newConfig = { ...(currentConfig || {}), ...config };
23
+ this.configSubject.next(newConfig);
24
+ }
25
+ getConfig() {
26
+ return this.configSubject.getValue();
27
+ }
28
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WebplayerConfigService, deps: [{ token: WEPLAYER_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
29
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WebplayerConfigService, providedIn: 'root' });
30
+ }
31
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WebplayerConfigService, decorators: [{
32
+ type: Injectable,
33
+ args: [{
34
+ providedIn: 'root'
35
+ }]
36
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
37
+ type: Optional
38
+ }, {
39
+ type: Inject,
40
+ args: [WEPLAYER_CONFIG]
41
+ }] }] });
42
+
43
+ class WebplayerProgressService {
44
+ progress$ = new Subject();
45
+ ended$ = new Subject();
46
+ key(videoKey) { return `webplayer:${videoKey}`; }
47
+ saveProgress(videoKey, time) {
48
+ try {
49
+ localStorage.setItem(this.key(videoKey), String(Math.floor(time)));
50
+ }
51
+ catch { }
52
+ }
53
+ getProgress(videoKey) {
54
+ try {
55
+ return Number(localStorage.getItem(this.key(videoKey)) || 0);
56
+ }
57
+ catch {
58
+ return 0;
59
+ }
60
+ }
61
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WebplayerProgressService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
62
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WebplayerProgressService, providedIn: 'root' });
63
+ }
64
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WebplayerProgressService, decorators: [{
65
+ type: Injectable,
66
+ args: [{ providedIn: 'root' }]
67
+ }] });
68
+
69
+ const PlyrPlayer = Plyr;
70
+ class PlyrPlayerComponent {
71
+ progressService;
72
+ document;
73
+ videoRef;
74
+ config;
75
+ plyr;
76
+ hls;
77
+ constructor(progressService, document) {
78
+ this.progressService = progressService;
79
+ this.document = document;
80
+ }
81
+ ngOnInit() {
82
+ console.log(this.config);
83
+ if (this.config?.ui?.colors) {
84
+ this.applyTheme(this.config.ui.colors);
85
+ }
86
+ }
87
+ ngAfterViewInit() {
88
+ this.setupPlayer();
89
+ }
90
+ ngOnChanges(changes) {
91
+ if (changes["config"] && this.plyr) {
92
+ this.setupPlayer();
93
+ }
94
+ }
95
+ applyTheme(colors) {
96
+ const STYLE_ID = 'webplayer-theme-vars';
97
+ let styleEl = this.document.getElementById(STYLE_ID);
98
+ if (!styleEl) {
99
+ styleEl = this.document.createElement('style');
100
+ styleEl.id = STYLE_ID;
101
+ this.document.head.appendChild(styleEl);
102
+ }
103
+ const rootStyle = this.document.documentElement.style;
104
+ const allPairs = [];
105
+ for (const key in colors) {
106
+ if (Object.prototype.hasOwnProperty.call(colors, key)) {
107
+ const cssVarName = `--theme-${key.replace('progresss-', 'progress-')}`;
108
+ const cssVarValue = String(colors[key]);
109
+ allPairs.push([cssVarName, cssVarValue]);
110
+ try {
111
+ rootStyle.setProperty(cssVarName, cssVarValue);
112
+ }
113
+ catch { }
114
+ }
115
+ }
116
+ const cssRule = `:root{
117
+ ${allPairs.map(([n, v]) => ` ${n}: ${v};`).join('\n')}
118
+ }`;
119
+ styleEl.textContent = cssRule;
120
+ }
121
+ setupPlayer() {
122
+ if (!this.videoRef)
123
+ 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 },
143
+ });
144
+ this.bindProgressEvents();
145
+ this.loadSource(this.videoRef.nativeElement).then(() => this.setupStartTime());
146
+ }
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);
156
+ }
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();
171
+ }
172
+ };
173
+ this.plyr?.on?.("timeupdate", onTimeUpdate);
174
+ this.plyr?.on?.("pause", emitProgress);
175
+ this.plyr?.on?.("ended", () => {
176
+ emitProgress();
177
+ this.progressService.ended$.next();
178
+ });
179
+ }
180
+ unmuteAfterDelay(video, delayMs = 2000, volume = 1) {
181
+ if (!this.config.autoPlay)
182
+ return;
183
+ setTimeout(() => {
184
+ try {
185
+ if (this.plyr) {
186
+ this.plyr.muted = false;
187
+ this.plyr.volume = volume;
188
+ }
189
+ }
190
+ catch { }
191
+ try {
192
+ if (video) {
193
+ video.muted = false;
194
+ if (typeof video.volume === "number")
195
+ video.volume = volume;
196
+ }
197
+ }
198
+ catch { }
199
+ }, delayMs);
200
+ }
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);
228
+ };
229
+ return new Promise((resolve) => {
230
+ if (!this.config) {
231
+ return resolve();
232
+ }
233
+ if (this.hls) {
234
+ this.hls.destroy();
235
+ this.hls = undefined;
236
+ }
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);
258
+ }
259
+ if (this.config.video_host === "youtube") {
260
+ this.plyr.source = {
261
+ type: "video",
262
+ sources: [{ src: this.config.video_id, provider: "youtube" }],
263
+ };
264
+ }
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
+ };
270
+ }
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
+ };
276
+ }
277
+ else if (this.config.video_host === "hls") {
278
+ let sourceX = "";
279
+ 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
+ });
366
+ }
367
+ else {
368
+ this.plyr.source = { type: "video", sources: [{ src: sourceX }] };
369
+ }
370
+ }
371
+ resolve();
372
+ });
373
+ }
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);
388
+ }
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);
400
+ }
401
+ return 0;
402
+ }
403
+ ngOnDestroy() {
404
+ this.hls?.destroy();
405
+ this.plyr?.destroy();
406
+ }
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-progress-view-on, #19a6d9);--plyr-color-hover: var(--theme-progress-view-on, #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 }] });
409
+ }
410
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlyrPlayerComponent, decorators: [{
411
+ 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-progress-view-on, #19a6d9);--plyr-color-hover: var(--theme-progress-view-on, #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"] }]
413
+ }], ctorParameters: () => [{ type: WebplayerProgressService }, { type: Document, decorators: [{
414
+ type: Inject,
415
+ args: [DOCUMENT]
416
+ }] }], propDecorators: { videoRef: [{
417
+ type: ViewChild,
418
+ args: ["video", { static: true }]
419
+ }], config: [{
420
+ type: Input
421
+ }] } });
422
+
423
+ class PandaPlayerComponent {
424
+ elementRef;
425
+ progressService;
426
+ config;
427
+ pandaPlayer;
428
+ pandaContainer;
429
+ pandaProgressThrottle = 0;
430
+ primaryColor;
431
+ controlsColor;
432
+ constructor(elementRef, progressService) {
433
+ this.elementRef = elementRef;
434
+ this.progressService = progressService;
435
+ }
436
+ ngOnInit() {
437
+ if (this.config?.video_host !== 'panda') {
438
+ return;
439
+ }
440
+ this.applyTheme();
441
+ this.loadPandaPlayer();
442
+ }
443
+ ngOnChanges(changes) {
444
+ if (changes['config'] && this.pandaPlayer) {
445
+ this.loadPandaPlayer();
446
+ }
447
+ }
448
+ applyTheme() {
449
+ if (this.config?.ui?.colors) {
450
+ this.primaryColor = this.config.ui.colors['progress-view-off'];
451
+ this.controlsColor = this.config.ui.colors['text-color'];
452
+ }
453
+ }
454
+ loadPandaPlayer() {
455
+ if (!this.config.embed) {
456
+ return;
457
+ }
458
+ this.ensurePandaScript().then(() => {
459
+ const host = this.elementRef.nativeElement;
460
+ if (!this.pandaContainer) {
461
+ this.pandaContainer = document.createElement("div");
462
+ this.pandaContainer.style.width = "100%";
463
+ this.pandaContainer.style.height = "100%";
464
+ this.pandaContainer.id = `panda-${this.config.video_id || Math.random().toString(36).slice(2)}`;
465
+ host.appendChild(this.pandaContainer);
466
+ }
467
+ try {
468
+ this.pandaPlayer?.destroy?.();
469
+ }
470
+ 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);
473
+ window.pandascripttag = window.pandascripttag || [];
474
+ window.pandascripttag.push(() => {
475
+ this.pandaPlayer = new window.PandaPlayer(this.pandaContainer.id, {
476
+ video_external_id: videoId,
477
+ library_id: libraryId,
478
+ onReady: () => {
479
+ if (this.primaryColor) {
480
+ try {
481
+ this.pandaPlayer.setColor('primary', this.primaryColor);
482
+ }
483
+ catch { }
484
+ }
485
+ if (this.controlsColor) {
486
+ try {
487
+ this.pandaPlayer.setColor('secondary', this.controlsColor);
488
+ }
489
+ catch { }
490
+ }
491
+ const dur = this.pandaPlayer?.getDuration?.() || 0;
492
+ const start = this.resolveStartTime(this.config.last_time, this.config.last_percent, dur);
493
+ if (start > 0) {
494
+ try {
495
+ this.pandaPlayer.setCurrentTime(start);
496
+ }
497
+ catch { }
498
+ }
499
+ if (this.config.autoPlay) {
500
+ try {
501
+ this.pandaPlayer.play();
502
+ }
503
+ catch { }
504
+ }
505
+ },
506
+ playerConfigs: {
507
+ autoplay: !!this.config.autoPlay,
508
+ smartAutoplay: !!this.config.autoPlay,
509
+ },
510
+ });
511
+ this.pandaPlayer.onEvent((event) => {
512
+ this.handlePandaEvent(event);
513
+ });
514
+ });
515
+ });
516
+ }
517
+ ngOnDestroy() {
518
+ try {
519
+ this.pandaPlayer?.destroy?.();
520
+ }
521
+ catch { }
522
+ try {
523
+ this.pandaContainer?.remove?.();
524
+ }
525
+ catch { }
526
+ }
527
+ async ensurePandaScript() {
528
+ return new Promise((resolve) => {
529
+ if (window.PandaPlayer)
530
+ return resolve();
531
+ const existing = document.querySelector('script[src^="https://player.pandavideo.com.br/api.v2.js"]');
532
+ if (existing) {
533
+ existing.addEventListener("load", () => resolve());
534
+ if (existing.readyState === "complete")
535
+ resolve();
536
+ return;
537
+ }
538
+ const script = document.createElement("script");
539
+ script.src = "https://player.pandavideo.com.br/api.v2.js";
540
+ script.async = true;
541
+ script.onload = () => resolve();
542
+ document.head.appendChild(script);
543
+ });
544
+ }
545
+ extractPandaVideoId(url) {
546
+ let u;
547
+ try {
548
+ u = new URL(url);
549
+ }
550
+ catch {
551
+ try {
552
+ u = new URL(decodeURIComponent(url));
553
+ }
554
+ catch {
555
+ return undefined;
556
+ }
557
+ }
558
+ try {
559
+ const v = u.searchParams.get("v") || undefined;
560
+ return v || undefined;
561
+ }
562
+ catch {
563
+ return undefined;
564
+ }
565
+ }
566
+ extractPandaLibraryId(url) {
567
+ let u;
568
+ try {
569
+ u = new URL(url);
570
+ }
571
+ catch {
572
+ try {
573
+ u = new URL(decodeURIComponent(url));
574
+ }
575
+ catch {
576
+ return undefined;
577
+ }
578
+ }
579
+ try {
580
+ const { hostname } = u;
581
+ const firstLabel = hostname.split(".")[0] || "";
582
+ const maybeLib = firstLabel.startsWith("player-")
583
+ ? firstLabel.slice("player-".length)
584
+ : firstLabel;
585
+ return maybeLib || undefined;
586
+ }
587
+ catch {
588
+ return undefined;
589
+ }
590
+ }
591
+ handlePandaEvent(evt) {
592
+ const type = evt?.message || evt?.type;
593
+ const THROTTLE_MS = 300;
594
+ const now = performance.now();
595
+ if (type === "panda_timeupdate") {
596
+ if (now - this.pandaProgressThrottle >= THROTTLE_MS) {
597
+ this.pandaProgressThrottle = now;
598
+ try {
599
+ const t = this.pandaPlayer?.getCurrentTime?.() || 0;
600
+ const d = this.pandaPlayer?.getDuration?.() || 0;
601
+ const p = d > 0 ? +((t / d) * 100).toFixed(2) : 0;
602
+ this.progressService.progress$.next({ currentTime: t, duration: d, percent: p });
603
+ }
604
+ catch { }
605
+ }
606
+ }
607
+ if (type === "panda_pause") {
608
+ try {
609
+ const t = this.pandaPlayer?.getCurrentTime?.() || 0;
610
+ const d = this.pandaPlayer?.getDuration?.() || 0;
611
+ const p = d > 0 ? +((t / d) * 100).toFixed(2) : 0;
612
+ this.progressService.progress$.next({ currentTime: t, duration: d, percent: p });
613
+ }
614
+ catch { }
615
+ }
616
+ 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 });
622
+ }
623
+ catch { }
624
+ this.progressService.ended$.next();
625
+ }
626
+ }
627
+ resolveStartTime(last_time, last_percent, duration, opts = {}) {
628
+ const minStartSec = opts.minStartSec ?? 3;
629
+ const endGuardSec = opts.endGuardSec ?? 2;
630
+ const dur = Number(duration) || 0;
631
+ if (dur <= 0)
632
+ return 0;
633
+ const hasTime = last_time !== undefined && last_time !== null && last_time !== "";
634
+ if (hasTime) {
635
+ let t = Number(last_time);
636
+ if (!isFinite(t) || t < 0)
637
+ t = 0;
638
+ if (t < minStartSec)
639
+ return 0;
640
+ return Math.min(dur - endGuardSec, t);
641
+ }
642
+ const hasPercent = last_percent !== undefined &&
643
+ last_percent !== null &&
644
+ last_percent !== "";
645
+ if (hasPercent) {
646
+ let p = Number(last_percent);
647
+ if (!isFinite(p) || p < 0)
648
+ p = 0;
649
+ if (p > 1)
650
+ p = p / 100;
651
+ let t = dur * p;
652
+ if (t < minStartSec)
653
+ return 0;
654
+ return Math.min(dur - endGuardSec, t);
655
+ }
656
+ return 0;
657
+ }
658
+ 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 }] });
660
+ }
661
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PandaPlayerComponent, decorators: [{
662
+ type: Component,
663
+ 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
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: WebplayerProgressService }], propDecorators: { config: [{
665
+ type: Input
666
+ }] } });
667
+
668
+ class ScaleupPlayerComponent {
669
+ elementRef;
670
+ progressService;
671
+ config;
672
+ scaleupIframe;
673
+ scaleupMessageHandler;
674
+ scaleupProgressThrottle = 0;
675
+ constructor(elementRef, progressService) {
676
+ this.elementRef = elementRef;
677
+ this.progressService = progressService;
678
+ }
679
+ ngOnInit() {
680
+ if (this.config?.video_host !== 'scaleup') {
681
+ return;
682
+ }
683
+ this.loadScaleupPlayer();
684
+ }
685
+ ngOnChanges(changes) {
686
+ if (changes['config'] && this.scaleupIframe) {
687
+ this.loadScaleupPlayer();
688
+ }
689
+ }
690
+ loadScaleupPlayer() {
691
+ const videoId = this.config.video_id;
692
+ 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}`;
695
+ const host = this.elementRef.nativeElement;
696
+ if (this.scaleupIframe) {
697
+ this.scaleupIframe.remove();
698
+ }
699
+ const iframe = document.createElement("iframe");
700
+ iframe.src = scaleupSrc;
701
+ iframe.allow = "autoplay; fullscreen; encrypted-media; picture-in-picture";
702
+ iframe.referrerPolicy = "origin-when-cross-origin";
703
+ iframe.frameBorder = "0";
704
+ iframe.style.width = "100%";
705
+ iframe.style.height = "100%";
706
+ iframe.style.position = "absolute";
707
+ iframe.style.top = "0";
708
+ iframe.style.left = "0";
709
+ host.appendChild(iframe);
710
+ this.scaleupIframe = iframe;
711
+ if (this.scaleupMessageHandler) {
712
+ window.removeEventListener("message", this.scaleupMessageHandler);
713
+ }
714
+ this.scaleupMessageHandler = (e) => {
715
+ try {
716
+ const originOk = typeof e.origin === "string" &&
717
+ e.origin.includes("scaleup.com.br");
718
+ if (!originOk)
719
+ return;
720
+ const data = typeof e.data === "string"
721
+ ? (() => {
722
+ try {
723
+ return JSON.parse(e.data);
724
+ }
725
+ catch {
726
+ return undefined;
727
+ }
728
+ })()
729
+ : e.data;
730
+ if (!data || typeof data !== "object")
731
+ return;
732
+ const type = (data.event ||
733
+ data.type ||
734
+ data.message ||
735
+ data.eventName ||
736
+ "")
737
+ .toString()
738
+ .toLowerCase();
739
+ if (!type)
740
+ return;
741
+ const t = Number((data.currentTime ?? data.time ?? data.position) || 0);
742
+ const d = Number((data.duration ?? data.totalDuration) || 0);
743
+ const p = d > 0 ? +((t / d) * 100).toFixed(2) : 0;
744
+ const THROTTLE_MS = 300;
745
+ const now = performance.now();
746
+ if (type.includes("timeupdate") ||
747
+ type === "playing" ||
748
+ type === "progress") {
749
+ if (now - this.scaleupProgressThrottle >= THROTTLE_MS) {
750
+ this.scaleupProgressThrottle = now;
751
+ this.progressService.progress$.next({ currentTime: t, duration: d, percent: p });
752
+ }
753
+ }
754
+ if (type === "pause" || type === "paused") {
755
+ this.progressService.progress$.next({ currentTime: t, duration: d, percent: p });
756
+ }
757
+ if (type === "ended" || type === "end" || type === "complete") {
758
+ this.progressService.progress$.next({ currentTime: t, duration: d, percent: p });
759
+ this.progressService.ended$.next();
760
+ }
761
+ }
762
+ catch { }
763
+ };
764
+ window.addEventListener("message", this.scaleupMessageHandler);
765
+ }
766
+ ngOnDestroy() {
767
+ try {
768
+ this.scaleupIframe?.remove();
769
+ }
770
+ catch { }
771
+ if (this.scaleupMessageHandler) {
772
+ window.removeEventListener("message", this.scaleupMessageHandler);
773
+ this.scaleupMessageHandler = undefined;
774
+ }
775
+ }
776
+ 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 }] });
778
+ }
779
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ScaleupPlayerComponent, decorators: [{
780
+ type: Component,
781
+ 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
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: WebplayerProgressService }], propDecorators: { config: [{
783
+ type: Input
784
+ }] } });
785
+
786
+ class PlayerLoaderComponent {
787
+ config;
788
+ ngOnInit() {
789
+ console.log(this.config);
790
+ }
791
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlayerLoaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
792
+ 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>
820
+ </div>
821
+ </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"] }] });
823
+ }
824
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlayerLoaderComponent, decorators: [{
825
+ 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>
854
+ </div>
855
+ </div>
856
+ `, standalone: true, imports: [
857
+ CommonModule,
858
+ PlyrPlayerComponent,
859
+ PandaPlayerComponent,
860
+ ScaleupPlayerComponent,
861
+ ], styles: [":host{display:block;width:100%;height:100%}\n"] }]
862
+ }], propDecorators: { config: [{
863
+ type: Input
864
+ }] } });
865
+
866
+ class WebplayerComponent {
867
+ configService;
868
+ progressService;
869
+ progress = new EventEmitter();
870
+ ended = new EventEmitter();
871
+ config = null;
872
+ subscriptions = new Subscription();
873
+ constructor(configService, progressService) {
874
+ this.configService = configService;
875
+ this.progressService = progressService;
876
+ }
877
+ ngOnInit() {
878
+ this.subscriptions.add(this.configService.config$.subscribe(config => {
879
+ this.config = config;
880
+ }));
881
+ this.subscriptions.add(this.progressService.progress$.subscribe(p => this.progress.emit(p)));
882
+ this.subscriptions.add(this.progressService.ended$.subscribe(() => this.ended.emit()));
883
+ }
884
+ ngOnDestroy() {
885
+ this.subscriptions.unsubscribe();
886
+ }
887
+ 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
+ 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
+ <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"] }] });
891
+ }
892
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WebplayerComponent, decorators: [{
893
+ type: Component,
894
+ args: [{ selector: 'edm-webplayer', template: `
895
+ <app-player-loader [config]="config" *ngIf="config"></app-player-loader>
896
+ `, standalone: true, imports: [CommonModule, PlayerLoaderComponent], styles: [":host{display:block;width:100%;height:100%}\n"] }]
897
+ }], ctorParameters: () => [{ type: WebplayerConfigService }, { type: WebplayerProgressService }], propDecorators: { progress: [{
898
+ type: Output,
899
+ args: ['playProgress']
900
+ }], ended: [{
901
+ type: Output,
902
+ args: ['playEnded']
903
+ }] } });
904
+
905
+ class TimeFormatPipe {
906
+ transform(seconds) {
907
+ const val = typeof seconds === 'number' && isFinite(seconds) ? seconds : 0;
908
+ const sec = Math.floor(val % 60).toString().padStart(2, '0');
909
+ const min = Math.floor(val / 60).toString().padStart(2, '0');
910
+ const hrs = Math.floor(val / 3600);
911
+ if (hrs > 0) {
912
+ const mm = Math.floor((val % 3600) / 60).toString().padStart(2, '0');
913
+ return `${hrs}:${mm}:${sec}`;
914
+ }
915
+ return `${min}:${sec}`;
916
+ }
917
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TimeFormatPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
918
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: TimeFormatPipe, isStandalone: true, name: "timeFormat" });
919
+ }
920
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TimeFormatPipe, decorators: [{
921
+ type: Pipe,
922
+ args: [{ name: 'timeFormat', standalone: true }]
923
+ }] });
924
+
925
+ class WebplayerModule {
926
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WebplayerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
927
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: WebplayerModule, imports: [CommonModule,
928
+ WebplayerComponent,
929
+ PlayerLoaderComponent,
930
+ PlyrPlayerComponent,
931
+ PandaPlayerComponent,
932
+ ScaleupPlayerComponent,
933
+ TimeFormatPipe], exports: [WebplayerComponent,
934
+ PlayerLoaderComponent,
935
+ PlyrPlayerComponent,
936
+ PandaPlayerComponent,
937
+ ScaleupPlayerComponent,
938
+ TimeFormatPipe] });
939
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WebplayerModule, imports: [CommonModule,
940
+ WebplayerComponent,
941
+ PlayerLoaderComponent,
942
+ PlyrPlayerComponent,
943
+ PandaPlayerComponent,
944
+ ScaleupPlayerComponent] });
945
+ }
946
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: WebplayerModule, decorators: [{
947
+ type: NgModule,
948
+ args: [{
949
+ imports: [
950
+ CommonModule,
951
+ WebplayerComponent,
952
+ PlayerLoaderComponent,
953
+ PlyrPlayerComponent,
954
+ PandaPlayerComponent,
955
+ ScaleupPlayerComponent,
956
+ TimeFormatPipe,
957
+ ],
958
+ exports: [
959
+ WebplayerComponent,
960
+ PlayerLoaderComponent,
961
+ PlyrPlayerComponent,
962
+ PandaPlayerComponent,
963
+ ScaleupPlayerComponent,
964
+ TimeFormatPipe,
965
+ ]
966
+ }]
967
+ }] });
968
+
969
+ /**
970
+ * Generated bundle index. Do not edit.
971
+ */
972
+
973
+ export { PandaPlayerComponent, PlayerLoaderComponent, PlyrPlayerComponent, ScaleupPlayerComponent, TimeFormatPipe, WEPLAYER_CONFIG, WebplayerComponent, WebplayerConfigService, WebplayerModule, WebplayerProgressService };
974
+ //# sourceMappingURL=edm-webplayer-webplayer-angular.mjs.map