YouTube iframe API: jak sterować odtwarzaczem iframe, który jest już w kodzie HTML?

149

Chcę mieć możliwość kontrolowania odtwarzaczy YouTube opartych na elementach iframe. Te odtwarzacze będą już w HTML, ale chcę kontrolować je przez JavaScript API.

Czytałem dokumentację dotyczącą iframe API, która wyjaśnia, jak dodać nowy film do strony za pomocą API, a następnie sterować nim za pomocą funkcji odtwarzacza YouTube:

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

Ten kod tworzy nowy obiekt gracza i przypisuje go do „gracza”, a następnie wstawia go do elementu div #container. Wtedy mogę działać na „gracza” i zaproszenia playVideo(), pauseVideo()itp na nim.

Ale chcę mieć możliwość obsługi odtwarzaczy iframe, które są już na stronie.

Mógłbym to bardzo łatwo zrobić za pomocą starej metody osadzania, z czymś takim:

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

Ale to nie działa z nowymi ramkami iframe. Jak mogę przypisać obiekt iframe już na stronie, a następnie użyć na nim funkcji API?

agente_secreto
źródło
Napisałem abstrakcję dotyczącą pracy z YouTube IFrame API github.com/gajus/playtube
Gajus

Odpowiedzi:

316

Fiddle Links: Kod źródłowy - Podgląd - Mała wersja
Aktualizacja: Ta mała funkcja wykonuje kod tylko w jednym kierunku. Jeśli chcesz mieć pełne wsparcie (np. Nasłuchujący / pobierający zdarzenia), spójrz na Listening for Youtube Event w jQuery

W wyniku dogłębnej analizy kodu utworzyłem funkcję: function callPlayerżąda wywołania funkcji w dowolnym obramowanym filmie YouTube. Zobacz odniesienie do interfejsu API YouTube, aby uzyskać pełną listę możliwych wywołań funkcji. Przeczytaj komentarze w kodzie źródłowym, aby uzyskać wyjaśnienie.

17 maja 2012 r. Podwojono rozmiar kodu, aby zadbać o stan gotowości gracza. Jeśli potrzebujesz kompaktowej funkcji, która nie radzi sobie ze stanem gotowości odtwarzacza, zobacz 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);
    }
}

Stosowanie:

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

Możliwe pytania (i odpowiedzi):

P : To nie działa!
Odp . : „Nie działa” nie jest jasnym opisem. Czy otrzymujesz komunikaty o błędach? Proszę pokazać odpowiedni kod.

P : playVideonie odtwarza wideo.
O : Odtwarzanie wymaga interakcji użytkownika i obecności allow="autoplay"w elemencie iframe. Zobacz https://developers.google.com/web/updates/2017/09/autoplay-policy-changes i https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide

P : Osadziłem film z YouTube za pomocą, <iframe src="http://www.youtube.com/embed/As2rZGPGKDY" />ale ta funkcja nie wykonuje żadnej funkcji! : Trzeba dodać na końcu adresu URL: .
?enablejsapi=1/embed/vid_id?enablejsapi=1

P : Otrzymuję komunikat o błędzie „Określono nieprawidłowy lub niedozwolony ciąg znaków”. Czemu?
Odp . : API nie działa poprawnie na lokalnym hoście ( file://). Hostuj swoją (testową) stronę online lub użyj JSFiddle . Przykłady: Zobacz linki u góry tej odpowiedzi.

P : Skąd o tym wiedziałeś?
Odp . : Spędziłem trochę czasu, aby ręcznie zinterpretować źródło API. Doszedłem do wniosku, że muszę użyć tej postMessagemetody. Aby wiedzieć, które argumenty przekazać, stworzyłem rozszerzenie Chrome, które przechwytuje wiadomości. Kod źródłowy rozszerzenia można pobrać tutaj .

P : Jakie przeglądarki są obsługiwane?
O : Każda przeglądarka obsługująca JSON i postMessage.

  • IE 8+
  • Firefox 3.6+ (właściwie 3.5, ale document.readyStatezostał zaimplementowany w 3.6)
  • Opera 10.50+
  • Safari 4+
  • Chrome 3+

Powiązana odpowiedź / implementacja: Zanikaj oprawione wideo za pomocą jQuery
Pełna obsługa interfejsu API: Słuchanie wydarzenia YouTube w jQuery
Oficjalny interfejs API: https://developers.google.com/youtube/iframe_api_reference

Historia zmian

  • 17 may 2012
    Zaimplementowane onYouTubePlayerReady: callPlayer('frame_id', function() { ... }).
    Funkcje są automatycznie umieszczane w kolejce, gdy gracz nie jest jeszcze gotowy.
  • 24 lipca 2012
    Zaktualizowano i pomyślnie przetestowano w obsługiwanych przeglądarkach (patrz w przyszłość).
  • 10 października 2013 Przekazanie funkcji jako argument callPlayerwymusza sprawdzenie gotowości. Jest to potrzebne, ponieważ gdy callPlayerjest wywoływane zaraz po wstawieniu elementu iframe, gdy dokument jest gotowy, nie może wiedzieć na pewno, że element iframe jest w pełni gotowy. W przeglądarkach Internet Explorer i Firefox ten scenariusz spowodował zbyt wczesne wywołanie programu postMessage, które zostało zignorowane.
  • 12 grudnia 2013, zaleca się dodanie &origin=*adresu URL.
  • 2 marca 2014 r. Wycofano rekomendację usunięcia &origin=*do adresu URL.
  • 9 kwietnia 2019, napraw błąd, który powodował nieskończoną rekursję, gdy YouTube ładuje się, zanim strona była gotowa. Dodaj notatkę o autoodtwarzaniu.
Rob W
źródło
@RobW Właściwie to próbowałem. Wygląda na to, że błędny JSON nie znajduje się w skrypcie, ale w ramce iframe jako część interfejsu API YouTube.
Fresheyeball
@RobW dzięki za ten miły fragment. Czy znalazłeś jakiś sposób na użycie Message Event zamiast korzystania z youtube JS API w celu dodania detektora zdarzeń?
Brillout
@ brillout.com PostMessageMetoda oparta jest na YT JS API ( ?enablejsapi=1). Bez włączenia JS API postMessagemetoda nic nie da. Zobacz połączoną odpowiedź, aby zapoznać się z łatwą implementacją detektorów zdarzeń. Stworzyłem również, ale nie opublikowałem, czytelny kod do komunikacji z ramą. Postanowiłem go nie publikować, ponieważ jego efekt jest podobny do domyślnego YouTube Frame API.
Rob W,
1
@MatthewBaker To wymaga nasłuchiwania zdarzenia wiadomości i analizowania stanu wyniku. Nie jest to tak proste, jak proste wywołania playVideo, więc polecam do tego użyć oficjalnego API. Zobacz developers.google.com/youtube/iframe_api_reference#Events .
Rob W.
1
@ffyeahh Nie widzę żadnego oczywistego błędu. Proszę zadać nowe pytanie z samodzielnymi krokami do odtworzenia zamiast dodawać pytania w komentarzach do tej odpowiedzi.
Rob W
33

Wygląda na to, że YouTube zaktualizował swoje API JS, więc jest to domyślnie dostępne! Możesz użyć istniejącego identyfikatora elementu iframe YouTube ...

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

... w twoim JS ...

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

function onPlayerStateChange() {
  //...
}

... a konstruktor użyje istniejącego elementu iframe zamiast zastępować go nowym. Oznacza to również, że nie musisz określać videoId konstruktorowi.

Zobacz Ładowanie odtwarzacza wideo

Cletus W.
źródło
1
@raven, w adresie URL brakuje parametru autoplay = 1. W Twoim przykładowym
adresie
@alengel nie chce używać parametru autoplay-url. Zamiast tego próbuje uruchomić wideo za pomocą funkcji autoodtwarzania js-APIs. Ale z jakiegoś powodu funkcja onYouTubeIframeAPIReady () nie jest wywoływana.
Humppakäräjät
@raven Rozgryzłem to. 1) usuń & origin = example.com z adresu URL ramki iframe. 2) w sekcji „Struktury i rozszerzenia” swojego jsfiddle ustaw drugie menu rozwijane na „Bez zawijania - <head>” 3) dodaj interfejs API iframe YouTube jako zasób zewnętrzny ( youtube.com/iframe_api ); Rozwidliłem
Humppakäräjät
Masz jakiś pomysł, co eventlub commandwysłać do iframe YT, aby zatrzymać listeningsię na statusie?
mkhatib
@CletusW: Otrzymuję ten błąd: Uncaught (w obietnicy) DOMException: Żądanie play () zostało przerwane przez wywołanie pause (). Promise (async) (anonymous) @ scripts.js: 20 dispatch @ jquery-1.12.4.js: 5226 elemData.handle @ jquery-1.12.4.js: 4878 cast_sender.js: 67 Uncaught DOMException: nie udało się utworzyć 'PresentationRequest ': Prezentacja niezabezpieczonego dokumentu [cast: 233637DE? Możliwości = video_out% 2Caudio_out & clientId = 153262711713390989 & autoJoinPolicy = tab_and_origin_scoped & defaultActionPolicy = cast_this_tab & launchTimeout = 30000] jest zabroniona w bezpiecznym kontekście.
LauraNMS
20

Możesz to zrobić przy znacznie mniejszej ilości kodu:

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

Przykład roboczy: http://jsfiddle.net/kmturley/g6P5H/296/

Kim T.
źródło
Naprawdę podobał mi się ten sposób, po prostu dostosowałem go do pracy z dyrektywą Angular, więc nie potrzebowałem całej pętli i nie przekazywałem funkcji w zależności od funkcji przełączania z zakresem (w zasadzie jeśli wideo jest wyświetlane -> autoodtwarzanie; else -> pauza wideo). Dzięki!
DD.
Powinieneś podzielić się dyrektywą, może być przydatna!
Kim T
1
Oto adaptacja kodu dla pióra: codepen.io/anon/pen/qERdza Mam nadzieję, że to pomoże! Przełącza również naciskanie klawisza ESC, gdy masz włączone wideo
DD.
To wydaje się zbyt piękne, aby mogło być prawdziwe! Czy są jakieś ograniczenia przeglądarki / bezpieczeństwa dotyczące korzystania z tej metody?
Dan
Tak, IE ma kilka ograniczeń, zwłaszcza IE10, który obsługuje MessageChannel zamiast postMessage: caniuse.com/#search=postMessage również pamiętaj, że wszelkie zasady bezpieczeństwa treści również ograniczą korzystanie z tej funkcji
Kim T
5

Moja własna wersja powyższego kodu Kim T, która łączy się z niektórymi jQuery i umożliwia kierowanie na określone elementy iframe.

$(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
źródło
Jak się dowiedzieć, że youtube gra? jakieś wywołanie zwrotne z iframe youtube, więc na zewnątrz można subskrybować?
Hammer
@Hammer Zapoznaj się z sekcją wydarzeń interfejsu API YouTube, a konkretnie OnStateChange: developers.google.com/youtube/iframe_api_reference#Events
adamj
@admj, czy możesz to sprawdzić? Niektóre dziwne zachowanie ... stackoverflow.com/questions/38389802/…
Hammer
0

Dziękuję Rob W za odpowiedź.

Używałem tego w aplikacji Cordova, aby uniknąć konieczności ładowania interfejsu API i aby móc łatwo kontrolować elementy iframe, które są ładowane dynamicznie.

Zawsze chciałem mieć możliwość wyodrębnienia informacji z iframe, takich jak stan (getPlayerState) i czas (getCurrentTime).

Rob W pomógł podkreślić, jak działa API przy użyciu postMessage, ale oczywiście wysyła to informacje tylko w jednym kierunku, z naszej strony internetowej do elementu iframe. Dostęp do getterów wymaga od nas nasłuchiwania wiadomości wysyłanych z powrotem do nas z elementu iframe.

Zajęło mi trochę czasu, zanim zrozumiałem, jak zmodyfikować odpowiedź Roba W., aby aktywować i odsłuchać wiadomości zwracane przez element iframe. Zasadniczo przeszukałem kod źródłowy w elemencie iframe YouTube, aż znalazłem kod odpowiedzialny za wysyłanie i odbieranie wiadomości.

Kluczem była zmiana „zdarzenia” na „nasłuchiwanie”, co w zasadzie dało dostęp do wszystkich metod, które zostały zaprojektowane do zwracania wartości.

Poniżej znajduje się moje rozwiązanie. Zwróć uwagę, że przełączyłem się na „słuchanie” tylko wtedy, gdy wymagane są metody pobierające. Możesz dostosować warunek, aby uwzględnić dodatkowe metody.

Zauważ ponadto, że możesz wyświetlić wszystkie wiadomości wysłane z ramki iframe, dodając console.log (e) do window.onmessage. Zauważysz, że po aktywowaniu słuchania będziesz otrzymywać ciągłe aktualizacje, które obejmują aktualny czas filmu. Wywołanie metod pobierających, takich jak getPlayerState, uaktywni te ciągłe aktualizacje, ale wyśle ​​wiadomość zawierającą stan wideo tylko wtedy, gdy stan się zmieni.

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
źródło