Skip to content

Commit 8093b81

Browse files
Nathan Tatejelbourn
authored andcommitted
feat(youtube-player): Add width and height as inputs (#16734)
1 parent 1de1b98 commit 8093b81

File tree

3 files changed

+100
-11
lines changed

3 files changed

+100
-11
lines changed

src/youtube-player/public-api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export * from './youtube-module';
2-
export * from './youtube-player';
2+
export {YouTubePlayer} from './youtube-player';

src/youtube-player/youtube-player.spec.ts

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
22
import {Component, ViewChild} from '@angular/core';
3-
import {YouTubePlayerModule} from './index';
4-
import {YouTubePlayer} from './youtube-player';
3+
import {YouTubePlayerModule} from './youtube-module';
4+
import {YouTubePlayer, DEFAULT_PLAYER_WIDTH, DEFAULT_PLAYER_HEIGHT} from './youtube-player';
55
import {createFakeYtNamespace} from './fake-youtube-player';
66

77
const VIDEO_ID = 'a12345';
@@ -48,6 +48,8 @@ describe('YoutubePlayer', () => {
4848
expect(playerCtorSpy).toHaveBeenCalledWith(
4949
containerElement, jasmine.objectContaining({
5050
videoId: VIDEO_ID,
51+
width: DEFAULT_PLAYER_WIDTH,
52+
height: DEFAULT_PLAYER_HEIGHT,
5153
}));
5254
});
5355

@@ -85,6 +87,51 @@ describe('YoutubePlayer', () => {
8587
containerElement, jasmine.objectContaining({videoId: 'otherId2'}));
8688
});
8789

90+
it('responds to changes in size', () => {
91+
testComponent.width = 5;
92+
fixture.detectChanges();
93+
94+
expect(playerSpy.setSize).not.toHaveBeenCalled();
95+
96+
events.onReady({target: playerSpy});
97+
98+
expect(playerSpy.setSize).toHaveBeenCalledWith(5, DEFAULT_PLAYER_HEIGHT);
99+
expect(testComponent.youtubePlayer.width).toBe(5);
100+
expect(testComponent.youtubePlayer.height).toBe(DEFAULT_PLAYER_HEIGHT);
101+
102+
testComponent.height = 6;
103+
fixture.detectChanges();
104+
105+
expect(playerSpy.setSize).toHaveBeenCalledWith(5, 6);
106+
expect(testComponent.youtubePlayer.width).toBe(5);
107+
expect(testComponent.youtubePlayer.height).toBe(6);
108+
109+
testComponent.videoId = undefined;
110+
fixture.detectChanges();
111+
testComponent.videoId = VIDEO_ID;
112+
fixture.detectChanges();
113+
114+
expect(playerCtorSpy).toHaveBeenCalledWith(
115+
jasmine.any(Element), jasmine.objectContaining({width: 5, height: 6}));
116+
expect(testComponent.youtubePlayer.width).toBe(5);
117+
expect(testComponent.youtubePlayer.height).toBe(6);
118+
119+
events.onReady({target: playerSpy});
120+
testComponent.width = undefined;
121+
fixture.detectChanges();
122+
123+
expect(playerSpy.setSize).toHaveBeenCalledWith(DEFAULT_PLAYER_WIDTH, 6);
124+
expect(testComponent.youtubePlayer.width).toBe(DEFAULT_PLAYER_WIDTH);
125+
expect(testComponent.youtubePlayer.height).toBe(6);
126+
127+
testComponent.height = undefined;
128+
fixture.detectChanges();
129+
130+
expect(playerSpy.setSize).toHaveBeenCalledWith(DEFAULT_PLAYER_WIDTH, DEFAULT_PLAYER_HEIGHT);
131+
expect(testComponent.youtubePlayer.width).toBe(DEFAULT_PLAYER_WIDTH);
132+
expect(testComponent.youtubePlayer.height).toBe(DEFAULT_PLAYER_HEIGHT);
133+
});
134+
88135
it('proxies events as output', () => {
89136
events.onReady({target: playerSpy});
90137
expect(testComponent.onReady).toHaveBeenCalledWith({target: playerSpy});
@@ -113,7 +160,7 @@ describe('YoutubePlayer', () => {
113160
@Component({
114161
selector: 'test-app',
115162
template: `
116-
<youtube-player #player [videoId]="videoId" *ngIf="visible"
163+
<youtube-player #player [videoId]="videoId" *ngIf="visible" [width]="width" [height]="height"
117164
(ready)="onReady($event)"
118165
(stateChange)="onStateChange($event)"
119166
(playbackQualityChange)="onPlaybackQualityChange($event)"
@@ -126,11 +173,13 @@ describe('YoutubePlayer', () => {
126173
class TestApp {
127174
videoId: string | undefined = VIDEO_ID;
128175
visible = true;
176+
width: number | undefined;
177+
height: number | undefined;
129178
onReady = jasmine.createSpy('onReady');
130179
onStateChange = jasmine.createSpy('onStateChange');
131180
onPlaybackQualityChange = jasmine.createSpy('onPlaybackQualityChange');
132181
onPlaybackRateChange = jasmine.createSpy('onPlaybackRateChange');
133182
onError = jasmine.createSpy('onError');
134183
onApiChange = jasmine.createSpy('onApiChange');
135-
@ViewChild('player', {static: true}) youtubePlayer: YouTubePlayer;
184+
@ViewChild('player', {static: false}) youtubePlayer: YouTubePlayer;
136185
}

src/youtube-player/youtube-player.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ declare global {
3939
interface Window { YT: typeof YT | undefined; }
4040
}
4141

42+
export const DEFAULT_PLAYER_WIDTH = 640;
43+
export const DEFAULT_PLAYER_HEIGHT = 390;
44+
4245
// The native YT.Player doesn't expose the set videoId, but we need it for
4346
// convenience.
4447
interface Player extends YT.Player {
@@ -62,17 +65,33 @@ type UninitializedPlayer = Pick<Player, 'videoId' | 'destroy' | 'addEventListene
6265
})
6366
export class YouTubePlayer implements AfterViewInit, OnDestroy {
6467
/** YouTube Video ID to view */
65-
get videoId(): string | undefined {
66-
return this._player && this._player.videoId;
67-
}
68-
6968
@Input()
69+
get videoId(): string | undefined { return this._player && this._player.videoId; }
7070
set videoId(videoId: string | undefined) {
7171
this._videoId.emit(videoId);
7272
}
73-
7473
private _videoId = new EventEmitter<string | undefined>();
7574

75+
/** Height of video player */
76+
@Input()
77+
get height(): number | undefined { return this._height; }
78+
set height(height: number | undefined) {
79+
this._height = height || DEFAULT_PLAYER_HEIGHT;
80+
this._heightObs.emit(this._height);
81+
}
82+
private _height = DEFAULT_PLAYER_HEIGHT;
83+
private _heightObs = new EventEmitter<number>();
84+
85+
/** Width of video player */
86+
@Input()
87+
get width(): number | undefined { return this._width; }
88+
set width(width: number | undefined) {
89+
this._width = width || DEFAULT_PLAYER_WIDTH;
90+
this._widthObs.emit(this._width);
91+
}
92+
private _width = DEFAULT_PLAYER_WIDTH;
93+
private _widthObs = new EventEmitter<number>();
94+
7695
/** Outputs are direct proxies from the player itself. */
7796
@Output() ready = new EventEmitter<YT.PlayerEvent>();
7897
@Output() stateChange = new EventEmitter<YT.OnStateChangeEvent>();
@@ -94,17 +113,25 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy {
94113
'Please install the YouTube Player API Reference for iframe Embeds: ' +
95114
'https://developers.google.com/youtube/iframe_api_reference');
96115
}
116+
// Add initial values to all of the inputs.
117+
const widthObs = this._widthObs.pipe(startWith(this._width));
118+
const heightObs = this._heightObs.pipe(startWith(this._height));
119+
97120
/** An observable of the currently loaded player. */
98121
const playerObs =
99122
createPlayerObservable(
100123
this._youtubeContainer,
101124
this._videoId,
125+
widthObs,
126+
heightObs,
102127
this.createEventsBoundInZone(),
103128
).pipe(waitUntilReady(), takeUntil(this._destroyed), publish());
104129

105130
/** Set up side effects to bind inputs to the player. */
106131
playerObs.subscribe(player => this._player = player);
107132

133+
bindSizeToPlayer(playerObs, widthObs, heightObs);
134+
108135
bindCueVideoCall(playerObs, this._videoId, this._destroyed);
109136

110137
// After all of the subscriptions are set up, connect the observable.
@@ -146,6 +173,16 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy {
146173
}
147174
}
148175

176+
/** Listens to changes to the given width and height and sets it on the player. */
177+
function bindSizeToPlayer(
178+
playerObs: Observable<YT.Player | undefined>,
179+
widthObs: Observable<number>,
180+
heightObs: Observable<number>
181+
) {
182+
return combineLatest(playerObs, widthObs, heightObs)
183+
.subscribe(([player, width, height]) => player && player.setSize(width, height));
184+
}
185+
149186
/**
150187
* Returns an observable that emits the loaded player once it's ready. Certain properties/methods
151188
* won't be available until the iframe finishes loading.
@@ -190,13 +227,16 @@ function fromPlayerOnReady(player: UninitializedPlayer): Observable<Player> {
190227
function createPlayerObservable(
191228
youtubeContainer: Observable<HTMLElement>,
192229
videoIdObs: Observable<string | undefined>,
230+
widthObs: Observable<number>,
231+
heightObs: Observable<number>,
193232
events: YT.Events,
194233
): Observable<UninitializedPlayer | undefined> {
195234

196235
const playerOptions =
197236
videoIdObs
198237
.pipe(
199-
map((videoId) => videoId ? ({videoId, events}) : undefined),
238+
withLatestFrom(combineLatest(widthObs, heightObs)),
239+
map(([videoId, [width, height]]) => videoId ? ({videoId, width, height, events}) : undefined),
200240
);
201241

202242
return combineLatest(youtubeContainer, playerOptions)

0 commit comments

Comments
 (0)