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>
Related
I have am having an issue with a wordpress site that displays Youtube videos inside an iframe. The issue is that sometimes the iframe will load, and other times it won't. After digging around I found that there are two definitions of the onYoutubeiframeAPIReady method. One of these methods is defined in a giant javascript file (named plugins.js) that includes several other possibly important javascript functions. This file resides in the parent theme.The other is defined inside a script tag in the DOM, and this one appears to work when I can get it to run. The mechanism that decides whether it runs or not is still a bit mysterious to me but I have found that disabling the cache in the network console tab or hard refreshing (Ctrl+Shift+R) will cause this script to run and eventually display the video as expected.
What I would like to know is what tools are there to ensure that the desired function definition is called instead of the other one?
I have tried disabling caching on the server side and inside wordpress, but only disabling the cache on the browser seems to work and this is not a permanent fix. I researched whether or not the parent theme had settings available to include or remove certain scripts. It does not. I have also successfully (the video loads) dequeued the plugins.js script and replaced it with a copy that does not include the defective onYoutubeiframeAPIReady method, but this is not a permanent solution either and is bad practice from a WordPress perspective (please convince me otherwise if I'm wrong). Last thing I've tried is to leave plugins.js alone, but also enqueue a new script that just contains the javascript that does work. This doesn't appear to work.
I am just really mystified as to how the browser decides which one of these two methods to register with the onYoutubeIframeAPIReady event, and also how would disabling the browser cache allow the in document script (which works) to run.
The problematic script from parent theme:
function onYouTubeIframeAPIReady() {
YTplayers = new Array();
jQuery('.no-touch .uncode-video-container.video').each(function() {
var playerY;
if (jQuery(this).attr('data-provider') == 'youtube') {
var id = jQuery(this).attr('data-id');
options = jQuery(window).data('okoptions-' + id);
options.time = jQuery(this).attr('data-t');
playerY = new YT.Player('okplayer-' + id, {
videoId: options.video ? options.video.id : null,
playerVars: {
'autohide': 1,
'autoplay': 0, //options.autoplay,
'disablekb': 1,
'cc_load_policy': options.captions,
'controls': 0,
'enablejsapi': 1,
'fs': 0,
'modestbranding': 1,
'origin': window.location.origin || (window.location.protocol + '//' + window.location.hostname),
'iv_load_policy': options.annotations,
'loop': options.loop,
'showinfo': 0,
'rel': 0,
'wmode': 'opaque',
'hd': options.hd,
'mute': 1
},
events: {
'onReady': OKEvents.yt.ready,
'onStateChange': OKEvents.yt.onStateChange,
'onError': OKEvents.yt.error
}
});
YTplayers[id] = playerY;
playerY.videoId = id;
}
});
}
The script in the DOM that works if only I could get it to run all the time:
<div id="steps">
<div class="step">
<div id="player"></div>
<script>
// create youtube player
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
width: '1120',
height: '630',
videoId: '<?php echo $videoID; ?>',
playerVars: {
'autoplay' : 0,
'controls' : 0,
'showinfo' : 0,
'rel' : 0,
'fs' : 0,
},
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange
}
});
}
// autoplay video
function onPlayerReady(event) {
event.target.playVideo();
}
// when video ends
function onPlayerStateChange(event) {
if(event.data === 0) {
window.location.assign('https://someurl.com');
}
}
</script>
</div>
</div>
The script responsible for calling onYoutubeIframeAPIReady (link to full script here):
"undefined" != typeof YTConfig && YTConfig.parsetags && "onload" != YTConfig.parsetags || En();
var bo = A.onYTReady;
bo && bo();
var co = A.onYouTubeIframeAPIReady;
co && co();
var eo = A.onYouTubePlayerAPIReady;
eo && eo();
This script loads the script above:
var scriptUrl = 'https:\/\/www.youtube.com\/s\/player\/8a298c38\/www-widgetapi.vflset\/www-widgetapi.js';
try {
var ttPolicy = window.trustedTypes.createPolicy("youtube-widget-api", {
createScriptURL: function(x) {
return x
}
});
scriptUrl = ttPolicy.createScriptURL(scriptUrl)
} catch (e) {}
if (!window["YT"])
var YT = {
loading: 0,
loaded: 0
};
if (!window["YTConfig"])
var YTConfig = {
"host": "https://www.youtube.com"
};
if (!YT.loading) {
YT.loading = 1;
(function() {
var l = [];
YT.ready = function(f) {
if (YT.loaded)
f();
else
l.push(f)
}
;
window.onYTReady = function() {
YT.loaded = 1;
for (var i = 0; i < l.length; i++)
try {
l[i]()
} catch (e$0) {}
}
;
YT.setConfig = function(c) {
for (var k in c)
if (c.hasOwnProperty(k))
YTConfig[k] = c[k]
}
;
var a = document.createElement("script");
a.type = "text/javascript";
a.id = "www-widgetapi-script";
a.src = scriptUrl;
a.async = true;
var c = document.currentScript;
if (c) {
var n = c.nonce || c.getAttribute("nonce");
if (n)
a.setAttribute("nonce", n)
}
var b = document.getElementsByTagName("script")[0];
b.parentNode.insertBefore(a, b)
}
)()
}
;
Link to page in question: https://www.ttstaff.com/safety-orientations/general-industry-orientation/
TL;DR:
There are two onYoutubeIframeAPIReady() definitions loaded. One of them works and the other doesn't. I don't think I have the power to remove the defective one, but it's the one that seems to get run. So how can I force the one that works to run, even though the defective one is still loaded?
The following JavaScript works great when I have only one instance of a YouTube video on a page:
function createVideo(playerElement, videoID, autoplay=false) {
const youtubeScriptId = 'youtube-api';
const youtubeScript = document.getElementById(youtubeScriptId);
if (youtubeScript === null) {
const tag = document.createElement('script');
const firstScript = document.getElementsByTagName('script')[0];
tag.src = 'https://www.youtube.com/iframe_api';
tag.id = youtubeScriptId;
firstScript.parentNode.insertBefore(tag, firstScript);
}
window.onYouTubeIframeAPIReady = function() {
return new window.YT.Player(playerElement, {
videoId: videoID,
playerVars: {
autoplay: autoplay,
modestbranding: 1,
rel: 0
}
});
}
}
But when I try to call this again, the video doesn't load. Only the first video. This method is called via a click event after the page has loaded, and I pass in a videoID data attribute and build my new YouTube video to show on the page.
I assume because my JavaScript is creating only one instance on the window object, and not separate multiple instances. After further research I can see that the onYouTubeIframeAPIReady() method only fires once on a page, so that explains why the subsequent calls when the click event fires this method fails. Since it is impossible for me to know how many exact instances to load on a page, how would I refactor this code to make it dynamic, so that unlimited instances can be created and played on a page via click events only? I've seen countless tutorials of building YouTube videos when you know the elements on a page. But in this scenario either I do not know what is on the DOM, or want to slow the site down on page load by building unlimited YT videos to be inserted into the DOM only to probably not be clicked to play by the user.
Is it even possible to dynamically create a new YouTube video on a page using their YT API bound to a click event? From what I am reading online, it looks like a person has to load all videos at once when onYouTubeIframeAPIReady() is first loaded on the page after the YT API loads, and I have to add extra logic in place to play/stop each and then bind additional click events per each video to show/hide/play/stop. This is going to increase my page load dramatically, and add a bunch of JS overhang to my page. I simply want to create a new video on a click event.
I hope you're not using the same videoID every time, because for multiple instances of anything you need to have some kind of sequential ID management, like this...
var ID=[], Vid;
for(var i=0; i<20; i++){ // create 20 video players
ID.push('V'+i);
Vid=document.createElement('video');
Vid.id=ID[i];
document.body.appendChild(Vid);
createVideo(e, ID[i], false); // Your function
}
You get the drift...
The core issue that you're experiencing is the YT.Player can only be initalized once on the page - and if you have multiple videos to be handled via the YT.Player you'd have to iterate over the videos one by one with the same YT.Player instance. It's a little weird at first, but can work - especially if you need to handle the videos via popup modals, etc.
Here's an example that I've used to iterate over a page that has multiple hidden modals with videos, and then handle the click events and playing the videos:
jQuery(document).ready(function ($) {
let modalTriggerElements = $('.video_play_icon'),
playerInfoList = [],
players = []
if (typeof (YT) == 'undefined' || typeof (YT.Player) == 'undefined') {
let tag = document.createElement('script'),
firstScript = document.getElementsByTagName('script')[0]
tag.src = 'https://www.youtube.com/iframe_api';
tag.id = 'youtube-api';
firstScript.parentNode.insertBefore(tag, firstScript)
}
if (modalTriggerElements.length > 0) {
$.each(modalTriggerElements, (index, element) => {
buildPlayersList(element)
modalTriggerClickEvent(element)
})
}
window.onYouTubePlayerAPIReady = function () {
if (typeof playerInfoList === 'undefined') return;
for (let i = 0; i < playerInfoList.length; i++) {
players[i] = createPlayer(playerInfoList[i]);
}
}
function createPlayer(playerInfo) {
return new YT.Player(playerInfo.playerId, {
videoId: playerInfo.videoId,
playerVars: {
showinfo: 0,
}
});
}
function buildPlayersList(element) {
let $modelDiv = $(element).closest('.hc_module').next('.video_model'),
$playerDiv = $($modelDiv).find('#video-player');
playerInfoList.push({
'videoId': $($modelDiv).data('video-id'),
'playerId': $($playerDiv)[0],
});
}
function modalTriggerClickEvent(element) {
$(element).on('click', () => {
let $parentDiv = $(element).closest('.hc_module'),
$nearestVideoDiv = $parentDiv.next('.video_model'),
$closeDiv = $nearestVideoDiv.find('.close_modal')
$nearestVideoDiv.css('display', 'block')
$nearestVideoDiv.attr('aria-hidden', 'false')
$closeDiv.on('click', () => {
$nearestVideoDiv.css('display', 'none')
$nearestVideoDiv.attr('aria-hidden', 'true')
players.forEach((el) => {
el.stopVideo();
});
})
})
}
});
When you don't know how many players you can use random numbers to avoid conflicts...
var ID=[]; // Global array
function AddVideoPlayer(){
var Vid, i=ID.length-1;
ID.push('V'+RandomNumber(99999999));
Vid=document.createElement('video');
Vid.id=ID[i];
document.body.appendChild(Vid);
createVideo(e, ID[i], false); // Your function
}
I want to build a custom youtube playlist from this tutorial using the YouTube API and I got stuck at a certain point.
I basically embedded the client.js script and execute it's function on loading and after that i embedded also the YouTubePlayList.js file as stated in the tutorial.
Here is a fiddle of what i'm trying to do. I do receive the YouTubePlayList object in console but it doesn't seem to give any proper data. I need a working script example or guidance on how to achieve it to work and have the playlist rendered in my client. Thanks in advance, any help appreciated!
JS:
<pre>
function YouTubePlayList (id, entries) {
this.id = id;
this.entries = entries;
this.currently_playing = 0;
this.randomizer = false;
}
var requestOptions = {
playlistId: 'PLLzJfby7cTLTbusOgXca-yIpVOImC1mWe',
part: 'contentDetails, snippet',
execute: function(response) {
var entries = [];
$.each(response.items, function(key, val){
var entry = {};
entry.video_id = val.snippet.resourceId.videoId;
entry.image_src = val.snippet.thumbnails.medium.url;
entry.title = val.snippet.title;
entry.note = val.contentDetails.note;
entries.push(entry);
});
}
};
window['PLLzJfby7cTLTbusOgXca-yIpVOImC1mWe'] = new YouTubePlayList('PLLzJfby7cTLTbusOgXca-yIpVOImC1mWe', 1);
console.log(window['PLLzJfby7cTLTbusOgXca-yIpVOImC1mWe']);
</pre>
You can visit Playlists: insert
This will help you create a new playlist in your channel. The page is jam packed of ideas that will help you to start. There are also example such as the .js code below.
// Define some variables used to remember state.
var playlistId, channelId;
// After the API loads, call a function to enable the playlist creation form.
function handleAPILoaded() {
enableForm();
}
// Enable the form for creating a playlist.
function enableForm() {
$('#playlist-button').attr('disabled', false);
}
// Create a private playlist.
function createPlaylist() {
var request = gapi.client.youtube.playlists.insert({
part: 'snippet,status',
resource: {
snippet: {
title: 'Test Playlist',
description: 'A private playlist created with the YouTube API'
},
status: {
privacyStatus: 'private'
}
}
});
request.execute(function(response) {
var result = response.result;
if (result) {
playlistId = result.id;
$('#playlist-id').val(playlistId);
$('#playlist-title').html(result.snippet.title);
$('#playlist-description').html(result.snippet.description);
} else {
$('#status').html('Could not create playlist');
}
});
}
// Add a video ID specified in the form to the playlist.
function addVideoToPlaylist() {
addToPlaylist($('#video-id').val());
}
// Add a video to a playlist. The "startPos" and "endPos" values let you
// start and stop the video at specific times when the video is played as
// part of the playlist. However, these values are not set in this example.
function addToPlaylist(id, startPos, endPos) {
var details = {
videoId: id,
kind: 'youtube#video'
}
if (startPos != undefined) {
details['startAt'] = startPos;
}
if (endPos != undefined) {
details['endAt'] = endPos;
}
var request = gapi.client.youtube.playlistItems.insert({
part: 'snippet',
resource: {
snippet: {
playlistId: playlistId,
resourceId: details
}
}
});
request.execute(function(response) {
$('#status').html('<pre>' + JSON.stringify(response.result) + '</pre>');
});
}
Try to explore YouTube Player API Reference for iframe Embeds.
The IFrame player API lets you embed a YouTube video player on your
website and control the player using JavaScript.
Using the API's JavaScript functions, you can queue videos for
playback; play, pause, or stop those videos; adjust the player volume;
or retrieve information about the video being played. You can also add
event listeners that will execute in response to certain player
events, such as a player state change or a video playback quality
change.
This guide explains how to use the IFrame API. It identifies the
different types of events that the API can send and explains how to
write event listeners to respond to those events. It also details the
different JavaScript functions that you can call to control the video
player as well as the player parameters you can use to further
customize the player.
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!
I've got some YouTube embedding code (I will paste only code which is causing the trouble for me and cut things which are not public):
console.log(ytplayer);
ytplayer.playVideo();
Console.log on Chrome and on FF shows me good objects with correct methods, and method playVideo() exists there. And it works for all other browsers I checked, but it doesn't work on FF!? What is even more interesting, that when I play video using normal YouTube play button then I can use pauseVideo() method (and all the others: seeking, controlling volume), but I can't use playVideo() method...
I use new way of embedding video:
ytplayer = new YT.Player(player, {
height: height,
width: width,
videoId: videoid,
allowfullscreen: 'true',
playerVars: {
controls: 0,
showinfo: 0,
wmode: 'opaque',
autoplay: (autoplay ? 1 : 0)
},
events: {
'onReady': function () {
console.log('I am ready');
}
}
});
Of course 'I am ready' is in console output. I have no idea what I do wrong and why only FF is not working... There is no JS error, and no clue... Hope someone had this problem before and got it resolved!:)
I was having a very similar issue and was struggling with an answer. My calls to playVideo() didn't seem to work.
ORIGINAL:
$('#play_movie').click(function(){
$('#video').show();
if(player)
{
if(typeof player.playVideo == 'function')
{
player.playVideo();
}
}
The issue was that the player was not yet available - if I just gave it a bit of time to show up, then the call worked
$('#play_movie').click(function(){
$('#video').show();
if(player)
{
var fn = function(){ player.playVideo(); }
setTimeout(fn, 1000);
}
Don't know if this is your exact issue, but I hope it helps someone
A more robust way to do that is to check if the player is ready. If the player is not ready, queue player.playVideo() and execute it when it is ready using the onReady event. Gist
var playerConfig = {}, // Define the player config here
queue = { // To queue a function and invoke when player is ready
content: null,
push: function(fn) {
this.content = fn;
},
pop: function() {
this.content.call();
this.content = null;
}
},
player;
window.onYouTubeIframeAPIReady = function() {
player = new YT.Player('player', {
videoId: 'player',
playerVars: playerConfig,
events: {
onReady: onPlayerReady
}
});
};
// API event: when the player is ready, call the function in the queue
function onPlayerReady() {
if (queue.content) queue.pop();
}
// Helper function to check if the player is ready
function isPlayerReady(player) {
return player && typeof player.playVideo === 'function';
}
// Instead of calling player.playVideo() directly,
// using this function to play the video.
// If the player is not ready, queue player.playVideo() and invoke it when the player is ready
function playVideo(player) {
isPlayerReady(player) ? player.playVideo() : queue.push(function() {
player.playVideo();
});
}
I came across this post looking for something similar. I found my answer here, by relic180:
YouTube API - Firefox/IE return error "X is not a function" for any 'player.' request
Basically, Chrome can initialize youtube embeds even when the divs are hidden (i.e. display:none), but FF and IE can't. My solution was a variant of relic180's:
I move my player to left:200% or whatever when I want it invisible but getting initialized (and available for other calls to player), then move it back on screen when I need it.
I personally found on preset IFrames, that the API won't work properly if you wouldn't use https://www.youtube.com as the domain. So be careful not to miss on the "www". Otherwise the API will create the player object but will fail to execute methods.