javascriptyoutube-apiyoutube-iframe-api

YT.Player doesn't return an instance with playVideo()


When I create an instance of YT.Player(HTMLIFrameElement, { options }) I get back an object that has:

But not playVideo, pauseVideo etc as described in the documentation.

I got a demo here: http://siesta-annotations.surge.sh/Siesta_webviewer_test/?page=3

I am creating the iframes via the DOM in trait.playable.youtube.js and adding the iframe to a documentFragment that eventually will be added to a div:

const element = document.createElement('iframe')
element.src = `https://www.youtube.com/embed/${id}?rel=0;&autoplay=${this.options.autostart ? 1 : 0};&enablejsapi=1;&origin=${location.hostname};`
element.style = this.inlineStyle

I then create an instance of YT.Player:

// nasty initialization because we're outside webpack and this is a demo
if (global.YT.loaded) {
  this.player = new global.YT.Player(element, {
    events: {
      'onStateChange': this.stateHandler,
      'onReady': onPlayerReady
    }
  })
} else {
  const oldHandler = global.onYouTubeIframeAPIReady
  global.onYouTubeIframeAPIReady = () => {
    if (oldHandler) oldHandler()

    this.player = new global.YT.Player(element, {
      events: {
        'onStateChange': this.stateHandler,
        'onReady': onPlayerReady
      }
    })
  }
}

The instance of YT.Player looks something like this:

YT.Player instance object tree

It looks like the minification process went very wrong. What am I doing wrong - how should I initialise YT.Player for my use-case?


Solution

  • Strangely enough you do not get access to the YTPlayer API until 'onReady' is called in the constructor player options object. https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player

    function onYouTubeIframeAPIReady() {
      new YT.Player('player', {
        height: '390',
        width: '640',
        videoId: 'M7lc1UVf-VE',
        events: {
          'onReady': function (event) {
            onPlayerReady(event.target)
          },
          'onStateChange': onPlayerStateChange
        }
      });
    }
    
    function onPlayerReady(player) {
      player.playVideo();
    }
    

    This of course only works for one player, so you need to roll out your own YouTube player manager if you want to handle multiple players. (I know the API is insane)

    A quick solution could be the following if you have iframes with youtube videos (untested):

    const YTManager = {
      videos: [],
      youtubeApiReady = false,
      initialize () {
        const matchId = /[^/]+$/
    
        this.youtubeApiReady = true
        
        document.querySelectorAll('iframe[src*="youtube"]')
          .forEach(iframe => this.videos.push({
             dom: iframe,
             player: null,
             videoId: iframe.src.exec(matchId) || iframe.src.exec(matchId)[0]
           }))
      },
      createPlayers () {
        if (this.youtubeApiReady === false) return
    
        this.videos.forEach(v => {
          new YT.Player(v.dom.id, {
            videoId: v.videoId,
            events: {
              'onReady': function (event) {
                v.player = event.target
              }
            }
          })
        })
      },
      find (id) {
        return this.videos.find(v => v.id === id)
      },
      play (id) {
        const video = this.find(id)
        if (this.youtubeApiReady && video && video.player) {
          video.playVideo()
        }
      },
      pause (id) {
        const video = this.find(id)
        if (this.youtubeApiReady && video && video.player) {
          video.pauseVideo()
        }
      },
    }
    
    window.onYouTubeIframeAPIReady = function onYouTubeIframeAPIReady() {
      // YouTube client side script has been loaded
      YTManager.youtubeApiReady = true
      YTManager.createPlayers()
    }
    
    document.addEventListener("DOMContentLoaded", function() {
      // all iframes has been parsed by the browser
      YTManager.initialize()
    });
    

    Of course you can not call YTManager.play or YTManager.pause without having the ID and wait for the YouTube script to load and then the video initialization to finish. All in all, a real mess. I'm sure you can come up with a better manager. I have written one at work that is better but also works with completely different objects and requirements, so it's not a good fit for general purpose YouTube video manager. But the one above is the gist of my current player.

    I recommend using iframes from the beginning - if anything goes wrong in your script, the videos will still be playable, you will just not have any control of them.

    Iframed youtube videos looks like this:

    <iframe
      id="uniqueDOMElementID1"
      width="560"
      height="315"
      src="https://www.youtube.com/embed/bHQqvYy5KYo"
      frameborder="0"
      allow="autoplay; encrypted-media"
      allowfullscreen></iframe>