Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

YouTube player features #28171

Merged
merged 2 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 1 addition & 14 deletions src/dev-app/youtube-player/youtube-player-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
OnDestroy,
ViewChild,
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {MatRadioModule} from '@angular/material/radio';
import {YouTubePlayer} from '@angular/youtube-player';
Expand Down Expand Up @@ -56,7 +55,7 @@ const VIDEOS: Video[] = [
templateUrl: 'youtube-player-demo.html',
styleUrls: ['youtube-player-demo.css'],
standalone: true,
imports: [CommonModule, FormsModule, MatRadioModule, MatCheckboxModule, YouTubePlayer],
imports: [FormsModule, MatRadioModule, MatCheckboxModule, YouTubePlayer],
})
export class YouTubePlayerDemo implements AfterViewInit, OnDestroy {
@ViewChild('demoYouTubePlayer') demoYouTubePlayer: ElementRef<HTMLDivElement>;
Expand All @@ -70,8 +69,6 @@ export class YouTubePlayerDemo implements AfterViewInit, OnDestroy {
disableCookies = false;

constructor(private _changeDetectorRef: ChangeDetectorRef) {
this._loadApi();

this.selectedVideo = VIDEOS[0];
}

Expand Down Expand Up @@ -121,14 +118,4 @@ export class YouTubePlayerDemo implements AfterViewInit, OnDestroy {

this._selectedVideoId = undefined;
}

private _loadApi() {
if (!window.YT) {
// We don't need to wait for the API to load since the
// component is set up to wait for it automatically.
const script = document.createElement('script');
script.src = 'https://www.youtube.com/iframe_api';
document.body.appendChild(script);
}
}
}
67 changes: 38 additions & 29 deletions src/youtube-player/README.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,60 @@
# Angular YouTube Player component

This component provides a simple angular wrapper around the embed [YouTube player API](https://developers.google.com/youtube/iframe_api_reference). File any bugs against the [angular/components repo](https://github.com/angular/components/issues).
This component provides a simple Angular wrapper around the
[YouTube player API](https://developers.google.com/youtube/iframe_api_reference).
File any bugs against the [angular/components repo](https://github.com/angular/components/issues).

## Installation

To install, run `npm install @angular/youtube-player`.

## Usage

Follow the following instructions for setting up the YouTube player component:

- First, follow the [instructions for installing the API script](https://developers.google.com/youtube/iframe_api_reference#Getting_Started).
- Then make sure the API is available before bootstrapping the YouTube Player component.
- Provide the video id by extracting it from the video URL.
Import the component either by adding the `YouTubePlayerModule` to your app or by importing
`YouTubePlayer` into a standalone component. Then add the `<yotube-player videoId="<your ID>"`
to your template.

## Example

If your video is found at https://www.youtube.com/watch?v=PRQCAL_RMVo, then your video id is `PRQCAL_RMVo`.
If your video is found at https://www.youtube.com/watch?v=mVjYG9TSN88, then your video id is `mVjYG9TSN88`.

```typescript
// example-component.ts
import {Component, OnInit} from '@angular/core';
import {Component} from '@angular/core';
import {YouTubePlayer} from '@angular/youtube-player';

let apiLoaded = false;

@Component({
standalone: true,
imports: [YouTubePlayer],
template: '<youtube-player videoId="PRQCAL_RMVo"></youtube-player>',
template: '<youtube-player videoId="mVjYG9TSN88"/>',
selector: 'youtube-player-example',
})
class YoutubePlayerExample implements OnInit {
ngOnInit() {
if (!apiLoaded) {
// This code loads the IFrame Player API code asynchronously, according to the instructions at
// https://developers.google.com/youtube/iframe_api_reference#Getting_Started
const tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
document.body.appendChild(tag);
apiLoaded = true;
}
}
}
export class YoutubePlayerExample {}
```

## API reference
Check out the [source](./youtube-player.ts) to read the API.

## YouTube iframe API usage
The `<youtube-player/>` component requires the YouTube `iframe` to work. If the API isn't loaded
by the time the player is initialized, it'll load the API automatically from `https://www.youtube.com/iframe_api`.
If you don't want it to be loaded, you can either control it on a per-component level using the
`loadApi` input:

```html
<youtube-player videoId="mVjYG9TSN88" loadApi="false"/>
```

## API
Or at a global level using the `YOUTUBE_PLAYER_CONFIG` injection token:

Check out the [source](./youtube-player.ts) to read the API.
```typescript
import {NgModule} from '@angular/core';
import {YouTubePlayer, YOUTUBE_PLAYER_CONFIG} from '@angular/youtube-player';

@NgModule({
imports: [YouTubePlayer],
providers: [{
provide: YOUTUBE_PLAYER_CONFIG,
useValue: {
loadApi: false
}
}]
})
export class YourApp {}
```
2 changes: 1 addition & 1 deletion src/youtube-player/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
*/

export * from './youtube-module';
export {YouTubePlayer} from './youtube-player';
export {YouTubePlayer, YOUTUBE_PLAYER_CONFIG, YouTubePlayerConfig} from './youtube-player';
7 changes: 2 additions & 5 deletions src/youtube-player/youtube-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,10 @@
*/

import {NgModule} from '@angular/core';

import {YouTubePlayer} from './youtube-player';

const COMPONENTS = [YouTubePlayer];

@NgModule({
imports: COMPONENTS,
exports: COMPONENTS,
imports: [YouTubePlayer],
exports: [YouTubePlayer],
})
export class YouTubePlayerModule {}
34 changes: 22 additions & 12 deletions src/youtube-player/youtube-player.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import {waitForAsync, ComponentFixture, TestBed} from '@angular/core/testing';
import {Component, ViewChild} from '@angular/core';
import {YouTubePlayer, DEFAULT_PLAYER_WIDTH, DEFAULT_PLAYER_HEIGHT} from './youtube-player';
import {Component, Provider, ViewChild} from '@angular/core';
import {
YouTubePlayer,
DEFAULT_PLAYER_WIDTH,
DEFAULT_PLAYER_HEIGHT,
YOUTUBE_PLAYER_CONFIG,
} from './youtube-player';
import {createFakeYtNamespace} from './fake-youtube-player';
import {Subscription} from 'rxjs';

const VIDEO_ID = 'a12345';
const YT_LOADING_STATE_MOCK = {loading: 1, loaded: 0};
const TEST_PROVIDERS: Provider[] = [
{
provide: YOUTUBE_PLAYER_CONFIG,
useValue: {
// Disable API loading in tests since we don't want to pull in any additional scripts.
loadApi: false,
},
},
];

describe('YoutubePlayer', () => {
let playerCtorSpy: jasmine.Spy;
Expand All @@ -20,12 +34,6 @@ describe('YoutubePlayer', () => {
playerSpy = fake.playerSpy;
window.YT = fake.namespace;
events = fake.events;

TestBed.configureTestingModule({
imports: [TestApp, StaticStartEndSecondsApp, NoEventsApp],
});

TestBed.compileComponents();
}));

describe('API ready', () => {
Expand Down Expand Up @@ -553,6 +561,7 @@ describe('YoutubePlayer', () => {
selector: 'test-app',
standalone: true,
imports: [YouTubePlayer],
providers: TEST_PROVIDERS,
template: `
@if (visible) {
<youtube-player #player [videoId]="videoId" [width]="width" [height]="height"
Expand All @@ -564,8 +573,7 @@ describe('YoutubePlayer', () => {
(playbackQualityChange)="onPlaybackQualityChange($event)"
(playbackRateChange)="onPlaybackRateChange($event)"
(error)="onError($event)"
(apiChange)="onApiChange($event)">
</youtube-player>
(apiChange)="onApiChange($event)"/>
}
`,
})
Expand All @@ -591,8 +599,9 @@ class TestApp {
@Component({
standalone: true,
imports: [YouTubePlayer],
providers: TEST_PROVIDERS,
template: `
<youtube-player [videoId]="videoId" [startSeconds]="42" [endSeconds]="1337"></youtube-player>
<youtube-player [videoId]="videoId" [startSeconds]="42" [endSeconds]="1337"/>
`,
})
class StaticStartEndSecondsApp {
Expand All @@ -602,7 +611,8 @@ class StaticStartEndSecondsApp {
@Component({
standalone: true,
imports: [YouTubePlayer],
template: `<youtube-player [videoId]="videoId"></youtube-player>`,
providers: TEST_PROVIDERS,
template: `<youtube-player [videoId]="videoId"/>`,
})
class NoEventsApp {
@ViewChild(YouTubePlayer) player: YouTubePlayer;
Expand Down
Loading
Loading