YouTube iframe API: ฉันจะควบคุมผู้เล่น iframe ที่มีอยู่ใน HTML ได้อย่างไร


149

ฉันต้องการควบคุมผู้เล่น YouTube ที่ใช้ iframe ผู้เล่นนี้จะอยู่ใน HTML อยู่แล้ว แต่ฉันต้องการควบคุมพวกเขาผ่าน JavaScript API

ฉันได้อ่านเอกสารประกอบสำหรับ iframe APIซึ่งอธิบายวิธีเพิ่มวิดีโอใหม่ไปยังหน้าด้วย API แล้วควบคุมด้วยฟังก์ชั่นเครื่องเล่น YouTube:

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

รหัสนั้นสร้างวัตถุผู้เล่นใหม่และกำหนดให้กับ 'ผู้เล่น' จากนั้นแทรกเข้าไปใน #container div แล้วฉันจะดำเนินการเกี่ยวกับ 'ผู้เล่น' และโทรplayVideo(), pauseVideo()ฯลฯ กับมัน

แต่ฉันต้องการที่จะสามารถทำงานกับผู้เล่น iframe ที่มีอยู่แล้วในหน้า

ฉันสามารถทำได้อย่างง่ายดายด้วยวิธีการฝังแบบเก่าด้วยสิ่งที่ชอบ:

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

แต่วิธีนี้ไม่สามารถใช้ได้กับ iframes ใหม่ ฉันจะกำหนดวัตถุ iframe บนหน้าแล้วใช้ฟังก์ชัน API กับมันได้อย่างไร


ฉันได้เขียนสิ่งที่เป็นนามธรรมสำหรับการทำงานกับ YouTube IFrame API github.com/gajus/playtube
Gajus

คำตอบ:


316

Fiddle Links: Source Source - Preview - Small version
Update: ฟังก์ชั่นเล็ก ๆ นี้จะรันโค้ดในทิศทางเดียวเท่านั้น หากคุณต้องการการสนับสนุนอย่างเต็มที่ (เช่นผู้ฟังเหตุการณ์ / ผู้ได้รับ) ลองดูที่การฟังเหตุการณ์ Youtube ใน jQuery

จากการวิเคราะห์โค้ดที่ลึกฉันได้สร้างฟังก์ชั่น: function callPlayerร้องขอการเรียกใช้ฟังก์ชันบนวิดีโอ YouTube ที่มีกรอบ ดูการอ้างอิง YouTube Apiเพื่อรับรายการการเรียกฟังก์ชันทั้งหมด อ่านความคิดเห็นที่ซอร์สโค้ดสำหรับคำอธิบาย

ในวันที่ 17 พฤษภาคม 2555 ขนาดรหัสเพิ่มเป็นสองเท่าเพื่อดูแลสถานะพร้อมของผู้เล่น หากคุณต้องการฟังก์ชั่นขนาดเล็กที่ไม่ได้จัดการกับสถานะพร้อมของผู้เล่นให้ดู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);
    }
}

การใช้งาน:

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

คำถามที่เป็นไปได้ (& คำตอบ):

ถาม : มันใช้งานไม่ได้!
ตอบ : "ไม่ทำงาน" ไม่ใช่คำอธิบายที่ชัดเจน คุณได้รับข้อความแสดงข้อผิดพลาดหรือไม่? กรุณาแสดงรหัสที่เกี่ยวข้อง

ถาม : playVideoไม่เล่นวิดีโอ
ตอบ : การเล่นต้องมีการโต้ตอบกับผู้ใช้และการมีอยู่ของallow="autoplay"iframe ดูhttps://developers.google.com/web/updates/2017/09/autoplay-policy-changesและhttps://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide

ถาม : ฉันได้ฝังวิดีโอ YouTube ไว้แล้ว<iframe src="http://www.youtube.com/embed/As2rZGPGKDY" />แต่ฟังก์ชั่นนี้ไม่ได้ใช้งานฟังก์ชั่นใด ๆ ! : คุณมีการเพิ่มในส่วนท้ายของ URL ของคุณ:
?enablejsapi=1/embed/vid_id?enablejsapi=1

ถาม : ฉันได้รับข้อความแสดงข้อผิดพลาด "มีการระบุสตริงที่ไม่ถูกต้องหรือผิดกฎหมาย" ทำไม?
ตอบ : API ทำงานไม่ถูกต้องที่โลคัลโฮสต์ ( file://) เป็นเจ้าภาพ (ทดสอบ) หน้าเว็บของคุณออนไลน์หรือใช้JSFiddle ตัวอย่าง: ดูลิงค์ที่ด้านบนของคำตอบนี้

ถาม : คุณรู้เรื่องนี้ได้อย่างไร
ตอบ : ฉันใช้เวลาแปลแหล่ง API ด้วยตนเอง ฉันสรุปว่าฉันต้องใช้postMessageวิธีนี้ หากต้องการทราบว่าต้องผ่านการโต้แย้งใดฉันได้สร้างส่วนขยายของ Chrome ซึ่งจะดักจับข้อความ รหัสที่มาสำหรับการขยายสามารถดาวน์โหลดได้ที่นี่

ถาม : เบราว์เซอร์ใดบ้างที่รองรับ : ทุกเบราว์เซอร์ที่สนับสนุนJSONและ
postMessage

  • IE 8+
  • Firefox 3.6+ (จริง 3.5 แต่document.readyStateใช้งานใน 3.6)
  • Opera 10.50+
  • Safari 4+
  • Chrome 3+

คำตอบ / การใช้งานที่เกี่ยวข้อง: จางหายไปในวิดีโอที่มีกรอบโดยใช้
การสนับสนุนjQuery Full API: การฟังสำหรับกิจกรรม Youtube ใน jQuery
Official API: https://developers.google.com/youtube/iframe_api_reference

ประวัติการแก้ไข

  • 17 พฤษภาคม 2012
    ดำเนินการ:onYouTubePlayerReady ฟังก์ชั่นจะเข้าคิวโดยอัตโนมัติเมื่อผู้เล่นยังไม่พร้อมcallPlayer('frame_id', function() { ... })
  • 24 กรกฎาคม 2012
    อัปเดตแล้วและผ่านการทดสอบในเบราว์เซอร์ที่รองรับแล้ว (ดูล่วงหน้า)
  • 10 ตุลาคม 2013 เมื่อฟังก์ชั่นถูกส่งผ่านเป็นข้อโต้แย้งcallPlayerบังคับให้ตรวจสอบความพร้อม สิ่งนี้จำเป็นเนื่องจากเมื่อcallPlayerมีการเรียกใช้หลังจากการแทรก iframe ในขณะที่เอกสารพร้อมจะไม่สามารถทราบได้อย่างแน่นอนว่า iframe พร้อมอย่างสมบูรณ์แล้ว ใน Internet Explorer และ Firefox สถานการณ์นี้ส่งผลให้มีการเรียกใช้ที่เร็วเกินไปpostMessageซึ่งถูกเพิกเฉย
  • 12 ธันวาคม 2013 แนะนำให้เพิ่ม&origin=*ใน URL
  • 2 มีนาคม 2014, คำแนะนำที่ถอนออก&origin=*เพื่อลบไปยัง URL
  • 9 เมษายน 2019 แก้ไขข้อผิดพลาดที่ทำให้เกิดการเรียกซ้ำไม่สิ้นสุดเมื่อ YouTube โหลดก่อนหน้าพร้อม เพิ่มบันทึกเกี่ยวกับการเล่นอัตโนมัติ

@ RobW ฉันลองใช้มันจริง ๆ ดูเหมือนว่าข้อผิดพลาด JSON ไม่ใช่สิ่งที่อยู่ในสคริปต์ของคุณ แต่ภายใน iframe เป็นส่วนหนึ่งของ API ของ youtube
Fresheyeball

@ RobW ขอบคุณสำหรับตัวอย่างที่ดีนี้ คุณพบวิธีการใช้กิจกรรมข้อความแทนการใช้ youtube JS API เพื่อเพิ่มฟังเหตุการณ์หรือไม่
brillout

@ brillout.com PostMessageวิธีนี้ใช้ YT JS API ( ?enablejsapi=1) หากไม่มีการเปิดใช้งาน JS API postMessageวิธีการนี้จะไม่ทำอะไรเลยดูคำตอบที่เชื่อมโยงเพื่อการปรับใช้ฟังเหตุการณ์ได้ง่าย ฉันได้สร้างขึ้น แต่ไม่ได้เผยแพร่โค้ดที่สามารถอ่านได้เพื่อสื่อสารกับเฟรม ฉันตัดสินใจที่จะไม่เผยแพร่เนื่องจากผลคล้ายกับ YouTube Frame API ที่เป็นค่าเริ่มต้น
Rob W

1
@MatthewBaker ที่ต้องฟังเหตุการณ์ข้อความและแยกวิเคราะห์สถานะของผลลัพธ์ นี่ไม่ใช่เรื่องง่ายเหมือนการโทรอย่างง่ายplayVideoดังนั้นฉันแนะนำให้ใช้ API อย่างเป็นทางการสำหรับสิ่งนั้น ดูdevelopers.google.com/youtube/iframe_api_reference#Events
Rob W

1
@ffyeahh ฉันไม่เห็นข้อผิดพลาดที่ชัดเจน กรุณาถามคำถามใหม่ที่มีขั้นตอนในการทำซ้ำตัวเองแทนการเพิ่มคำถามในความคิดเห็นในคำตอบนี้
Rob W

33

ดูเหมือนว่า YouTube จะอัปเดต JS API ของตนเพื่อให้สามารถใช้งานได้ตามค่าเริ่มต้น! คุณสามารถใช้ ID iframe ของ YouTube ที่มีอยู่ ...

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

... ใน JS ของคุณ ...

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

function onPlayerStateChange() {
  //...
}

... และตัวสร้างจะใช้ iframe ที่มีอยู่ของคุณแทนการแทนที่ด้วยอันใหม่ นอกจากนี้ยังหมายความว่าคุณไม่จำเป็นต้องระบุ videoId ให้กับ Constructor

ดูการโหลดเครื่องเล่นวิดีโอ


1
@raven คุณไม่มีพารามิเตอร์ autoplay = 1 ใน URL ใน URL ตัวอย่างของคุณจะเป็นyoutube.com/embed/M7lc1UVf-VE?enablejsapi=1& autoplay = 1 & origin = example.com
alengel

@alengel เขาไม่ต้องการใช้พารามิเตอร์ autoplay-url แต่เขาพยายามเริ่มวิดีโอโดยใช้ฟังก์ชั่นเล่นอัตโนมัติ js-API แต่ด้วยเหตุผลบางอย่างฟังก์ชั่น onYouTubeIframeAPIReady () จะไม่ถูกเรียกใช้
Humppakäräjät

@raven ฉันคิดออก 1) ลบ & origin = example.com ออกจาก iframe url 2) ในส่วน "Frameworks & Extensions" ของ jsfiddle ของคุณตั้งค่าเมนูแบบเลื่อนลงที่สองเป็น "No wrap in - <head>" 3) เพิ่ม youtube iframe api เป็นทรัพยากรภายนอก ( youtube.com/iframe_api ); ฉันแยกแยะ
Humppakäräjät

มีความคิดอะไรeventหรือcommandจะส่งไปยัง YT iframe เพื่อหยุดlisteningสถานะ
mkhatib

@CletusW: ฉันได้รับข้อผิดพลาดนี้: Uncaught (ตามสัญญา) DOMException: คำขอ play () ถูกขัดจังหวะโดยการเรียกเพื่อหยุดชั่วคราว () สัญญา (async) (ไม่ระบุชื่อ) @ scripts.js: 20 ส่ง @ jquery-1.12.4.js: 5226 elemData.handle @ jquery-1.12.4.js: 4878 cast_sender.js: 67 Uncaught DOMException: ล้มเหลวในการสร้าง 'PresentationRequest ': การนำเสนอเอกสารที่ไม่ปลอดภัย [นักแสดง: 233637DE? ความสามารถ = video_out% 2Caudio_out & clientId = 153262711713390989 & autoJoinPolicy = tab_and_origin_scoped & defaultActionPolicy = cast_this_tab & launchTimeout = 30000] เป็นสิ่งต้องห้ามจากบริบทที่ปลอดภัย
LauraNMS

20

คุณสามารถทำได้ด้วยรหัสน้อยกว่า:

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

ตัวอย่างการทำงาน: http://jsfiddle.net/kmturley/g6P5H/296/


ฉันชอบวิธีนี้จริง ๆ เพียงแค่ปรับให้ทำงานกับ Angular directive ดังนั้นไม่จำเป็นต้องวนซ้ำทั้งหมดและผ่าน func ขึ้นอยู่กับฟังก์ชันสลับกับขอบเขต (โดยทั่วไปถ้าวิดีโอแสดง -> เล่นอัตโนมัติ; วีดีโอ). ขอบคุณ!
DD

คุณควรแบ่งปันคำสั่งอาจมีประโยชน์!
Kim T

1
นี่คือการปรับรหัสสำหรับปากกา: codepen.io/anon/pen/qERdza ฉันหวังว่ามันจะช่วยได้! นอกจากนี้ยังสลับการกดปุ่ม ESC เมื่อคุณมีวิดีโอเปิด
DD

ดูเหมือนว่าจะดีเกินจริง! มีข้อ จำกัด ของเบราว์เซอร์ / ความปลอดภัยในการใช้วิธีนี้หรือไม่
Dan

ใช่ IE มีข้อ จำกัด เล็กน้อยโดยเฉพาะอย่างยิ่ง IE10 ที่รองรับ MessageChannel แทน postMessage: caniuse.com/#search=postMessageนอกจากนี้โปรดทราบว่านโยบายความปลอดภัยเนื้อหาใด ๆ จะ จำกัด การใช้คุณลักษณะนี้ด้วย
Kim T

5

โค้ดของ Kim T เวอร์ชันของฉันด้านบนซึ่งรวมกับ jQuery และอนุญาตให้กำหนดเป้าหมายของ 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 || []
        } ), '*');
    }
}

จะรู้ได้อย่างไรว่า youtube กำลังเล่นอยู่? การโทรกลับจาก iframe บน YouTube ดังนั้นภายนอกจึงสามารถสมัครรับข้อมูลได้
Hammer

@Hammer ตรวจสอบส่วนกิจกรรม YouTube API เฉพาะOnStateChange: developers.google.com/youtube/iframe_api_reference#Events
adamj

@admj, สามารถตรวจสอบนี้ได้หรือไม่ พฤติกรรมที่แปลกประหลาดบางอย่าง ... stackoverflow.com/questions/38389802/…
Hammer

0

ขอบคุณ Rob W สำหรับคำตอบของคุณ

ฉันใช้สิ่งนี้ในแอปพลิเคชัน Cordova เพื่อหลีกเลี่ยงการโหลด API และเพื่อให้ฉันสามารถควบคุม iframes ที่โหลดแบบไดนามิกได้อย่างง่ายดาย

ฉันต้องการความสามารถในการดึงข้อมูลจาก iframe เช่นสถานะ (getPlayerState) และเวลา (getCurrentTime) เสมอ

Rob W ช่วยเน้นว่า API ทำงานอย่างไรโดยใช้ postMessage แต่แน่นอนว่าวิธีนี้จะส่งข้อมูลในทิศทางเดียวเท่านั้นจากหน้าเว็บของเราไปยัง iframe การเข้าถึงผู้เรียกใช้ต้องให้เราฟังข้อความที่โพสต์กลับมาให้เราจาก iframe

ฉันใช้เวลาพอสมควรในการหาวิธีปรับแต่งคำตอบของ Rob W เพื่อเปิดใช้งานและฟังข้อความที่ส่งคืนโดย iframe โดยทั่วไปฉันค้นหาจากซอร์สโค้ดภายใน iframe ของ YouTube จนกระทั่งพบโค้ดที่รับผิดชอบในการส่งและรับข้อความ

กุญแจสำคัญคือการเปลี่ยน 'เหตุการณ์' เป็น 'การฟัง' ซึ่งเป็นการให้สิทธิ์การเข้าถึงวิธีการทั้งหมดที่ออกแบบมาเพื่อคืนค่า

ด้านล่างเป็นวิธีการแก้ปัญหาของฉันโปรดทราบว่าฉันได้เปลี่ยนเป็น 'ฟัง' เฉพาะเมื่อได้รับการร้องขอ getters คุณสามารถปรับสภาพเพื่อรวมวิธีการพิเศษ

โปรดทราบเพิ่มเติมว่าคุณสามารถดูข้อความทั้งหมดที่ส่งจาก iframe ได้โดยเพิ่ม console.log (e) ไปที่ window.onmessage คุณจะสังเกตเห็นว่าเมื่อเปิดใช้งานการฟังคุณจะได้รับการอัพเดทอย่างต่อเนื่องซึ่งรวมถึงเวลาปัจจุบันของวิดีโอ การเรียกใช้ getters เช่น getPlayerState จะเปิดใช้งานการอัปเดตคงที่เหล่านี้ แต่จะส่งข้อความที่เกี่ยวข้องกับสถานะวิดีโอเมื่อมีการเปลี่ยนแปลงสถานะ

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);
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.