web-dev-qa-db-fra.com

YouTube iframe API: comment contrôler un lecteur iframe déjà présent dans le code HTML?

Je souhaite pouvoir contrôler les lecteurs YouTube basés sur l'iframe. Ce lecteur sera déjà dans le code HTML, mais je souhaite le contrôler via l’API JavaScript.

J'ai lu documentation relative à l'API iframe qui explique comment ajouter une nouvelle vidéo à la page avec l'API, puis la contrôler avec les fonctions du lecteur YouTube:

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

Ce code crée un nouvel objet joueur et l'assigne à "joueur", puis l'insère dans le conteneur #container. Ensuite, je peux utiliser le lecteur et appeler playVideo(), pauseVideo(), etc.

Mais je veux pouvoir utiliser les lecteurs iframe déjà présents sur la page.

Je pouvais le faire très facilement avec l'ancienne méthode d'intégration, avec quelque chose comme:

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

Mais cela ne fonctionne pas avec les nouveaux iframes. Comment attribuer un objet iframe déjà sur la page, puis utiliser les fonctions de l'API dessus?

142
agente_secreto

Liens de violon: Code source - Aperç - Petite version
Mise à jour: Cette petite fonction n'exécutera le code que dans un seul sens. Si vous souhaitez une assistance complète (par exemple, écouteurs/getters d'événements), consultez sous Événement d'écoute Youtube dans jQuery

À la suite d’une analyse approfondie du code, j’ai créé une fonction: function callPlayer demande un appel de fonction pour n’importe quelle vidéo YouTube encadrée. Consultez le Référence YouTube Api pour obtenir la liste complète des appels de fonction possibles. Lisez les commentaires sur le code source pour une explication.

Le 17 mai 2012, la taille du code a été doublée afin de prendre en compte l'état de préparation du joueur. Si vous avez besoin d’une fonction compacte qui ne traite pas de l’état de disponibilité du lecteur, voir http://jsfiddle.net/8R5y6/ .

/**
 * @author       Rob W <[email protected]>
 * @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");

Questions possibles (et réponses):

Q: Cela ne fonctionne pas!
A: "Ne fonctionne pas" n'est pas une description claire. Avez-vous des messages d'erreur? S'il vous plaît montrer le code approprié.

Q: playVideo ne lit pas la vidéo.
A: la lecture nécessite une interaction de l'utilisateur, ainsi que la présence de allow="autoplay" sur l'iframe. Voir https://developers.google.com/web/updates/2017/09/autoplay-policy-changes et https://developer.mozilla.org/en-US/ docs/Web/Media/Autoplay_guide

Q: J'ai intégré une vidéo YouTube à l'aide de <iframe src="http://www.youtube.com/embed/As2rZGPGKDY" /> mais la fonction n'exécute aucune fonction!
A: Vous devez ajouter ?enablejsapi=1 à la fin de votre URL: /embed/vid_id?enablejsapi=1.

Q: le message d'erreur suivant s'affiche "Une chaîne non valide ou illégale a été spécifiée". Pourquoi?
A: l'API ne fonctionne pas correctement sur un hôte local (file://). Hébergez votre page (de test) en ligne ou utilisez JSFiddle . Exemples: Voir les liens en haut de cette réponse.

Q: Comment avez-vous su cela?
A: J'ai passé un certain temps à interpréter manuellement le code source de l'API. J'ai conclu que je devais utiliser la méthode postMessage . Pour savoir quels arguments passer, j'ai créé une extension Chrome qui intercepte les messages. Le code source de l'extension peut être téléchargé ici .

Q: Quels navigateurs sont supportés?
A: Tous les navigateurs prenant en charge JSON et postMessage .

  • IE 8+
  • Firefox 3.6+ (en réalité 3.5, mais document.readyState a été implémenté dans 3.6)
  • Opera 10.50+
  • Safari 4+
  • Chrome 3+

Réponse/implémentation associée: Ouverture en fondu d'une vidéo avec cadre à l'aide de jQuery
Prise en charge complète de l'API: Écoute de l'événement Youtube dans jQuery
API officielle: https://developers.google.com/youtube/iframe_api_reference

Historique des révisions

  • 17 mai 2012
    Implémenté onYouTubePlayerReady: callPlayer('frame_id', function() { ... }).
    Les fonctions sont automatiquement mises en file d'attente lorsque le lecteur n'est pas encore prêt.
  • 24 juillet 2012
    Mis à jour et testé avec succès dans les navigateurs pris en charge (regardez vers l'avenir).
  • 10 octobre 2013 Lorsqu'une fonction est transmise en tant qu'argument, callPlayer force une vérification de l'état de préparation. Cela est nécessaire car, lorsque callPlayer est appelé juste après l'insertion de l'iframe alors que le document est prêt, il ne peut pas savoir avec certitude que l'iframe est entièrement prêt. Dans Internet Explorer et Firefox, ce scénario aboutissait à un appel trop précoce de postMessage, qui était ignoré.
  • 12 déc 2013, recommandé d'ajouter &Origin=* dans l'URL.
  • 2 mars 2014, retrait de la recommandation &Origin=* à l'URL.
  • 9 avril 2019, correction d'un bug qui entraînait une récursion infinie lorsque YouTube était chargé avant que la page soit prête. Ajouter une note sur la lecture automatique.
300
Rob W

On dirait que YouTube a mis à jour leur API JS, donc celle-ci est disponible par défaut! Vous pouvez utiliser un identifiant iframe YouTube existant ...

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

... dans votre JS ...

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

function onPlayerStateChange() {
  //...
}

... et le constructeur utilisera votre iframe existant au lieu de le remplacer par un nouveau. Cela signifie également que vous n'avez pas à spécifier le videoId au constructeur.

Voir Chargement d'un lecteur vidéo

31
CletusW

Vous pouvez le faire avec beaucoup moins de 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 || []
            }), '*');
        }
    }
}

Exemple de travail: http://jsfiddle.net/kmturley/g6P5H/296/

17
Kim T

Ma propre version du code de Kim T ci-dessus, qui se combine avec du jQuery et permet de cibler des iframes spécifiques.

$(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 || []
        } ), '*');
    }
}
4
adamj