Why is playVideo() not in the code?

When adding autoload, we first update one of the players so that it has an autoload property.

    {
        autoload: true,
        height: 207,
        target: ".jacket-left",
        width: 277
    },

Right now the coverClickHandler automatically initializes the video player when the cover is clicked. We don’t want it to do that with all covers. The videos with autoload will already be initialized and not playing, so we only want the videos that aren’t autoload to be initialized.

To achieve that, we need access to the playerConfig from the click handler. The cover is easily available so we can add the player config to the cover.

    const cover = document.querySelector(opts.target);
    cover.playerConfig = opts;
    cover.addEventListener("click", coverClickHandler);

The coverClickHandler can now be told to only init the player when autoload is not present.

    function coverClickHandler(evt) {
        const cover = evt.currentTarget;
        const videoWrapper = cover.nextElementSibling;
        show(videoWrapper);
        if (!cover.playerConfig.autoload) {
            initPlayer(videoWrapper);
        }
    }

That only leaves us with initializing the player when autoload is present.

    const cover = document.querySelector(opts.target);
    if (opts.autoload) {
        const videoWrapper = cover.nextElementSibling;
        initPlayer(videoWrapper);
    }

    cover.playerOpts = opts;
    cover.addEventListener("click", coverClickHandler);

The player currently still plays behind the cover when its autoloaded, so we need to update autoplay so that the player doesn’t play.

    if (opts.autoload) {
        const videoWrapper = cover.nextElementSibling;
        opts.autoplay = false;
        initPlayer(videoWrapper);
    }

There’s only one thing left to do now, and that’s playing the autoloaded video when the cover is clicked.

        const playerConfig = cover.playerConfig;
        if (!playerConfig.autoload) {
            initPlayer(videoWrapper);
        } else {
            videoPlayer.play();
        }

And this is where we get that play function working. We need a playerIndex for that player.

The initPlayer function needs to return the playerIndex value that we get from addPlayer:

    function initPlayer(wrapper) {
        ...
        return videoPlayer.addPlayer(video, settings);
    }

We can add the playerIndex to opts, so that they are available for us from the cover:

    if (opts.autoload) {
        const videoWrapper = cover.nextElementSibling;
        opts.autoplay = false;
        opts.playerIndex = initPlayer(videoWrapper);
    }
    cover.playerConfig = opts;

We can now tell the cover click handler to play the video when autoload was used.

    function coverClickHandler(evt) {
        ...
        if (!playerConfig.autoload) {
            initPlayer(videoWrapper);
        } else {
            videoPlayer.play(playerConfig.playerIndex);
        }
    }

It is a rather simple level of plumbing, having the playerIndex returned and stored in an object on the cover, so that it can later on be retrieved. But it works well.

The updated code that lets you also configure players to autoload, is found at: https://jsfiddle.net/143bq6zw/7/

1 Like

I’m trying to figure this way out, I don’t know if it is possible or not.
https://jsitor.com/YVOb8ulZV

function play() {
   player.playVideo();
  }
  return {
    addPlayer,
    play
  };
}());

function onYouTubeIframeAPIReady() {
  const cover = document.querySelector(".jacket");
  const wrapper = cover.parentElement;
  const frameContainer = wrapper.querySelector(".video");
  videoPlayer.addPlayer(frameContainer);
}


function loadPlayer(opts) {
  "use strict";

  function show(el) {
    el.classList.remove("hide");
  }

  function initPlayer(wrapper) {
    const video = wrapper.querySelector(".video");
    opts.width = opts.width || 277;
    opts.height = opts.height || 207;
    opts.autoplay = 0;
    opts.controls = 1;
    opts.rel = 0;
    opts.enablejsapi = 1;
    opts.iv_load_policy = 3;
    opts.fs = 0;
    opts.disablekb = 1;

    function paramInOpts(settings, param) {
      if (opts[param] !== undefined) {
        settings[param] = opts[param];
      }
      return settings;
    }
    const settingsParams = ["width", "height", "videoid", "host"];
    const settings = settingsParams.reduce(paramInOpts, {});
    settings.playerVars = opts;
    videoPlayer.addPlayer(video, settings);

  }

  function coverClickHandler(evt) {
    const wrapper = evt.currentTarget.nextElementSibling;
    show(wrapper);
    initPlayer(wrapper);
  }
  const cover = document.querySelector(opts.target);
  cover.addEventListener("click", coverClickHandler);
}

 {

  loadPlayer({
    target: ".jacket-left",
  });
  loadPlayer({
    start: 4,
    target: ".jacket-middle"
  });
  loadPlayer({
    target: ".jacket-right"
  });
}
(function iife() {
  "use strict";

  function coverClickHandler() {
    videoPlayer.play();
  }

  const cover = document.querySelector(".jacket");
  cover.addEventListener("click", coverClickHandler);
}());

You are presenting code that fails to do something, and have provided no details about what it is supposed to achieve.

Please rethink your approach.

1 Like

I tried this:
That didn’t work.

  function coverClickHandler(evt) {
    const wrapper = evt.currentTarget.nextElementSibling;
    show(wrapper);
    initPlayer(wrapper);
    videoPlayer.play();
  }
  const cover = document.querySelector(opts.target);
  cover.addEventListener("click", coverClickHandler);
}

Would I be able to place this on something?
https://jsitor.com/YVOb8ulZV

videoPlayer.play();

It’s not quite that easy.

Time for some cleaning up then.

I have reduced the multiple manageCover codes to just the one manageCover function that uses an init method:

const manageCover = (function makeManageCover() {
  ...
  function init(coverSelector) {
    const cover = document.querySelector(coverSelector);
    cover.addEventListener("click", coverClickHandler);
  }
  return {
    init
  };
}());
...
manageCover.init(".jacket-left");
manageCover.init(".jacket-middle");
manageCover.init(".jacket-right");

In the videoPlayer code, the player variable is of no use as there are multiple players here, and that playerVars object can be removed too. That lets us use proper const variables for the player.

const videoPlayer = (function makeVideoPlayer() {
  "use strict";
  const players = [];
  // let playerVars = {};
  // let player = null;
...
  function onPlayerReady(event) {
    const player = event.target;
...
  function onPlayerStateChange(event) {
    const player = event.target;

I’ve moved code into a loadIframeScript function that is called from an init method.

const videoPlayer = (function makeVideoPlayer() {
  "use strict";
  const players = [];

  function loadIframeScript() {
    const tag = document.createElement("script");
    tag.src = "https://www.youtube.com/player_api";
    const firstScriptTag = document.getElementsByTagName("script")[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  }
...
  function init() {
    loadIframeScript();
  }

  return {
    addPlayer,
    init,
    play
  };
}());
...
videoPlayer.init();

The onYouTubeIframeAPIReady function should be contained in the videoPlayer code too, which I’ve placed just after loadIframeScript. The init function is a good place to attach that onYouTubeIframeAPIReady function to the window object.

  function loadIframeScript() {
    const tag = document.createElement("script");
    tag.src = "https://www.youtube.com/player_api";
    const firstScriptTag = document.getElementsByTagName("script")[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  }

  function onYouTubeIframeAPIReady() {
    const cover = document.querySelector(".jacket");
    const wrapper = cover.parentElement;
    const frameContainer = wrapper.querySelector(".video");
    videoPlayer.addPlayer(frameContainer);
  }
...
  function init() {
    loadIframeScript();
    window.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady;
  }

Shuffle has no business being in the onPlayerStateChange event handler. Instead it more properly belongs in the onPlayerReady event handler, where I’ve put it into a shufflePlaylist function.

  function shufflePlaylist(player) {
    player.setShuffle(true);
    player.playVideoAt(0);
  }
  function onPlayerReady(event) {
    const player = event.target;
    player.setVolume(100); // percent
    shufflePlaylist(player);
  }

That lets us remove the hasShuffled variable, and other shuffle code from onPlayerStateChange.

In the onPlayerStateChange code, the first set of if statements should be moved out to a pauseOtherVideos function.

  function pauseOtherVideos(player) {
    if (event.data === YT.PlayerState.PLAYING) {
      players.forEach(function pauseOtherVideo(player) {
        if (player !== event.target) {
          player.pauseVideo();
        }
      });
    }
  }

  function onPlayerStateChange(event) {
    const player = event.target;
    pauseOtherVideos(player);

And the other if statements in there are moved out to a loopResetCheck function.

  function loopResetCheck(player, state) {
    if (playerVars.loop && state === YT.PlayerState.ENDED) {
      player.seekTo(playerVars.start);
    }
  }

  function onPlayerStateChange(event) {
    const player = event.target;
    const state = event.data;
    pauseOtherVideos(player);
    loopResetCheck(player, state);
  }

This is where I find the first main problem with the code. There are multiple players and multiple sets of playerVars, but the code only expects one of them.

We can get playerVars from the player.i.j.playerVars, but there’s no guarantee that they are going to continue to be accessible from that location. So, we should use a separate function to get playerVars, that can be easily updated if things change.

  function getPlayerVars(player) {
    return player.i.j.playerVars;
  }
...
  function loopResetCheck(player, state) {
    const playerVars = getPlayerVars(player);
    if (playerVars.loop && state === YT.PlayerState.ENDED) {
      player.seekTo(playerVars.start);
    }
  }

The loadPlayer function doesn’t load the player, it adds a click handler to a cover, which when clicked loads the player. We should rename loadPlayer to initCover instead.

// function loadPlayer(opts) {
function initCover(opts) {
  ...
}
initCover({
  target: ".jacket-left",
});
initCover({
  start: 4,
  target: ".jacket-middle"
});
initCover({
  target: ".jacket-right"
});

There’s a completely separate click event added to the covers too, that doesn’t need to be there. Its only job is to play the video, which is something that should be arranged for when initializing the player. We can remove all of that and instead add it to the cover click handler that initalizes the player.

  function coverClickHandler(evt) {
    const wrapper = evt.currentTarget.nextElementSibling;
    show(wrapper);
    initPlayer(wrapper);
    videoPlayer.onPlayerReady = function (player) {
      videoPlayer.play(player);
    }
  }

After adding an onPlayerReady method to the videoPlayer code, we can check if that exists and run it.

  function onPlayerReady(event) {
    const player = event.target;
    player.setVolume(100); // percent
    shufflePlaylist(player);
    if (videoPlayer.onPlayerReady) {
      videoPlayer.onPlayerReady(player);
    }
  }

I was also trying to figure out why the addPlayer is run before the cover is clicked. Your videos don’t seem to be designed so that they run first before the cover is clicked.

I found that old code to add a video was hiding in the onYouTubeIframeAPIReady function.

  function onYouTubeIframeAPIReady() {
    // const cover = document.querySelector(".jacket");
    // const wrapper = cover.parentElement;
    // const frameContainer = wrapper.querySelector(".video");
    // videoPlayer.addPlayer(frameContainer);
  }

Instead of doing that, we want it to run the initCover code so that after the youtube iFrame API is ready, we can initialise the players.

Here is the videoPlayer init being given the initCovers function.

function initCovers() {
  initCover({
    target: ".jacket-left",
  });
  initCover({
    start: 4,
    target: ".jacket-middle"
  });
  initCover({
    target: ".jacket-right"
  });
}

videoPlayer.init(initCovers);

That initCovers callback function is placed as an onIframeReady function:

  function init(callback) {
    loadIframeScript();
    videoPlayer.onIframeReady = callback;
    window.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady;
  }

That way the onYouTubeIframeAPIReady function can run that onIframeReady code.

  function onYouTubeIframeAPIReady() {
    videoPlayer.onIframeReady();
  }

And we are left with a good structure, where we can give any callback function to videoPlayer.init(callback).

Lastly the play function can be given a player parameter so that it knows which player to play.

  function play(player) {
    player.playVideo();
  }

When the cover is clicked and the video is loaded, we want to run that play function. We can’t run the play function immediately though. We can add a function for the onPlayerReady event handler to play the video, and clean up afterwards.

  function coverClickHandler(evt) {
    const wrapper = evt.currentTarget.nextElementSibling;
    show(wrapper);
    initPlayer(wrapper);
    videoPlayer.onPlayerReady = function (player) {
      videoPlayer.play(player);
      delete videoPlayer.onPlayerReady;
    }
  }

And we can connect things together by having the onPlayerReady function look for that onPlayerReady method:

  function onPlayerReady(event) {
    ...
    if (videoPlayer.onPlayerReady) {
      videoPlayer.onPlayerReady(player);
    }
  }

The updated code is found at https://jsitor.com/YVOb8ulZV

The players don’t pause now.

You gave the impression that you want to get the play function working.

Please provide more detail about what you actually want to happen.

On my end the players pause fine when you press the pause button.
Is there a different type of pause that you intend to refer to instead?

I’m trying to figure something out.

What differences of behaviour are you wanting, from my updated code?

Wait, I’m doing something different.

By pause, did you mean when starting one player, the other players stop?

That is something next that I can work on with the code, now that the play is working.

After doing some tweaks to keep JSLint happy, I found a state issue with the player pause code and it’s now working well, pausing other players when you play another one.

  function pauseOtherVideos(player, state) {
    if (state === YT.PlayerState.PLAYING) {
      players.forEach(function pauseOtherVideo(otherPlayer) {
        if (otherPlayer !== player) {
          otherPlayer.pauseVideo();
        }
      });
    }
  }
...
  function onPlayerStateChange(event) {
    const player = event.target;
    const state = event.data;
    pauseOtherVideos(player, state);
    loopResetCheck(player, state);
  }

The code at https://jsitor.com/YVOb8ulZV is updated with the above code, and some other tweaks for JSLint too.

Playbutton visible

How do I get it not to be visible?
https://jsitor.com/YVOb8ulZV

Like how this code works.

Playbutton not visible
https://jsitor.com/hSe8ENAJG

That’s what I am trying to do.

Playbutton not visible
https://jsitor.com/hSe8ENAJG

That’s exactly how it is currently working in the code.

It’s set up differently from the other one.

Not having the separate play and using autoplay, removes the visibility of the play button.

    opts = Object.assign(opts, {
      autoplay: 1,
      controls: 1,
      disablekb: 1,
      enablejsapi: 1,
      fs: 0,
      height: opts.height || 207,
      iv_load_policy: 3,
      rel: 0,
      width: opts.width || 277
    });
...
  function coverClickHandler(evt) {
    const wrapper = evt.currentTarget.nextElementSibling;
    show(wrapper);
    initPlayer(wrapper);
  }

The code at https://jsitor.com/YVOb8ulZV has been updated.

If you want the separate play function to start the player, you must put up with a briefly visible play button.
Not using that separate play function means autoplaying the video, which removes the briefly visible play button.

Now this is visible.

That shouldn’t be visible.

That might be achievable by having all of the videos load first before the cover is clicked, then using play to play the video. I’ll try to switch things around like that.

1 Like