44

I've been battling with the youtube iframe api for quite some time now. Somehow the method onYouTubeIframeAPIReady is not always triggered.

From the symptoms it seems a loading problem. No errors are shown in the inspector.

Here is my code:

HTML

<div id="player"></div>
          <script>
            videoId = 'someVideoId';
            var tag = document.createElement('script');
            tag.src = "//www.youtube.com/iframe_api";
            var firstScriptTag = document.getElementsByTagName('script')[0];
            firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
          </script>

JS

(called at the end of the page. I tried to place the code right after the above script and the result was the same.)

var isReady = false
  , player
  , poster
  , video;

$(function () {
$('.js-play').click(function (e) {
    e.preventDefault();
    interval = setInterval(videoLoaded, 100);
  });
});
function onYouTubeIframeAPIReady() {
  console.log(videoId)
  player = new YT.Player('player', {
    height: '445',
    width: '810',
    videoId: videoId,
    events: {
      'onReady': onPlayerReady//,
      //'onStateChange': onPlayerStateChange
    }
  });
}

function onPlayerReady(event) {
  isReady = true;
  console.log("youtube says play")
}

function videoLoaded (){
  if (isReady) {
      console.log("ready and play")
      poster.hide();
      video.show();

      $('body').trigger('fluidvideos');

      player.playVideo();
      clearInterval(interval);
  } 
}

The problem is that sometimes nothing gets printed by the console.log and nothing happens.

On mobile phones this happens all the time. Any ideas?

Liam
  • 27,717
  • 28
  • 128
  • 190
jribeiro
  • 3,387
  • 8
  • 43
  • 70

9 Answers9

73

It is not a timeout issue, and you should not need to fire this function manually.

Make sure your onYouTubeIframeAPIReady function is available at the global level, not nested (hidden away) within another function.

Liam
  • 27,717
  • 28
  • 128
  • 190
bcm
  • 5,470
  • 10
  • 59
  • 92
  • 1
    This is the correct solution, BY FAR. I had the same problem, but my function was in $(document).ready(). I moved it out and it worked. – Don Boots Mar 20 '13 at 19:38
  • 3
    Ain't there a custom event we could listen to instead of making the function available in the global scope? This feels really dirty! Something like `$('document').on('youTubeIframeAPIReady', function(e){});`... – Augustin Riedinger Feb 09 '15 at 14:30
  • @AugustinRiedinger did you find a suitable event to listen for? – Ben Swinburne May 18 '16 at 13:35
  • 2
    Not for now. Maybe a custom script: `window.onYouTubeIframeAPIReady = function() { document.dispatchEvent(new CustomEvent('onYouTubeIframeAPIReady', {})) })` – Augustin Riedinger Aug 22 '16 at 20:50
56

You can always append it to the window object to make sure it is evoked globally. This is helpful if your code is using amd.

window.onYouTubeIframeAPIReady = function() {}
Erick R Soto
  • 1,331
  • 9
  • 6
  • 1
    I know this is an old thread. For anyone else using requireJS this solution fixes the issue. – DavidT Aug 29 '14 at 11:35
  • Helped me with Meteor and the YouTube Iframe API package as well. – Kodie Grantham Aug 19 '16 at 15:17
  • This answer fixed an issue for me with the onYouTubeIframeAPIReady function not being called on in Safari only. The function was already in the global scope and was working fine in Firefox and Chrome. Appending it to window fixed the issue in Safari. – Yabsley May 31 '18 at 09:41
15

If you have to put in inside to a function, one possible solution is instead of:

function onYouTubeIframeAPIReady() {
  // func body...
}

you can declare it like this:

window.onYouTubeIframeAPIReady = function() {
  // func body...
}

In this case make sure, that you insert the certain script to the dom after the declaration (the insertBefore() call what is in the global scope now in your case):

Pengő Dzsó
  • 855
  • 1
  • 8
  • 17
10

If you load the IFrame Player API code asynchronously as follows:

<script>
var tag = document.createElement('script');
tag.src = "//www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
</script>

and you are also loading the script code like this:

<script src="http://www.youtube.com/player_api"></script>

then the function onYouTubeIframeAPIReady won't be called due to the same code is being loaded twice (the function is called just one time when the api is completely loaded).

So use just one of the ways according to your needs.

Liam
  • 27,717
  • 28
  • 128
  • 190
Joar
  • 224
  • 2
  • 14
8

After revisiting this, I found a working solution for me when using webpack (or I assume any other commongJS module system), or if you find the youtube api is ready before your own JS file, was to use an interval - until youtube provides some form of promise callback:

var checkYT = setInterval(function () {
    if(YT.loaded){
        //...setup video here using YT.Player()

        clearInterval(checkYT);
    }
}, 100);

This seemed to be the most error-proof in my case.

Joao
  • 2,696
  • 2
  • 25
  • 35
3

Make sure you are testing the page by having it actually served on the web, not just loaded locally. YouTube iFrame API behavior is inconsistent/nondeterministic

Community
  • 1
  • 1
Ben Wheeler
  • 6,788
  • 2
  • 45
  • 55
2

I actually made this to lazy load the youtube player. Seems bullet proof to me.

window.videoApiLoaded = [];
window.videoApiLoaded.youtube = false;

function loadYoutubeVideo(videoId) {

    window.onYouTubeIframeAPIReady = function() { document.dispatchEvent(new CustomEvent('onYouTubeIframeAPIReady', {})) };

    if(window.videoApiLoaded.youtube == false) {
        var tag = document.createElement('script');
        tag.src = "https://www.youtube.com/iframe_api";
        var firstScriptTag = document.getElementsByTagName('script')[0];
        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
        window.videoApiLoaded.youtube = true;
    }

    var player;

    document.addEventListener('onYouTubeIframeAPIReady', function (e) {
        player = new YT.Player('player', {
          height: '400',
          width: '600',
          videoId: videoId,
          events: {
            'onStateChange': onYtStateChange
          }
        });

    }, false);

}
Matt Loye
  • 1,301
  • 2
  • 11
  • 14
0

I had a similar problem, In my case onYouTubeIframeAPIReady was being called so fast that the actual implementation wasn't ready for the right call. So after youtube player_api call I added a simple implementation with a var for verification of onYouTubeIframeAPIReady just like that:

<script src="http://www.youtube.com/player_api"></script>
<script>
var playerReady = false
window.onYouTubeIframeAPIReady = function() { //simple implementation
    playerReady = true;
}
</script>
</header>
.
.
.
<body>
<script>
window.onYouTubeIframeAPIReady = function() { //real and fully implemented
    var player; // ... and so on ...
}
if(playerReady) { onYouTubeIframeAPIReady(); console.log("force to call again") }
</script>
Diogo Garcia
  • 536
  • 1
  • 8
  • 20
-7

I was able to make this work under almost all circumstances with a simple setTimeout on load of the page. Not ideal, I know, but it is just another check that fixes the problem most of the time.

setTimeout(function(){
    if (typeof(player) == 'undefined'){
        onYouTubeIframeAPIReady();
    }
}, 3000)
nosforz
  • 39
  • 4
  • 1
    This is wrong on every level. Even if we were waiting for the player to be ready, you would use setInterval. – Don Boots Mar 20 '13 at 19:37
  • Voted down as this is not the correct solution. bcm's solution is the correct way to do this. ... 'Make sure your onYouTubeIframeAPIReady function is available at the global level, not nested (hidden away) within another function.' – r8n5n Apr 18 '13 at 11:23