Skip to content

Commit 995d47e

Browse files
author
Nathan Tate
committed
feat(youtube-player): Add width and height as inputs
1 parent 6416bab commit 995d47e

File tree

2 files changed

+99
-4
lines changed

2 files changed

+99
-4
lines changed

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

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

@@ -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

@@ -84,18 +86,65 @@ describe('YoutubePlayer', () => {
8486
expect(playerCtorSpy).toHaveBeenCalledWith(
8587
containerElement, jasmine.objectContaining({videoId: 'otherId2'}));
8688
});
89+
90+
it('responds to changes in size', () => {
91+
testComponent.width = 5;
92+
fixture.detectChanges();
93+
94+
expect(playerSpy.setSize).not.toHaveBeenCalled();
95+
96+
onPlayerReady();
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+
onPlayerReady();
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+
});
87134
});
88135

89136
/** Test component that contains a YouTubePlayer. */
90137
@Component({
91138
selector: 'test-app',
92139
template: `
93-
<youtube-player #player [videoId]="videoId" *ngIf="visible">
140+
<youtube-player #player [videoId]="videoId" *ngIf="visible" [width]="width" [height]="height">
94141
</youtube-player>
95142
`
96143
})
97144
class TestApp {
98145
videoId: string | undefined = VIDEO_ID;
99146
visible = true;
100-
@ViewChild('player', {static: true}) youtubePlayer: YouTubePlayer;
147+
width: number | undefined;
148+
height: number | undefined;
149+
@ViewChild('player', {static: false}) youtubePlayer: YouTubePlayer;
101150
}

src/youtube-player/youtube-player.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ declare global {
3737
interface Window { YT: typeof YT | undefined; }
3838
}
3939

40+
export const DEFAULT_PLAYER_WIDTH = 640;
41+
export const DEFAULT_PLAYER_HEIGHT = 390;
42+
4043
// The native YT.Player doesn't expose the set videoId, but we need it for
4144
// convenience.
4245
interface Player extends YT.Player {
@@ -71,6 +74,29 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy {
7174

7275
private _videoId = new EventEmitter<string | undefined>();
7376

77+
/** Height of video player */
78+
get height(): number | undefined {
79+
return this._height;
80+
}
81+
82+
@Input() set height(height: number | undefined) {
83+
this._height = height || DEFAULT_PLAYER_HEIGHT;
84+
this._heightObs.emit(this._height);
85+
}
86+
private _height = DEFAULT_PLAYER_HEIGHT;
87+
private _heightObs = new EventEmitter<number>();
88+
89+
/** Width of video player */
90+
get width(): number | undefined {
91+
return this._width;
92+
}
93+
@Input() set width(width: number | undefined) {
94+
this._width = width || DEFAULT_PLAYER_WIDTH;
95+
this._widthObs.emit(this._width);
96+
}
97+
private _width = DEFAULT_PLAYER_WIDTH;
98+
private _widthObs = new EventEmitter<number>();
99+
74100
/** The element that will be replaced by the iframe. */
75101
@ViewChild('youtube_container', {static: false}) youtubeContainer: ElementRef | undefined;
76102
private _youtubeContainer = new EventEmitter<HTMLElement>();
@@ -84,17 +110,24 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy {
84110
'Please install the YouTube Player API Reference for iframe Embeds: ' +
85111
'https://developers.google.com/youtube/iframe_api_reference');
86112
}
113+
// Add initial values to all of the inputs.
114+
const widthObs = this._widthObs.pipe(startWith(this._width));
115+
const heightObs = this._heightObs.pipe(startWith(this._height));
87116

88117
/** An observable of the currently loaded player. */
89118
const playerObs =
90119
createPlayerObservable(
91120
this._youtubeContainer,
92121
this._videoId,
122+
widthObs,
123+
heightObs,
93124
).pipe(waitUntilReady(), takeUntil(this._destroyed), publish());
94125

95126
/** Set up side effects to bind inputs to the player. */
96127
playerObs.subscribe(player => this._player = player);
97128

129+
bindSizeToPlayer(playerObs, widthObs, heightObs);
130+
98131
bindCueVideoCall(playerObs, this._videoId, this._destroyed);
99132

100133
// After all of the subscriptions are set up, connect the observable.
@@ -117,6 +150,16 @@ export class YouTubePlayer implements AfterViewInit, OnDestroy {
117150
}
118151
}
119152

153+
/** Listens to changes to the given width and height and sets it on the player. */
154+
function bindSizeToPlayer(
155+
playerObs: Observable<YT.Player | undefined>,
156+
widthObs: Observable<number>,
157+
heightObs: Observable<number>
158+
) {
159+
return combineLatest(playerObs, widthObs, heightObs)
160+
.subscribe(([player, width, height]) => player && player.setSize(width, height));
161+
}
162+
120163
/**
121164
* Returns an observable that emits the loaded player once it's ready. Certain properties/methods
122165
* won't be available until the iframe finishes loading.
@@ -161,12 +204,15 @@ function fromPlayerOnReady(player: UninitializedPlayer): Observable<Player> {
161204
function createPlayerObservable(
162205
youtubeContainer: Observable<HTMLElement>,
163206
videoIdObs: Observable<string | undefined>,
207+
widthObs: Observable<number>,
208+
heightObs: Observable<number>,
164209
): Observable<UninitializedPlayer | undefined> {
165210

166211
const playerOptions =
167212
videoIdObs
168213
.pipe(
169-
map((videoId) => videoId ? {videoId} : undefined),
214+
withLatestFrom(combineLatest(widthObs, heightObs)),
215+
map(([videoId, [width, height]]) => videoId ? ({videoId, width, height}) : undefined),
170216
);
171217

172218
return combineLatest(youtubeContainer, playerOptions)

0 commit comments

Comments
 (0)