@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.
@@ -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(this.apiLoadRetries);
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 with retry mechanism
764
+ * Load Vimeo API using the shared APILoader utility
825
765
  */
826
- private static async loadVimeoAPIWithRetry(retries = 0): Promise<void> {
827
- try {
828
- await this.loadVimeoAPI();
829
- } catch (error) {
830
- if (retries < this.MAX_API_RETRIES) {
831
- console.warn(
832
- `VimeoEmbed: API load failed, retrying (${retries + 1}/${
833
- this.MAX_API_RETRIES
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(this.apiLoadRetries);
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 with retry mechanism
866
+ * Load YouTube API using the shared APILoader utility
936
867
  */
937
- private static async loadYouTubeAPIWithRetry(retries = 0): Promise<void> {
938
- try {
939
- await this.loadYouTubeAPI();
940
- } catch (error) {
941
- if (retries < this.MAX_API_RETRIES) {
942
- console.warn(
943
- `YouTubeEmbed: API load failed, retrying (${retries + 1}/${
944
- this.MAX_API_RETRIES
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.");