YouTube iframe API: bagaimana cara mengontrol pemain iframe yang sudah ada di HTML?


149

Saya ingin dapat mengontrol pemutar YouTube berbasis iframe. Pemain ini sudah ada dalam HTML, tapi saya ingin mengendalikan mereka melalui JavaScript API.

Saya telah membaca dokumentasi untuk API iframe yang menjelaskan cara menambahkan video baru ke halaman dengan API, dan kemudian mengontrolnya dengan fungsi pemutar YouTube:

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

Kode itu menciptakan objek pemain baru dan menugaskannya ke 'pemain', lalu memasukkannya ke dalam #container div. Lalu aku dapat beroperasi pada 'pemain' dan panggilan playVideo(), pauseVideo()dll di atasnya.

Tapi saya ingin dapat beroperasi pada pemain iframe yang sudah ada di halaman.

Saya bisa melakukan ini dengan sangat mudah dengan metode sematan lama, dengan sesuatu seperti:

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

Tetapi ini tidak bekerja dengan iframe baru. Bagaimana saya bisa menetapkan objek iframe sudah di halaman dan kemudian menggunakan fungsi API di atasnya?


Saya telah menulis abstraksi untuk bekerja dengan YouTube IFrame API github.com/gajus/playtube
Gajus

Jawaban:


316

Tautan Fiddle: Kode sumber - Pratinjau -
Pembaruan versi kecil: Fungsi kecil ini hanya akan mengeksekusi kode dalam satu arah. Jika Anda ingin dukungan penuh (misalnya acara pendengar / getter), kita lihat di Listening untuk Youtube acara di jQuery

Sebagai hasil dari analisis kode yang mendalam, saya telah membuat sebuah fungsi: function callPlayermeminta pemanggilan fungsi pada setiap video YouTube berbingkai. Lihat referensi YouTube Api untuk mendapatkan daftar lengkap kemungkinan panggilan fungsi. Baca komentar di kode sumber untuk penjelasan.

Pada 17 Mei 2012, ukuran kode digandakan untuk menjaga status siap pemain. Jika Anda membutuhkan fungsi ringkas yang tidak berurusan dengan status siap pemain, lihat 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);
    }
}

Pemakaian:

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

Kemungkinan pertanyaan (& jawaban):

T : Tidak berfungsi!
A : "Tidak berfungsi" bukan deskripsi yang jelas. Apakah Anda mendapatkan pesan kesalahan? Tolong tunjukkan kode yang relevan.

T : playVideotidak memutar video.
A : Playback membutuhkan interaksi pengguna, dan keberadaan allow="autoplay"iframe. Lihat https://developers.google.com/web/updates/2017/09/autoplay-policy-changes dan https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide

T : Saya telah menyematkan video YouTube menggunakan <iframe src="http://www.youtube.com/embed/As2rZGPGKDY" />tetapi fungsinya tidak menjalankan fungsi apa pun!
A : Anda harus menambahkan ?enablejsapi=1pada akhir URL Anda: /embed/vid_id?enablejsapi=1.

T : Saya mendapatkan pesan kesalahan "String tidak valid atau ilegal telah ditentukan". Mengapa?
A : API tidak berfungsi dengan baik di host lokal ( file://). Host halaman Anda (uji) secara online, atau gunakan JSFiddle . Contoh: Lihat tautan di bagian atas jawaban ini.

T : Bagaimana Anda tahu ini?
A : Saya telah menghabiskan beberapa waktu untuk menafsirkan sumber API secara manual. Saya menyimpulkan bahwa saya harus menggunakan postMessagemetode ini. Untuk mengetahui argumen yang harus dilewati, saya membuat ekstensi Chrome yang memotong pesan. Kode sumber untuk ekstensi dapat diunduh di sini .

T : Browser apa yang didukung?
A : Setiap browser yang mendukung JSON dan postMessage.

  • IE 8+
  • Firefox 3.6+ (sebenarnya 3.5, tetapi document.readyStatediterapkan pada 3.6)
  • Opera 10.50+
  • Safari 4+
  • Chrome 3+

Jawaban / implementasi terkait: Memudar dalam video berbingkai menggunakan jQuery
Dukungan API Lengkap: Mendengarkan Acara Youtube di jQuery
API Resmi: https://developers.google.com/youtube/iframe_api_reference

Riwayat revisi

  • 17 mungkin 2012
    Dilaksanakan onYouTubePlayerReady: callPlayer('frame_id', function() { ... }).
    Fungsi secara otomatis antri ketika pemain belum siap.
  • 24 Juli 2012
    Diperbarui dan diuji secara sukses di browser yang didukung (lihat ke depan).
  • 10 oktober 2013 Ketika suatu fungsi dilewatkan sebagai argumen, callPlayermemaksa pemeriksaan kesiapan. Ini diperlukan, karena ketika callPlayerdipanggil tepat setelah penyisipan iframe saat dokumen siap, ia tidak dapat mengetahui dengan pasti bahwa iframe sepenuhnya siap. Di Internet Explorer dan Firefox, skenario ini mengakibatkan permohonan yang terlalu dini postMessage, yang diabaikan.
  • 12 Des 2013, disarankan untuk menambahkan &origin=*URL.
  • 2 Mar 2014, mencabut rekomendasi untuk menghapus &origin=*ke URL.
  • 9 April 2019, perbaiki bug yang mengakibatkan rekursi tak terbatas saat YouTube dimuat sebelum halaman siap. Tambahkan catatan tentang putar otomatis.

@ Bob aku benar-benar mencobanya. Sepertinya kesalahan JSON bukan yang ada di skrip Anda, tetapi di dalam iframe sebagai bagian dari API youtube.
Fresheyeball

@RobW terima kasih atas cuplikan yang bagus ini. Sudahkah Anda menemukan cara untuk menggunakan Acara Pesan alih-alih menggunakan youtube JS API untuk menambah pendengar acara?
brillout

@ brillout.com PostMessageMetode ini didasarkan pada API JS YT ( ?enablejsapi=1). Tanpa mengaktifkan API JS, postMessagemetode ini tidak akan melakukan apa pun, Lihat jawaban tertaut untuk implementasi pendengar acara yang mudah. Saya juga telah membuat, tetapi tidak menerbitkan, kode yang dapat dibaca untuk berkomunikasi dengan bingkai. Saya memutuskan untuk tidak menerbitkannya, karena efeknya mirip dengan YouTube Frame API default.
Rob W

1
@MatthewBaker Itu membutuhkan mendengarkan acara pesan dan menguraikan status hasilnya. Ini tidak semudah panggilan sederhana playVideo, jadi saya sarankan untuk menggunakan API resmi untuk itu. Lihat developers.google.com/youtube/iframe_api_reference#Events .
Rob W

1
@ffyeahh Saya tidak melihat kesalahan yang jelas. Tolong ajukan pertanyaan baru dengan langkah-langkah untuk mereproduksi mandiri daripada menambahkan pertanyaan dalam komentar untuk jawaban ini.
Rob W

33

Sepertinya YouTube telah memperbarui JS API mereka sehingga ini tersedia secara default! Anda dapat menggunakan ID iframe YouTube yang ada ...

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

... di JS Anda ...

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

function onPlayerStateChange() {
  //...
}

... dan konstruktor akan menggunakan iframe Anda yang sudah ada alih-alih menggantinya dengan yang baru. Ini juga berarti Anda tidak harus menentukan videoID ke konstruktor.

Lihat Memuat pemutar video


1
@Aven Anda kehilangan parameter putar otomatis = 1 di url. Dalam contoh url Anda, ini akan menjadi youtube.com/embed/M7lc1UVf-VE?enablejsapi=1& autoplay = 1 & origin = example.com
alengel

@alengel dia tidak ingin menggunakan parameter autoplay-url. Sebaliknya ia mencoba memulai video dengan menggunakan fungsi putar otomatis js-API. Tetapi untuk beberapa alasan fungsi onYouTubeIframeAPIReady () tidak dipanggil.
Humppakäräjät

@ Elven aku berhasil menemukannya. 1) hapus & origin = example.com dari url iframe. 2) di bagian "Frameworks & Extensions" dari jsfiddle Anda, atur menu drop down kedua ke "No wrap in - <head>" 3) tambahkan api iframe youtube sebagai sumber daya eksternal ( youtube.com/iframe_api ); Saya memotong biola Anda dan menerapkan perubahan ini: jsfiddle.net/e97famd1/1
Humppakäräjät

Adakah yang tahu eventatau commandmengirim ke iframe YT untuk berhenti listeningke status?
mkhatib

@CletusW: Saya mendapatkan kesalahan ini: Tidak tertangkap (berjanji) DOMException: Permintaan play () terputus oleh panggilan untuk jeda (). Promise (async) (anonim) @ scripts.js: 20 dispatch @ jquery-1.12.4.js: 5226 elemData.handle @ jquery-1.12.4.js: 4878 cast_sender.js: 67 Tidak Ditangkap DOMException: Gagal membangun 'PresentationRequest' Permintaan ': Penyajian dokumen tidak aman [cast: 233637DE? Kapabilitas = video_out% 2Caudio_out & clientId = 153262711713390989 & autoJoinPolicy = tab_and_origin_scoped & defaultActionPolicy = cast_this_tab & launchTimeout = 30000] dilarang dari konteks aman dari konteks aman.
LauraNMS

20

Anda dapat melakukan ini dengan kode yang jauh lebih sedikit:

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

Contoh kerja: http://jsfiddle.net/kmturley/g6P5H/296/


Saya sangat menyukai cara ini, hanya mengadaptasinya agar bekerja dengan arahan Angular sehingga tidak memerlukan semua loop dan meneruskan fungsi tergantung pada fungsi sakelar dengan cakupan (pada dasarnya jika video ditampilkan -> autoplay; lain -> jeda video). Terima kasih!
DD.

Anda harus membagikan arahannya, bisa bermanfaat!
Kim T

1
Berikut ini adalah adaptasi dari kode untuk Pena: codepen.io/anon/pen/qERdza Saya harap ini membantu! Itu juga beralih menekan tombol ESC ketika Anda memiliki video di
DD.

Tampaknya ini terlalu bagus untuk menjadi kenyataan! Apakah ada batasan browser / keamanan untuk menggunakan metode ini?
Dan

Ya IE memiliki beberapa keterbatasan, terutama IE10 yang mendukung MessageChannel bukan postMessage: caniuse.com/#search=postMessage juga menyadari setiap Kebijakan Keamanan Konten juga akan membatasi penggunaan fitur ini
Kim T

5

Versi saya sendiri dari kode Kim T di atas yang menggabungkan dengan beberapa jQuery dan memungkinkan untuk penargetan iframe tertentu.

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

Bagaimana cara mengetahui youtube sedang diputar? ada panggilan balik dari iframe youtube, jadi di luar bisa berlangganan?
Palu

@Hammer Lihat bagian Acara API YouTube khusus OnStateChange: developers.google.com/youtube/iframe_api_reference#Events
adamj

@ Admj, Bisakah lihat ini? Beberapa perilaku aneh ... stackoverflow.com/questions/38389802/…
Hammer

0

Terima kasih Rob W atas jawaban Anda.

Saya telah menggunakan ini dalam aplikasi Cordova untuk menghindari keharusan memuat API dan agar saya dapat dengan mudah mengontrol iframe yang dimuat secara dinamis.

Saya selalu menginginkan kemampuan untuk dapat mengekstrak informasi dari iframe, seperti status (getPlayerState) dan waktu (getCurrentTime).

Rob W membantu menyoroti bagaimana API bekerja menggunakan postMessage, tetapi tentu saja ini hanya mengirimkan informasi dalam satu arah, dari halaman web kami ke iframe. Mengakses getter mengharuskan kita untuk mendengarkan pesan yang dikirim kembali kepada kita dari iframe.

Butuh beberapa waktu untuk memikirkan cara mengubah jawaban Rob W untuk mengaktifkan dan mendengarkan pesan yang dikembalikan oleh iframe. Saya pada dasarnya mencari melalui kode sumber dalam iframe YouTube sampai saya menemukan kode yang bertanggung jawab untuk mengirim dan menerima pesan.

Kuncinya adalah mengubah 'acara' menjadi 'mendengarkan', ini pada dasarnya memberi akses ke semua metode yang dirancang untuk mengembalikan nilai.

Di bawah ini adalah solusi saya, harap dicatat bahwa saya telah beralih ke 'mendengarkan' hanya ketika getter diminta, Anda dapat mengubah kondisi untuk memasukkan metode tambahan.

Perhatikan lebih lanjut bahwa Anda dapat melihat semua pesan yang dikirim dari iframe dengan menambahkan console.log (e) ke window.onmessage. Anda akan melihat bahwa setelah mendengarkan diaktifkan, Anda akan menerima pembaruan konstan yang mencakup waktu video saat ini. Getter panggilan seperti getPlayerState akan mengaktifkan pembaruan konstan ini tetapi hanya akan mengirim pesan yang melibatkan status video saat status telah berubah.

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);
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.