YouTube-Iframe-API: Wie steuere ich einen Iframe-Player, der bereits im HTML-Code enthalten ist?

148

Ich möchte in der Lage sein, iframe-basierte YouTube-Player zu steuern. Diese Player befinden sich bereits im HTML-Code, ich möchte sie jedoch über die JavaScript-API steuern.

Ich habe die Dokumentation zur iframe-API gelesen, in der erklärt wird, wie Sie der Seite mit der API ein neues Video hinzufügen und es dann mit den Funktionen des YouTube-Players steuern können:

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

Dieser Code erstellt ein neues Player-Objekt, weist es 'player' zu und fügt es dann in das #container div ein. Dann kann ich arbeite auf ‚Player‘ und Anruf playVideo(), pauseVideo()usw. darauf.

Aber ich möchte in der Lage sein, mit Iframe-Spielern zu arbeiten, die bereits auf der Seite sind.

Ich könnte dies sehr einfach mit der alten Einbettungsmethode tun, mit so etwas wie:

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

Dies funktioniert jedoch nicht mit den neuen Iframes. Wie kann ich ein Iframe-Objekt zuweisen, das sich bereits auf der Seite befindet, und dann die API-Funktionen darauf verwenden?

agente_secreto
quelle
Ich habe eine Abstraktion für die Arbeit mit YouTube IFrame API geschrieben github.com/gajus/playtube
Gajus

Antworten:

315

Fiddle Links: Quellcode - Vorschau - Kleine Version
Update: Diese kleine Funktion führt Code nur in einer einzigen Richtung aus. Wenn Sievolle Unterstützung möchten (zB EventListener / Getter), haben einen Blick auf Listening für Youtube Ereignis in jQuery

Als Ergebnis einer umfassenden Code-Analyse habe ich eine Funktion erstellt: function callPlayerFordert einen Funktionsaufruf für jedes gerahmte YouTube-Video an. In der YouTube-API-Referenz finden Sie eine vollständige Liste möglicher Funktionsaufrufe. Lesen Sie die Kommentare im Quellcode, um eine Erklärung zu erhalten.

Am 17. Mai 2012 wurde die Codegröße verdoppelt, um den Bereitschaftszustand des Spielers zu gewährleisten. Wenn Sie eine kompakte Funktion benötigen, die den Bereitschaftsstatus des Players nicht berücksichtigt, lesen Sie 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);
    }
}

Verwendung:

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");

Mögliche Fragen (& Antworten):

F : Es funktioniert nicht!
A : "Funktioniert nicht" ist keine klare Beschreibung. Erhalten Sie Fehlermeldungen? Bitte zeigen Sie den entsprechenden Code.

F : playVideoDas Video wird nicht abgespielt.
A : Die Wiedergabe erfordert Benutzerinteraktion und das Vorhandensein von allow="autoplay"auf dem Iframe. Siehe https://developers.google.com/web/updates/2017/09/autoplay-policy-changes und https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide

F : Ich habe ein YouTube-Video mit eingebettet, <iframe src="http://www.youtube.com/embed/As2rZGPGKDY" />aber die Funktion führt keine Funktion aus!
A : Sie müssen ?enablejsapi=1am Ende Ihrer URL hinzufügen : /embed/vid_id?enablejsapi=1.

F : Ich erhalte die Fehlermeldung "Es wurde eine ungültige oder unzulässige Zeichenfolge angegeben". Warum?
A : Die API funktioniert auf einem lokalen Host nicht ordnungsgemäß ( file://). Hosten Sie Ihre (Test-) Seite online oder verwenden Sie JSFiddle . Beispiele: Siehe die Links oben in dieser Antwort.

F : Woher wusstest du das?
A : Ich habe einige Zeit damit verbracht, die Quelle der API manuell zu interpretieren. Ich kam zu dem Schluss, dass ich die postMessageMethode anwenden musste. Um zu wissen, welche Argumente übergeben werden müssen, habe ich eine Chrome-Erweiterung erstellt, die Nachrichten abfängt. Der Quellcode für die Erweiterung kann hier heruntergeladen werden .

F : Welche Browser werden unterstützt?
A : Jeder Browser, der JSON und unterstützt postMessage.

  • IE 8+
  • Firefox 3.6+ (eigentlich 3.5, wurde aber document.readyStatein 3.6 implementiert)
  • Opera 10.50+
  • Safari 4+
  • Chrome 3+

Verwandte Antwort / Implementierung: Einblenden eines gerahmten Videos mit jQuery
Vollständige API-Unterstützung: Abhören von Youtube-Ereignissen in der
offiziellen jQuery- API: https://developers.google.com/youtube/iframe_api_reference

Versionsgeschichte

  • 17. Mai 2012
    Implementiert onYouTubePlayerReady: callPlayer('frame_id', function() { ... }).
    Funktionen werden automatisch in die Warteschlange gestellt, wenn der Player noch nicht bereit ist.
  • 24. Juli 2012
    In den unterstützten Browsern aktualisiert und erfolgreich getestet (siehe vorne).
  • 10. Oktober 2013 Wenn eine Funktion als Argument übergeben wird, callPlayerwird eine Überprüfung der Bereitschaft erzwungen. Dies ist erforderlich, da beim callPlayerAufrufen direkt nach dem Einfügen des Iframes, während das Dokument bereit ist, nicht sicher ist, ob der Iframe vollständig bereit ist. In Internet Explorer und Firefox führte dieses Szenario zu einem zu frühen Aufruf von postMessage, der ignoriert wurde.
  • 12. Dezember 2013, empfohlen, &origin=*die URL hinzuzufügen .
  • 2. März 2014, zurückgezogene Empfehlung zum Entfernen &origin=*zur URL.
  • 9. April 2019, Fehler behoben, der zu einer unendlichen Rekursion führte, wenn YouTube geladen wurde, bevor die Seite fertig war. Hinweis zur automatischen Wiedergabe hinzufügen.
Rob W.
quelle
@RobW Das habe ich eigentlich versucht. Scheint, als ob der fehlerhafte JSON nicht derjenige in Ihrem Skript ist, sondern im Iframe als Teil der YouTube-API.
Fresheyeball
@RobW danke für diesen schönen Ausschnitt. Hast du Möglichkeiten gefunden, das Nachrichtenereignis anstelle der youtube JS-API zu verwenden, um einen Ereignis-Listener hinzuzufügen?
Brillout
@ brillout.com Die PostMessageMethode basiert auf der YT JS API ( ?enablejsapi=1). Ohne Aktivierung der JS-API führt die postMessageMethode nichts aus. Eine einfache Implementierung der Ereignis-Listener finden Sie in der verknüpften Antwort . Ich habe auch lesbaren Code erstellt, aber nicht veröffentlicht, um mit dem Frame zu kommunizieren. Ich habe beschlossen, es nicht zu veröffentlichen, da seine Wirkung der Standard-YouTube-Frame-API ähnelt.
Rob W
1
@MatthewBaker Dazu muss das Nachrichtenereignis abgehört und der Status des Ergebnisses analysiert werden. Dies ist nicht so einfach wie einfache Aufrufe playVideo, daher empfehle ich, dafür die offizielle API zu verwenden. Siehe developer.google.com/youtube/iframe_api_reference#Events .
Rob W
1
@ffyeahh Ich sehe keinen offensichtlichen Fehler. Bitte stellen Sie eine neue Frage mit in sich geschlossenen Schritten zur Reproduktion, anstatt dieser Antwort Fragen in Kommentaren hinzuzufügen.
Rob W
32

YouTube hat anscheinend die JS-API aktualisiert, sodass diese standardmäßig verfügbar ist! Du kannst die ID eines vorhandenen YouTube-Iframes verwenden ...

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

... in Ihrem JS ...

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

function onPlayerStateChange() {
  //...
}

... und der Konstruktor verwendet Ihren vorhandenen Iframe, anstatt ihn durch einen neuen zu ersetzen. Dies bedeutet auch, dass Sie die Video-ID nicht für den Konstruktor angeben müssen.

Siehe Laden eines Videoplayers

CletusW
quelle
1
@raven Ihnen fehlt der Parameter autoplay = 1 in der URL. In deiner Beispiel-URL wäre es youtube.com/embed/M7lc1UVf-VE?enablejsapi=1& autoplay = 1 & origin = example.com
alengel
@alengel er möchte den autoplay-url Parameter nicht verwenden. Stattdessen versucht er, das Video mithilfe der Autoplay-Funktion von js-APIs zu starten. Aus irgendeinem Grund wird die Funktion onYouTubeIframeAPIReady () jedoch nicht aufgerufen.
Humppakäräjät
@raven Ich habe es herausgefunden. 1) Entfernen Sie & origin = example.com aus der iframe-URL. 2) Stellen Sie im Abschnitt "Frameworks & Extensions" Ihrer jsfiddle das zweite Dropdown-Menü auf "No wrap in - <head>". 3) Fügen Sie die youtube iframe-API als externe Ressource hinzu ( youtube.com/iframe_api ). Ich gabelte Ihre Geige und wendete diese Änderungen an: jsfiddle.net/e97famd1/1
Humppakäräjät
Irgendeine Idee, was eventoder commandan den YT-Iframe zu senden, listeningum den Status zu beenden ?
Mkhatib
@CletusW: Ich erhalte folgende Fehlermeldung: Nicht erfasst (im Versprechen) DOMException: Die play () -Anforderung wurde durch einen Aufruf von pause () unterbrochen. Promise (async) (anonym) @ scripts.js: 20 dispatch @ jquery-1.12.4.js: 5226 elemData.handle @ jquery-1.12.4.js: 4878 cast_sender.js: 67 Nicht erfasste DOMException: Fehler beim Erstellen von 'PresentationRequest ': Die Präsentation eines unsicheren Dokuments [cast: 233637DE? Capabilities = video_out% 2Caudio_out & clientId = 153262711713390989 & autoJoinPolicy = tab_and_origin_scoped & defaultActionPolicy = cast_this_tab & launchTimeout = 30000] ist in einem sicheren Kontext verboten.
LauraNMS
20

Sie können dies mit weitaus weniger Code tun:

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

Arbeitsbeispiel: http://jsfiddle.net/kmturley/g6P5H/296/

Kim T.
quelle
Diese Art hat mir sehr gut gefallen, ich habe sie nur für die Arbeit mit einer Angular-Direktive angepasst, brauchte also nicht die gesamte Schleife und übergab die Funktion abhängig von einer Umschaltfunktion mit dem Bereich (im Grunde genommen, wenn das Video angezeigt wird -> Autoplay; sonst -> Pause das Video). Vielen Dank!
DD.
Sie sollten die Richtlinie teilen, könnte nützlich sein!
Kim T
1
Hier ist eine Anpassung des Codes für einen Stift: codepen.io/anon/pen/qERdza Ich hoffe, es hilft! Es schaltet auch das Drücken der ESC-Taste um, wenn das Video eingeschaltet ist
DD.
Das scheint zu schön um wahr zu sein! Gibt es Browser- / Sicherheitsbeschränkungen bei der Verwendung dieser Methode?
Dan
Ja, IE hat einige Einschränkungen, insbesondere IE10, das MessageChannel anstelle von postMessage unterstützt: caniuse.com/#search=postMessage Beachten Sie auch, dass Richtlinien zur Inhaltssicherheit die Verwendung dieser Funktion ebenfalls einschränken
Kim T
5

Meine eigene Version des obigen Codes von Kim T, die mit etwas jQuery kombiniert wird und das Targeting bestimmter Iframes ermöglicht.

$(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
quelle
Woher weiß ich, dass YouTube spielt? Gibt es einen Rückruf vom Youtube-Iframe, damit außerhalb abonniert werden kann?
Hammer
@Hammer Schauen Sie sich den Abschnitt mit den YouTube-API-Ereignissen an, und zwar speziell unter OnStateChange: developer.google.com/youtube/iframe_api_reference#Events
adamj
@admj, Kannst du das überprüfen? Etwas seltsames Verhalten ... stackoverflow.com/questions/38389802/…
Hammer
0

Vielen Dank, Rob W, für Ihre Antwort.

Ich habe dies in einer Cordova-Anwendung verwendet, um zu vermeiden, dass die API geladen werden muss, und um Iframes, die dynamisch geladen werden, einfach steuern zu können.

Ich wollte immer die Möglichkeit haben, Informationen aus dem Iframe zu extrahieren, wie z. B. den Status (getPlayerState) und die Zeit (getCurrentTime).

Rob W half dabei, die Funktionsweise der API mit postMessage hervorzuheben, aber dies sendet natürlich nur Informationen in eine Richtung, von unserer Webseite in den Iframe. Um auf die Getter zugreifen zu können, müssen wir auf Nachrichten warten, die vom Iframe an uns zurückgesendet wurden.

Ich brauchte einige Zeit, um herauszufinden, wie ich Rob Ws Antwort optimieren konnte, um die vom Iframe zurückgegebenen Nachrichten zu aktivieren und abzuhören. Grundsätzlich habe ich den Quellcode im YouTube-Iframe durchsucht, bis ich den Code gefunden habe, der für das Senden und Empfangen von Nachrichten verantwortlich ist.

Der Schlüssel bestand darin, das 'Ereignis' in 'Abhören' zu ändern. Dies ermöglichte im Grunde den Zugriff auf alle Methoden, die zur Rückgabe von Werten entwickelt wurden.

Im Folgenden ist meine Lösung aufgeführt. Bitte beachten Sie, dass ich nur dann auf "Zuhören" umgeschaltet habe, wenn Getter angefordert werden. Sie können die Bedingung so anpassen, dass zusätzliche Methoden hinzugefügt werden.

Beachten Sie außerdem, dass Sie alle vom iframe gesendeten Nachrichten anzeigen können, indem Sie der window.onmessage eine console.log (e) hinzufügen. Sie werden feststellen, dass Sie nach Aktivierung des Hörens ständig Updates erhalten, die die aktuelle Uhrzeit des Videos enthalten. Wenn Sie Getter wie getPlayerState aufrufen, werden diese ständigen Aktualisierungen aktiviert, es wird jedoch nur dann eine Nachricht gesendet, die den Videostatus betrifft, wenn sich der Status geändert hat.

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
quelle