API iframe YouTube: comment contrôler un lecteur iframe qui est déjà dans le HTML?

149

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

J'ai lu la documentation de 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 player et l'assigne à 'player', puis l'insère dans le #container div. Ensuite , je peux fonctionner sur « lecteur » et appel playVideo(), pauseVideo()etc. sur elle.

Mais je veux pouvoir opérer sur des lecteurs iframe qui sont déjà sur la page.

Je pourrais 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 puis-je attribuer un objet iframe déjà sur la page, puis utiliser les fonctions API dessus?

agente_secreto
la source
J'ai écrit une abstraction pour travailler avec l'API YouTube IFrame github.com/gajus/playtube
Gajus

Réponses:

316

Liens Fiddle: Code source - Aperçu - Petite version
Mise à jour: Cette petite fonction n'exécutera du code que dans un seul sens. Si vous voulez une prise en charge complète (par exemple, les écouteurs / getters d'événements), jetez un œil à Listening for Youtube Event dans jQuery

À la suite d'une analyse approfondie du code, j'ai créé une fonction: function callPlayerdemande un appel de fonction sur n'importe quelle vidéo YouTube encadrée. Consultez la référence YouTube Api pour obtenir une 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 charge l'état prêt du joueur. Si vous avez besoin d'une fonction compacte qui ne traite pas l'état prêt du lecteur, consultez 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 (et réponses) possibles:

Q : Cela ne fonctionne pas!
R : «Ne fonctionne pas» n'est pas une description claire. Recevez-vous des messages d'erreur? Veuillez montrer le code correspondant.

Q : playVideone lit pas la vidéo.
R : La lecture nécessite une interaction de l'utilisateur et 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 en utilisant <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 : J'obtiens le message d'erreur "Une chaîne non valide ou illégale a été spécifiée". Pourquoi?
R : 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 le saviez-vous?
R : J'ai passé un certain temps à interpréter manuellement la source de l'API. J'ai conclu que je devais utiliser lepostMessage méthode. 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 pris en charge?
R : Tous les navigateurs prenant en charge JSON etpostMessage .

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

Réponse / implémentation associée: Fade-in a frame video using jQuery
Full API support: Listening for Youtube Event in jQuery
Official API: https://developers.google.com/youtube/iframe_api_reference

Historique des révisions

  • 17 mai 2012
    Mise en œuvre 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 (à venir).
  • 10 octobre 2013 Lorsqu'une fonction est passée en argument, callPlayerforce un contrôle de disponibilité. Cela est nécessaire, car quand callPlayerest 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 a entraîné une invocation trop précoce depostMessage , qui a été ignoré.
  • 12 décembre 2013, recommandé d'ajouter &origin=*l'URL.
  • 2 mars 2014, retrait &origin=*de la recommandation de suppression de l'URL.
  • 9 avril 2019, correction d'un bug qui entraînait une récursivité infinie lorsque YouTube se charge avant que la page ne soit prête. Ajoutez une note sur la lecture automatique.
Rob W
la source
@RobW J'ai essayé ça en fait. On dirait que le JSON en erreur n'est pas celui de votre script, mais à l'intérieur de l'iframe en tant que partie de l'API de YouTube.
Fresheyeball
@RobW merci pour ce bel extrait. Avez-vous trouvé des moyens d'utiliser l'événement Message au lieu d'utiliser l'API JS youtube afin d'ajouter un écouteur d'événements?
brillout
@ brillout.com La PostMessageméthode est basée sur l'API YT JS ( ?enablejsapi=1). Sans activer l'API JS, la postMessageméthode ne fera rien, voir la réponse liée pour une implémentation facile des écouteurs d'événements. J'ai également créé, mais pas publié, du code lisible pour communiquer avec le cadre. J'ai décidé de ne pas le publier, car son effet est similaire à l'API YouTube Frame par défaut.
Rob W
1
@MatthewBaker Cela nécessite d'écouter l'événement de message et d'analyser l'état du résultat. Ce n'est pas aussi simple que de simples appels playVideo, je recommande donc d'utiliser l'API officielle pour cela. Voir developer.google.com/youtube/iframe_api_reference#Events .
Rob W
1
@ffyeahh Je ne vois aucune erreur évidente. Veuillez poser une nouvelle question avec une procédure de reproduction autonome au lieu d'ajouter des questions dans les commentaires à cette réponse.
Rob W
33

On dirait que YouTube a mis à jour son API JS, elle est donc disponible par défaut! Vous pouvez utiliser l'identifiant d'un 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

CletusW
la source
1
@raven vous manquez le paramètre autoplay = 1 dans l'URL. Dans votre exemple d'URL, il s'agirait de youtube.com/embed/M7lc1UVf-VE?enablejsapi=1& autoplay = 1 & origin = example.com
alengel
@alengel il ne veut pas utiliser le paramètre autoplay-url. Au lieu de cela, il essaie de démarrer la vidéo en utilisant la fonction de lecture automatique js-APIs. Mais pour une raison quelconque, la fonction onYouTubeIframeAPIReady () n'est pas appelée.
Humppakäräjät
@raven je l'ai compris. 1) supprimez & origin = example.com de l'url iframe. 2) dans la section "Frameworks & Extensions" de votre jsfiddle, définissez le deuxième menu déroulant sur "No wrap in - <head>" 3) ajoutez l'api iframe youtube en tant que ressource externe ( youtube.com/iframe_api ); J'ai bifurqué votre violon et appliqué ces changements: jsfiddle.net/e97famd1/1
Humppakäräjät
Une idée quoi eventou commandenvoyer à l'iframe YT pour s'arrêter listeningau statut?
mkhatib
@CletusW: J'obtiens cette erreur: Uncaught (in promise) DOMException: La requête play () a été interrompue par un appel à pause (). Promise (async) (anonyme) @ scripts.js: 20 dispatch @ jquery-1.12.4.js: 5226 elemData.handle @ jquery-1.12.4.js: 4878 cast_sender.js: 67 Uncaught DOMException: impossible de construire 'PresentationRequest ': Présentation d'un document non sécurisé [cast: 233637DE? Capabilities = video_out% 2Caudio_out & clientId = 153262711713390989 & autoJoinPolicy = tab_and_origin_scoped & defaultActionPolicy = cast_this_tab & launchTimeout = 30000] est interdit dans un contexte sécurisé.
LauraNMS
20

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/

Kim T
la source
J'ai vraiment aimé cette façon, je l'ai juste adaptée pour fonctionner avec une directive angulaire, donc je n'ai pas besoin de toute la boucle et de passer la fonction en fonction d'une fonction de basculement avec la portée (essentiellement si la vidéo est affichée -> lecture automatique; sinon -> pause la vidéo). Merci!
DD.
Vous devriez partager la directive, cela pourrait être utile!
Kim T
1
Voici une adaptation du code pour un Pen: codepen.io/anon/pen/qERdza J'espère que ça aide! Il permet également de basculer en appuyant sur la touche ESC lorsque vous avez la vidéo sur
DD.
Cela semble trop beau pour être vrai! Y a-t-il des limitations du navigateur / de la sécurité à l'utilisation de cette méthode?
Dan
Oui, IE a quelques limitations, en particulier IE10 qui prend en charge MessageChannel au lieu de postMessage: caniuse.com/#search=postMessage sachez également que toute politique de sécurité du contenu restreindra également l'utilisation de cette fonctionnalité
Kim T
5

Ma propre version du code de Kim T ci-dessus qui se combine avec certains 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 || []
        } ), '*');
    }
}
Adamj
la source
Comment savoir que YouTube joue? tout rappel de l'iframe youtube, donc l'extérieur peut s'abonner?
Hammer le
@Hammer Consultez la section Événements de l'API YouTube, en particulier sur OnStateChange: developer.google.com/youtube/iframe_api_reference#Events
adamj
@admj, pouvez-vous vérifier ça? Quelques comportements bizarres ... stackoverflow.com/questions/38389802/…
Hammer
0

Merci Rob W pour votre réponse.

J'utilise ceci dans une application Cordova pour éviter d'avoir à charger l'API et pour pouvoir contrôler facilement les iframes qui sont chargées dynamiquement.

J'ai toujours voulu pouvoir extraire des informations de l'iframe, telles que l'état (getPlayerState) et l'heure (getCurrentTime).

Rob W a aidé à mettre en évidence le fonctionnement de l'API à l'aide de postMessage, mais bien sûr, cela n'envoie les informations que dans une seule direction, de notre page Web à l'iframe. Pour accéder aux getters, nous devons écouter les messages qui nous sont renvoyés depuis l'iframe.

Il m'a fallu un certain temps pour comprendre comment modifier la réponse de Rob W pour activer et écouter les messages renvoyés par l'iframe. J'ai essentiellement cherché dans le code source dans l'iframe YouTube jusqu'à ce que je trouve le code responsable de l'envoi et de la réception des messages.

La clé était de changer «l'événement» en «écoute», cela donnait essentiellement accès à toutes les méthodes conçues pour renvoyer des valeurs.

Voici ma solution, veuillez noter que je suis passé à «écouter» uniquement lorsque les getters sont demandés, vous pouvez modifier la condition pour inclure des méthodes supplémentaires.

Notez en outre que vous pouvez afficher tous les messages envoyés depuis l'iframe en ajoutant un console.log (e) à window.onmessage. Vous remarquerez qu'une fois l'écoute activée, vous recevrez des mises à jour constantes qui incluent l'heure actuelle de la vidéo. L'appel de getters tels que getPlayerState activera ces mises à jour constantes, mais n'enverra un message impliquant l'état de la vidéo que lorsque l'état a changé.

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
la source