Generate new iframe YouTube player for multiple instances on a page

1.9k views Asked by At

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.

3

There are 3 answers

0
Zach Smith On BEST ANSWER

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();
                });
            })
        })
    }
});
1
user4723924 On

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...

1
user4723924 On

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

}