Using Youtube API with Angular 6 (Typescript)
Asked Answered
E

2

7

I'm able to get the Youtube Iframe API (somehow) working in my Angular 6 application. However, I would like to however feed it dynamic videoIds from a list that I control. My plan is to listen to events from the player and then load another video once its done playing the current video. But I could not seem to get past this issue. I'm still learning Typescript and Angular and I'm curious as to why my any method that I call from any function that I call from within the IFrame API player gets an Uncaught TypeError:

Here is my code:

import { Component, OnInit, AfterViewInit } from '@angular/core';
import { Video } from '../shared/model/video.model';
import { Subscription } from 'rxjs';
import { VideoPlayerService } from '../shared/services/video-player.service';

@Component({
  selector: 'ntv-video-player',
  templateUrl: './video-player.component.html',
  styleUrls: ['./video-player.component.css']
})
export class VideoPlayerComponent implements OnInit, AfterViewInit {

  player;
  video: Video;
  videos: Video[];
  videoSubscription: Subscription;

  constructor(private videoService: VideoPlayerService) { }

  ngAfterViewInit() {
    const doc = (<any>window).document;
    const playerApiScript = doc.createElement('script');
    playerApiScript.type = 'text/javascript';
    playerApiScript.src = 'https://www.youtube.com/iframe_api';
    doc.body.appendChild(playerApiScript);
  }

  ngOnInit() {

    (<any>window).onYouTubeIframeAPIReady = () => {
      this.player = new (<any>window).YT.Player('player', {
        height: '100%',
        width: '100%',
        // videoId: this.getVideo(),
        events: {
          'onReady': this.onPlayerReady,
          'onStateChange': this.onPlayerStateChange
        },
        playerVars: {
          autoplay: 1,
          controls: 0,
          modestbranding: 1,
          // playlist: 'UG3sfZKtCQI,ALZHF5UqnU4,x9ZkC3OgI78',
          rel: 0,
          showInfo: 0
        }
      });
    };
  }



  // The API calls this function when the player's state changes.
  onPlayerStateChange(event) {
    console.log('onPlayerStateChange');
    console.log(event.data);
  }

  // The API will call this function when the video player is ready
  onPlayerReady(event) {
    console.log(event);

    const videoId = this.getVideo();
    event.target.cueVideoById({
      'videoId': videoId
    });
    event.target.playVideo();
  }

  getVideo() {
    return '60ItHLz5WEA';
  }

}

And here is the error that I'm getting:

    Uncaught TypeError: Cannot read property 'getVideo' of undefined
    at push../src/app/video-player/video-player.component.ts.VideoPlayerComponent.onPlayerReady (video-player.component.ts:63)
    at M.h.G (www-widgetapi.js:48)
    at Y.h.o (www-widgetapi.js:92)
    at Y.h.H (www-widgetapi.js:105)
    at Wa.g (www-widgetapi.js:81)
    at Oa.f (www-widgetapi.js:70)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
    at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:188)
    at ZoneTask.push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask [as invoke] (zone.js:496)
    at invokeTask (zone.js:1540)
push../src/app/video-player/video-player.component.ts.VideoPlayerComponent.onPlayerReady    @   video-player.component.ts:63
h.G @   www-widgetapi.js:48
h.o @   www-widgetapi.js:92
h.H @   www-widgetapi.js:105
Wa.g    @   www-widgetapi.js:81
Oa.f    @   www-widgetapi.js:70
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask    @   zone.js:421
push../node_modules/zone.js/dist/zone.js.Zone.runTask   @   zone.js:188
push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask    @   zone.js:496
invokeTask  @   zone.js:1540
globalZoneAwareCallback

this.getVideo() seems to work if I call it from the 'videoId' property, but I'd like to be able to call it either onPlayerReady or onPlayerStateChange so I can make it more dynamic. Anybody have done this before? There's not a lot of documentation for Angular.

Thanks in advance.

Elvyn answered 11/9, 2018 at 3:6 Comment(0)
D
3

I think what your experiencing is a scope issue, your OnPlayerReady function is not being executed in the scope of your class, but rather the scope of the youtube player. I'm inferring this from the error message "Cannot read property 'getVideo' of undefined". At this point of execution "this" is not what it appears to be.

Try changing your event assignment code like this:

 events: {
          'onReady': (event) => { this.onPlayerReady(event); },
          'onStateChange': (event) => { this.onPlayerStateChange(event); }
        },
Der answered 11/9, 2018 at 5:31 Comment(1)
This helped me get past the issue. Thanks!Elvyn
E
1

Thanks Joel! This help me get past the issue. I did get an error at first because I was not passing a parameter. So I did it like this and it worked!

events: { 'onReady': (event) => { this.onPlayerReady(event); }, 
'onStateChange': (event) => { this.onPlayerStateChange(event); } 
},

I guess I'm having a hard time understanding the API's documentation and trying to translate it to Typescript. I just wish they'll add more documentation there. Also doing things like this (<any>window).onYouTubeIframeAPIReady = () => { just feels kinda hacky to me. Is there a better way of doing this?

Sorry I couldn't upvote your answer since I don't have enough rep points yet.

Elvyn answered 12/9, 2018 at 3:1 Comment(3)
Glad it got you going. I updated my answer. You could take a look at 3rd party npm packages for wrapping the youtube api like npmjs.com/package/angular-youtube-player or npmjs.com/package/ngx-youtube-player.Der
Thanks Joel. I will give those a look. I've seen those mentioned here on Stack Overlow in other posts but I was afraid it might add another level of complexity/learning curve for me. Thanks again.Elvyn
Noel - you could give this package a try as well. I found it to be the smallest and more simple to use (it's a single index.js file) although it's not in typescript. github.com/feross/yt-playerHall

© 2022 - 2024 — McMap. All rights reserved.