Ninethousand
Ninethousand

Reputation: 535

Youtube iframe not responding to postMessage commands

I'm trying to control a YouTube iframe using postMessage commands from the parent, but it doesn't seem to work.

For a number of reasons I'm not using the YouTube API, just a normal iframe with a youtube embedded video.

<iframe id="video-player" :src="'https://www.youtube.com/embed/' + code + '?autoplay=1'"
  seamless sandbox="allow-scripts allow-same-origin allow-presentation"></iframe>

The way I'm trying to send commands is:

var iframe = document.getElementById('video-player');
iframe.contentWindow.postMessage(JSON.stringify(
  { event: 'command', func: 'pauseVideo' }), 'https://www.youtube.com');

It seems that the iframe is being selected correctly, but I'm not sure if the postMessage commands are being sent, as the video ignores them.

¿What am I doing wrong?

Upvotes: 5

Views: 9770

Answers (3)

mattpr
mattpr

Reputation: 3230

two-way communication

If you want the youtube embedded iframe to postMessage events back to your containing page (e.g. initialDelivery, onReady and after playback starts infoDelivery), you need to tell youtube's iframe that you are listening first:

iframeEl.contentWindow.postMessage('{"event":"listening","id":1,"channel":"widget"}', '*');

You probably want to wait for the load event on the iframe element before trying this, and you may want to use window.setInterval to run this over and over until the iframe starts responding just in case.

Of course you need to add a "message" listener on the window containing the iframe to see the messages coming back.

window.addEventListener('message', function (msgEvt) {
    console.log(msgEvt);
});

If successful you will see messages come back like the following...

{
    "event": "initialDelivery",
    "info": {
        "apiInterface": ["cueVideoById", "loadVideoById", "cueVideoByUrl", "loadVideoByUrl", "playVideo", "pauseVideo", "stopVideo", "clearVideo", "getVideoBytesLoaded", "getVideoBytesTotal", "getVideoLoadedFraction", "getVideoStartBytes", "cuePlaylist", "loadPlaylist", "nextVideo", "previousVideo", "playVideoAt", "setShuffle", "setLoop", "getPlaylist", "getPlaylistIndex", "getPlaylistId", "loadModule", "unloadModule", "setOption", "getOption", "getOptions", "mute", "unMute", "isMuted", "setVolume", "getVolume", "seekTo", "getPlayerMode", "getPlayerState", "getPlaybackRate", "setPlaybackRate", "getAvailablePlaybackRates", "getPlaybackQuality", "setPlaybackQuality", "getAvailableQualityLevels", "getCurrentTime", "getDuration", "addEventListener", "removeEventListener", "getDebugText", "getVideoData", "addCueRange", "removeCueRange", "setSize", "getApiInterface", "destroy", "showVideoInfo", "hideVideoInfo", "isVideoInfoVisible", "getSphericalProperties", "setSphericalProperties", "getVideoEmbedCode", "getVideoUrl", "getMediaReferenceTime", "getSize", "logImaAdEvent"],
        "videoBytesLoaded": 0,
        "videoBytesTotal": 1,
        "videoLoadedFraction": 0,
        "videoStartBytes": 0,
        "playlist": null,
        "playlistIndex": -1,
        "playlistId": null,
        "option": null,
        "options": [],
        "muted": false,
        "volume": 100,
        "playerMode": {},
        "playerState": 5,
        "playbackRate": 1,
        "availablePlaybackRates": [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2],
        "playbackQuality": "unknown",
        "availableQualityLevels": [],
        "currentTime": 0,
        "duration": 1344,
        "debugText": "{\n  \"debug_videoId\": \"M7lc1UVf-VE\",\n  \"debug_playbackQuality\": \"unknown\",\n  \"debug_date\": \"Fri Jan 20 2023 16:26:12 GMT+0100 (Central European Standard Time)\"\n}",
        "videoInfoVisible": false,
        "sphericalProperties": {},
        "videoEmbedCode": "<iframe width=\"640\" height=\"390\" src=\"https://www.youtube.com/embed/M7lc1UVf-VE\" title=\"YouTube Developers Live: Embedded Web Player Customization\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen></iframe>",
        "videoUrl": "https://www.youtube.com/watch?v=M7lc1UVf-VE",
        "mediaReferenceTime": 0,
        "size": {
            "width": 640,
            "height": 390
        },
        "videoData": {
            "video_id": "M7lc1UVf-VE",
            "author": "",
            "title": "YouTube Developers Live: Embedded Web Player Customization",
            "isPlayable": true,
            "errorCode": null,
            "backgroundable": false,
            "cpn": "XXXXXXXXXXX",
            "isLive": false,
            "isWindowedLive": false,
            "isManifestless": false,
            "allowLiveDvr": false,
            "isListed": false,
            "isMultiChannelAudio": false,
            "hasProgressBarBoundaries": false,
            "isPremiere": false,
            "progressBarStartPositionUtcTimeMillis": null,
            "progressBarEndPositionUtcTimeMillis": null,
            "paidContentOverlayDurationMs": 0
        },
        "currentTimeLastUpdated_": 1674228372.118
    },
    "id": 1,
    "channel": "widget"
}
{
    "event": "onReady",
    "info": null,
    "id": 1,
    "channel": "widget"
}
{
    "event": "infoDelivery",
    "info": {
        "currentTime": 8.082562,
        "videoBytesLoaded": 0.03721251119143891,
        "videoLoadedFraction": 0.03721251119143891,
        "currentTimeLastUpdated_": 1674228683.626,
        "playbackRate": 1,
        "mediaReferenceTime": 8.083505
    },
    "id": 1,
    "channel": "widget"
}

The youtube javascript library is obviously much more robust and full featured if you want to do fancy things and don't want to have to maintain your own library.

But if you want to avoid loading extra third party scripts on your page and just need a simple feature like not loading the iframe until the user clicks on a youtube preview image (e.g. http://img.youtube.com/vi/${vidId}/maxresdefault.jpg) and you don't want to show the iframe until it is actually loaded, you can use something like this...

  • show preview image (the youtube iframes even without user interaction cause huge number of requests and bandwidth usage)
  • when user clicks on preview image...
    • hide preview image
    • show loader animation (so the user knows something is happening if they are on slow connection)
    • setup iframe (e.g. move url from data-src to `src attribute) and show loader animation or inject iframe element into dom).
    • add load event listener to iframe element and the postMessage stuff so you can listen for onReady coming from iframe
  • when onReady comes in over postMessage you know youtube is ready and you can hide the loader animation and show the iframe.

The youtube iframe url used in testing above was:

  • https://www.youtube.com/embed/M7lc1UVf-VE?enablejsapi=1&amp;origin=http%3A%2F%2Flocalhost%3A8080&amp;widgetid=1&amp;autoplay=1

Upvotes: 3

eddyloewen
eddyloewen

Reputation: 31

If someone comes to this issues later it might not work anymore to start playing videos this way. Although it works in Firefox and Safari, it is not working anymore in Chrome. They probably changed some security mechanisms.

Chrome only acts on the posted messages after the user has interacted with the video.

To fix it for Chrome you have to add the "allow" attribute with the value of "autoplay"

<iframe src="'https://www.youtube.com/embed/' + code + '?enablejsapi=1'" allow="autoplay"></iframe>

Upvotes: 3

Ninethousand
Ninethousand

Reputation: 535

I found the solution. The YouTube url needs the query parameter "enablejsapi=1".

<iframe id="video-player" :src="'https://www.youtube.com/embed/' + code + '?autoplay=1&enablejsapi=1'"
  seamless sandbox="allow-scripts allow-same-origin allow-presentation"></iframe>

Like this it correctly listens to postMessage() commands.

Upvotes: 10

Related Questions