API iframe di YouTube: come posso controllare un lettore iframe che è già nell'HTML?


149

Voglio essere in grado di controllare i giocatori di YouTube basati su iframe. Questo player sarà già nell'HTML, ma voglio controllarli tramite l'API JavaScript.

Ho letto la documentazione per l'API iframe che spiega come aggiungere un nuovo video alla pagina con l'API e quindi controllarlo con le funzioni del lettore YouTube:

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

Quel codice crea un nuovo oggetto player e lo assegna a "player", quindi lo inserisce nel div #container. Allora posso operare su 'player' e la chiamata playVideo(), pauseVideo()ecc su di esso.

Ma voglio essere in grado di operare su giocatori iframe che sono già nella pagina.

Potrei farlo molto facilmente con il vecchio metodo embed, con qualcosa del tipo:

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

Ma questo non funziona con i nuovi iframe. Come posso assegnare un oggetto iframe già sulla pagina e quindi utilizzare le funzioni API su di esso?


Ho scritto un'astrazione per lavorare con l'API IFrame di YouTube github.com/gajus/playtube
Gajus

Risposte:


316

Fiddle Links: codice sorgente - Anteprima -
Aggiornamento versione piccola : questa piccola funzione eseguirà il codice in una sola direzione. Se desideri un supporto completo (ad es. Listener / getter di eventi), dai un'occhiata a Listening for Youtube Event in jQuery

Come risultato di un'analisi approfondita del codice, ho creato una funzione: function callPlayerrichiede una chiamata di funzione su qualsiasi video YouTube con cornice. Consulta il riferimento API YouTube per ottenere un elenco completo di possibili chiamate di funzione. Leggi i commenti sul codice sorgente per una spiegazione.

Il 17 maggio 2012, la dimensione del codice è stata raddoppiata per occuparsi dello stato pronto del giocatore. Se hai bisogno di una funzione compatta che non si occupa dello stato pronto del lettore, vedi 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);
    }
}

Uso:

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

Possibili domande (e risposte):

D : Non funziona!
A : "Non funziona" non è una descrizione chiara. Ricevi messaggi di errore? Si prega di mostrare il codice pertinente.

Q : playVideonon riproduce il video.
A : La riproduzione richiede l'interazione dell'utente e la presenza di allow="autoplay"sull'iframe. Vedi https://developers.google.com/web/updates/2017/09/autoplay-policy-changes e https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide

D : Ho incorporato un video di YouTube utilizzando <iframe src="http://www.youtube.com/embed/As2rZGPGKDY" />ma la funzione non esegue alcuna funzione!
A : È necessario aggiungere ?enablejsapi=1alla fine del tuo URL: /embed/vid_id?enablejsapi=1.

D : Viene visualizzato il messaggio di errore "È stata specificata una stringa non valida o non valida". Perché?
A : L'API non funziona correttamente su un host locale ( file://). Ospita la tua pagina (test) online o usa JSFiddle . Esempi: vedere i collegamenti nella parte superiore di questa risposta.

Q : Come lo sapevi?
A : Ho impiegato del tempo per interpretare manualmente l'origine dell'API. Ho concluso che dovevo usare il postMessagemetodo. Per sapere quali argomenti passare, ho creato un'estensione di Chrome che intercetta i messaggi. Il codice sorgente per l'estensione può essere scaricato qui .

D : Quali browser sono supportati?
A : Ogni browser che supporta JSON e postMessage.

  • IE 8+
  • Firefox 3.6+ (in realtà 3.5, ma è document.readyStatestato implementato in 3.6)
  • Opera 10.50+
  • Safari 4+
  • Chrome 3+

Risposta / implementazione correlate: dissolvenza in un video incorniciato utilizzando il
supporto API completo di jQuery : ascolto dell'evento Youtube
nell'API ufficiale di jQuery : https://developers.google.com/youtube/iframe_api_reference

Cronologia delle revisioni

  • 17 MAGGIO 2012
    Implemented onYouTubePlayerReady: callPlayer('frame_id', function() { ... }).
    Le funzioni vengono messe automaticamente in coda quando il giocatore non è ancora pronto.
  • 24 luglio 2012
    Aggiornato e testato successivamente nei browser supportati (guarda avanti).
  • 10 ottobre 2013 Quando una funzione viene passata come argomento, callPlayerforza un controllo di prontezza. Questo è necessario, perché quando callPlayerviene chiamato subito dopo l'inserimento dell'iframe mentre il documento è pronto, non può sapere con certezza che l'iframe è completamente pronto. In Internet Explorer e Firefox, questo scenario ha provocato una chiamata troppo precoce postMessage, che è stata ignorata.
  • 12 dic 2013, si consiglia di aggiungere &origin=*l'URL.
  • 2 marzo 2014, raccomandazione ritirata da rimuovere &origin=*all'URL.
  • 9 aprile 2019, risolto bug che causava una ricorsione infinita quando YouTube carica prima che la pagina fosse pronta. Aggiungi una nota sulla riproduzione automatica.

@RobW L'ho provato davvero. Sembra che JSON per errore non sia quello nel tuo script, ma all'interno dell'iframe come parte dell'API di YouTube.
Fresheyeball

@RobW grazie per questo bel frammento. Hai trovato un modo per utilizzare l'Evento Messaggio invece di utilizzare l'API JS di YouTube per aggiungere un listener di eventi?
Brillout,

@ brillout.com Il PostMessagemetodo si basa sull'API YT JS ( ?enablejsapi=1). Senza abilitare l'API JS, il postMessagemetodo non farà nulla, vedere la risposta collegata per una facile implementazione dei listener di eventi. Ho anche creato, ma non pubblicato, un codice leggibile per comunicare con il frame. Ho deciso di non pubblicarlo, perché il suo effetto è simile all'API Frame YouTube predefinita.
Rob W,

1
@MatthewBaker Ciò richiede l'ascolto dell'evento messaggio e l'analisi dello stato del risultato. Non è così semplice come le chiamate semplici playVideo, quindi ti consiglio di usare l'API ufficiale per questo. Vedi developers.google.com/youtube/iframe_api_reference#Events .
Rob W,

1
@ffyeahh Non vedo alcun errore evidente. Si prega di porre una nuova domanda con una procedura autonoma per la riproduzione anziché aggiungere domande nei commenti a questa risposta.
Rob W,

33

Sembra che YouTube abbia aggiornato la sua API JS, quindi questa è disponibile per impostazione predefinita! Puoi utilizzare un ID iframe YouTube esistente ...

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

... nella tua JS ...

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

function onPlayerStateChange() {
  //...
}

... e il costruttore utilizzerà il tuo iframe esistente invece di sostituirlo con uno nuovo. Questo significa anche che non è necessario specificare il videoId per il costruttore.

Vedi Caricamento di un lettore video


1
@raven ti manca il parametro autoplay = 1 nell'URL. Nel tuo URL di esempio, sarebbe youtube.com/embed/M7lc1UVf-VE?enablejsapi=1& autoplay = 1 & origin = example.com
alengel

@alengel non vuole usare il parametro autoplay-url. Invece tenta di avviare il video utilizzando la funzione di riproduzione automatica js-API. Ma per qualche motivo la funzione onYouTubeIframeAPIReady () non viene invocata.
Humppakäräjät,

@raven l'ho capito. 1) rimuovi & origin = example.com dall'URL iframe. 2) nella sezione "Frameworks & Extensions" di jsfiddle impostare il secondo menu a discesa su "No wrap in - <head>" 3) aggiungere l'iframe iframe di YouTube come risorsa esterna ( youtube.com/iframe_api ); Ho modificato il tuo violino e ho applicato queste modifiche: jsfiddle.net/e97famd1/1
Humppakäräjät

Hai idea di cosa evento commanddi inviare all'iframe YT per interrompere listeninglo stato?
mkhatib,

@CletusW: Ottengo questo errore: DOMException non promessa (promessa): la richiesta play () è stata interrotta da una chiamata a pause (). Promise (asincrono) (anonimo) @ scripts.js: 20 dispatch @ jquery-1.12.4.js: 5226 elemData.handle @ jquery-1.12.4.js: 4878 cast_sender.js: 67 DOMException non rilevata: impossibile creare 'PresentationRequest ': Presentazione di un documento non sicuro [cast: 233637DE? Capacità = video_out% 2Caudio_out & clientId = 153262711713390989 & autoJoinPolicy = tab_and_origin_scoped & defaultActionPolicy = cast_this_tab & launchTimeout = 30000] è proibito da un contesto sicuro.
LauraNMS,

20

Puoi farlo con molto meno codice:

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

Esempio di lavoro: http://jsfiddle.net/kmturley/g6P5H/296/


Mi è piaciuto molto in questo modo, l'ho adattato per funzionare con una direttiva angolare, quindi non avevo bisogno di tutto il loop e passavo la funzione a seconda di una funzione di attivazione / disattivazione con l'ambito (fondamentalmente se il video è visualizzato -> autoplay; else -> pause il video). Grazie!
DD.

Dovresti condividere la direttiva, potrebbe essere utile!
Kim T

1
Ecco un adattamento del codice per una penna: codepen.io/anon/pen/qERdza Spero che sia di aiuto! Attiva anche la pressione del tasto ESC quando il video è
attivo

Sembra troppo bello per essere vero! Esistono limiti di browser / sicurezza all'utilizzo di questo metodo?
Dan

Sì, IE ha alcune limitazioni, in particolare IE10 che supporta MessageChannel invece di postMessage: caniuse.com/#search=postMessage, inoltre, tieni presente che qualsiasi politica di sicurezza dei contenuti limiterà anche l'uso di questa funzione
Kim T

5

La mia versione del codice di Kim T sopra che si combina con alcuni jQuery e consente il targeting di iframe specifici.

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

Come sapere che YouTube sta giocando? qualsiasi callback dall'iframe di YouTube, quindi al di fuori può iscriversi?
Martello

@Hammer Dai un'occhiata alla sezione Eventi API di YouTube, nello specifico OnStateChange: developers.google.com/youtube/iframe_api_reference#Events
adamj,

@admj, puoi dare un'occhiata? Alcuni comportamenti strani ... stackoverflow.com/questions/38389802/…
Hammer

0

Grazie Rob W per la tua risposta.

Lo sto usando all'interno di un'applicazione Cordova per evitare di dover caricare l'API e in modo da poter controllare facilmente gli iframe che vengono caricati in modo dinamico.

Ho sempre voluto poter estrarre informazioni dall'iframe, come lo stato (getPlayerState) e l'ora (getCurrentTime).

Rob W ha contribuito a evidenziare come funziona l'API usando postMessage, ma ovviamente questo invia informazioni solo in una direzione, dalla nostra pagina web all'iframe. L'accesso ai getter ci richiede di ascoltare i messaggi che ci vengono inviati dall'iframe.

Mi ci è voluto del tempo per capire come modificare la risposta di Rob W per attivare e ascoltare i messaggi restituiti dall'iframe. Ho praticamente cercato il codice sorgente all'interno dell'iframe di YouTube fino a quando non ho trovato il codice responsabile dell'invio e della ricezione dei messaggi.

La chiave stava cambiando l '"evento" in "ascolto", in pratica questo dava accesso a tutti i metodi progettati per restituire valori.

Di seguito è la mia soluzione, tieni presente che sono passato ad "ascolto" solo quando sono richiesti i getter, puoi modificare la condizione per includere metodi extra.

Si noti inoltre che è possibile visualizzare tutti i messaggi inviati dall'iframe aggiungendo console.log (e) a window.onmessage. Noterai che una volta attivato l'ascolto riceverai aggiornamenti costanti che includono l'ora corrente del video. Chiamare getter come getPlayerState attiverà questi aggiornamenti costanti ma invierà un messaggio relativo allo stato del video quando lo stato è cambiato.

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);
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.