YouTube API - Dynamically add event listeners - javascript

I'm using the Youtube API to restyle embedded videos which are dynamically added to the page.
I'm having trouble figuring out how to set up the custom controls. Each video embed has a custom control which needs to fire the playVideo() function on the relevant embed.
When each player is initialised it fires the onPlayerReady function. My problem is I don't know how to bind a click event to the new players custom button which will fire the playVideo() function for the correct player.
I've done a lot of searching on here and can't find reference to working with multiple embeds.
FINAL UPDATE
As jQuery is available, i'm using it in this solution...
function onPlayerReady(event) {
// bind events
var playButton = $(event.target.c).parent().find('.immersive-video__play');
playButton.on('click', function() {
event.target.playVideo();
});
}
UPDATE
This is my current working solution...
var buttonCount = 0;
function onPlayerReady(event) {
// bind events
var playButtons = document.getElementsByClassName("immersive-video__play");
playButtons[buttonCount].addEventListener('click', function() {
event.target.playVideo();
});
buttonCount++;
}
ORIGINAL
function onPlayerReady(event) {
// bind event
var playButtons = document.getElementsByClassName("immersive-video__play");
// I don't know how to link the button to the player?
playButtons[].addEventListener('click', function() {
player[].playVideo();
});
}
var player = [];
function checkYT() {
var check = setInterval(function() {
if (typeof YT !== 'undefined' && typeof YT.Player !== 'undefined') {
var tar = document.getElementsByClassName('immersive-video__embed');
for (var i = 0; i < tar.length; i++) {
var id = tar[i].dataset.video;
var container = document.getElementsByClassName('video-holder');
player[i] = new YT.Player(container[i], {
videoId: id,
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
clearInterval(check);
return;
} else {
console.log('not ready');
}
}, 20);
}

Make sure that the iframe src URL has ?enablejsapi=1 at the end like this:
<iframe src="//www.youtube.com/embed/FKWwdQu6_ok?enablejsapi=1" frameborder="0" allowfullscreen id="video"></iframe>
Setting the parameter's value to 1 enables the player to be controlled via IFrame or JavaScript Player API calls. The default value is 0, which means that the player cannot be controlled using those APIs.
Check your Player object which has the ability to control that video. Create it using the id attribute on that iframe. Here is an example from this tutorial:
var player;
function onYouTubePlayerAPIReady() {
// create the global player from the specific iframe (#video)
player = new YT.Player('video', {
events: {
// call this function when player is ready to use
'onReady': onPlayerReady
}
});
}
Then check the "player ready" callback and bind events. It will automatically be passed the event object, in which event.target is the player.
function onPlayerReady(event) {
// bind events
var playButton = document.getElementById("play-button");
playButton.addEventListener("click", function() {
player.playVideo();
});
var pauseButton = document.getElementById("pause-button");
pauseButton.addEventListener("click", function() {
player.pauseVideo();
});
}
Hope this helps!

Related

YouTube API not loading consistently

For the college I work for I am trying to get a youtube video to auto play in the banner of the webpage. The API is not loading every time properly. I am not sure what I could do to try to fix this.
I have tried:
Moving the adding of the youtube API script to only when the DOM is ready.
Placing the code on the top and bottom of the page.
Checking to see if the player object was created properly and if not try to recreate it which I learned breaks the API.
If I strip the page of everything and just have the banner the video loads more often but still not 100% of the time.
Here I provided the script on the page that is loading the video via the YouTube API.
Update:
From what I can tell by logging to the console the player object is not being fully initialized.
When it is not created correctly this is what the object print out looks like:
Y {b: null, a: null, h: null, closure_uid_165988549: 1, g: 1, …}
And here it is when is done correctly:
Y {b: Wa, a: iframe#ytplayer, h: div#ytplayer, closure_uid_16195269: 1, g: 1, …}
I was wrong about it not finding the videoId. The video is set every time before the function is called so it should not be an issue.
<div id="ytplayer"></div>
<script>
defer(function() {if(window.mobile === true) { $('#ytplayer').remove(); }});
var ready;
// Replace the 'ytplayer' element with an <iframe> and
// YouTube player after the API code downloads.
var player;
// var videoId = 'ZCESafUzSRo';
function onYouTubeIframeAPIReady() {
checkFunction();
}
function checkFunction() {
console.log("checkFunction called");
setYTready(ready);
}
function setYTready(ready) {
console.log("setYT ready called");
ready = true;
mtuPlayerCreate(player);
}
function mtuPlayerCreate(player) {
console.log("mtu create called");
if((player == undefined) && ( document.getElementById("play-yt") != null)) {
console.log("Creating player");
player = new YT.Player('ytplayer', {
height: '52.65%',
width: '100%',
playerVars: {
'controls': 0,
'showinfo': 0,
'rel': 0,
'iv_load_policy': 3
},
videoId: videoId,
events: {
'onReady': onPlayerReady,
// Removing the following if loop shouldn't be enabled
onStateChange:
function(e){
if (e.data === YT.PlayerState.ENDED) {
player.playVideo();
}
}
}
});
}
console.log("player redo below");
console.log(player);
if(player.A == false) {
//setTimeout(function() {location.reload();}, 2000);
} else {
return;
}
}
function onPlayerReady(event) { /* configuring default playing and mute settings */
console.log("on ready has been called")
var autoPlay = 1;
var autoMute = 1;
if(autoPlay === 1) { event.target.playVideo(); }
if(document.getElementById('play-yt') && autoPlay === 1) { document.getElementById('play-yt').className = 'pause'; }else if(document.getElementById('play-yt')) { document.getElementById('play-yt').className = 'play'; }
if(autoMute === 1) { event.target.mute(); }
if(document.getElementById('mute-yt') && autoMute === 1) { document.getElementById('mute-yt').className = 'mute'; }else if(document.getElementById('mute-yt')) { document.getElementById('mute-yt').className = 'loud'; }
if(document.getElementById('yt-yt')) { document.getElementById('yt-yt').className = ''; }
}
window.onload = function() { /* this handles what happens when the player and its buttons are clicked on */
document.getElementsByClassName('media-black')[0].onclick=function(e) {
if(e.target.id !== 'yt-yt' && e.target.id !== 'mute-yt') {
if(player.getPlayerState() === -1 || player.getPlayerState() === 2 || player.getPlayerState() === 5) {
player.playVideo();
if(document.getElementById('play-yt')) { document.getElementById('play-yt').className = 'pause'; }
if (window._gaq) _gaq.push(['_trackEvent', 'Wide Video', 'Played', window.location.href]);
}else{
player.pauseVideo();
if(document.getElementById('play-yt')) { document.getElementById('play-yt').className = 'play'; }
if (window._gaq) _gaq.push(['_trackEvent', 'Wide Video', 'Paused', window.location.href]);
}
}
else if(e.target.id === 'yt-yt' && e.target.id !== 'mute-yt') {
window.open('http://www.youtube.com/watch?v=' + videoId, '_blank');
player.pauseVideo();
if(document.getElementById('play-yt') && document.getElementById('play-yt').className === 'pause') { document.getElementById('play-yt').className = 'play'; }
if (window._gaq) _gaq.push(['_trackEvent', 'Wide Video', 'Went to YouTube', window.location.href]);
}else{
if(player.isMuted() || player.getVolume() === 0) { player.unMute(); player.setVolume(100); document.getElementById('mute-yt').className = 'loud'; }else{ player.mute(); document.getElementById('mute-yt').className = 'mute'; }
if (window._gaq) _gaq.push(['_trackEvent', 'Wide Video', 'Sound Adjusted', window.location.href]);
}
};
}
//
//This could be improved, test if it's mobile and load if not instead of loading and then removing if mobile
//
// Load the IFrame Player API code asynchronously.
document.addEventListener("DOMContentLoaded", function(event) {
console.log("dom ready!");
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/player_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
});
</script>
</div>
What it needs to do is:
Load the API
Load the player object with the given video ID
Add the iframe to the correct spot
Autoplay the video.
It needs to do this every time the page loads.
I'm not sure why you're loading the YouTube iframe player in that way, but, I made this jsfiddle - by modifying your code - and I get the YouTube Player iframe working.
These are the modifications I made - you can find more comments in the modified code:
I commented the line mtuPlayerCreate - are you trying to add another YouTube Iframe?
Add the onPlayerStateChange function for set the functionality for HTML elements - able to control the iframe - "play/pause, (un)mute the video".
The controls value was 0, I set it to 1 for check whether it was properly controlling the YouTube iframe by clicking the HTML elements.
This is the jsfiddle code:
// No need for replace the elemnt - YouTube Iframe Player will load in this object and HTML element:
var player;
// This is your videoId "you had it commented".
// it was the video is not available:
//var videoId = 'ZCESafUzSRo';
// Video: "Microsoft Windows Mixed Reality update | October 2018" - by Microsoft Hololens.
var videoId = '00vnln25HBg';
// Check whether iframe is mute.
var isUnMuted = false;
// Here, the YouTube Player API will build the iframe in the "ytplayer" HTML element
// and in the "player" variable.
function onYouTubeIframeAPIReady() {
player = new YT.Player('ytplayer', {
height: '52.65%',
width: '100%',
playerVars: {
'controls': 1,
'showinfo': 1,
'rel': 0,
'iv_load_policy': 3,
'autoplay': 1,
'loop': 1,
},
videoId: videoId,
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
// After loading the iframe, set the "playVideo" onclick event on "playButton" anchor element.
document.getElementById('play-yt').onclick = function() {
player.playVideo();
};
// Thanks to this answer, here you can check the status of "mute" - I culdn't make it work, in the "onPlayerStateChange" function though.
// Source: https://stackoverflow.com/a/36436765/4092887
document.getElementById('mute-yt').innerHTML = 'MUTE';
document.getElementById('mute-yt').onclick = function() {
player.mute();
};
document.getElementById('yt-yt').innerHTML = 'UNMUTE';
document.getElementById('yt-yt').onclick = function() {
player.unMute();
};
}
function checkFunction() {
console.log("checkFunction called");
setYTready(ready);
}
function setYTready(ready) {
console.log("setYT ready called");
// Not sure what you're doing here = do you want load another youtube iframe?
//mtuPlayerCreate(player);
}
function mtuPlayerCreate(player) {
console.log("mtu create called");
if ((player == undefined) && (document.getElementById("play-yt") != null)) {
}
console.log("player redo below");
console.log(player);
if (player.A == false) {
//setTimeout(function() {location.reload();}, 2000);
} else {
return;
}
}
/* configuring default playing and mute settings */
/* You only need set here the following line: */
function onPlayerReady(event) {
event.target.playVideo();
}
/* this handles what happens when the player and its buttons are clicked on */
/* You can do it in this way instead: */
function onPlayerStateChange(e) {
// Removing the following if loop shouldn't be enabled.
// This is for emulate "loop" functionality.
if (e.data === YT.PlayerState.ENDED) {
player.playVideo();
}
// If the video is PLAYING, set the onclick element for pause the video.
// Once the "playButton" is clicked, the video will be paused.
if (e.data == YT.PlayerState.PLAYING) {
document.getElementById('play-yt').innerHTML = 'PAUSE';
document.getElementById('play-yt').onclick = function() {
player.pauseVideo();
};
}
// If the video is PAUSED, set the onclick element for pause the video.
// Once the "playButton" is clicked, the video will resume the video = continue playing the video.
if (e.data == YT.PlayerState.PAUSED) {
document.getElementById('play-yt').innerHTML = 'PLAY';
document.getElementById('play-yt').onclick = function() {
player.playVideo();
};
}
}
//
//This could be improved, test if it's mobile and load if not instead of loading and then removing if mobile
//
// Load the IFrame Player API code asynchronously.
document.addEventListener("DOMContentLoaded", function(event) {
console.log("dom ready!");
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/player_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<div id="ytplayer"></div>
<p>
<div>
<span>Control your Youtube Iframe Player:</span>
</div>
<div id="play-yt">PLAY</div>
<div id="mute-yt">MUTE</div>
<div id="yt-yt">UNMUTE</div>
</p>

YT.Player doesn't return an instance with playVideo()

When I create an instance of YT.Player(HTMLIFrameElement, { options }) I get back an object that has:
destroy
setSize
getIframe
addEventListener
getVideoEmbedCode
getOptions
getOption
But not playVideo, pauseVideo etc as described in the documentation.
I got a demo here: http://siesta-annotations.surge.sh/Siesta_webviewer_test/?page=3
I am creating the iframes via the DOM in trait.playable.youtube.js and adding the iframe to a documentFragment that eventually will be added to a div:
const element = document.createElement('iframe')
element.src = `https://www.youtube.com/embed/${id}?rel=0;&autoplay=${this.options.autostart ? 1 : 0};&enablejsapi=1;&origin=${location.hostname};`
element.style = this.inlineStyle
I then create an instance of YT.Player:
// nasty initialization because we're outside webpack and this is a demo
if (global.YT.loaded) {
this.player = new global.YT.Player(element, {
events: {
'onStateChange': this.stateHandler,
'onReady': onPlayerReady
}
})
} else {
const oldHandler = global.onYouTubeIframeAPIReady
global.onYouTubeIframeAPIReady = () => {
if (oldHandler) oldHandler()
this.player = new global.YT.Player(element, {
events: {
'onStateChange': this.stateHandler,
'onReady': onPlayerReady
}
})
}
}
The instance of YT.Player looks something like this:
It looks like the minification process went very wrong. What am I doing wrong - how should I initialise YT.Player for my use-case?
This is my second post, I hope that I can solve your problem.
I am not 100% sure about how your code works, but I suspect ...
It seems like a double instantiation of the YT.Player object, which will cause the YT.Player object unstable.
I myself also struggled for a long time (ps: I am an amateur programmer.😅)
As an example, the following snippets is modifed from Youtube Iframe API website and an example of how the second instantiation will break the player object try it! JS Bin:
<!DOCTYPE html>
<html>
<body>
<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
<div id="player"></div>
</br>
<button onclick="instantiateOneMore()">
Call
<pre>new YT.Player('player')</pre>
</button>
<button onclick="pauseVideo()">
pauseVideo();
</button>
<script>
// 2. This code loads the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: 'M7lc1UVf-VE',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
let callTime = 0;
// 4. The API will call this function when the video player is ready.
function onPlayerReady(event) {
event.target.playVideo();
callTime ++;
console.log(callTime);
//document.querySelector('#dd').innerHTML = player.pauseVideo;
}
function pauseVideo(){
player.pauseVideo();
}
instantiateOneMore = () => {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: 'M7lc1UVf-VE',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
//document.querySelector('#dd').innerHTML = player.pauseVideo;
}
// 5. The API calls this function when the player's state changes.
// The function indicates that when playing a video (state=1),
// the player should play for six seconds and then stop.
var done = false;
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.PLAYING && !done) {
/* setTimeout(stopVideo, 6000) */
;
done = true;
}
}
/*
function stopVideo() {
player.stopVideo();
} */
</script>
</body>
</html>
Furthermore, the second onPlayerReady(), won't even be called. So if you intend to put some counter to see how many times the new YT.Player(...) is called, you will suspect it only be called once. Which makes the effect ghost away from debugging.
Strangely enough you do not get access to the YTPlayer API until 'onReady' is called in the constructor player options object. https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player
function onYouTubeIframeAPIReady() {
new YT.Player('player', {
height: '390',
width: '640',
videoId: 'M7lc1UVf-VE',
events: {
'onReady': function (event) {
onPlayerReady(event.target)
},
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerReady(player) {
player.playVideo();
}
This of course only works for one player, so you need to roll out your own YouTube player manager if you want to handle multiple players. (I know the API is insane)
A quick solution could be the following if you have iframes with youtube videos (untested):
const YTManager = {
videos: [],
youtubeApiReady = false,
initialize () {
const matchId = /[^/]+$/
this.youtubeApiReady = true
document.querySelectorAll('iframe[src*="youtube"]')
.forEach(iframe => this.videos.push({
dom: iframe,
player: null,
videoId: iframe.src.exec(matchId) || iframe.src.exec(matchId)[0]
}))
},
createPlayers () {
if (this.youtubeApiReady === false) return
this.videos.forEach(v => {
new YT.Player(v.dom.id, {
videoId: v.videoId,
events: {
'onReady': function (event) {
v.player = event.target
}
}
})
})
},
find (id) {
return this.videos.find(v => v.id === id)
},
play (id) {
const video = this.find(id)
if (this.youtubeApiReady && video && video.player) {
video.playVideo()
}
},
pause (id) {
const video = this.find(id)
if (this.youtubeApiReady && video && video.player) {
video.pauseVideo()
}
},
}
window.onYouTubeIframeAPIReady = function onYouTubeIframeAPIReady() {
// YouTube client side script has been loaded
YTManager.youtubeApiReady = true
YTManager.createPlayers()
}
document.addEventListener("DOMContentLoaded", function() {
// all iframes has been parsed by the browser
YTManager.initialize()
});
Of course you can not call YTManager.play or YTManager.pause without having the ID and wait for the YouTube script to load and then the video initialization to finish. All in all, a real mess. I'm sure you can come up with a better manager. I have written one at work that is better but also works with completely different objects and requirements, so it's not a good fit for general purpose YouTube video manager. But the one above is the gist of my current player.
I recommend using iframes from the beginning - if anything goes wrong in your script, the videos will still be playable, you will just not have any control of them.
Iframed youtube videos looks like this:
<iframe
id="uniqueDOMElementID1"
width="560"
height="315"
src="https://www.youtube.com/embed/bHQqvYy5KYo"
frameborder="0"
allow="autoplay; encrypted-media"
allowfullscreen></iframe>

onReady not firing for embedded youtube iframe

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.

Get the current value of a function which returns random values

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

loading multiple video players with youtube api

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>

Categories

Resources