API iframe do YouTube: como controle um player de iframe que já está no HTML?

149

Quero controlar os players do YouTube baseados em iframe. Esses players já estarão no HTML, mas quero controlá-los por meio da API JavaScript.

Li a documentação da API iframe, que explica como adicionar um novo vídeo à página com a API e controlá-lo com as funções do player do YouTube:

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

Esse código cria um novo objeto player e o atribui a 'player', depois o insere dentro da div #container. Então eu posso operar em 'jogador' e chamada playVideo(), pauseVideo()etc. sobre ele.

Mas eu quero poder operar com players de iframe que já estão na página.

Eu poderia fazer isso muito facilmente com o antigo método de incorporação, com algo como:

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

Mas isso não funciona com os novos iframes. Como posso atribuir um objeto iframe já na página e depois usar as funções da API?

agente_secreto
fonte
Eu escrevi uma abstração para trabalhar com a API IFrame do YouTube github.com/gajus/playtube
Gajus

Respostas:

316

Fiddle Links: Código fonte - Visualização - Versão pequena
Atualização: Esta pequena função executa apenas o código em uma única direção. Se você deseja suporte total (por exemplo, ouvintes / receptores de eventos), dê uma olhada em Listening for Youtube Event no jQuery

Como resultado de uma análise profunda do código, criei uma função: function callPlayersolicita uma chamada de função em qualquer vídeo do YouTube emoldurado. Consulte a referência da API do YouTube para obter uma lista completa das possíveis chamadas de função. Leia os comentários no código fonte para obter uma explicação.

Em 17 de maio de 2012, o tamanho do código foi dobrado para cuidar do estado de prontidão do jogador. Se você precisar de uma função compacta que não lide com o estado pronto do player, consulte 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);
    }
}

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

Possíveis perguntas (e respostas):

Q : Não funciona!
R : "Não funciona" não é uma descrição clara. Você recebe alguma mensagem de erro? Por favor, mostre o código relevante.

Q : playVideonão reproduz o vídeo.
R : A reprodução requer interação do usuário e presença allow="autoplay"no iframe. Consulte https://developers.google.com/web/updates/2017/09/autoplay-policy-changes e https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide

P : Incorporei um vídeo do YouTube usando, <iframe src="http://www.youtube.com/embed/As2rZGPGKDY" />mas a função não executa nenhuma função!
A : Você tem que adicionar ?enablejsapi=1no final do seu URL: /embed/vid_id?enablejsapi=1.

P : Recebo a mensagem de erro "Uma cadeia inválida ou ilegal foi especificada". Por quê?
R : A API não funciona corretamente em um host local ( file://). Hospede sua página (teste) online ou use o JSFiddle . Exemplos: Veja os links na parte superior desta resposta.

Q : Como você sabia disso?
R : Passei algum tempo interpretando manualmente a fonte da API. Concluí que tinha que usar o postMessagemétodo. Para saber quais argumentos passar, criei uma extensão do Chrome que intercepta mensagens. O código fonte da extensão pode ser baixado aqui .

P : Quais navegadores são suportados?
R : Todo navegador que suporta JSON e postMessage.

  • IE 8+
  • Firefox 3.6+ (na verdade 3.5, mas document.readyStatefoi implementado na 3.6)
  • Opera 10.50+
  • Safari 4+
  • Chrome 3 ou superior

Resposta / implementação relacionada: Incorporação gradual de um vídeo emoldurado usando o jQuery
Suporte completo à API: Ouvindo eventos do YouTube na jQuery
Official API: https://developers.google.com/youtube/iframe_api_reference

Histórico de Revisão

  • Maio 17, 2012
    Implementado onYouTubePlayerReady: callPlayer('frame_id', function() { ... }).
    As funções são colocadas na fila automaticamente quando o player ainda não está pronto.
  • 24 de julho de 2012
    Atualizado e testado com êxito nos navegadores suportados (veja a frente).
  • 10 de outubro de 2013 Quando uma função é passada como argumento, callPlayerforça uma verificação de prontidão. Isso é necessário, porque quando callPlayeré chamado logo após a inserção do iframe enquanto o documento está pronto, ele não pode ter certeza de que o iframe está totalmente pronto. No Internet Explorer e Firefox, esse cenário resultou em uma chamada muito precoce de postMessage, o que foi ignorado.
  • 12 de dezembro de 2013, recomendado para adicionar &origin=*a URL.
  • Em 2 de março de 2014, foi retirada a recomendação de remoção &origin=*para o URL.
  • 9 de abril de 2019, corrija um bug que resultasse em recursão infinita quando o YouTube fosse carregado antes que a página estivesse pronta. Adicione uma nota sobre a reprodução automática.
Rob W
fonte
@ RobW Eu tentei isso, na verdade. Parece que o JSON por erro não é o do seu script, mas o iframe como parte da API do youtube.
Fresheyeball
@ RobW obrigado por este bom trecho. Você encontrou alguma maneira de usar o Evento de Mensagem em vez de usar a API JS do youtube para adicionar um ouvinte de evento?
Brillout
@ brillout.com O PostMessagemétodo é baseado na API YT JS ( ?enablejsapi=1). Sem ativar a API JS, o postMessagemétodo não fará nada. Consulte a resposta vinculada para uma fácil implementação de listeners de eventos. Também criei, mas não publiquei, código legível para se comunicar com o quadro. Decidi não publicá-lo, porque seu efeito é semelhante à API de quadros padrão do YouTube.
Rob W
1
@MatthewBaker Isso requer ouvir o evento da mensagem e analisar o status do resultado. Isso não é tão fácil quanto as chamadas simples playVideo, por isso recomendo usar a API oficial para isso. Consulte developers.google.com/youtube/iframe_api_reference#Events .
Rob W
1
@ffyeahh Não vejo nenhum erro óbvio. Faça uma nova pergunta com etapas independentes para reproduzir em vez de adicionar perguntas nos comentários a esta resposta.
Rob W
33

Parece que o YouTube atualizou a API JS para que esteja disponível por padrão! Você pode usar o ID de iframe do YouTube existente ...

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

... no seu JS ...

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

function onPlayerStateChange() {
  //...
}

... e o construtor usará o iframe existente em vez de substituí-lo por um novo. Isso também significa que você não precisa especificar o videoId para o construtor.

Consulte Carregando um player de vídeo

CletusW
fonte
1
@venven, você está perdendo o parâmetro autoplay = 1 no URL. No seu exemplo url, seria youtube.com/embed/M7lc1UVf-VE?enablejsapi=1& autoplay = 1 & origem = example.com
alengel
@alengel ele não quer usar o parâmetro autoplay-url. Em vez disso, ele tenta iniciar o vídeo usando a função de reprodução automática de js-APIs. Mas, por algum motivo, a função onYouTubeIframeAPIReady () não é invocada.
Humppakäräjät
@venven eu descobri isso. 1) remova o & origin = example.com do URL do iframe. 2) na seção "Frameworks & Extensions" do seu jsfiddle, defina o segundo menu suspenso como "No wrap in - <head>" 3) adicione a API iframe do youtube como recurso externo ( youtube.com/iframe_api ); Bifurquei seu violino e apliquei as seguintes alterações: jsfiddle.net/e97famd1/1
Humppakäräjät
Alguma idéia do que enviar eventou commandenviar ao iframe do YT para parar listeningno status?
Mkhatib
@CletusW: Eu recebo este erro: Não detectado (em promessa) DOMException: A solicitação play () foi interrompida por uma chamada para pausar (). Promessa (assíncrona) (anônima) @ scripts.js: 20 dispatch @ jquery-1.12.4.js: 5226 elemData.handle @ jquery-1.12.4.js: 4878 cast_sender.js: 67 Não detectado DOMException: falha ao construir 'PresentationRequest ': A apresentação de um documento não seguro [cast: 233637DE? Capacidades = video_out% 2Caudio_out & clientId = 153262711713390989 & autoJoinPolicy = tab_and_origin_scoped & defaultActionPolicy = cast_this_tab & launchTimeout = 30000] é proibida em um contexto seguro.
26718 LauraNMS
20

Você pode fazer isso com muito menos código:

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

Exemplo de trabalho: http://jsfiddle.net/kmturley/g6P5H/296/

Kim T
fonte
Eu realmente gostei dessa maneira, apenas a adaptei para funcionar com uma diretiva Angular, portanto não precisava de todo o loop e passou a função dependendo de uma função de alternância com o escopo (basicamente se o vídeo for exibido -> reprodução automática; else -> pause o vídeo). Obrigado!
DD.
Você deve compartilhar a diretiva, pode ser útil!
Kim T
1
Aqui está uma adaptação do código para uma caneta: codepen.io/anon/pen/qERdza Espero que ajude! Também pressiona a tecla ESC quando o vídeo está
ativado
Isso parece bom demais para ser verdade! Existem limitações de segurança / navegador para usar este método?
Dan
Sim IE tem algumas limitações, especialmente IE10 que suporta MessageChannel vez de postMessage: caniuse.com/#search=postMessage também estar ciente de quaisquer políticas de segurança de conteúdo também irá restringir o uso desse recurso
Kim T
5

Minha própria versão do código de Kim T acima, que combina com alguns jQuery e permite o direcionamento de iframes específicos.

$(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
fonte
Como saber que o youtube está tocando? qualquer retorno de chamada do iframe do youtube, para que o exterior possa se inscrever?
Martelo
@Hammer Confira a seção Eventos da API do YouTube especificamente OnStateChange: developers.google.com/youtube/iframe_api_reference#Events
adamj
@admj, pode verificar isso? Alguns comportamento estranho ... stackoverflow.com/questions/38389802/...
Martelo
0

Obrigado Rob W pela sua resposta.

Eu tenho usado isso em um aplicativo Cordova para evitar ter que carregar a API e para que eu possa controlar facilmente iframes carregados dinamicamente.

Eu sempre quis poder extrair informações do iframe, como o estado (getPlayerState) e a hora (getCurrentTime).

Rob W ajudou a destacar como a API funciona usando o postMessage, mas é claro que isso só envia informações em uma direção, da nossa página da web para o iframe. O acesso aos getters requer que escutemos as mensagens enviadas de volta a partir do iframe.

Levei algum tempo para descobrir como ajustar a resposta de Rob W para ativar e ouvir as mensagens retornadas pelo iframe. Basicamente, pesquisei o código-fonte no iframe do YouTube até encontrar o código responsável pelo envio e recebimento de mensagens.

A chave foi mudar o 'evento' para 'ouvir', isso basicamente deu acesso a todos os métodos que foram projetados para retornar valores.

Abaixo está a minha solução, observe que eu mudei para 'ouvindo' apenas quando getters são solicitados; você pode ajustar a condição para incluir métodos extras.

Observe ainda que você pode visualizar todas as mensagens enviadas do iframe adicionando um console.log (e) à janela window.onmessage. Você notará que uma vez ativada a audição, você receberá atualizações constantes que incluem a hora atual do vídeo. Chamadores de getters como o getPlayerState ativarão essas atualizações constantes, mas enviarão apenas uma mensagem envolvendo o estado do vídeo quando o estado for alterado.

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
fonte