I am having issues getting the youtube api working. The API itself loads and the onYouTubeIframeAPIReady() function gets called, but the onReady doesn't work.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
function onYouTubeIframeAPIReady() {
log('API')
log(document.getElementById('yt-pilezspnvu'));
var player = new YT.Player('yt-pilezspnvu', {
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerReady(event) {
log(event);
}
function onPlayerStateChange(event) {
log(event);
}
The code above isn't wrapped in any functions or anything. The errors in the picture below are just my adblock stuff. I've tried adding origin=http://example.com as pointed out in this thread https://stackoverflow.com/a/20505337/736967 but still not working.
I think that you need some extra bits that are missing.
Try adding in this to your code.
ytplayer = new YT.Player('ytplayer', {
height: '385',
width: '640',
videoId: '[your-videoID]',
playerVars: {'wmode': 'opaque', 'autohide': 1 , 'enablejsapi': 1 , 'origin': 'http://www.yousite.com', 'rel': 0},
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange,
}
playerVars has many parts and you can use or not. But you definatly need to have something load into the player. VideoID or PlayList. Else nothing will happen.
I had a similar issue with an overlay image being clicked and the onReady event never seemed to fire even though all the other events did; obvious through console logs in each.
The issue was apparently a race event. Clicking the overlay image should remove the image and start the YouTube video playing. I also have several videos on the same page. The click event to play the video should be placed inside the onReady event instead of the click event firing the play event. I have trimmed this down as much as I could as our DOM has several elements inside the video container, including the overlay image, captions, etc.
Here is my solution:
var player = new Array;
window.onYouTubeIframeAPIReady = function() {
// Loop through the existing video iframes designated with the inital string 'vidframe_' in the id
jQuery('iframe[id^="vidframe_"]').each(function (i, val) {
var $this = $(this); // iframe container
var $vidWrapper = $this.closest('.video-wrapper');
// Make sure it is a YouTube video. This data attribute is set when the iframe is created by my code
if( $vidWrapper.data("video-source") == "youtube" )
{
// Initialize each YouTube video present
player[i] = new YT.Player($this.attr('id'), {
events: {
onReady: function(event) {
// Set the click event listener here
$vidWrapper.on("click", function() {
// Target the overlay image
// 'this' is now the .video-wrapper image element
var imgOver = $(this).find('figure');
// Remove image overlay
if( imgOver )
imgOver.fadeOut();
// Target the right player
player[i].playVideo();
});
}
}
});
}
});
};
I hope this helps anyone else who may be searching for an errorless onReady event not seemingly working. This css-tricks article is what really gave me the best clue.
I would like to pause the video when the user click on another tab in their browser. I have tried to do this with Youtube and Vimeo.
This is the basic idea behind the javascript:
For Youtube:
//YouTube
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var youtubePlayer;
var onYouTubeIframeAPIReady = function() {
youtubePlayer = new YT.Player('youtube', {
height: '390',
width: '640',
videoId: 'sXtekwuT8R0',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
};
var onPlayerReady = function(event) {
event.target.playVideo();
};
var onPlayerStateChange = function(event) {
if (event.data == YT.PlayerState.PLAYING) {
parent.focus();
window.onblur = function() {
status.text("You went away!");
youtubePlayer.stopVideo();
};
}
};
For Vimeo (using their Froogaloop library):
$(function() {
//Vimeo
var iframe = $('#vimeo');
var vimeoPlayer = $f(iframe[0]);
var status = $('#status');
vimeoPlayer.addEvent('ready', function() {
vimeoPlayer.addEvent('play', function(){
status.text("Playing!");
parent.focus();
window.onblur = function() {
status.text("You went away!");
vimeoPlayer.api("pause");
};
});
});
});
Here are some codepen examples of these attempts:
Youtube:
http://codepen.io/earlonrails/pen/jugAm
Vimeo:
http://codepen.io/earlonrails/pen/KBAmd
Both examples will work if after I click the play button, I click on the parent document of the iframe then click a different window or tab. The Youtube example will also work when it is first loaded, but not if you click the play then pause then play. I believe both of these problems are due to the fact that you are clicking the iframe, therefore the event either is trigger at the same time or cannot be triggered because the event can't be captured there. I thought using the postMessages sent through the iframes along with callbacks would make this work, but alas I have not been able to figure this out.
Need to set the current window before adding the event callback and use this window variable in the callback function. IE
var myWindow = window;
vimeoPlayer.addEvent('play', function(){
status.text("Playing!");
myWindow.focus();
myWindow.onblur = function() {
status.text("You went away!");
vimeoPlayer.api("pause");
};
});
These can be confirmed and tested at the previously linked codepen examples:
Youtube: http://codepen.io/earlonrails/pen/jugAm
Vimeo: http://codepen.io/earlonrails/pen/KBAmd
I guess the window when clicked in the iframe would be setting onblur on the iframe window, which would then never get called, but I still can't explain why when you click play on the iframe, then click the body of the page, then click a different tab then it works. Perhaps the callback is being fired more than once from the iframe or the postMessage is being fired or still in some buffer.
The thing I am trying to do is to get the current video Id of the youtube videos that are currently playing and display it in a div.
This is my YT iframe API code (like in the basic example on the google dev site):
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: currentVideoId,
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange,
'onError': onError,
},
});
}
as you can see, the VideoId (the first video it will play) is set by a variable called "currentVideoId", which was set before like this:
var currentVideoId = '9z4Kft47kBM'
I want to display the current ID in a div
<div id="currentVideoDiv"></div>
To do this i used this code:
$('#currentVideoDiv').html('Currently Playing:' + currentVideoId);
so far so good, it shows my first videoID correctly.
Now after the first video has ended, the onStateChange event will call this function:
function onPlayerStateChange(event) {
if(event.data === 0) {
swapVideo();
}
}
The "swapVideo" function will call the function getId();
function swapVideo() {
player.stopVideo();
player.loadVideoById(getId());
}
The getId() function will get a random videoID from an xml file, donĀ“t worry about that in detail.
function getId() {
return videos[Math.floor(Math.random() * videos.length)];
}
So now a new video with a new videoId is playing, but the value in the currentVideoDiv is still the same since nothing did change it.
The question is, how can i get the new current id, which was set randomly by the getId function and display it in the currentVideoDiv?
This is based on the assumption that you update the display id after loadVideoById
function swapVideo() {
var currentVideoId = getId();
player.stopVideo();
player.loadVideoById(currentVideoId);
$('#currentVideoDiv').html('Currently Playing:' + currentVideoId);
}
I need to load more than one video with youtube's API. This is my first time using it so I'm not sure what I'm doing wrong, but this is what I'm trying:
var player;
var player2;
function onYouTubePlayerAPIReady() {
player = new YT.Player('player', {
videoId: 'hdy78ehsjdi'
});
player2 = new YT.Player('player', {
videoId: '81hdjskilct'
});
}
Since onYouTubeIframeAPIReady function is supposed to called only once the following approach could be used:
initialize and save video player information
(ControlId,width,height,VideoId) in array
call onYouTubeIframeAPIReady function to create all the video
players
Example
var playerInfoList = [{id:'player',height:'390',width:'640',videoId:'M7lc1UVf-VE'},{id:'player1',height:'390',width:'640',videoId:'M7lc1UVf-VE'}];
function onYouTubeIframeAPIReady() {
if(typeof playerInfoList === 'undefined')
return;
for(var i = 0; i < playerInfoList.length;i++) {
var curplayer = createPlayer(playerInfoList[i]);
}
}
function createPlayer(playerInfo) {
return new YT.Player(playerInfo.id, {
height: playerInfo.height,
width: playerInfo.width,
videoId: playerInfo.videoId
});
}
The first parameter of new YT.Player needs to be the id of the HTML element (f.e. a DIV) to be replaced with an iframe to the video.
As you use 'player' for both of these objects, you will load both into the same element.
<div id="ytplayer1"></div>
<div id="ytplayer2"></div>
<script>
var player;
var player2;
function onYouTubePlayerAPIReady() {
player = new YT.Player('ytplayer1', {
height: '390',
width: '640',
videoId: 'hdy78ehsjdi'
});
player2 = new YT.Player('ytplayer2', {
height: '390',
width: '640',
videoId: '81hdjskilct'
});
}
</script>
Parameters of the functions are described in the Youtube API documentation: https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player (EDIT: changed to the right link)
The HTML
<div data-id="youtubevideoidhere" class="video"></div>
<div data-id="youtubevideoidhere" class="video"></div>
<div data-id="youtubevideoidhere" class="video"></div>
The JS for Videos
// CREATE VIDEOS "CLASS" to handler videos
var Videos = (function() {
// VARIABLES
var $ = jQuery, // The jquery
players = [], // players array (to coltrol players individually)
queue = []; // videos queue (once api is ready, transform this into YT player)
// Constructor
function Videos() {}
// METHODS
// Add elements to queue
Videos.prototype.add = function($video) {
queue.push($video);
};
// Load YT API
Videos.prototype.loadApi = function() {
// jQuery get script
$.getScript("//www.youtube.com/iframe_api", function() {
// once loaded, create the onYouTubeIframeAPIReady function
window.onYouTubeIframeAPIReady = function() {
queue.forEach(function($video) {
// Create the YT player
var player = new YT.Player($video.get(0), {
'width': "100%",
'height': "100%",
'videoId': $video.data("id")
});
// add to players array
players.push(player);
});
};
});
};
return Videos;
})();
And then, create videos like this
var videos = new Videos();
$('.video').each( function () {
videos.add( $(this) );
})
videos.loadApi();
I had a more expansive issue that boiled down to this same problem. The requirements I had were to write a JS class to manage one or more (the number can vary from 1 to infinity) video embeds. The backend system is ExpressionEngine (but that's irrelevant here). The primary goal was to set up a framework for analytics that pushes individual data to our Adobe Analytics platform. Shown here is merely the part that gives play count, it can be expanded a lot from here.
The CMS allows editors to create modules on the page that present a video. One video per module. Each module is basically a section of HTML arranged via Bootstrap 3 (irrelevant for this answer).
The relevant HTML looks like this:
<div id="js_youTubeContainer_{innov_mod_ytplayer:id}" class="embed-responsive embed-responsive-16by9">
<div id="js_youTubeFrame_{innov_mod_ytplayer:id}" class="embed-responsive-item"></div>
</div>
The part that says "{innov_mod_ytplayer:id}" is the YouTube Video ID from our CMS. This allows for a unique ID for each embeded item. This is important later.
Below this, I then render out:
var innovYouTube_{innov_mod_ytplayer:id} = new Ariba.Innovations.YouTube.Class({
'innovYouTubeVideoId': '{innov_mod_ytplayer:id}',
'innovYouTubeVideoTitle': '{innov_mod_ytplayer:title}',
'innovYouTubeDivId' : 'js_youTubeFrame_{innov_mod_ytplayer:id}'
});
innovYouTube_{innov_mod_ytplayer:id}.Init(); // And... Go!
var onYouTubeIframeAPIReady = (function() {
try{ //wrap this in try/catch because it actually throws errors when it runs subsequent times - this is expected as it's related to YouTube "rerunning" the function on other videos.
innovYouTube_{innov_mod_ytplayer:id}.config.functionCache = onYouTubeIframeAPIReady; //cache the existing global function
return function() {
try{
innovYouTube_{innov_mod_ytplayer:id}.onYouTubeIframeAPIReady(); //execute this instance's function
var newOnYouTubeIframeAPIReady = innovYouTube_{innov_mod_ytplayer:id}.config.functionCache.apply(this, arguments); //add instances to global function
return newOnYouTubeIframeAPIReady; //update global function
}catch(err){}
};
}catch(err){}
})();
You'll see some ExpressionEngine template tags here too - those are just the Video ID and the Video Title from YouTube. To replicate this, you'll need to change those of course.
What this does is allow me to dynamically update the single global callback with new code for each newly embedded video. In the end, this callback will contain calls to their own instances of my class. You need those try/catch blocks because it throws a false-positive error for all the "other" embeds except the one it's actually executing "right now" - remember this script runs once for every embed on the page. The errors are expected and actually cause no problem, so the try/catch suppresses them.
Using the CMS template tag, I create each instance based on the YouTube video ID. I would run into a problem if someone added the same video module more than once, but that's a business problem easily handled since that's not supposed to happen. This allows me to instantiate unique instances of my class over and over for each video.
The critical part of that script is based on this extremely helpful SO answer: Adding code to a javascript function programmatically
Here's the actual class. It's commented mostly... We use jQuery, so you'll see one important use of it here in the $.extend() method. I use that as a convenience in the class constructor method, but you could do that with vanilla JS too (JavaScript equivalent of jQuery's extend method) I just find the jQuery easier to read, and since it's available to me, I use it.
if (typeof Ariba === "undefined") { var Ariba = {}; }
if (typeof Ariba.Innovations === "undefined") { Ariba.Innovations = {}; }
if (typeof Ariba.Innovations.YouTube === "undefined") { Ariba.Innovations.YouTube = {}; }
if (typeof Ariba.Innovations.YouTube.Class === "undefined") {//this script may be embedded more than once - do this to avoid re-processing it on subsequent loads
Ariba.Innovations.YouTube.Class = function (config) {
this.static = {
'ytScriptId': 'js_youtubeFrameAPI',
'ytScriptUrl': 'https://www.youtube.com/iframe_api'
};//static configuration. Will overwrite any other settings with the same name
this.config = {//optional configuration variables. Will be overridden by instance or static settings with the same name.
'adobeAnalyticsFired': false
};
this.config = $.extend(true, this.config, config);//inserts (destructively!) the instance settings.
this.config = $.extend(true, this.config, this.static);//inserts (destructively!) the static settings.
this.config.this = this;
};
Ariba.Innovations.YouTube.Class.prototype.Init = function () {
//Note: have to allow it to write it over an over because calling the API script is what makes YouTube call onYouTubeIframeAPIReady.
//if (document.getElementById('js_youtubeFrameAPI') === null) { // don't add the script again if it already exists!
this.config.apiScript = document.createElement('script');
this.config.apiScript.src = 'https://www.youtube.com/iframe_api';
this.config.apiScript.id = 'js_youtubeFrameAPI' + this.config.innovYouTubeVideoId;
this.config.firstScriptTag = document.getElementsByTagName('script')[0];
this.config.firstScriptTag.parentNode.insertBefore(this.config.apiScript, this.config.firstScriptTag);
//}
//else { console.log("iframe script already embedded", this.config.innovYouTubeVideoId); }
}
Ariba.Innovations.YouTube.Class.prototype.onYouTubeIframeAPIReady = function (event) {
//console.log("onYouTubeIframeAPIReady", this.config.innovYouTubeVideoId, arguments);
var _this = this;
//console.log(this);
this.config.ytPlayer = new YT.Player(this.config.innovYouTubeDivId, {
videoId: this.config.innovYouTubeVideoId,
events: {
'onReady': _this.onPlayerReady.bind(_this),
'onStateChange': _this.onPlayerStateChange.bind(_this)
}
});
}
Ariba.Innovations.YouTube.Class.prototype.onPlayerReady = function (event) {
//console.log("onPlayerReady", this.config.innovYouTubeVideoId, event);
}
Ariba.Innovations.YouTube.Class.prototype.onPlayerStateChange = function (event) {
//console.log("onPlayerStateChange", this.config.innovYouTubeVideoId, event, this);
if (event.data === YT.PlayerState.PLAYING && !this.config.adobeAnalyticsFired) {
//console.log("YouTube Video is PLAYING!!", this.config.innovYouTubeVideoId);
this.config.adobeAnalyticsFired = true;
if (typeof _satellite !== "undefined") {
window._satellite.data.customVars.adhoc_tracker_val = "Innovations Video: " + this.config.innovYouTubeVideoTitle + " (" + this.config.innovYouTubeVideoId + ")";
_satellite.track('adhoctrack');
}
}
}
}
A few other notes:
Keeping scope in the class instance is easy once you get the main global callback problem solved. You just have to add .bind(). For example:
'onReady': _this.onPlayerReady.bind(_this)
You might also see:
var _this = this;
This is so the "this" scope for the instance isn't lost accidentally. Maybe not necessary, but it's a convention I've adopted over the years.
Anyway, I've been working on this for a week now, and figured I'd share it with the SO community since it's clear from my looking for answers a lot of others have been searching for solutions to this too.
I needed this same thing in React. Expanding upon Vadim's answer you could do something like the following and add them to an object then create the player if you don't know what the array of players will look like prior.
const YoutubeAPILoader = {
_queue: [],
_isLoaded: false,
load: function (component) {
// if the API is loaded just create the player
if (this._isLoaded) {
component._createPlayer()
} else {
this._queue.push(component)
// load the Youtube API if this was the first component added
if (this._queue.length === 1) {
this._loadAPI()
}
}
},
_loadAPI: function () {
// load the api however you like
loadAPI('//youtube.com/player_api')
window.onYouTubeIframeAPIReady = () => {
this._isLoaded = true
for (let i = this._queue.length; i--;) {
this._queue[i]._createPlayer()
}
this._queue = []
}
}
}
what i have done to load multiples videos was destroy the iframe when i click outside the video (you could use the event you want) then i created the div again so you can reuse the div with another video ID
<iframe title="YouTube video player" src="https:YOUR CHANNEL Full Link" width="560" height="315" frameborder="0" allowfullscreen="allowfullscreen"></iframe>
As an addendum to Vadim's answer, the following worked for me with events:
const iframes = [{id: 'hello'},...];
const inOnReadyScope = "I can be accessed by onPlayerReady"
function onYouTubeIframeAPIReady() {
for (let i = 0; i < iframes.length; i++) {
const player = new YT.Player(iframe.id, {
events {
onReady: onPlayerReady
}
}
function onPlayerReady(event){
event.target.setVolume(0);
console.log(inOnReadyScope)
// use anything on event
}
}
}
<script type="text/javascript">
$(document).ready(function () {
$(".youtube-player").each(function () {
var playerid = $(this).attr("id");
setTimeout(function () {
onYouTubeIframeAPIReady2(playerid);
}, 2000);
});
});
function onYouTubeIframeAPIReady2(PlayerID) {
var ctrlq = document.getElementById(PlayerID);
console.log(ctrlq);
var player = new YT.Player(PlayerID, {
height: ctrlq.dataset.height,
width: ctrlq.dataset.width,
events: {
'onReady': function (e) {
e.target.cueVideoById({
videoId: ctrlq.dataset.video,
startSeconds: ctrlq.dataset.startseconds,
endSeconds: ctrlq.dataset.endseconds
});
}
}
});
}
</script>
I'm using jQuery to try to show a hidden YouTube player, and then start playing the video once its containing div is shown.
The problem is that this only works on Chrome, not in IE or FireFox. In IE, I see the error: JSON is undefined.
What can I do to make this work right in all three browsers?
Here's the code I have so far:
var tag = document.createElement('script');
tag.src = "http://www.youtube.com/player_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var player;
function onYouTubePlayerAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: 'r3lPq7qY3TU',
events: {
'onReady': onPlayerReady
}
});
$('div#target_wrap img, div#main_vid').click(function(event){
event.preventDefault();
var p = $('#player');
p.slideToggle(function(){
if (p.is(":visible")) {
player.playVideo();
} else {
player.stopVideo();
}
});
});
}
My guess is that it's lucky accident that it works on Chrome in the first place. For example a common problem is not waiting for the call to "onYouTubePlayerReady". At least for debugging purposes you'll also want to do something like:
$('div#target_wrap img, div#main_vid').click(function(event){
event.preventDefault();
$('#player').slideToggle(function(){
if (player.getPlayerState) {
alert("player state: " + player.getPlayerState());
if ($('#player').is(":visible")) {
player.playVideo();
} else {
player.stopVideo();
}
} else {
alert("Player not ready yet.");
}
});
});
If you can share more of your code we might have a better answer.
Don't really know anything about playing YouTube videos via JS, but some versions of IE doesn't include an implementation of the JSON object which serializes/deserializes to/from JSON. You can find an implementation here.
You may wish to include this via conditional comments or use a script loader so it's loaded only when needed.
if you can't achieve crossbrowser compatibility i give you a suggestion:
onClick call the same video again but with the parameter &autoplay=1