165

I want to be able to control iframe based YouTube players. This players will be already in the HTML, but I want to control them via the JavaScript API.

I've been reading the documentation for the iframe API which explain how to add a new video to the page with the API, and then control it with the YouTube player functions:

var player;
function onYouTubePlayerAPIReady() {
    player = new YT.Player('container', {
        height: '390',
        width: '640',
        videoId: 'u1zgFlCw8Aw',
        events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
        }
    });
}

That code creates a new player object and assigns it to 'player', then inserts it inside the #container div. Then I can operate on 'player' and call playVideo(), pauseVideo(), etc. on it.

But I want to be able to operate on iframe players which are already on the page.

I could do this very easily with the old embed method, with something like:

player = getElementById('whateverID');
player.playVideo();

But this doesn't work with the new iframes. How can I assign a iframe object already on the page and then use the API functions on it?

Silviya
  • 30
  • 4
agente_secreto
  • 7,959
  • 16
  • 57
  • 83
  • I have written an abstraction for working with YouTube IFrame API https://github.com/gajus/playtube – Gajus Feb 22 '15 at 19:31
  • In case you're dumb like me, the iframe api script is here: https://www.youtube.com/iframe_api – slim Sep 19 '21 at 19:44

7 Answers7

336

Fiddle Links: Source code - Preview - Small version
Update: This small function will only execute code in a single direction. If you want full support (eg event listeners / getters), have a look at Listening for Youtube Event in jQuery

As a result of a deep code analysis, I've created a function: function callPlayer requests a function call on any framed YouTube video. See the YouTube Api reference to get a full list of possible function calls. Read the comments at the source code for an explanation.

On 17 may 2012, the code size was doubled in order to take care of the player's ready state. If you need a compact function which does not deal with the player's ready state, see http://jsfiddle.net/8R5y6/.

/**
 * @author       Rob W <gwnRob@gmail.com>
 * @website      https://stackoverflow.com/a/7513356/938089
 * @version      20190409
 * @description  Executes function on a framed YouTube video (see website link)
 *               For a full list of possible functions, see:
 *               https://developers.google.com/youtube/js_api_reference
 * @param String frame_id The id of (the div containing) the frame
 * @param String func     Desired function to call, eg. "playVideo"
 *        (Function)      Function to call when the player is ready.
 * @param Array  args     (optional) List of arguments to pass to function func*/
function callPlayer(frame_id, func, args) {
    if (window.jQuery && frame_id instanceof jQuery) frame_id = frame_id.get(0).id;
    var iframe = document.getElementById(frame_id);
    if (iframe && iframe.tagName.toUpperCase() != 'IFRAME') {
        iframe = iframe.getElementsByTagName('iframe')[0];
    }

    // When the player is not ready yet, add the event to a queue
    // Each frame_id is associated with an own queue.
    // Each queue has three possible states:
    //  undefined = uninitialised / array = queue / .ready=true = ready
    if (!callPlayer.queue) callPlayer.queue = {};
    var queue = callPlayer.queue[frame_id],
        domReady = document.readyState == 'complete';

    if (domReady && !iframe) {
        // DOM is ready and iframe does not exist. Log a message
        window.console && console.log('callPlayer: Frame not found; id=' + frame_id);
        if (queue) clearInterval(queue.poller);
    } else if (func === 'listening') {
        // Sending the "listener" message to the frame, to request status updates
        if (iframe && iframe.contentWindow) {
            func = '{"event":"listening","id":' + JSON.stringify(''+frame_id) + '}';
            iframe.contentWindow.postMessage(func, '*');
        }
    } else if ((!queue || !queue.ready) && (
               !domReady ||
               iframe && !iframe.contentWindow ||
               typeof func === 'function')) {
        if (!queue) queue = callPlayer.queue[frame_id] = [];
        queue.push([func, args]);
        if (!('poller' in queue)) {
            // keep polling until the document and frame is ready
            queue.poller = setInterval(function() {
                callPlayer(frame_id, 'listening');
            }, 250);
            // Add a global "message" event listener, to catch status updates:
            messageEvent(1, function runOnceReady(e) {
                if (!iframe) {
                    iframe = document.getElementById(frame_id);
                    if (!iframe) return;
                    if (iframe.tagName.toUpperCase() != 'IFRAME') {
                        iframe = iframe.getElementsByTagName('iframe')[0];
                        if (!iframe) return;
                    }
                }
                if (e.source === iframe.contentWindow) {
                    // Assume that the player is ready if we receive a
                    // message from the iframe
                    clearInterval(queue.poller);
                    queue.ready = true;
                    messageEvent(0, runOnceReady);
                    // .. and release the queue:
                    while (tmp = queue.shift()) {
                        callPlayer(frame_id, tmp[0], tmp[1]);
                    }
                }
            }, false);
        }
    } else if (iframe && iframe.contentWindow) {
        // When a function is supplied, just call it (like "onYouTubePlayerReady")
        if (func.call) return func();
        // Frame exists, send message
        iframe.contentWindow.postMessage(JSON.stringify({
            "event": "command",
            "func": func,
            "args": args || [],
            "id": frame_id
        }), "*");
    }
    /* IE8 does not support addEventListener... */
    function messageEvent(add, listener) {
        var w3 = add ? window.addEventListener : window.removeEventListener;
        w3 ?
            w3('message', listener, !1)
        :
            (add ? window.attachEvent : window.detachEvent)('onmessage', listener);
    }
}

Usage:

callPlayer("whateverID", function() {
    // This function runs once the player is ready ("onYouTubePlayerReady")
    callPlayer("whateverID", "playVideo");
});
// When the player is not ready yet, the function will be queued.
// When the iframe cannot be found, a message is logged in the console.
callPlayer("whateverID", "playVideo");

Possible questions (& answers):

Q: It doesn't work!
A: "Doesn't work" is not a clear description. Do you get any error messages? Please show the relevant code.

Q: playVideo does not play the video.
A: Playback requires user interaction, and the presence of allow="autoplay" on the iframe. See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes and https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide

Q: I have embedded a YouTube video using <iframe src="http://www.youtube.com/embed/As2rZGPGKDY" />but the function doesn't execute any function!
A: You have to add ?enablejsapi=1 at the end of your URL: /embed/vid_id?enablejsapi=1.

Q: I get error message "An invalid or illegal string was specified". Why?
A: The API doesn't function properly at a local host (file://). Host your (test) page online, or use JSFiddle. Examples: See the links at the top of this answer.

Q: How did you know this?
A: I have spent some time to manually interpret the API's source. I concluded that I had to use the postMessage method. To know which arguments to pass, I created a Chrome extension which intercepts messages. The source code for the extension can be downloaded here.

Q: What browsers are supported?
A: Every browser which supports JSON and postMessage.

  • IE 8+
  • Firefox 3.6+ (actually 3.5, but document.readyState was implemented in 3.6)
  • Opera 10.50+
  • Safari 4+
  • Chrome 3+

Related answer / implementation: Fade-in a framed video using jQuery
Full API support: Listening for Youtube Event in jQuery
Official API: https://developers.google.com/youtube/iframe_api_reference

Revision history

  • 17 may 2012
    Implemented onYouTubePlayerReady: callPlayer('frame_id', function() { ... }).
    Functions are automatically queued when the player is not ready yet.
  • 24 july 2012
    Updated and successully tested in the supported browsers (look ahead).
  • 10 october 2013 When a function is passed as an argument, callPlayer forces a check of readiness. This is needed, because when callPlayer is called right after the insertion of the iframe while the document is ready, it can't know for sure that the iframe is fully ready. In Internet Explorer and Firefox, this scenario resulted in a too early invocation of postMessage, which was ignored.
  • 12 Dec 2013, recommended to add &origin=* in the URL.
  • 2 Mar 2014, retracted recommendation to remove &origin=* to the URL.
  • 9 april 2019, fix bug that resulted in infinite recursion when YouTube loads before the page was ready. Add note about autoplay.
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • @RobW I tried that actually. Seems like the JSON in error is not the one in your script, but inside the iframe as a part of youtube's API. – Fresheyeball Nov 09 '11 at 07:07
  • @RobW thanks for this nice snippet. Have you found any ways to use the Message Event instead of using the youtube JS API in order to add an event listener? – brillout Dec 15 '11 at 17:48
  • @brillout.com The `PostMessage` method is based on the YT JS API (`?enablejsapi=1`). Without enabling the JS API, the `postMessage` method will not do anything, See the [linked answer](http://stackoverflow.com/questions/7988476/listening-for-youtube-event-in-jquery/7988536#7988536) for an easy implementation of event listeners. I have also created, but not published, readable code to communicate with the frame. I decided to not publish it, because its effect is similar to the default YouTube Frame API. – Rob W Dec 15 '11 at 17:55
  • Note: **This method does not work in – Rob W Feb 26 '12 at 10:29
  • Does anyone have any idea why the `window.onload` event doesn't work in webkit? The demo (and my own tests) autoplay in Firefox, but not in Chrome or Safari. The button links to play, pause, etc do work, however. – Tim Mackey May 16 '12 at 23:04
  • I managed to get the autoplay to work by adding a `setTimeout()` wrapper, but this is a really hacky solution. Anyone have anything better? (Here's a fork of the original demo with the fix: http://jsfiddle.net/LMqL9/) – Tim Mackey May 16 '12 at 23:26
  • @TimMackey I have updated the method so that it automatically takes care of the player's ready state. `callPlayer("whateverID", "playVideo")` now executes the function when the player is ready. Another method: `callPlayer("whateverID", function() {callPlayer('whateverID', 'playVideo');});`. – Rob W May 17 '12 at 10:00
  • @RobW That fixed the problem, thanks! In the end I used your code from this question, because I needed a couple event listeners: http://stackoverflow.com/questions/7988476/listening-for-youtube-event-in-javascript-or-jquery – Tim Mackey May 17 '12 at 18:41
  • This does not work in any browser. Even the Jsfiddle does not work. Chrome shows this error: Unsafe JavaScript attempt to access frame with URL http://jsfiddle.net/hrVcy/ from frame with URL http://www.youtube.com/embed/u1zgFlCw8Aw?enablejsapi=1. Domains, protocols and ports must match. – McFarlane Jul 24 '12 at 12:15
  • @McFarlane Revised and tested code in all listed browsers - see http://jsfiddle.net/g6P5H/ (the lite version which does not queue events hasn't changed, because it still worked fine: http://jsfiddle.net/8R5y6/). – Rob W Jul 24 '12 at 21:32
  • Off Topic: From a similarly named friend who maintains the Drupal Media: Youtube module, our users thank you. – RobW Sep 10 '12 at 15:08
  • amazing work! I managed to make it perfectly works for function with no return value. I'm now wondering how obtain the result of a function like "getCurrentTime"? Thanks! – arnaud.breton Jun 14 '13 at 00:45
  • @RobW Truly brilliant. Using something similar, is it possible to detect when the video has finished? – ᴍᴀᴛᴛ ʙᴀᴋᴇʀ Sep 06 '13 at 13:04
  • 2
    @MatthewBaker That requires listening to the message event and parsing the status of the result. This is not as easy as simple calls like `playVideo`, so I recommend to use the official API for that. See https://developers.google.com/youtube/iframe_api_reference#Events. – Rob W Sep 06 '13 at 14:30
  • I'm still having to wrap my initial call (e.g. to mute) in a timeout block. Not sure if there is a race condition here somewhere. I was unable to reproduce using a simple JS Fiddle example though. Is anyone else having similar problems? – Willster Jan 08 '14 at 14:10
  • This doesn't seem to work anymore (tested the jsfiddle page on PC: Chrome, FF, and IE 11 which actually crashed on load, Mac: Chrome). Any idea how to make this work again? No JS errors by the way, just doesn't respond to play/pause/stop commands. – TheZuck Jan 26 '14 at 09:06
  • @TheZuck Works for me on Chromium 31, http://jsfiddle.net/g6P5H/214/show/. Did you append `&origin=*` to the player URL? – Rob W Jan 26 '14 at 09:38
  • I did not test Chromium. However, if it causes a crash when there's no &origin=* it's hardly something I'm inclined to use... Can't really tell the customer: "listen, it's not my fault that your customers' browser is crashing, YOU didn't append &origin=* to your youtube url..." – TheZuck Jan 29 '14 at 11:50
  • Hey @RobW, when you have time, could you look at my question too? http://stackoverflow.com/questions/22854231/playing-youtube-videos-from-embedded-player – Maximus S Apr 04 '14 at 05:41
  • what a fine answer! @Rob W by the way, functions like playVideo(). pauseVideo etc didn't take any parameter, how to do it if we want to use functions like loadVideoById("bHQqvYy5KYo", 5, "large") ? do i just put all that syntax as a 'func' parameter? – Ferry Tan Jun 02 '14 at 04:22
  • @Rob W actually, the function i'm going to use is cuePlaylist(String|Array ...etc).. that's where i'm stuck at, so i'm confused in how to pass that array to the 'args' in your CallPlayer function... for example for cuePlaylist, i need 4 parameters: array of String, Number, Number,String.. do i only need to push that 4 parameters to the parameter 'args' in your CallPlayer function? (ps: sorry i'm actually weak at javascript, i'm calling the function from java android) and thank you for replying – Ferry Tan Jun 02 '14 at 08:02
  • @ffyeah Yes, you need to put these 4 arguments in the array: `callPlayer('whateverID', 'funcname', ['arg1', 'arg2', 'arg3', 'etc']);` – Rob W Jun 02 '14 at 08:09
  • @Rob W hi rob sorry to bother you once more, so i have this syntax: var args = "[['8N-K4lcI7Jg','cu4uF6XVnO8','p0jPtpCgsRw'],1,0,'highres']"; and i call callPlayer("container", "cuePlaylist", args); but the player didn't cue the videos, can you help me rob? – Ferry Tan Jun 03 '14 at 02:50
  • @Rob W ah sorry it's a mis-syntax i used, detected the args as string not array haha.. now it's queueing the playlist, but the player said invalid parameter.. what did i do wrong? so i've listed 4 parameter for cuePlaylist(String|Array,Number,Number,String) but it tells me invalid parameter – Ferry Tan Jun 03 '14 at 02:57
  • i'm calling callPlayer('container','cuePlaylist',[['id1','id2','id3'],1,0,'highres']); did i do something wrong on the syntax? – Ferry Tan Jun 03 '14 at 03:55
  • 1
    @ffyeahh I don't see any obvious error. Please ask a new question with a self-contained steps-to-reproduce instead of adding questions in comments to this answer. – Rob W Jun 03 '14 at 08:39
  • ah it's now working! i wrapped the [] inside '' so the javascript read string instead of array. my bad haha, thank you so much for your help @Rob W , this is a great code from you – Ferry Tan Jun 03 '14 at 08:55
  • I had to change domReady to `domReady = document.readyState == 'complete' || document.readyState == 'interactive'`. Otherwise it would freeze the page in some situtations in an endless loop. I have not fully tested this change yet, but so far it appears to work in a variety of situations. – Spectre87 Oct 26 '15 at 20:46
  • great answer dude. would you happen to know the answer to my recent youtube javascript question?: http://stackoverflow.com/questions/37724246/get-youtube-playlist-index – Webeng Jun 09 '16 at 11:11
  • I do not want to be rude but no one have a version that you don't need to add javascript inside the a href and use a query selector or can create control button on the fly – Gino Sep 28 '17 at 16:16
  • @RobW: I attached your code as a file, then tried calling it as function onYouTubeIframeAPIReady() { $('.playerLocation').on('mouseover',function(){ callPlayer("youtube-video", function() { // This function runs once the player is ready ("onYouTubePlayerReady") callPlayer("youtube-video", "playVideo"); }); }); } but I get the error Uncaught ReferenceError: callPlayer is not defined – LauraNMS Jul 26 '18 at 17:40
  • Tried setPlaybackRate(0.25): Uncaught TypeError: a.getDuration is not a function at f (js?id=G-985756462N&l=dataLayer&cx=c:313:224) at Object.onStateChange (js?id=G-985756462N&l=dataLayer&cx=c:315:400) at js?id=G-985756462N&l=dataLayer&cx=c:316:456 at n.da (www-widgetapi.js:469:277) at ho (www-widgetapi.js:991:52) at n.Ka (www-widgetapi.js:1020:318) at www-widgetapi.js:979:215 at Xh.g (www-widgetapi.js:566:28) – php-b-grader Aug 09 '22 at 02:24
38

Looks like YouTube has updated their JS API so this is available by default! You can use an existing YouTube iframe's ID...

<iframe id="player" src="http://www.youtube.com/embed/M7lc1UVf-VE?enablejsapi=1&origin=http://example.com" frameborder="0"></iframe>

...in your JS...

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

function onPlayerStateChange() {
  //...
}

...and the constructor will use your existing iframe instead of replacing it with a new one. This also means you don't have to specify the videoId to the constructor.

See Loading a video player

CletusW
  • 3,890
  • 1
  • 27
  • 42
  • 1
    @raven you're missing the autoplay=1 parameter in the url. In your example url, it would be http://www.youtube.com/embed/M7lc1UVf-VE?enablejsapi=1& **autoplay=1** &origin=http://example.com – alengel Feb 03 '14 at 21:45
  • @alengel he does not want to use the autoplay-url parameter. Instead he tries to start the video by using the js-APIs autoplay function. But for some reason the onYouTubeIframeAPIReady() function is not invoked. – Humppakäräjät Feb 04 '15 at 18:21
  • @raven I figured it out. 1) remove the &origin=example.com from the iframe url. 2) in the "Frameworks & Extensions" section of your jsfiddle set the second drop down menu to "No wrap in - " 3) add the youtube iframe api as external resource (https://www.youtube.com/iframe_api); I forked your fiddle and applied these changes: http://jsfiddle.net/e97famd1/1/ – Humppakäräjät Feb 05 '15 at 15:47
  • Any idea what `event` or `command` to send to the YT iframe to stop `listening` to the status? – mkhatib Feb 16 '16 at 19:54
  • @CletusW: I get this error: Uncaught (in promise) DOMException: The play() request was interrupted by a call to pause(). Promise (async) (anonymous) @ scripts.js:20 dispatch @ jquery-1.12.4.js:5226 elemData.handle @ jquery-1.12.4.js:4878 cast_sender.js:67 Uncaught DOMException: Failed to construct 'PresentationRequest': Presentation of an insecure document [cast:233637DE?capabilities=video_out%2Caudio_out&clientId=153262711713390989&autoJoinPolicy=tab_and_origin_scoped&defaultActionPolicy=cast_this_tab&launchTimeout=30000] is prohibited from a secure context. – LauraNMS Jul 26 '18 at 17:45
  • @CletusW: I figured it out. I had to initiate different players. Thanks for your reply! – LauraNMS Sep 04 '18 at 15:34
  • @raven you can add a list of `playerVars` to get it to autoplay ```player = new YT.Player('player', { 'playerVars': { 'autoplay': 0, 'controls': 0, }, events: { 'onStateChange': onPlayerStateChange } });``` https://developers.google.com/youtube/player_parameters?playerVersion=HTML5 – guest Nov 19 '19 at 23:21
  • 1
    The `enablejsapi=1` in the url is critical. I missed this at first. https://developers.google.com/youtube/iframe_api_reference#Example_Video_Player_Constructors – Chad von Nau Jul 14 '21 at 16:49
23

You can do this with far less code:

function callPlayer(func, args) {
    var i = 0,
        iframes = document.getElementsByTagName('iframe'),
        src = '';
    for (i = 0; i < iframes.length; i += 1) {
        src = iframes[i].getAttribute('src');
        if (src && src.indexOf('youtube.com/embed') !== -1) {
            iframes[i].contentWindow.postMessage(JSON.stringify({
                'event': 'command',
                'func': func,
                'args': args || []
            }), '*');
        }
    }
}

Working example: http://jsfiddle.net/kmturley/g6P5H/296/

Kim T
  • 5,770
  • 1
  • 52
  • 79
  • I really liked this way, just adapted it to work with an Angular directive so didn't need all the loop and pass the func depending on a toggle function with the scope (basically if the video is displayed -> autoplay; else -> pause the video). Thanks! – DD. Jan 07 '15 at 13:23
  • You should share the directive, could be useful! – Kim T Jan 07 '15 at 14:56
  • 1
    Here is an adaptation of the code for a Pen: http://codepen.io/anon/pen/qERdza I hope it helps! It also toggles pressing ESC key when you have the video on – DD. Jan 07 '15 at 15:52
  • This seems too good to be true! Are there any browser/security limitations to using this method? – Dan Jul 31 '15 at 09:27
  • Yes IE has a few limitations, especially IE10 which supports MessageChannel instead of postMessage: http://caniuse.com/#search=postMessage also be aware any Content Security Policies will also restrict use of this feature – Kim T Aug 02 '15 at 16:49
  • How to know youtube is playing? any callback from the youtube iframe, so outside can subscribe to? – Hammer Jul 14 '16 at 08:05
  • Best answer so far. Thanks. Can you show how to add fullscreen button? –  Aug 17 '16 at 11:35
  • This plays/stops every youtube video in the page at the same time. – a.barbieri Apr 13 '17 at 14:05
  • Yep, you can modify to call a single player e.g. by id: document.getElementById('myvideo1').contentWindow.postMessage(JSON.stringify({ 'event': 'command', 'func': func, 'args': args || [] }), '*'); – Kim T Apr 14 '17 at 14:44
  • It works for me except that, if I want to start the video, I have the first time to click on the native player command. After pausing the video all the external commands work. Have you an idea why they don't work since the page is loaded? Thank you! – Federico Tomasi Nov 02 '22 at 22:35
  • Autoplay of videos is blocked by default unless the user has interacted, to prevent spamming videos. You can read about it and workarounds here https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide – Kim T Nov 03 '22 at 03:00
  • @KimT tahnk you but that doesn't solve my problem. I have noticed that it doesn't work only in Chrome, while in Firefox everything is perfectly running. The strange thing is that I get, both in Chrome and in Firefox, an error (in the consolle) regarding cross origin resources (CORS). Anyway in Firefox, I can use whatevere element to start the video play since the page is loaded, while in Chrome I have still to make it play, just the first time, by native controls... – Federico Tomasi Nov 04 '22 at 09:44
  • @KimT it seems what is causing my problem is not the CORS issue. What I have noticed now, with both Chrome and Edge is that, after the page is loaded (not just refreshing the fiddle code), if I click on the "start" link, the player "tries" to start: the yt video first frame is refreshed (it turns black and then reappers) and the player begins to load the video (the video data grey bar begins to move). But it immediately stops. Furthermore, up to when I just click the external command, no CORS error appears. Once I click on a native control, I get the CORS error but then everything works fine. – Federico Tomasi Nov 04 '22 at 11:05
  • I would recommend testing using the official demo first https://developers.google.com/youtube/youtube_player_demo once you have it working, then create your own version – Kim T Nov 05 '22 at 05:14
  • @KimT Thank you again. Eventually the problem is that with both Chrome and Edge, they don't allow to let the video play by an external control (or autoplay, that is the same thing as the video is started by a js code) if the video is not muted (or it is unmuted). Even when the allow="autoplay" attribute is added to the iframe, contrary to what I read in the Chrome policy article and in that you linked as well, the video doesn't start if some user interaction with the native control has not previously happened. After 3 days of search I can't find any way to get autoplay unmuted on Chrome & Edge – Federico Tomasi Nov 05 '22 at 10:56
5

My own version of Kim T's code above which combines with some jQuery and allows for targeting of specific iframes.

$(function() {
    callPlayer($('#iframe')[0], 'unMute');
});

function callPlayer(iframe, func, args) {
    if ( iframe.src.indexOf('youtube.com/embed') !== -1) {
        iframe.contentWindow.postMessage( JSON.stringify({
            'event': 'command',
            'func': func,
            'args': args || []
        } ), '*');
    }
}
adamj
  • 4,672
  • 5
  • 49
  • 60
  • How to know youtube is playing? any callback from the youtube iframe, so outside can subscribe to? – Hammer Jul 14 '16 at 08:05
  • @Hammer Check out the YouTube API Events section specifically the `OnStateChange`: https://developers.google.com/youtube/iframe_api_reference#Events – adamj Jul 15 '16 at 03:12
  • @admj, Can check this out? Some wierd behavior ... http://stackoverflow.com/questions/38389802/youtubes-onstatechange-is-not-fired-if-play-is-triggered-by-postmessage – Hammer Jul 15 '16 at 07:03
1

Thank you Rob W for your answer.

I have been using this within a Cordova application to avoid having to load the API and so that I can easily control iframes which are loaded dynamically.

I always wanted the ability to be able to extract information from the iframe, such as the state (getPlayerState) and the time (getCurrentTime).

Rob W helped highlight how the API works using postMessage, but of course this only sends information in one direction, from our web page into the iframe. Accessing the getters requires us to listen for messages posted back to us from the iframe.

It took me some time to figure out how to tweak Rob W's answer to activate and listen to the messages returned by the iframe. I basically searched through the source code within the YouTube iframe until I found the code responsible for sending and receiving messages.

The key was changing the 'event' to 'listening', this basically gave access to all the methods which were designed to return values.

Below is my solution, please note that I have switched to 'listening' only when getters are requested, you can tweak the condition to include extra methods.

Note further that you can view all messages sent from the iframe by adding a console.log(e) to the window.onmessage. You will notice that once listening is activated you will receive constant updates which include the current time of the video. Calling getters such as getPlayerState will activate these constant updates but will only send a message involving the video state when the state has changed.

function callPlayer(iframe, func, args) {
    iframe=document.getElementById(iframe);
    var event = "command";
    if(func.indexOf('get')>-1){
        event = "listening";
    }

    if ( iframe&&iframe.src.indexOf('youtube.com/embed') !== -1) {
      iframe.contentWindow.postMessage( JSON.stringify({
          'event': event,
          'func': func,
          'args': args || []
      }), '*');
    }
}
window.onmessage = function(e){
    var data = JSON.parse(e.data);
    data = data.info;
    if(data.currentTime){
        console.log("The current time is "+data.currentTime);
    }
    if(data.playerState){
        console.log("The player state is "+data.playerState);
    }
}
Danbardo
  • 761
  • 7
  • 12
1

I was having issues with the above examples so instead, I just inserted the iframe on click in with JS with autoplay in the source and it works fine for me. I also had the possibility for Vimeo or YouTube so I needed to be able to handle that.

This solution isn't amazing and could be cleaned up but this worked for me. I also don't like jQuery but the project was already using it and I was just refactoring existing code, feel free to clean up or convert to vanilla JS :)

<!-- HTML -->
<div class="iframe" data-player="viemo" data-src="$PageComponentVideo.VideoId"></div>


<!-- jQuery -->
$(".btnVideoPlay").on("click", function (e) {
        var iframe = $(this).parents(".video-play").siblings(".iframe");
        iframe.show();

        if (iframe.data("player") === "youtube") {
            autoPlayVideo(iframe, iframe.data("src"), "100%", "100%");
        } else {
            autoPlayVideo(iframe, iframe.data("src"), "100%", "100%", true);
        }
    });

    function autoPlayVideo(iframe, vcode, width, height, isVimeo) {
        if (isVimeo) {
            iframe.html(
                '<iframe width="' +
                    width +
                    '" height="' +
                    height +
                    '" src="https://player.vimeo.com/video/' +
                    vcode +
                    '?color=ff9933&portrait=0&autoplay=1" frameborder="0" allowfullscreen wmode="Opaque"></iframe>'
            );
        } else {
            iframe.html(
                '<iframe width="' +
                    width +
                    '" height="' +
                    height +
                    '" src="https://www.youtube.com/embed/' +
                    vcode +
                    '?autoplay=1&loop=1&rel=0&wmode=transparent" frameborder="0" allowfullscreen wmode="Opaque"></iframe>'
            );
        }
    }
Brady Edgar
  • 486
  • 7
  • 27
1

One quick solution, if requests are not an issue, and you're wanting this behavior for something like a show/hide video, is to remove/add the iframe, or cleaning and filling the src.

const stopPlayerHack = (iframe) => {
    let src = iframe.getAttribute('src');
    iframe.setAttribute('src', '');
    iframe.setAttribute('src', src);
}

The iframe will be removed, stop to play and will be loaded right after that. In my case I've improved the code to only set the src again on lightbox open, so the load will only happen if user demands to see the video.