Generate new iframe YouTube player for multiple instances on a page - javascript

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
}

Related

I can't get Youtube API to work in Squarespace

I'm trying to control a youtube iframe in my squarespace website. I want it to autoplay when the page loads and then if I scroll and it's not in the viewport anymore, I want it to pause.
Here is what I did:
This is my iframe:
<iframe id="ytplayer" width="560" height="315" src="https://www.youtube.com/embed/uuRC8Pmud3o?enablejsapi=1&rel=0&showinfo=0&controls=0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
And this is my script:
<script>
// 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);
// This function YouTube player
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('ytplayer', {
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
// The API will call this function when the video player is ready.
function onPlayerReady(event) {
event.target.playVideo();
}
// The API calls this function when the player's state changes.
var done = false;
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.PLAYING) {
done = true;
}
// Loop the video
if (event.data == YT.PlayerState.ENDED) {
player.playVideo();
}
}
// On scroll Pause/Play the video player
$(window).scroll(function() {
var top_of_element = $("#ytplayer").offset().top;
var bottom_of_element = $("#ytplayer").offset().top + $("#ytplayer").outerHeight();
var bottom_of_screen = $(window).scrollTop() + $(window).height();
var top_of_screen = $(window).scrollTop();
if((bottom_of_screen > top_of_element) && (top_of_screen < bottom_of_element)){
// The element is visible, trigger play click event
player.playVideo()
}
else {
// The element is not visible, trigger pause click event
player.pauseVideo()
}
});
</script>
I tried adding the script to squarespace using many ways but it's not working. Where has the script to be injected?
What's wrong with my code?
According to Youtube IFRAME API, just add autoplay=1 at the end of your URL like:
<iframe id="ytplayer" type="text/html" width="640" height="360"
src="https://www.youtube.com/embed/M7lc1UVf-VE?autoplay=1&origin=http://example.com"
frameborder="0"></iframe>
I think this is also confirmed in the squarespace forum.
I have just figured this out.
Squarespace creates it's own players and also it dynamically creates iframes so it's a bit complicated. Also getting player using DOM element doesn't seems to work and all iframe elements have the same id = "player" so that adds up to the problem.
The code below is what I have come to literally 5min ago.
Just copy/paste to the header code injection in the Squarespace config.
What it does is it acquire all the original YouTube API players as soon as possible and pause playback to avoid horrible lag when my old GPU tries to video-decode all those video instances. Then I have a code which plays the video when it appears on the screen and pauses videos that are not visible anymore. Now it plays smoothly and I can add even more videos. In my case you can see one full-screen video at a time as you scroll down. I have 7x 720p videos stacked up. It looks cool, but I found out that my GPU is lagging badly when more than 5 videos were used, initially I was thinking that it's a network speed problem but my task manager showed 100% on GPU video-encoding. So far worked on Firefox and Chrome.
I have just managed to make this work so there is a lot of testing and tweaking to be done.
USE ON YOUR OWN RISK ! :)
<script type="text/javascript">
// Prepare YouTube iframe API ..
// this is probably redundant, but works fine
var tag = document.createElement ('script');
tag.id = 'iframe-api';
tag.src = 'https://www.youtube.com/iframe_api';
var firstScriptTag = document.getElementsByTagName ('script')[0];
firstScriptTag.parentNode.insertBefore (tag, firstScriptTag);
// When Iframe is ready ..
var iframes = [];
var players = [];
function onYouTubeIframeAPIReady () {
// console.log ("onYouTubeIframeAPIReady");
var player_containers = document.querySelectorAll (".sqs-video-background.content-fill");
for (var i = 0; i < player_containers.length; i ++) {
player_containers [i].addEventListener ("DOMNodeInserted", function (e) {
var node = e.target;
if (node.tagName.toUpperCase () === "IFRAME") {
// console.log ("PLAYER : " + node.id + " TAG : " + node.tagName);
iframes.push (node);
// unfortunately it seems that we cannot catch the player creation event so
// the actual players are probably created after this code
// by calling this function we can catch previous players early on
// except the last one
initPlayers ();
}
}, false);
}
}
function initPlayers () {
// they have all the same id (!!!)
// however it seems it picks the last created one so not the same one over and over
// I didn't touched this, it works so far
var player = YT.get ("player");
if (player !== undefined) {
players.push (player);
pausePlayer (player);
}
}
function playPlayer (player) {
// this happends when player is not fully initialized yet
if (typeof player.pauseVideo === "function") {
player.playVideo ();
} else {
// if the player is not ready yet catch the event
player.addEventListener ('onReady', function (event) {
player.playVideo ();
});
}
}
function pausePlayer (player) {
// this happends when player is not fully initialized yet
if (typeof player.pauseVideo === "function") {
player.pauseVideo ();
} else {
// if the player is not ready yet catch the event
player.addEventListener ('onReady', function (event) {
player.pauseVideo ();
});
}
}
function isInViewport (element, edge) {
var h = window.innerHeight; // viewport height
var r = element.getBoundingClientRect (); // elements bounding rect in viewport coordinates
// console.log ("top : " + r.top);
// console.log ("bottom : " + r.bottom);
// console.log ("height : " + h);
// add extra margin to the viewport to ensure that
// big enough portion of the object is already visible
var e = h * Math.min (edge, 0.4); // relative viewport factor, max 40%
var top = r.top - e;
var bottom = r.bottom - e;
h = h - e*2;
return (!((top > h) || (bottom < 0)));
}
window.onload = function(e){
// console.log ("loaded");
initPlayers (); // to catch the last one !
// console.log ("players : " + players.length);
function onScroll () {
for (var i = 0; i < players.length; i ++) {
var player = players [i];
var iframe = player.getIframe ();
var container = iframe.parentNode;
if (isInViewport (container, 0.0)) {
playPlayer (player); } else {
pausePlayer (player);
}
}
}
// in the case object is already in the viewport
onScroll ();
window.addEventListener ("resize", onScroll);
window.addEventListener ("scroll", onScroll);
}
</script>

Generating YouTube Custom PlayList issue

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.

Stopping/Pausing audio when another audio file is clicking using jQuery

I have created a site with image thumbnails of people I have photographed. When a visitor clicks on one of the thumbnails the full image is revealed using jQuery, and an audio introduction plays. I have a different audio introduction for each thumbnail/image combination - 15 at present with more being added daily.
I would like to ensure that if a visitor clicks on another thumbnail before the previous audio file has completed, that the previous audio file is stopped/paused to allow the new audio file to be played - thereby ensuring two or more tracks do not play simultaneously.
I am currently using the following snippet of code, wrapped in an anonymous function, to play each audio file individually when the appropriate thumbnail is clicked - so this snippet is duplicated for each audio file, but don't know how to ensure they do not play over one another.
$(".bridget-strevens").click(function(){
var audio = $('#bridget-strevens-intro')[0];
if (audio.paused){
audio.play();
} else {
audio.pause();
}
});
Any help you could give me would be very grateful, as I am just starting to learn jQuery, and don't have the knowledge to come up with a workable solution.
Thanks in advance for your help!
Add a .audio class to all your audio elements and loop through all of them when an audio is clicked.
$(".bridget-strevens").click(function () {
$('.audio').each(function (index, value) {
if (!value.paused) {
value.pause();
}
});
var audio = $('#bridget-strevens-intro')[0];
if (audio.paused) {
audio.play();
} else {
audio.pause();
}
});
If that seems too heavy for you then simply add the audio element in a global variable such as:
var currentAudio;
Then when a new audio is clicked, simply pause that one, play the new one and update the currentAudio variable with the new element currently being played.
var currentAudio = null;
$(".bridget-strevens").click(function () {
if(currentAudio != null && !currentAudio.paused){
currentAudio.pause();
}
var audio = $('#bridget-strevens-intro')[0];
if (audio.paused) {
audio.play();
currentAudio = audio;
} else {
audio.pause();
}
});
Update:
Thanks for the prompt responses! Grimbode, I've tried what you
suggest, and that seems to work. However is there the ability to stop
and reset rather than just pause - so if they clicked on 1 then [2]
before 1 finished, then clicked 1 again, that 1 would start from
the beginning again rather than the point at which it was paused? And
is there any way of check the state 'globally', and then add code for
each individual audio file - just to keep the amount of code and
duplication down? Thanks again!! –
Yes. Play audio and restart it onclick explains in detail how to do this. The final result would look something like this:
var currentAudio = null;
$(".bridget-strevens").click(function () {
if(currentAudio != null && !currentAudio.paused && currentAudio != this){
currentAudio.pause();
//Here we reset the audio and put it back to 0.
currentAudio.currentTime = 0;
}
var audio = $('#bridget-strevens-intro')[0];
if (audio.paused) {
audio.play();
currentAudio = audio;
} else {
audio.pause();
}
});
You can't really optimize the code much more. You're going to have apply the click event on every audio element. You're going to have to keep the current playing audio element memorized so you don't have to loop through all the audio files.
If you really want to take this further you could create a library to handle everything. Here is an example:
(function(){
var _ = function(o){
if(!(this instanceof _)){
return new _(o);
}
if(typeof o === 'undefined'){
o = {};
}
//here you set attributes
this.targets = o.targets || {};
this.current = o.current || null;
};
//create fn shortcut
_.fn = _.prototype = {
init: function(){}
}
//Here you create your methods
_.fn.load = function(){
//here you load all the files in your this.targets.. meaning you load the source
//OR you add the click events on them.
//returning this for chainability
return this
};
//exporting
window._ = _;
})();
//here is how you use it
_({
targets: $('.audio')
}).load();

Reset Vimeo player when finished

How can I reset an embed Vimeo video to how it was onload after it's done playing?
The Vimeo API offers an unload method
player.api("unload")
But it isn't working for non-flash players.
Using the Vimeo API, you can add an event for finish to trigger the reload. The Vimeo API includes a method unload(), but it isn't supported in HTML players. Instead, reset the URL in the iframe to return the video to it's original state.
HTML
<iframe src="//player.vimeo.com/video/77984632?api=1" id="video"></iframe>
JS
var iframe = document.getElementById("video"),
player = $f(iframe);
player.addEvent("ready", function() {
player.addEvent('finish', function() {
player.element.src = player.element.src;
});
});
unload() should now work properly across all players.
Variation of Steve Robbins solution, with Vimeo specific solution. You don't have to reach the end of the video, but anytime the user bails out, including clicking on a button:
Simple Javascript solution with Vimeo Library loaded:
https://player.vimeo.com/api/player.js
function ResetVideo()
{
var Field = "iframe-video"; // <iframe id=iframe-video
var iframe = document.getElementById(Field);
var bLoad = LoadVimeoLib(); // Is the Vimeo lib loaded
if(bLoad > 0)
{
var Videoplayer = new Vimeo.Player(iframe);
Videoplayer.pause(); // Pause the video and audio
Videoplayer.setCurrentTime(0); // Reset the video position
// Reset the video back to the iframe
VideoSrc = Videoplayer.element.src; // Save the video source
Videoplayer.element.src = ""; // Empty the source
Videoplayer.element.src = VideoSrc; // Reset the video source
}
}
function LoadVimeoLib()
{
if (typeof jQuery === 'undefined')
{
alert('no jquery installed');
return 0;
}
var scriptlen = jQuery('script[src="https://player.vimeo.com/api/player.js"]').length;
if (scriptlen == 0)
{
jQuery.ajax({
type: "GET",
url: "https://player.vimeo.com/api/player.js",
dataType: "script"
});
}
return 1;
}

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