23

I have a slider which includes 4 youtube videos that are embedded via the iframe embed code

http://www.youtube.com/embed/'.$i.'?enablejsapi=1

I'm trying to make the onStateChange event of any of the four videos call a function I have called stopCycle() which will stop the slider when the video begins to play. The iframes do not have an id. I'm not sure about how to capture this event properly and could use any advice as to what i'm doing wrong.

<script charset="utf-8" type="text/javascript" src="http://www.youtube.com/player_api"></script>

var playerObj = document.getElementById("tab2"); // the container for 1 of the 4 iframes

playerObj.addEventListener("onStateChange", "stopCycle");

function stopCycle(event) {
    alert('Stopped!');
}
Rob W
  • 341,306
  • 83
  • 791
  • 678
Dave Kiss
  • 10,289
  • 11
  • 53
  • 75

4 Answers4

44

The YouTube Frame API does support existing frames. To improve the usage, I have created some helper functions. Have a look at the code + comments below and the demo: http://jsfiddle.net/YzvXa/197

To bind functions to existent frames, you have to pass an ID reference to the frame. In your case, the frame is contained within a container with id="tab2". I have defined a custom function for an easier implementation:

function getFrameID(id){
    var elem = document.getElementById(id);
    if (elem) {
        if(/^iframe$/i.test(elem.tagName)) return id; //Frame, OK
        // else: Look for frame
        var elems = elem.getElementsByTagName("iframe");
        if (!elems.length) return null; //No iframe found, FAILURE
        for (var i=0; i<elems.length; i++) {
           if (/^https?:\/\/(?:www\.)?youtube(?:-nocookie)?\.com(\/|$)/i.test(elems[i].src)) break;
        }
        elem = elems[i]; //The only, or the best iFrame
        if (elem.id) return elem.id; //Existing ID, return it
        // else: Create a new ID
        do { //Keep postfixing `-frame` until the ID is unique
            id += "-frame";
        } while (document.getElementById(id));
        elem.id = id;
        return id;
    }
    // If no element, return null.
    return null;
}

// Define YT_ready function.
var YT_ready = (function() {
    var onReady_funcs = [], api_isReady = false;
    /* @param func function     Function to execute on ready
     * @param func Boolean      If true, all qeued functions are executed
     * @param b_before Boolean  If true, the func will added to the first
                                 position in the queue*/
    return function(func, b_before) {
        if (func === true) {
            api_isReady = true;
            while (onReady_funcs.length) {
                // Removes the first func from the array, and execute func
                onReady_funcs.shift()();
            }
        } else if (typeof func == "function") {
            if (api_isReady) func();
            else onReady_funcs[b_before?"unshift":"push"](func); 
        }
    }
})();
// This function will be called when the API is fully loaded
function onYouTubePlayerAPIReady() {YT_ready(true)}

// Load YouTube Frame API
(function() { // Closure, to not leak to the scope
  var s = document.createElement("script");
  s.src = (location.protocol == 'https:' ? 'https' : 'http') + "://www.youtube.com/player_api";
  var before = document.getElementsByTagName("script")[0];
  before.parentNode.insertBefore(s, before);
})();

// Previously, core functions were defined. Look ahead for the implementation:

var player; //Define a player object, to enable later function calls, without
            // having to create a new class instance again.

// Add function to execute when the API is ready
YT_ready(function(){
    var frameID = getFrameID("tabs2");
    if (frameID) { //If the frame exists
        player = new YT.Player(frameID, {
            events: {
                "onStateChange": stopCycle
            }
        });
    }
});

// Example: function stopCycle, bound to onStateChange
function stopCycle(event) {
    alert("onStateChange has fired!\nNew state:" + event.data);
}

If you want to invoke additional functions at a later point, e.g. mute a video, use:

player.mute();
  • If you only have to call simple single-direction functions, it's not necessary to use this code. Instead, use the function callPlayer as defined at this answer.
  • If you want to implement this feature for multiple frames, simultaneously, have a look at this answer. Also includes a detailed explanation of getFrameID and YT_ready.
Community
  • 1
  • 1
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • can you give an example of what i'd need to do to utilize that function with my scenario? – Dave Kiss Nov 03 '11 at 01:19
  • @DaveKiss If the IFrames are dynamically generated using the YouTube Frame API, you can have a look at the second link. Otherwise, you have to wait a bit, because two-way event listeners are not yet implemented. I'm working on that. – Rob W Nov 03 '11 at 11:37
  • Can you update this question when that feature has been implemented? – Dave Kiss Nov 03 '11 at 16:51
  • @RobW's script seems awesome, a simple question but how can I get the video to play on the stopCycle(event)? I guessed at player.playVideo() - like the YouTube API docs, but this doesn't seem to work for me http://code.google.com/apis/youtube/iframe_api_reference.html#Examples- am I doing something wrong? – SparrwHawk Jan 20 '12 at 23:44
  • `player.playVideo()` should work. Are you using multiple iframes? If "it" does not work, it would be helpful if you create a new question with more details: Relevant code and a link to this question, for the reference. – Rob W Jan 20 '12 at 23:52
  • Thanks Rob, yes using multiple iframes, will create a jsfiddle and link to it here. – SparrwHawk Jan 21 '12 at 00:01
  • 1
    @RobW I've provided an example on my original question so you can see what I'm trying to do - basically start a video when clicking the customised thumbnail. Here is the link to my question for future visitors - http://stackoverflow.com/questions/8948403/youtube-api-target-existing-iframe – SparrwHawk Jan 21 '12 at 00:25
  • @RobW, how would I modify this to look for a specific onStateChange integer? The problem with onStateChange in general is the video sends an onStateChange of -1 when it's loaded. – absynthe minded web smith May 20 '13 at 16:00
  • 1
    @absynthemindedwebsmith Use an `if`-statement, e.g.: `if (event.data == -1) return;` – Rob W May 20 '13 at 16:02
  • Thanks for the super quick reply. – absynthe minded web smith May 20 '13 at 16:31
  • The onstateChanged does not always fire, because the state may be cued already, so after invocing the player with `new YT.Player` check for the state with `player.getPlayerState()`. If is is `5` ( `== YT.PlayerState.CUED`) call the function, you wanted to call in the first place – yunzen Sep 03 '14 at 09:49
  • I was wondering if you know about any way around the frame boundary when I want to access the player's native HTMLMediaElement interface? For example, if I want to get a property of the `video` element like `webkitVideoBytesDecoded` from outside the iframe? – slhck Mar 16 '15 at 14:33
  • @slhck That's not possible due to the same-origin policy. If you really want that info, you could try to recreate the player's functionality in JS. That's quite a lot of work, so... – Rob W Mar 16 '15 at 14:35
  • Hm, I guess that's not practical :) But what about the `origin` parameter mentioned in the [iFrame API Reference](https://developers.google.com/youtube/iframe_api_reference)? Does that do anything useful then? – slhck Mar 16 '15 at 14:52
  • @slhck That's used for the communication between the iframe content and the embedder, and using it will not magically get the same origin policy to disappear (this policy is enforced by the browser). – Rob W Mar 16 '15 at 14:55
  • Thanks for your explanations. I guess I'll have to convince YouTube devs to give me the information then :) – slhck Mar 16 '15 at 15:10
  • Great solution. I had some "decorative" videos that I wanted to use in place of images, and this allows me to autoplay and mute them (on desktop browsers) on my HTML5 AngularJS Web App. Sadly, "mute" is not allowed by iOS, which will force me to make duplicate of the videos without the sound. Other than that, you get full control of your video, even in iOS (Simulator 8.1, Ionic framework). – Christian Bonato Mar 21 '15 at 21:53
  • Having both `var YT_ready = (function() { ... }` and `YT_ready(function() { ... }` strikes me as quite unusual. Is it an anti-pattern? Is it necessary, or could it be rewritten for enhanced readability? – trailing slash Jan 07 '16 at 20:45
  • @trailingslash The first is a function definition using the module pattern, the second one is a function call that takes a callback. They are different and both necessary. – Rob W Jan 07 '16 at 20:48
  • @RobW Ah, a dense moment on my part. Thank you for your reply. – trailing slash Jan 07 '16 at 20:53
  • This works great, I came here because I couldn't find a solution to _hide a video title with the youtube iframe API_. For others with the same issue, I followed this post with showinfo=0 in the query string. It works well, hides the title and fires events. – Davey Mar 07 '17 at 15:41
1

Since yesterday this isn't working any more. The onStateChange isn't fired. Jeffrey posted an quick fix but I am not able to update the above answer

More info about this issue https://code.google.com/p/gdata-issues/issues/detail?id=4706

Got it working :-) with the fix

Add the following function

function onReady() {
    player.addEventListener('onStateChange', function(e) {
        console.log('State is:', e.data);
    });
}

and before "onStateChange": stopCycle add "onReady": onReady,

ceasar
  • 1,512
  • 3
  • 17
  • 25
1

You can use the tubeplayer plugin, it comes with lots of events to listen for.

topek
  • 18,609
  • 3
  • 35
  • 43
0

I have this feature on my chat that enables users to post vids from youtube. The url of the vid is attached as source of existing iframe with id.

What i notice is that -although ?enablejsapi=1 is added - that the script only works far existing iframes on pageload.

How would one go about when wanting this script to bind after a new source is set for the iframe?

ingridsede
  • 329
  • 1
  • 5
  • 19