Javascript YouTube iframe API:如何控制 HTML 中已有的 iframe 播放器?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7443578/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
YouTube iframe API: how do I control a iframe player that's already in the HTML?
提问by agente_secreto
I want to be able to control iframe based YouTube players. This players will be already in the HTML, but I want to control them via the JavaScript API.
我希望能够控制基于 iframe 的 YouTube 播放器。这个播放器已经在 HTML 中了,但我想通过 JavaScript API 控制它们。
I've been reading the documentation for the iframe APIwhich explain how to add a new video to the page with the API, and then control it with the YouTube player functions:
我一直在阅读iframe API的文档,其中解释了如何使用 API 向页面添加新视频,然后使用 YouTube 播放器功能对其进行控制:
var player;
function onYouTubePlayerAPIReady() {
player = new YT.Player('container', {
height: '390',
width: '640',
videoId: 'u1zgFlCw8Aw',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
That code creates a new player object and assigns it to 'player', then inserts it inside the #container div. Then I can operate on 'player' and call playVideo()
, pauseVideo()
, etc. on it.
该代码创建了一个新的播放器对象并将其分配给“播放器”,然后将其插入到#container div 中。然后,我可以在“播放器”和呼叫操作playVideo()
,pauseVideo()
就可以了,等等。
But I want to be able to operate on iframe players which are already on the page.
但我希望能够对页面上已经存在的 iframe 播放器进行操作。
I could do this very easily with the old embed method, with something like:
我可以使用旧的 embed 方法很容易地做到这一点,例如:
player = getElementById('whateverID');
player.playVideo();
But this doesn't work with the new iframes. How can I assign a iframe object already on the page and then use the API functions on it?
但这不适用于新的 iframe。如何分配页面上已有的 iframe 对象,然后在其上使用 API 函数?
回答by Rob W
Fiddle Links: Source code- Preview- Small version
Update: This small function will only execute code in a single direction. If you want full support (eg event listeners / getters), have a look at Listening for Youtube Event in jQuery
小提琴链接:源代码-预览-小版本
更新:这个小函数只会在一个方向上执行代码。如果你想完全支持(如事件监听器/吸气),看看在侦听YouTube事件中的jQuery
As a result of a deep code analysis, I've created a function: function callPlayer
requests a function call on any framed YouTube video. See the YouTube Api referenceto get a full list of possible function calls. Read the comments at the source code for an explanation.
作为深入代码分析的结果,我创建了一个函数:function callPlayer
请求对任何带框的 YouTube 视频进行函数调用。请参阅YouTube Api 参考以获取可能的函数调用的完整列表。阅读源代码中的注释以获得解释。
On 17 may 2012, the code size was doubled in order to take care of the player's ready state. If you need a compact function which does not deal with the player's ready state, see http://jsfiddle.net/8R5y6/.
2012 年 5 月 17 日,代码大小增加了一倍,以照顾玩家的就绪状态。如果您需要一个不处理播放器就绪状态的紧凑函数,请参阅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);
}
}
Usage:
用法:
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");
Possible questions (& answers):
可能的问题(和答案):
Q: It doesn't work!
A: "Doesn't work" is not a clear description. Do you get any error messages? Please show the relevant code.
问:它不起作用!
答:“不起作用”不是一个明确的描述。您是否收到任何错误消息?请出示相关代码。
Q: playVideo
does not play the video.
A: Playback requires user interaction, and the presence of allow="autoplay"
on the iframe. See https://developers.google.com/web/updates/2017/09/autoplay-policy-changesand https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide
问:playVideo
不播放视频。
A: Playback 需要用户交互,并且存在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
Q: I have embedded a YouTube video using <iframe src="http://www.youtube.com/embed/As2rZGPGKDY" />
but the function doesn't execute any function!
A: You have to add ?enablejsapi=1
at the end of your URL: /embed/vid_id?enablejsapi=1
.
Q: 我已经嵌入了一个 YouTube 视频,<iframe src="http://www.youtube.com/embed/As2rZGPGKDY" />
但该功能没有执行任何功能!
A: 您必须?enablejsapi=1
在 URL 末尾添加:/embed/vid_id?enablejsapi=1
。
Q: I get error message "An invalid or illegal string was specified". Why?
A: The API doesn't function properly at a local host (file://
). Host your (test) page online, or use JSFiddle. Examples: See the links at the top of this answer.
问:我收到错误消息“指定了无效或非法的字符串”。为什么?
答:API 在本地主机 ( file://
)上无法正常运行。在线托管您的(测试)页面,或使用JSFiddle。示例:请参阅此答案顶部的链接。
Q: How did you know this?
A: I have spent some time to manually interpret the API's source. I concluded that I had to use the postMessage
method. To know which arguments to pass, I created a Chrome extension which intercepts messages. The source code for the extension can be downloaded here.
问:你是怎么知道这个的?
A: 我花了一些时间来手动解释 API 的源代码。我得出结论,我必须使用该postMessage
方法。为了知道要传递哪些参数,我创建了一个拦截消息的 Chrome 扩展程序。可以在此处下载扩展的源代码。
Q: What browsers are supported?
A: Every browser which supports JSONand postMessage
.
问:支持哪些浏览器?
答:所有支持JSON和postMessage
.
- IE 8+
- Firefox 3.6+ (actually 3.5, but
document.readyState
was implemented in 3.6) - Opera 10.50+
- Safari 4+
- Chrome 3+
- 浏览器 8+
- Firefox 3.6+(实际上是 3.5,但
document.readyState
在 3.6 中实现) - 歌剧 10.50+
- 野生动物园 4+
- 铬 3+
Related answer / implementation: Fade-in a framed video using jQuery
Full API support: Listening for Youtube Event in jQuery
Official API: https://developers.google.com/youtube/iframe_api_reference
相关答案/实现:使用 jQuery
完整 API 支持淡入带框视频:在 jQuery
官方 API 中侦听 Youtube 事件:https: //developers.google.com/youtube/iframe_api_reference
Revision history
修订记录
- 17 may 2012
ImplementedonYouTubePlayerReady
:callPlayer('frame_id', function() { ... })
.
Functions are automatically queued when the player is not ready yet. - 24 july 2012
Updated and successully tested in the supported browsers (look ahead). - 10 october 2013
When a function is passed as an argument,
callPlayer
forces a check of readiness. This is needed, because whencallPlayer
is called right after the insertion of the iframe while the document is ready, it can't know for sure that the iframe is fully ready. In Internet Explorer and Firefox, this scenario resulted in a too early invocation ofpostMessage
, which was ignored. - 12 Dec 2013, recommended to add
&origin=*
in the URL. - 2 Mar 2014, retracted recommendation to remove
&origin=*
to the URL. - 9 april 2019, fix bug that resulted in infinite recursion when YouTube loads before the page was ready. Add note about autoplay.
- 2012 年 5 月 17 日
实施onYouTubePlayerReady
:callPlayer('frame_id', function() { ... })
。
当播放器尚未准备好时,函数会自动排队。 - 2012 年 7 月 24 日
在支持的浏览器中更新并成功测试(展望未来)。 - 2013 年 10 月 10 日当函数作为参数传递时,
callPlayer
强制检查准备情况。这是必需的,因为在callPlayer
文档准备好插入 iframe 之后立即调用when ,它无法确定 iframe 是否完全准备好。在 Internet Explorer 和 Firefox 中,这种情况导致过早调用postMessage
,而这被忽略了。 - 2013 年 12 月 12 日,建议
&origin=*
在 URL 中添加。 - 2014 年 3 月 2 日,撤回了删除
&origin=*
URL 的建议。 - 2019 年 4 月 9 日,修复了在页面准备就绪之前加载 YouTube 时导致无限递归的错误。添加有关自动播放的注释。
回答by CletusW
Looks like YouTube has updated their JS API so this is available by default! You can use an existing YouTube iframe's ID...
看起来 YouTube 已经更新了他们的 JS API,因此默认情况下可以使用它!您可以使用现有 YouTube iframe 的 ID...
<iframe id="player" src="http://www.youtube.com/embed/M7lc1UVf-VE?enablejsapi=1&origin=http://example.com" frameborder="0"></iframe>
...in your JS...
...在你的JS ...
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
events: {
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerStateChange() {
//...
}
...and the constructor will use your existing iframe instead of replacing it with a new one. This also means you don't have to specify the videoId to the constructor.
...并且构造函数将使用您现有的 iframe 而不是用新的 iframe 替换它。这也意味着您不必为构造函数指定 videoId。
请参阅加载视频播放器
回答by Kim T
You can do this with far less code:
你可以用更少的代码做到这一点:
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 || []
}), '*');
}
}
}
Working example: http://jsfiddle.net/kmturley/g6P5H/296/
回答by adamj
My own version of Kim T's code above which combines with some jQuery and allows for targeting of specific iframes.
我自己的上面的 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 || []
} ), '*');
}
}
回答by Danbardo
Thank you Rob W for your answer.
谢谢 Rob W 的回答。
I have been using this within a Cordova application to avoid having to load the API and so that I can easily control iframes which are loaded dynamically.
我一直在 Cordova 应用程序中使用它来避免加载 API,这样我就可以轻松控制动态加载的 iframe。
I always wanted the ability to be able to extract information from the iframe, such as the state (getPlayerState) and the time (getCurrentTime).
我一直希望能够从 iframe 中提取信息,例如状态 (getPlayerState) 和时间 (getCurrentTime)。
Rob W helped highlight how the API works using postMessage, but of course this only sends information in one direction, from our web page into the iframe. Accessing the getters requires us to listen for messages posted back to us from the iframe.
Rob W 帮助强调了 API 是如何使用 postMessage 工作的,但当然这只会向一个方向发送信息,从我们的网页到 iframe。访问 getter 需要我们监听从 iframe 回传给我们的消息。
It took me some time to figure out how to tweak Rob W's answer to activate and listen to the messages returned by the iframe. I basically searched through the source code within the YouTube iframe until I found the code responsible for sending and receiving messages.
我花了一些时间来弄清楚如何调整 Rob W 的答案以激活和收听 iframe 返回的消息。我基本上搜索了 YouTube iframe 中的源代码,直到找到负责发送和接收消息的代码。
The key was changing the 'event' to 'listening', this basically gave access to all the methods which were designed to return values.
关键是将“事件”更改为“侦听”,这基本上可以访问所有旨在返回值的方法。
Below is my solution, please note that I have switched to 'listening' only when getters are requested, you can tweak the condition to include extra methods.
以下是我的解决方案,请注意,我仅在请求 getter 时才切换到“侦听”,您可以调整条件以包含额外的方法。
Note further that you can view all messages sent from the iframe by adding a console.log(e) to the window.onmessage. You will notice that once listening is activated you will receive constant updates which include the current time of the video. Calling getters such as getPlayerState will activate these constant updates but will only send a message involving the video state when the state has changed.
进一步注意,您可以通过将 console.log(e) 添加到 window.onmessage 来查看从 iframe 发送的所有消息。您会注意到,一旦激活聆听,您将收到持续更新,其中包括视频的当前时间。调用 getPlayerState 等 getter 将激活这些持续更新,但只会在状态更改时发送涉及视频状态的消息。
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);
}
}