loading multiple video players with youtube api
Asked Answered
H

9

22

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'
    });
  }
Heres answered 9/6, 2013 at 18:38 Comment(0)
B
33

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
          });
      }
Brocky answered 21/9, 2013 at 19:37 Comment(1)
have you tried this using events? For some reason, I cannot get my events to work. function createPlayer(playerInfo) { return new YT.Player(playerInfo.id, { videoId: playerInfo.videoId, events: { 'onReady': onPlayerReady, 'onStateChange': onPlayerStateChange } }); } function onPlayerReady(event) { console.log('ready'); }Chanty
C
26

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)

Chronometer answered 9/6, 2013 at 22:41 Comment(2)
nice. However, looks like the player vars are not needed.Hacker
You're right mbee, they can be left out. I simply corrected the OPs snippet.Chronometer
P
5

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();
Prevision answered 10/8, 2018 at 15:3 Comment(3)
Gracias. por la ayuda. Me soluciono un problema que llevaba días pensando. SaludosReprehension
This is exactly what I need but how can I get this to play the video on a button click? ' player.playVideo();' does not seem to work. Do you have a codepen of this?Quassia
I like this variant. How can I control players in this case? I added custom buttons under video and I want to control video. On google site I found function player.playVideo() but I don't know how to use with code above. When I run this function I got player is not definedPhosphor
S
2

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.

Scully answered 17/3, 2017 at 22:48 Comment(1)
Note: I've since fixed a bug..... you need to filter the YouTube IDs for dashes, since those will screw up the script. for example: innovYouTube_{innov_mod_ytplayer:id} where the ID is like xyz123-123, since that would make a variable name of innovYouTube_xyz123-123 which is clearly invalid. Just wanted to throw that out there.Scully
B
1

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 = []
    }
  }
}
Blakely answered 9/7, 2016 at 19:8 Comment(0)
A
0

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

Acetum answered 3/7, 2020 at 16:15 Comment(0)
S
0
<iframe title="YouTube video player" src="https:YOUR CHANNEL Full Link" width="560" height="315" frameborder="0" allowfullscreen="allowfullscreen"></iframe>
Seduce answered 9/2, 2022 at 18:44 Comment(1)
While this code snippet may solve the problem, it doesn't explain why or how it answers the question. Please include an explanation for your code, as that really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion.Szechwan
O
0

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
    }
  }
}
Obsolesce answered 9/12, 2022 at 14:0 Comment(0)
A
-2
<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>
Astereognosis answered 12/8, 2021 at 12:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.