javascriptyoutube-apiyoutube-javascript-apiyoutube-iframe-api

Generate new iframe YouTube player for multiple instances on a page


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.


Solution

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