@angular/youtube-player 17.0.2 → 17.1.0-next.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,12 +1,83 @@
1
1
  import * as i0 from '@angular/core';
2
- import { PLATFORM_ID, Component, ChangeDetectionStrategy, ViewEncapsulation, Inject, Input, Output, ViewChild, NgModule } from '@angular/core';
2
+ import { Component, ChangeDetectionStrategy, ViewEncapsulation, Input, InjectionToken, numberAttribute, inject, CSP_NONCE, ChangeDetectorRef, PLATFORM_ID, booleanAttribute, Inject, Output, ViewChild, NgModule } from '@angular/core';
3
3
  import { isPlatformBrowser } from '@angular/common';
4
4
  import { Subject, BehaviorSubject, fromEventPattern, of, Observable } from 'rxjs';
5
5
  import { switchMap, takeUntil } from 'rxjs/operators';
6
6
 
7
+ class YouTubePlayerPlaceholder {
8
+ /** Gets the background image showing the placeholder. */
9
+ _getBackgroundImage() {
10
+ let url;
11
+ if (this.quality === 'low') {
12
+ url = `https://i.ytimg.com/vi/${this.videoId}/hqdefault.jpg`;
13
+ }
14
+ else if (this.quality === 'high') {
15
+ url = `https://i.ytimg.com/vi/${this.videoId}/maxresdefault.jpg`;
16
+ }
17
+ else {
18
+ url = `https://i.ytimg.com/vi_webp/${this.videoId}/sddefault.webp`;
19
+ }
20
+ return `url(${url})`;
21
+ }
22
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: YouTubePlayerPlaceholder, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
23
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.0.4", type: YouTubePlayerPlaceholder, isStandalone: true, selector: "youtube-player-placeholder", inputs: { videoId: "videoId", width: "width", height: "height", isLoading: "isLoading", buttonLabel: "buttonLabel", quality: "quality" }, host: { properties: { "class.youtube-player-placeholder-loading": "isLoading", "style.background-image": "_getBackgroundImage()", "style.width.px": "width", "style.height.px": "height" }, classAttribute: "youtube-player-placeholder" }, ngImport: i0, template: `
24
+ <button type="button" class="youtube-player-placeholder-button" [attr.aria-label]="buttonLabel">
25
+ <svg
26
+ height="100%"
27
+ version="1.1"
28
+ viewBox="0 0 68 48"
29
+ focusable="false"
30
+ aria-hidden="true">
31
+ <path d="M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z" fill="#f00"></path>
32
+ <path d="M 45,24 27,14 27,34" fill="#fff"></path>
33
+ </svg>
34
+ </button>
35
+ `, isInline: true, styles: [".youtube-player-placeholder{display:flex;align-items:center;justify-content:center;width:100%;overflow:hidden;cursor:pointer;background-color:#000;background-position:center center;background-size:cover;transition:box-shadow 300ms ease;box-shadow:inset 0 120px 90px -90px rgba(0,0,0,.8)}.youtube-player-placeholder-button{transition:opacity 300ms ease;-moz-appearance:none;-webkit-appearance:none;background:none;border:none;padding:0;display:flex}.youtube-player-placeholder-button svg{width:68px;height:48px}.youtube-player-placeholder-loading{box-shadow:none}.youtube-player-placeholder-loading .youtube-player-placeholder-button{opacity:0}"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
36
+ }
37
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: YouTubePlayerPlaceholder, decorators: [{
38
+ type: Component,
39
+ args: [{ selector: 'youtube-player-placeholder', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
40
+ <button type="button" class="youtube-player-placeholder-button" [attr.aria-label]="buttonLabel">
41
+ <svg
42
+ height="100%"
43
+ version="1.1"
44
+ viewBox="0 0 68 48"
45
+ focusable="false"
46
+ aria-hidden="true">
47
+ <path d="M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z" fill="#f00"></path>
48
+ <path d="M 45,24 27,14 27,34" fill="#fff"></path>
49
+ </svg>
50
+ </button>
51
+ `, standalone: true, host: {
52
+ 'class': 'youtube-player-placeholder',
53
+ '[class.youtube-player-placeholder-loading]': 'isLoading',
54
+ '[style.background-image]': '_getBackgroundImage()',
55
+ '[style.width.px]': 'width',
56
+ '[style.height.px]': 'height',
57
+ }, styles: [".youtube-player-placeholder{display:flex;align-items:center;justify-content:center;width:100%;overflow:hidden;cursor:pointer;background-color:#000;background-position:center center;background-size:cover;transition:box-shadow 300ms ease;box-shadow:inset 0 120px 90px -90px rgba(0,0,0,.8)}.youtube-player-placeholder-button{transition:opacity 300ms ease;-moz-appearance:none;-webkit-appearance:none;background:none;border:none;padding:0;display:flex}.youtube-player-placeholder-button svg{width:68px;height:48px}.youtube-player-placeholder-loading{box-shadow:none}.youtube-player-placeholder-loading .youtube-player-placeholder-button{opacity:0}"] }]
58
+ }], propDecorators: { videoId: [{
59
+ type: Input
60
+ }], width: [{
61
+ type: Input
62
+ }], height: [{
63
+ type: Input
64
+ }], isLoading: [{
65
+ type: Input
66
+ }], buttonLabel: [{
67
+ type: Input
68
+ }], quality: [{
69
+ type: Input
70
+ }] } });
71
+
7
72
  /// <reference types="youtube" />
73
+ /** Injection token used to configure the `YouTubePlayer`. */
74
+ const YOUTUBE_PLAYER_CONFIG = new InjectionToken('YOUTUBE_PLAYER_CONFIG');
8
75
  const DEFAULT_PLAYER_WIDTH = 640;
9
76
  const DEFAULT_PLAYER_HEIGHT = 390;
77
+ /** Coercion function for time values. */
78
+ function coerceTime(value) {
79
+ return value == null ? value : numberAttribute(value, 0);
80
+ }
10
81
  /**
11
82
  * Angular component that renders a YouTube player via the YouTube player
12
83
  * iframe API.
@@ -18,23 +89,38 @@ class YouTubePlayer {
18
89
  return this._height;
19
90
  }
20
91
  set height(height) {
21
- this._height = height || DEFAULT_PLAYER_HEIGHT;
92
+ this._height = height == null || isNaN(height) ? DEFAULT_PLAYER_HEIGHT : height;
22
93
  }
23
94
  /** Width of video player */
24
95
  get width() {
25
96
  return this._width;
26
97
  }
27
98
  set width(width) {
28
- this._width = width || DEFAULT_PLAYER_WIDTH;
99
+ this._width = width == null || isNaN(width) ? DEFAULT_PLAYER_WIDTH : width;
29
100
  }
30
101
  constructor(_ngZone, platformId) {
31
102
  this._ngZone = _ngZone;
32
103
  this._destroyed = new Subject();
33
104
  this._playerChanges = new BehaviorSubject(undefined);
105
+ this._nonce = inject(CSP_NONCE, { optional: true });
106
+ this._changeDetectorRef = inject(ChangeDetectorRef);
107
+ this._isLoading = false;
108
+ this._hasPlaceholder = true;
34
109
  this._height = DEFAULT_PLAYER_HEIGHT;
35
110
  this._width = DEFAULT_PLAYER_WIDTH;
36
111
  /** Whether cookies inside the player have been disabled. */
37
112
  this.disableCookies = false;
113
+ /**
114
+ * By default the player shows a placeholder image instead of loading the YouTube API which
115
+ * improves the initial page load performance. This input allows for the behavior to be disabled.
116
+ */
117
+ this.disablePlaceholder = false;
118
+ /**
119
+ * Whether the iframe will attempt to load regardless of the status of the api on the
120
+ * page. Set this to true if you don't want the `onYouTubeIframeAPIReady` field to be
121
+ * set on the global window.
122
+ */
123
+ this.showBeforeIframeApiLoads = false;
38
124
  /** Outputs are direct proxies from the player itself. */
39
125
  this.ready = this._getLazyEmitter('onReady');
40
126
  this.stateChange = this._getLazyEmitter('onStateChange');
@@ -42,32 +128,19 @@ class YouTubePlayer {
42
128
  this.apiChange = this._getLazyEmitter('onApiChange');
43
129
  this.playbackQualityChange = this._getLazyEmitter('onPlaybackQualityChange');
44
130
  this.playbackRateChange = this._getLazyEmitter('onPlaybackRateChange');
131
+ const config = inject(YOUTUBE_PLAYER_CONFIG, { optional: true });
132
+ this.loadApi = config?.loadApi ?? true;
133
+ this.disablePlaceholder = !!config?.disablePlaceholder;
134
+ this.placeholderButtonLabel = config?.placeholderButtonLabel || 'Play video';
135
+ this.placeholderImageQuality = config?.placeholderImageQuality || 'standard';
45
136
  this._isBrowser = isPlatformBrowser(platformId);
46
137
  }
47
138
  ngAfterViewInit() {
48
- // Don't do anything if we're not in a browser environment.
49
- if (!this._isBrowser) {
50
- return;
51
- }
52
- if (!window.YT || !window.YT.Player) {
53
- if (this.showBeforeIframeApiLoads && (typeof ngDevMode === 'undefined' || ngDevMode)) {
54
- throw new Error('Namespace YT not found, cannot construct embedded youtube player. ' +
55
- 'Please install the YouTube Player API Reference for iframe Embeds: ' +
56
- 'https://developers.google.com/youtube/iframe_api_reference');
57
- }
58
- this._existingApiReadyCallback = window.onYouTubeIframeAPIReady;
59
- window.onYouTubeIframeAPIReady = () => {
60
- this._existingApiReadyCallback?.();
61
- this._ngZone.run(() => this._createPlayer());
62
- };
63
- }
64
- else {
65
- this._createPlayer();
66
- }
139
+ this._conditionallyLoad();
67
140
  }
68
141
  ngOnChanges(changes) {
69
142
  if (this._shouldRecreatePlayer(changes)) {
70
- this._createPlayer();
143
+ this._conditionallyLoad();
71
144
  }
72
145
  else if (this._player) {
73
146
  if (changes['width'] || changes['height']) {
@@ -245,6 +318,57 @@ class YouTubePlayer {
245
318
  getVideoEmbedCode() {
246
319
  return this._player ? this._player.getVideoEmbedCode() : '';
247
320
  }
321
+ /**
322
+ * Loads the YouTube API and sets up the player.
323
+ * @param playVideo Whether to automatically play the video once the player is loaded.
324
+ */
325
+ _load(playVideo) {
326
+ // Don't do anything if we're not in a browser environment.
327
+ if (!this._isBrowser) {
328
+ return;
329
+ }
330
+ if (!window.YT || !window.YT.Player) {
331
+ if (this.loadApi) {
332
+ this._isLoading = true;
333
+ loadApi(this._nonce);
334
+ }
335
+ else if (this.showBeforeIframeApiLoads && (typeof ngDevMode === 'undefined' || ngDevMode)) {
336
+ throw new Error('Namespace YT not found, cannot construct embedded youtube player. ' +
337
+ 'Please install the YouTube Player API Reference for iframe Embeds: ' +
338
+ 'https://developers.google.com/youtube/iframe_api_reference');
339
+ }
340
+ this._existingApiReadyCallback = window.onYouTubeIframeAPIReady;
341
+ window.onYouTubeIframeAPIReady = () => {
342
+ this._existingApiReadyCallback?.();
343
+ this._ngZone.run(() => this._createPlayer(playVideo));
344
+ };
345
+ }
346
+ else {
347
+ this._createPlayer(playVideo);
348
+ }
349
+ }
350
+ /** Loads the player depending on the internal state of the component. */
351
+ _conditionallyLoad() {
352
+ // If the placeholder isn't shown anymore, we have to trigger a load.
353
+ if (!this._shouldShowPlaceholder()) {
354
+ this._load(false);
355
+ }
356
+ else if (this.playerVars?.autoplay === 1) {
357
+ // If it's an autoplaying video, we have to hide the placeholder and start playing.
358
+ this._load(true);
359
+ }
360
+ }
361
+ /** Whether to show the placeholder element. */
362
+ _shouldShowPlaceholder() {
363
+ if (this.disablePlaceholder) {
364
+ return false;
365
+ }
366
+ // Since we don't load the API on the server, we show the placeholder permanently.
367
+ if (!this._isBrowser) {
368
+ return true;
369
+ }
370
+ return this._hasPlaceholder && !!this.videoId && !this._player;
371
+ }
248
372
  /** Gets an object that should be used to store the temporary API state. */
249
373
  _getPendingState() {
250
374
  if (!this._pendingPlayerState) {
@@ -257,11 +381,17 @@ class YouTubePlayer {
257
381
  * requires the YouTube player to be recreated.
258
382
  */
259
383
  _shouldRecreatePlayer(changes) {
260
- const change = changes['videoId'] || changes['playerVars'] || changes['disableCookies'];
384
+ const change = changes['videoId'] ||
385
+ changes['playerVars'] ||
386
+ changes['disableCookies'] ||
387
+ changes['disablePlaceholder'];
261
388
  return !!change && !change.isFirstChange();
262
389
  }
263
- /** Creates a new YouTube player and destroys the existing one. */
264
- _createPlayer() {
390
+ /**
391
+ * Creates a new YouTube player and destroys the existing one.
392
+ * @param playVideo Whether to play the video once it loads.
393
+ */
394
+ _createPlayer(playVideo) {
265
395
  this._player?.destroy();
266
396
  this._pendingPlayer?.destroy();
267
397
  // A player can't be created if the API isn't loaded,
@@ -276,26 +406,33 @@ class YouTubePlayer {
276
406
  host: this.disableCookies ? 'https://www.youtube-nocookie.com' : undefined,
277
407
  width: this.width,
278
408
  height: this.height,
279
- playerVars: this.playerVars,
409
+ // Calling `playVideo` on load doesn't appear to actually play
410
+ // the video so we need to trigger it through `playerVars` instead.
411
+ playerVars: playVideo ? { ...(this.playerVars || {}), autoplay: 1 } : this.playerVars,
280
412
  }));
281
413
  const whenReady = () => {
282
414
  // Only assign the player once it's ready, otherwise YouTube doesn't expose some APIs.
283
- this._player = player;
284
- this._pendingPlayer = undefined;
285
- player.removeEventListener('onReady', whenReady);
286
- this._playerChanges.next(player);
287
- this._setSize();
288
- this._setQuality();
289
- if (this._pendingPlayerState) {
290
- this._applyPendingPlayerState(player, this._pendingPlayerState);
291
- this._pendingPlayerState = undefined;
292
- }
293
- // Only cue the player when it either hasn't started yet or it's cued,
294
- // otherwise cuing it can interrupt a player with autoplay enabled.
295
- const state = player.getPlayerState();
296
- if (state === YT.PlayerState.UNSTARTED || state === YT.PlayerState.CUED || state == null) {
297
- this._cuePlayer();
298
- }
415
+ this._ngZone.run(() => {
416
+ this._isLoading = false;
417
+ this._hasPlaceholder = false;
418
+ this._player = player;
419
+ this._pendingPlayer = undefined;
420
+ player.removeEventListener('onReady', whenReady);
421
+ this._playerChanges.next(player);
422
+ this._setSize();
423
+ this._setQuality();
424
+ if (this._pendingPlayerState) {
425
+ this._applyPendingPlayerState(player, this._pendingPlayerState);
426
+ this._pendingPlayerState = undefined;
427
+ }
428
+ // Only cue the player when it either hasn't started yet or it's cued,
429
+ // otherwise cuing it can interrupt a player with autoplay enabled.
430
+ const state = player.getPlayerState();
431
+ if (state === YT.PlayerState.UNSTARTED || state === YT.PlayerState.CUED || state == null) {
432
+ this._cuePlayer();
433
+ }
434
+ this._changeDetectorRef.markForCheck();
435
+ });
299
436
  };
300
437
  this._pendingPlayer = player;
301
438
  player.addEventListener('onReady', whenReady);
@@ -381,7 +518,21 @@ class YouTubePlayer {
381
518
  takeUntil(this._destroyed));
382
519
  }
383
520
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: YouTubePlayer, deps: [{ token: i0.NgZone }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Component }); }
384
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.0.4", type: YouTubePlayer, selector: "youtube-player", inputs: { videoId: "videoId", height: "height", width: "width", startSeconds: "startSeconds", endSeconds: "endSeconds", suggestedQuality: "suggestedQuality", playerVars: "playerVars", disableCookies: "disableCookies", showBeforeIframeApiLoads: "showBeforeIframeApiLoads" }, outputs: { ready: "ready", stateChange: "stateChange", error: "error", apiChange: "apiChange", playbackQualityChange: "playbackQualityChange", playbackRateChange: "playbackRateChange" }, viewQueries: [{ propertyName: "youtubeContainer", first: true, predicate: ["youtubeContainer"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: '<div #youtubeContainer></div>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
521
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.0.4", type: YouTubePlayer, isStandalone: true, selector: "youtube-player", inputs: { videoId: "videoId", height: ["height", "height", numberAttribute], width: ["width", "width", numberAttribute], startSeconds: ["startSeconds", "startSeconds", coerceTime], endSeconds: ["endSeconds", "endSeconds", coerceTime], suggestedQuality: "suggestedQuality", playerVars: "playerVars", disableCookies: ["disableCookies", "disableCookies", booleanAttribute], loadApi: ["loadApi", "loadApi", booleanAttribute], disablePlaceholder: ["disablePlaceholder", "disablePlaceholder", booleanAttribute], showBeforeIframeApiLoads: ["showBeforeIframeApiLoads", "showBeforeIframeApiLoads", booleanAttribute], placeholderButtonLabel: "placeholderButtonLabel", placeholderImageQuality: "placeholderImageQuality" }, outputs: { ready: "ready", stateChange: "stateChange", error: "error", apiChange: "apiChange", playbackQualityChange: "playbackQualityChange", playbackRateChange: "playbackRateChange" }, viewQueries: [{ propertyName: "youtubeContainer", first: true, predicate: ["youtubeContainer"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
522
+ @if (_shouldShowPlaceholder()) {
523
+ <youtube-player-placeholder
524
+ [videoId]="videoId!"
525
+ [width]="width"
526
+ [height]="height"
527
+ [isLoading]="_isLoading"
528
+ [buttonLabel]="placeholderButtonLabel"
529
+ [quality]="placeholderImageQuality"
530
+ (click)="_load(true)"/>
531
+ }
532
+ <div [style.display]="_shouldShowPlaceholder() ? 'none' : ''">
533
+ <div #youtubeContainer></div>
534
+ </div>
535
+ `, isInline: true, dependencies: [{ kind: "component", type: YouTubePlayerPlaceholder, selector: "youtube-player-placeholder", inputs: ["videoId", "width", "height", "isLoading", "buttonLabel", "quality"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
385
536
  }
386
537
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: YouTubePlayer, decorators: [{
387
538
  type: Component,
@@ -389,8 +540,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.4", ngImpor
389
540
  selector: 'youtube-player',
390
541
  changeDetection: ChangeDetectionStrategy.OnPush,
391
542
  encapsulation: ViewEncapsulation.None,
392
- // This div is *replaced* by the YouTube player embed.
393
- template: '<div #youtubeContainer></div>',
543
+ standalone: true,
544
+ imports: [YouTubePlayerPlaceholder],
545
+ template: `
546
+ @if (_shouldShowPlaceholder()) {
547
+ <youtube-player-placeholder
548
+ [videoId]="videoId!"
549
+ [width]="width"
550
+ [height]="height"
551
+ [isLoading]="_isLoading"
552
+ [buttonLabel]="placeholderButtonLabel"
553
+ [quality]="placeholderImageQuality"
554
+ (click)="_load(true)"/>
555
+ }
556
+ <div [style.display]="_shouldShowPlaceholder() ? 'none' : ''">
557
+ <div #youtubeContainer></div>
558
+ </div>
559
+ `,
394
560
  }]
395
561
  }], ctorParameters: () => [{ type: i0.NgZone }, { type: Object, decorators: [{
396
562
  type: Inject,
@@ -398,20 +564,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.4", ngImpor
398
564
  }] }], propDecorators: { videoId: [{
399
565
  type: Input
400
566
  }], height: [{
401
- type: Input
567
+ type: Input,
568
+ args: [{ transform: numberAttribute }]
402
569
  }], width: [{
403
- type: Input
570
+ type: Input,
571
+ args: [{ transform: numberAttribute }]
404
572
  }], startSeconds: [{
405
- type: Input
573
+ type: Input,
574
+ args: [{ transform: coerceTime }]
406
575
  }], endSeconds: [{
407
- type: Input
576
+ type: Input,
577
+ args: [{ transform: coerceTime }]
408
578
  }], suggestedQuality: [{
409
579
  type: Input
410
580
  }], playerVars: [{
411
581
  type: Input
412
582
  }], disableCookies: [{
413
- type: Input
583
+ type: Input,
584
+ args: [{ transform: booleanAttribute }]
585
+ }], loadApi: [{
586
+ type: Input,
587
+ args: [{ transform: booleanAttribute }]
588
+ }], disablePlaceholder: [{
589
+ type: Input,
590
+ args: [{ transform: booleanAttribute }]
414
591
  }], showBeforeIframeApiLoads: [{
592
+ type: Input,
593
+ args: [{ transform: booleanAttribute }]
594
+ }], placeholderButtonLabel: [{
595
+ type: Input
596
+ }], placeholderImageQuality: [{
415
597
  type: Input
416
598
  }], ready: [{
417
599
  type: Output
@@ -429,18 +611,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.4", ngImpor
429
611
  type: ViewChild,
430
612
  args: ['youtubeContainer', { static: true }]
431
613
  }] } });
614
+ let apiLoaded = false;
615
+ /** Loads the YouTube API from a specified URL only once. */
616
+ function loadApi(nonce) {
617
+ if (apiLoaded) {
618
+ return;
619
+ }
620
+ // We can use `document` directly here, because this logic doesn't run outside the browser.
621
+ const url = 'https://www.youtube.com/iframe_api';
622
+ const script = document.createElement('script');
623
+ const callback = (event) => {
624
+ script.removeEventListener('load', callback);
625
+ script.removeEventListener('error', callback);
626
+ if (event.type === 'error') {
627
+ apiLoaded = false;
628
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
629
+ console.error(`Failed to load YouTube API from ${url}`);
630
+ }
631
+ }
632
+ };
633
+ script.addEventListener('load', callback);
634
+ script.addEventListener('error', callback);
635
+ script.src = url;
636
+ script.async = true;
637
+ if (nonce) {
638
+ script.nonce = nonce;
639
+ }
640
+ // Set this immediately to true so we don't start loading another script
641
+ // while this one is pending. If loading fails, we'll flip it back to false.
642
+ apiLoaded = true;
643
+ document.body.appendChild(script);
644
+ }
432
645
 
433
- const COMPONENTS = [YouTubePlayer];
434
646
  class YouTubePlayerModule {
435
647
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: YouTubePlayerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
436
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.0.4", ngImport: i0, type: YouTubePlayerModule, declarations: [YouTubePlayer], exports: [YouTubePlayer] }); }
648
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.0.4", ngImport: i0, type: YouTubePlayerModule, imports: [YouTubePlayer], exports: [YouTubePlayer] }); }
437
649
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: YouTubePlayerModule }); }
438
650
  }
439
651
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: YouTubePlayerModule, decorators: [{
440
652
  type: NgModule,
441
653
  args: [{
442
- declarations: COMPONENTS,
443
- exports: COMPONENTS,
654
+ imports: [YouTubePlayer],
655
+ exports: [YouTubePlayer],
444
656
  }]
445
657
  }] });
446
658
 
@@ -448,5 +660,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.4", ngImpor
448
660
  * Generated bundle index. Do not edit.
449
661
  */
450
662
 
451
- export { YouTubePlayer, YouTubePlayerModule };
663
+ export { YOUTUBE_PLAYER_CONFIG, YouTubePlayer, YouTubePlayerModule };
452
664
  //# sourceMappingURL=youtube-player.mjs.map