@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.
- package/esm2022/public-api.mjs +2 -2
- package/esm2022/youtube-module.mjs +4 -5
- package/esm2022/youtube-player-placeholder.mjs +74 -0
- package/esm2022/youtube-player.mjs +201 -52
- package/fesm2022/youtube-player.mjs +268 -56
- package/fesm2022/youtube-player.mjs.map +1 -1
- package/index.d.ts +70 -4
- package/package.json +3 -3
|
@@ -1,12 +1,83 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
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
|
-
|
|
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.
|
|
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'] ||
|
|
384
|
+
const change = changes['videoId'] ||
|
|
385
|
+
changes['playerVars'] ||
|
|
386
|
+
changes['disableCookies'] ||
|
|
387
|
+
changes['disablePlaceholder'];
|
|
261
388
|
return !!change && !change.isFirstChange();
|
|
262
389
|
}
|
|
263
|
-
/**
|
|
264
|
-
|
|
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
|
-
|
|
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.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
this.
|
|
291
|
-
this.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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: "
|
|
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
|
-
|
|
393
|
-
|
|
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,
|
|
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
|
-
|
|
443
|
-
exports:
|
|
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
|