@smartimpact-it/modern-video-embed 2.0.7 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/BaseVideoEmbed.d.ts +22 -5
- package/dist/components/BaseVideoEmbed.d.ts.map +1 -1
- package/dist/components/BaseVideoEmbed.js +51 -0
- package/dist/components/BaseVideoEmbed.js.map +1 -1
- package/dist/components/VideoEmbed.d.ts +6 -0
- package/dist/components/VideoEmbed.d.ts.map +1 -1
- package/dist/components/VideoEmbed.js +26 -37
- package/dist/components/VideoEmbed.js.map +1 -1
- package/dist/components/VimeoEmbed.d.ts +7 -5
- package/dist/components/VimeoEmbed.d.ts.map +1 -1
- package/dist/components/VimeoEmbed.js +34 -92
- package/dist/components/VimeoEmbed.js.map +1 -1
- package/dist/components/VimeoEmbed.min.js +1 -1
- package/dist/components/YouTubeEmbed.d.ts +7 -6
- package/dist/components/YouTubeEmbed.d.ts.map +1 -1
- package/dist/components/YouTubeEmbed.js +40 -101
- package/dist/components/YouTubeEmbed.js.map +1 -1
- package/dist/components/YouTubeEmbed.min.js +1 -1
- package/dist/utils/APILoader.d.ts +55 -0
- package/dist/utils/APILoader.d.ts.map +1 -0
- package/dist/utils/APILoader.js +148 -0
- package/dist/utils/APILoader.js.map +1 -0
- package/package.json +1 -1
- package/src/components/BaseVideoEmbed.ts +75 -6
- package/src/components/VideoEmbed.ts +31 -40
- package/src/components/VimeoEmbed.ts +40 -109
- package/src/components/YouTubeEmbed.ts +46 -122
- package/src/utils/APILoader.ts +192 -0
|
@@ -37,6 +37,12 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
37
37
|
|
|
38
38
|
this.log(`Attribute changed - ${name}: ${oldValue} -> ${newValue}`);
|
|
39
39
|
|
|
40
|
+
// Try common attribute handling first
|
|
41
|
+
if (this.handleCommonAttributeChange(name, oldValue, newValue)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Handle VideoEmbed-specific attributes
|
|
40
46
|
switch (name) {
|
|
41
47
|
case "url":
|
|
42
48
|
this.#url = newValue || "";
|
|
@@ -44,44 +50,6 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
44
50
|
this.reinitializePlayer();
|
|
45
51
|
}
|
|
46
52
|
break;
|
|
47
|
-
case "autoplay":
|
|
48
|
-
this._autoplay = newValue !== null;
|
|
49
|
-
if (
|
|
50
|
-
!this._playing &&
|
|
51
|
-
this._autoplay &&
|
|
52
|
-
!this._lazy &&
|
|
53
|
-
this.initialized
|
|
54
|
-
) {
|
|
55
|
-
this.play();
|
|
56
|
-
}
|
|
57
|
-
break;
|
|
58
|
-
case "controls":
|
|
59
|
-
this._controls = newValue !== null;
|
|
60
|
-
if (this.video) {
|
|
61
|
-
this.video.controls = this._controls;
|
|
62
|
-
}
|
|
63
|
-
break;
|
|
64
|
-
case "lazy":
|
|
65
|
-
this._lazy = newValue !== null;
|
|
66
|
-
break;
|
|
67
|
-
case "muted":
|
|
68
|
-
this._muted = newValue !== null;
|
|
69
|
-
if (this.video) {
|
|
70
|
-
this.video.muted = this._muted;
|
|
71
|
-
}
|
|
72
|
-
break;
|
|
73
|
-
case "poster":
|
|
74
|
-
this._poster = newValue || "";
|
|
75
|
-
if (this._lazy && !this.video) {
|
|
76
|
-
this.showPoster();
|
|
77
|
-
} else if (this.video) {
|
|
78
|
-
this.video.poster = this._poster;
|
|
79
|
-
}
|
|
80
|
-
break;
|
|
81
|
-
case "background":
|
|
82
|
-
this._background = newValue !== null;
|
|
83
|
-
this.updateBackgroundMode();
|
|
84
|
-
break;
|
|
85
53
|
case "loop":
|
|
86
54
|
this.#loop = newValue !== null;
|
|
87
55
|
if (this.video) {
|
|
@@ -499,6 +467,29 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
499
467
|
}
|
|
500
468
|
}
|
|
501
469
|
|
|
470
|
+
/**
|
|
471
|
+
* Sync methods for common attributes (required by BaseVideoEmbed)
|
|
472
|
+
*/
|
|
473
|
+
protected syncMutedState(): void {
|
|
474
|
+
if (this.video) {
|
|
475
|
+
this.video.muted = this._muted;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
protected syncControlsState(): void {
|
|
480
|
+
if (this.video) {
|
|
481
|
+
this.video.controls = this._controls;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
protected syncPosterState(): void {
|
|
486
|
+
if (this._lazy && !this.video) {
|
|
487
|
+
this.showPoster();
|
|
488
|
+
} else if (this.video) {
|
|
489
|
+
this.video.poster = this._poster;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
502
493
|
private setupVideoEvents() {
|
|
503
494
|
if (!this.video) return;
|
|
504
495
|
|
|
@@ -675,7 +666,7 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
675
666
|
}
|
|
676
667
|
|
|
677
668
|
// Public API methods
|
|
678
|
-
public async play(): Promise<void> {
|
|
669
|
+
public override async play(): Promise<void> {
|
|
679
670
|
if (this._lazy && !this.video) {
|
|
680
671
|
this.log("Lazy video needs to be loaded first. Initializing...");
|
|
681
672
|
this.setAttribute("data-should-autoplay", "true");
|
|
@@ -699,7 +690,7 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
699
690
|
}
|
|
700
691
|
}
|
|
701
692
|
|
|
702
|
-
public pause(): void {
|
|
693
|
+
public override pause(): void {
|
|
703
694
|
if (this.video) {
|
|
704
695
|
this.video.pause();
|
|
705
696
|
this.log("Video paused.");
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { BaseVideoEmbed } from "./BaseVideoEmbed.js";
|
|
2
|
+
import { APILoader } from "../utils/APILoader.js";
|
|
2
3
|
|
|
3
4
|
export class VimeoEmbed extends BaseVideoEmbed {
|
|
4
5
|
private iframe: HTMLIFrameElement | null = null;
|
|
5
6
|
private player: any = null; // Vimeo Player instance
|
|
6
|
-
private static apiLoaded = false;
|
|
7
|
-
private static apiReady = false;
|
|
8
7
|
private static readonly MAX_API_RETRIES = 3;
|
|
9
8
|
private static readonly API_RETRY_DELAY = 2000;
|
|
10
9
|
private static readonly API_LOAD_TIMEOUT = 10000;
|
|
11
10
|
private updatingMutedState = false;
|
|
12
|
-
private apiLoadRetries = 0;
|
|
13
11
|
|
|
14
12
|
/** The full Vimeo URL (e.g., "https://vimeo.com/...") */
|
|
15
13
|
#url: string = "";
|
|
@@ -46,6 +44,12 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
46
44
|
`VimeoEmbed: Attribute changed - ${name}: ${oldValue} -> ${newValue}`,
|
|
47
45
|
);
|
|
48
46
|
|
|
47
|
+
// Try common attribute handling first
|
|
48
|
+
if (this.handleCommonAttributeChange(name, oldValue, newValue)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Handle Vimeo-specific attributes
|
|
49
53
|
switch (name) {
|
|
50
54
|
case "url":
|
|
51
55
|
this.#url = newValue || "";
|
|
@@ -60,42 +64,6 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
60
64
|
this.reinitializePlayer();
|
|
61
65
|
}
|
|
62
66
|
break;
|
|
63
|
-
case "autoplay":
|
|
64
|
-
this._autoplay = newValue !== null;
|
|
65
|
-
if (
|
|
66
|
-
!this._playing &&
|
|
67
|
-
this._autoplay &&
|
|
68
|
-
!this._lazy &&
|
|
69
|
-
this.initialized
|
|
70
|
-
) {
|
|
71
|
-
this.play();
|
|
72
|
-
}
|
|
73
|
-
break;
|
|
74
|
-
case "controls":
|
|
75
|
-
this._controls = newValue !== null;
|
|
76
|
-
if (this.initialized) {
|
|
77
|
-
this.reinitializePlayer();
|
|
78
|
-
}
|
|
79
|
-
break;
|
|
80
|
-
case "lazy":
|
|
81
|
-
this._lazy = newValue !== null;
|
|
82
|
-
break;
|
|
83
|
-
case "muted":
|
|
84
|
-
this._muted = newValue !== null;
|
|
85
|
-
if (this.player && this.playerReady) {
|
|
86
|
-
this.player.setVolume(this._muted ? 0 : 1);
|
|
87
|
-
}
|
|
88
|
-
break;
|
|
89
|
-
case "poster":
|
|
90
|
-
this._poster = newValue || "";
|
|
91
|
-
if (this._lazy && !this.player) {
|
|
92
|
-
this.showPoster(this.#videoId);
|
|
93
|
-
}
|
|
94
|
-
break;
|
|
95
|
-
case "background":
|
|
96
|
-
this._background = newValue !== null;
|
|
97
|
-
this.updateBackgroundMode();
|
|
98
|
-
break;
|
|
99
67
|
case "player-vars":
|
|
100
68
|
try {
|
|
101
69
|
this.#playerVars = newValue ? JSON.parse(newValue) : {};
|
|
@@ -275,7 +243,6 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
275
243
|
}
|
|
276
244
|
|
|
277
245
|
protected override handleRetry(): void {
|
|
278
|
-
this.apiLoadRetries = 0;
|
|
279
246
|
if (this.#videoId) {
|
|
280
247
|
this.initializePlayer(this.#videoId);
|
|
281
248
|
}
|
|
@@ -296,6 +263,27 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
296
263
|
}
|
|
297
264
|
}
|
|
298
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Sync methods for common attributes (required by BaseVideoEmbed)
|
|
268
|
+
*/
|
|
269
|
+
protected syncMutedState(): void {
|
|
270
|
+
if (this.player && this.playerReady) {
|
|
271
|
+
this.player.setVolume(this._muted ? 0 : 1);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
protected syncControlsState(): void {
|
|
276
|
+
if (this.initialized) {
|
|
277
|
+
this.reinitializePlayer();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
protected syncPosterState(): void {
|
|
282
|
+
if (this._lazy && !this.player) {
|
|
283
|
+
this.showPoster(this.#videoId);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
299
287
|
private isValidPosterUrl(url: string): boolean {
|
|
300
288
|
if (!url) return false;
|
|
301
289
|
try {
|
|
@@ -406,7 +394,6 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
406
394
|
const retryButton = errorDiv.querySelector(".retry-button");
|
|
407
395
|
if (retryButton) {
|
|
408
396
|
retryButton.addEventListener("click", () => {
|
|
409
|
-
this.apiLoadRetries = 0;
|
|
410
397
|
errorDiv.remove();
|
|
411
398
|
if (this.#videoId) {
|
|
412
399
|
this.initializePlayer(this.#videoId);
|
|
@@ -680,7 +667,7 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
680
667
|
}
|
|
681
668
|
|
|
682
669
|
try {
|
|
683
|
-
await VimeoEmbed.loadVimeoAPIWithRetry(
|
|
670
|
+
await VimeoEmbed.loadVimeoAPIWithRetry();
|
|
684
671
|
|
|
685
672
|
// Check if component was disconnected during API load
|
|
686
673
|
if (!this.isConnected || !iframe.isConnected) {
|
|
@@ -773,73 +760,17 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
773
760
|
}
|
|
774
761
|
}
|
|
775
762
|
|
|
776
|
-
private static async loadVimeoAPI(): Promise<void> {
|
|
777
|
-
return new Promise((resolve, reject) => {
|
|
778
|
-
if (this.apiReady) {
|
|
779
|
-
resolve();
|
|
780
|
-
return;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
if (!this.apiLoaded) {
|
|
784
|
-
const script = document.createElement("script");
|
|
785
|
-
script.src = "https://player.vimeo.com/api/player.js";
|
|
786
|
-
script.onload = () => {
|
|
787
|
-
this.apiReady = true;
|
|
788
|
-
this.apiLoaded = true;
|
|
789
|
-
resolve();
|
|
790
|
-
};
|
|
791
|
-
script.onerror = () => {
|
|
792
|
-
reject(
|
|
793
|
-
new Error(
|
|
794
|
-
"Failed to load Vimeo API script. Please check your network connection or disable ad blockers.",
|
|
795
|
-
),
|
|
796
|
-
);
|
|
797
|
-
};
|
|
798
|
-
document.head.appendChild(script);
|
|
799
|
-
this.apiLoaded = true;
|
|
800
|
-
} else {
|
|
801
|
-
// Wait for API to be ready
|
|
802
|
-
const checkReady = setInterval(() => {
|
|
803
|
-
// @ts-ignore
|
|
804
|
-
if (typeof Vimeo !== "undefined") {
|
|
805
|
-
this.apiReady = true;
|
|
806
|
-
clearInterval(checkReady);
|
|
807
|
-
resolve();
|
|
808
|
-
}
|
|
809
|
-
}, 100);
|
|
810
|
-
|
|
811
|
-
setTimeout(() => {
|
|
812
|
-
clearInterval(checkReady);
|
|
813
|
-
reject(
|
|
814
|
-
new Error(
|
|
815
|
-
"Vimeo API loading timeout. The API script may be blocked by an ad blocker or network issue.",
|
|
816
|
-
),
|
|
817
|
-
);
|
|
818
|
-
}, this.API_LOAD_TIMEOUT);
|
|
819
|
-
}
|
|
820
|
-
});
|
|
821
|
-
}
|
|
822
|
-
|
|
823
763
|
/**
|
|
824
|
-
* Load Vimeo API
|
|
764
|
+
* Load Vimeo API using the shared APILoader utility
|
|
825
765
|
*/
|
|
826
|
-
private static async loadVimeoAPIWithRetry(
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
})...`,
|
|
835
|
-
);
|
|
836
|
-
await new Promise((resolve) =>
|
|
837
|
-
setTimeout(resolve, this.API_RETRY_DELAY),
|
|
838
|
-
);
|
|
839
|
-
return this.loadVimeoAPIWithRetry(retries + 1);
|
|
840
|
-
}
|
|
841
|
-
throw error;
|
|
842
|
-
}
|
|
766
|
+
private static async loadVimeoAPIWithRetry(): Promise<void> {
|
|
767
|
+
return APILoader.loadScript("vimeo", {
|
|
768
|
+
scriptUrl: "https://player.vimeo.com/api/player.js",
|
|
769
|
+
globalCheck: () => typeof (window as any).Vimeo !== "undefined",
|
|
770
|
+
timeout: this.API_LOAD_TIMEOUT,
|
|
771
|
+
maxRetries: this.MAX_API_RETRIES,
|
|
772
|
+
retryDelay: this.API_RETRY_DELAY,
|
|
773
|
+
});
|
|
843
774
|
}
|
|
844
775
|
|
|
845
776
|
protected override addCustomControls(containerClass: string) {
|
|
@@ -884,7 +815,7 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
884
815
|
this.appendChild(buttonOverlay);
|
|
885
816
|
}
|
|
886
817
|
|
|
887
|
-
public async play(): Promise<void> {
|
|
818
|
+
public override async play(): Promise<void> {
|
|
888
819
|
if (this._lazy && !this.player && !this.iframe) {
|
|
889
820
|
this.log(
|
|
890
821
|
"VimeoEmbed: Lazy video needs to be loaded first. Initializing...",
|
|
@@ -925,7 +856,7 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
925
856
|
this.dispatchCustomEvent("play");
|
|
926
857
|
}
|
|
927
858
|
|
|
928
|
-
public pause() {
|
|
859
|
+
public override pause() {
|
|
929
860
|
if (this.player) {
|
|
930
861
|
this.player.pause();
|
|
931
862
|
this.log("VimeoEmbed: Video paused via API.");
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { BaseVideoEmbed } from "./BaseVideoEmbed.js";
|
|
2
|
+
import { APILoader } from "../utils/APILoader.js";
|
|
2
3
|
|
|
3
4
|
export class YouTubeEmbed extends BaseVideoEmbed {
|
|
4
5
|
private iframe: HTMLIFrameElement | null = null;
|
|
5
6
|
private player: YT["Player"] | null = null;
|
|
6
|
-
private static apiLoaded = false;
|
|
7
|
-
private static apiReady = false;
|
|
8
|
-
private static apiLoadingPromise: Promise<void> | null = null;
|
|
9
7
|
private static readonly MAX_API_RETRIES = 3;
|
|
10
8
|
private static readonly API_RETRY_DELAY = 2000;
|
|
11
9
|
private static readonly API_LOAD_TIMEOUT = 10000;
|
|
12
10
|
private updatingMutedState = false; // Flag to prevent sync from overwriting programmatic mute changes
|
|
13
|
-
private apiLoadRetries = 0;
|
|
14
11
|
|
|
15
12
|
/** The full YouTube URL (e.g., "https://www.youtube.com/watch?v=...") */
|
|
16
13
|
#url: string = "";
|
|
@@ -48,6 +45,12 @@ export class YouTubeEmbed extends BaseVideoEmbed {
|
|
|
48
45
|
`YouTubeEmbed: Attribute changed - ${name}: ${oldValue} -> ${newValue}`,
|
|
49
46
|
);
|
|
50
47
|
|
|
48
|
+
// Try common attribute handling first
|
|
49
|
+
if (this.handleCommonAttributeChange(name, oldValue, newValue)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Handle YouTube-specific attributes
|
|
51
54
|
switch (name) {
|
|
52
55
|
case "url":
|
|
53
56
|
this.#url = newValue || "";
|
|
@@ -63,46 +66,6 @@ export class YouTubeEmbed extends BaseVideoEmbed {
|
|
|
63
66
|
this.reinitializePlayer();
|
|
64
67
|
}
|
|
65
68
|
break;
|
|
66
|
-
case "autoplay":
|
|
67
|
-
this._autoplay = newValue !== null;
|
|
68
|
-
if (
|
|
69
|
-
!this._playing &&
|
|
70
|
-
this._autoplay &&
|
|
71
|
-
!this._lazy &&
|
|
72
|
-
this.initialized
|
|
73
|
-
) {
|
|
74
|
-
this.play();
|
|
75
|
-
}
|
|
76
|
-
break;
|
|
77
|
-
case "controls":
|
|
78
|
-
this._controls = newValue !== null;
|
|
79
|
-
if (this.initialized) {
|
|
80
|
-
this.reinitializePlayer();
|
|
81
|
-
}
|
|
82
|
-
break;
|
|
83
|
-
case "lazy":
|
|
84
|
-
this._lazy = newValue !== null;
|
|
85
|
-
break;
|
|
86
|
-
case "muted":
|
|
87
|
-
this._muted = newValue !== null;
|
|
88
|
-
if (this.player && this.playerReady) {
|
|
89
|
-
if (this._muted) {
|
|
90
|
-
this.player.mute();
|
|
91
|
-
} else {
|
|
92
|
-
this.player.unMute();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
break;
|
|
96
|
-
case "poster":
|
|
97
|
-
this._poster = newValue || "";
|
|
98
|
-
if (this._lazy && !this.player) {
|
|
99
|
-
this.showPoster(this.#videoId);
|
|
100
|
-
}
|
|
101
|
-
break;
|
|
102
|
-
case "background":
|
|
103
|
-
this._background = newValue !== null;
|
|
104
|
-
this.updateBackgroundMode();
|
|
105
|
-
break;
|
|
106
69
|
case "player-vars":
|
|
107
70
|
try {
|
|
108
71
|
this.#playerVars = newValue ? JSON.parse(newValue) : {};
|
|
@@ -338,7 +301,6 @@ export class YouTubeEmbed extends BaseVideoEmbed {
|
|
|
338
301
|
}
|
|
339
302
|
|
|
340
303
|
protected override handleRetry(): void {
|
|
341
|
-
this.apiLoadRetries = 0;
|
|
342
304
|
if (this.#videoId) {
|
|
343
305
|
this.initializePlayer(this.#videoId);
|
|
344
306
|
}
|
|
@@ -359,6 +321,31 @@ export class YouTubeEmbed extends BaseVideoEmbed {
|
|
|
359
321
|
}
|
|
360
322
|
}
|
|
361
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Sync methods for common attributes (required by BaseVideoEmbed)
|
|
326
|
+
*/
|
|
327
|
+
protected syncMutedState(): void {
|
|
328
|
+
if (this.player && this.playerReady) {
|
|
329
|
+
if (this._muted) {
|
|
330
|
+
this.player.mute();
|
|
331
|
+
} else {
|
|
332
|
+
this.player.unMute();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
protected syncControlsState(): void {
|
|
338
|
+
if (this.initialized) {
|
|
339
|
+
this.reinitializePlayer();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
protected syncPosterState(): void {
|
|
344
|
+
if (this._lazy && !this.player) {
|
|
345
|
+
this.showPoster(this.#videoId);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
362
349
|
private isValidPosterUrl(url: string): boolean {
|
|
363
350
|
if (!url) return false;
|
|
364
351
|
try {
|
|
@@ -470,7 +457,6 @@ export class YouTubeEmbed extends BaseVideoEmbed {
|
|
|
470
457
|
const retryButton = errorDiv.querySelector(".retry-button");
|
|
471
458
|
if (retryButton) {
|
|
472
459
|
retryButton.addEventListener("click", () => {
|
|
473
|
-
this.apiLoadRetries = 0;
|
|
474
460
|
errorDiv.remove();
|
|
475
461
|
if (this.#videoId) {
|
|
476
462
|
this.initializePlayer(this.#videoId);
|
|
@@ -735,7 +721,7 @@ export class YouTubeEmbed extends BaseVideoEmbed {
|
|
|
735
721
|
|
|
736
722
|
// Initialize the YouTube API player - it will create the iframe
|
|
737
723
|
try {
|
|
738
|
-
await YouTubeEmbed.loadYouTubeAPIWithRetry(
|
|
724
|
+
await YouTubeEmbed.loadYouTubeAPIWithRetry();
|
|
739
725
|
this.player = new YT.Player(playerContainer, {
|
|
740
726
|
videoId,
|
|
741
727
|
playerVars,
|
|
@@ -876,81 +862,19 @@ export class YouTubeEmbed extends BaseVideoEmbed {
|
|
|
876
862
|
}
|
|
877
863
|
}
|
|
878
864
|
|
|
879
|
-
private static async loadYouTubeAPI(): Promise<void> {
|
|
880
|
-
// If API is already ready, return immediately
|
|
881
|
-
if (this.apiReady) {
|
|
882
|
-
return Promise.resolve();
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
// If another instance is already loading, wait for that promise
|
|
886
|
-
if (this.apiLoadingPromise) {
|
|
887
|
-
return this.apiLoadingPromise;
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
// Create a new loading promise that all instances will share
|
|
891
|
-
this.apiLoadingPromise = new Promise((resolve, reject) => {
|
|
892
|
-
const timeoutId = setTimeout(() => {
|
|
893
|
-
cleanup();
|
|
894
|
-
this.apiLoadingPromise = null;
|
|
895
|
-
reject(
|
|
896
|
-
new Error(
|
|
897
|
-
"YouTube API loading timeout. The API script may be blocked by an ad blocker or network issue.",
|
|
898
|
-
),
|
|
899
|
-
);
|
|
900
|
-
}, this.API_LOAD_TIMEOUT);
|
|
901
|
-
|
|
902
|
-
const cleanup = () => {
|
|
903
|
-
clearTimeout(timeoutId);
|
|
904
|
-
delete window.onYouTubeIframeAPIReady;
|
|
905
|
-
};
|
|
906
|
-
|
|
907
|
-
// Only add the script if it hasn't been added yet
|
|
908
|
-
if (!this.apiLoaded) {
|
|
909
|
-
const script = document.createElement("script");
|
|
910
|
-
script.src = "https://www.youtube.com/iframe_api";
|
|
911
|
-
script.onerror = () => {
|
|
912
|
-
cleanup();
|
|
913
|
-
this.apiLoadingPromise = null;
|
|
914
|
-
reject(
|
|
915
|
-
new Error(
|
|
916
|
-
"Failed to load YouTube API script. Please check your network connection or disable ad blockers.",
|
|
917
|
-
),
|
|
918
|
-
);
|
|
919
|
-
};
|
|
920
|
-
document.head.appendChild(script);
|
|
921
|
-
this.apiLoaded = true;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
window.onYouTubeIframeAPIReady = () => {
|
|
925
|
-
this.apiReady = true;
|
|
926
|
-
cleanup();
|
|
927
|
-
resolve();
|
|
928
|
-
};
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
return this.apiLoadingPromise;
|
|
932
|
-
}
|
|
933
|
-
|
|
934
865
|
/**
|
|
935
|
-
* Load YouTube API
|
|
866
|
+
* Load YouTube API using the shared APILoader utility
|
|
936
867
|
*/
|
|
937
|
-
private static async loadYouTubeAPIWithRetry(
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
await new Promise((resolve) =>
|
|
948
|
-
setTimeout(resolve, this.API_RETRY_DELAY),
|
|
949
|
-
);
|
|
950
|
-
return this.loadYouTubeAPIWithRetry(retries + 1);
|
|
951
|
-
}
|
|
952
|
-
throw error;
|
|
953
|
-
}
|
|
868
|
+
private static async loadYouTubeAPIWithRetry(): Promise<void> {
|
|
869
|
+
return APILoader.loadScript("youtube", {
|
|
870
|
+
scriptUrl: "https://www.youtube.com/iframe_api",
|
|
871
|
+
globalCheck: () =>
|
|
872
|
+
typeof YT !== "undefined" && typeof YT.Player !== "undefined",
|
|
873
|
+
globalCallback: "onYouTubeIframeAPIReady",
|
|
874
|
+
timeout: this.API_LOAD_TIMEOUT,
|
|
875
|
+
maxRetries: this.MAX_API_RETRIES,
|
|
876
|
+
retryDelay: this.API_RETRY_DELAY,
|
|
877
|
+
});
|
|
954
878
|
}
|
|
955
879
|
|
|
956
880
|
protected override addCustomControls(containerClass: string) {
|
|
@@ -1018,7 +942,7 @@ export class YouTubeEmbed extends BaseVideoEmbed {
|
|
|
1018
942
|
*
|
|
1019
943
|
* @see https://developers.google.com/youtube/iframe_api_reference#playVideo
|
|
1020
944
|
*/
|
|
1021
|
-
public async play(): Promise<void> {
|
|
945
|
+
public override async play(): Promise<void> {
|
|
1022
946
|
// Check if this is a lazy-loaded video that hasn't been initialized yet
|
|
1023
947
|
if (this._lazy && !this.player && !this.iframe) {
|
|
1024
948
|
this.log(
|
|
@@ -1084,7 +1008,7 @@ export class YouTubeEmbed extends BaseVideoEmbed {
|
|
|
1084
1008
|
*
|
|
1085
1009
|
* @see https://developers.google.com/youtube/iframe_api_reference#pauseVideo
|
|
1086
1010
|
*/
|
|
1087
|
-
public pause() {
|
|
1011
|
+
public override pause() {
|
|
1088
1012
|
if (this.player && typeof this.player.pauseVideo === "function") {
|
|
1089
1013
|
this.player.pauseVideo();
|
|
1090
1014
|
this.log("YouTubeEmbed: Video paused via API.");
|