1

I have tried in JSFiddle, on my local server, and on a production server, and I cannot figure out what is wrong with my event handler. It never fires. Also, when I run in Google Chrome I get no errors, when I run in Safari, I get:

Blocked a frame with origin "http://www.youtube.com" from accessing a frame with origin "http://jsfiddle.net". Protocols, domains, and ports must match.

What am I missing?

Code:

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

<iframe 
    id="player" 
    width="426" 
    height="240" 
    src="http://www.youtube.com/embed/21eGxOKUxeM?enablejsapi=1&origin=*" 
    frameborder="0" 
    allowfullscreen></iframe>

<script>
    function player_state_changed(state) {
        alert('state changed');           
    }
    document.getElementById('player').addEventListener(
        'onStateChange', player_state_changed
    );
</script>

Example in JS Fiddle

Justin
  • 26,443
  • 16
  • 111
  • 128

2 Answers2

3

@Justin -- in response to your comment, here's some code and a short explanation. There are two things to watch out for ... first of all, I generally load the iFrame API library like this:

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

This way, it won't load until the page is loaded, which is key to the second step -- when the code loads, it will automatically call the function onYouTubeIframeAPIReady, which you need to define. It's within this function that you can create your bound player object. If you try to do it outside this callback function, you A) run the risk of creating your player object before the iframe API library loads which means it couldn't trigger any player functions, and B) can't bind to elements that already exist on the page as they might not yet be loaded. It looks like this:

var player;
function onYouTubeIframeAPIReady() {
   player = new YT.Player("ytplayer", {
    events: {
      'onStateChange': onPlayerStateChange
    }
  });
}

function onPlayerStateChange(event) {
  if (event.data == YT.PlayerState.PLAYING) {
    alert("now playing ...");
  }
}
</script>

This assumes I've given my iframe the id of ytplayer, and that I've appended "?enablejsapi=1" to the URL of the iframe's src attribute.

Here's a working fiddle: http://jsfiddle.net/jlmcdonald/PQy4C/261/

jlmcdonald
  • 13,408
  • 2
  • 54
  • 64
  • The linked fiddle doesn't work for me on either Firefox 42.0a2 or Chrome 45.0.2454.99 m. I've checked the YouTube docs, and your method is consistent with them. Really bizarre that it doesn't work. – Henry Merriam Sep 24 '15 at 18:43
  • 1
    In the 18 months since I posted this answer, YouTube started enforcing the need to append the "enablejsapi=1" parameter to the URL of the iframe's src attribute ... otherwise, the iframe loads the movie in a sandbox that the iframe API can't bind to. I've edited the answer above, including changing the fiddle link to a new fiddle that demonstrates that this works. – jlmcdonald Sep 24 '15 at 20:37
  • Much appreciated! It now works in Chrome, but not Firefox—which is consistent with what I get on my own site. (I do get the `onReady` event in Firefox, but the many workarounds I've found haven't fixed `onStateChange`.) I don't understand why Google can't figure out how to emit an event across modern browsers in 2015, so I hope there's just something simple we're missing! – Henry Merriam Sep 25 '15 at 00:38
1

I was able to get it working by including the video via JavaScript instead of an <iframe>. Still not sure why it wouldn't work if I had the <iframe> added myself... but this does work:

http://jsfiddle.net/Mm88p/4/

<div id="player"></div>

<script src="https://www.youtube.com/player_api"></script>
<script>
// YouTube API
player = new YT.Player('player', {
    height: '240',
    width: '426',
    videoId: '21eGxOKUxeM',        
    events: {      
      'onStateChange': onPlayerStateChange
    }
  });

function onPlayerStateChange(state) {                 
    alert('state changed');           
}
</script>
Justin
  • 26,443
  • 16
  • 111
  • 128
  • 1
    Nice, you beat me to it. The other way is bugged :) – m59 Jan 15 '14 at 05:16
  • It works just fine to bind the iframe API to an existing iframe -- however, you can't just add an event listener like you could with the old AS3/Javascript API. Instead, you still create a YT.Player object and pass it the id of the iframe (or a DOM reference to your iframe). You omit the height, width, and videoId parameters, but include the events parameter to bind to your listener. – jlmcdonald Jan 15 '14 at 05:34
  • @jlmcdonald could you post an answer with code/JSFiddle? I couldn't get it to work, though I may just not be understanding you. This is what I tried based on your comment: http://jsfiddle.net/Mm88p/3/ – Justin Jan 15 '14 at 05:52
  • Agree. This apparently *should* work but it doesn't seem to actually work. If anyone can demonstrate it working I'd be thrilled. – Michael Natkin Mar 13 '14 at 18:43