Why is playVideo() not in the code?

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

Having the videos load first, means not initing them when the cover is clicked.

    function coverClickHandler(evt) {
        const wrapper = evt.currentTarget.nextElementSibling;
        show(wrapper);
        // initPlayer(wrapper);
    }

and instead initing them when we init the cover event handler.

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

    const wrapper = cover.nextElementSibling;
    initPlayer(wrapper);

We now have a few different ways to get the wrapper, so we should use a function to simplify that.

    function getVideoWrapper(cover) {
        return cover.nextElementSibling;
    }
...
    function coverClickHandler(evt) {
        const cover = evt.currentTarget;
        const wrapper = getVideoWrapper(cover);
        show(wrapper);
    }
...
    const wrapper = cover.nextElementSibling;
    initPlayer(wrapper);

Autoplay gets set to 0 so that they don’t automatically play:

        opts = Object.assign(opts, {
            autoplay: 0,
            controls: 1,

We don’t need to use onPlayerReady anymore to play the video, because the video is already loaded. It’s just waiting for us to play it.

    function coverClickHandler(evt) {
        const cover = evt.currentTarget;
        const wrapper = getVideoWrapper(cover);
        show(wrapper);
        videoPlayer.play(player);
    }

But how do we get the player? We can attach a reference to it on the cover, as was similarly done in the other code.

We’ll start by having the addPlayer function return the player:

    function addPlayer(video, settings) {
        ...
        const player = new YT.Player(video, playerVars);
        players.push(player);
        return player;
    }

We can have the initPlayer function take that player, and add it to the videoWrapper.

    function initPlayer(videoWrapper) {
        ...
        const player = videoPlayer.addPlayer(video, settings);
        videoWrapper.player = player;
    }

There is another problem though, it takes time for the players are are loading to load. We don’t want the cover click to occur before the players are ready.

We can have the onPlayerReady event handler initialise the covers. That way we won’t be able to click on the covers until the video is loaded.

    function onPlayerReady() {
        const coverSelector = opts.target;
        manageCover.init(coverSelector);
        const cover = document.querySelector(coverSelector);
        cover.addEventListener("click", coverClickHandler);
    }

    const cover = document.querySelector(opts.target);
    const videoWrapper = cover.nextElementSibling;
    initPlayer(videoWrapper, onPlayerReady);

and because manageCover.init is being done when the video is ready, we can remove the manageCover.init sections from the end of the code too.

The initPlayer code just needs to know what to do with the onReady callback:

    function initPlayer(videoWrapper, onReady) {
        ...
        const player = videoPlayer.addPlayer(video, settings, onReady);
        videoWrapper.player = player;
    }

And similarly, the videoPlayer puts the onReady callback somewhere useful, so that it can be retrieved later.

    function addPlayer(video, settings, onReady) {
        ...
        const player = new YT.Player(video, playerVars);
        player.onReady = onReady;
        players.push(player);
        return player;
    }

That way, the onPlayerReady event handler can check for onReady and run that code.

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

The videos now play without the play symbol, and without the loading animation.

There is one more thing to do though, and that is to give visual feedback for when the preloaded videos are ready to play, which I’ll do in the next post.

Providing a visual feedback for when the videos are ready to play is important, as it can be upsetting to try and click on the play button while the video is loading, and have nothing happening.

What I’ll do is to have the manageCover code show the cover when its initialized.

    function init(coverSelector) {
        const cover = document.querySelector(coverSelector);
        show(cover);
        cover.addEventListener("click", coverClickHandler);
    }

That way we can have the cover start off as being hidden.

			<div class="jacket jacket-left hide">
...
			<div class="jacket jacket-middle hide">
...
			<div class="jacket jacket-right hide">

After the video has loaded and is ready to play, the manageCover.init code is then run and shows the cover. When we click on the cover the video starts playing as desired.

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

A potential alternative is to instead have a spinner animation running on top of the play button while the video loads. That I’ll work on next.

Here is a spinner that we can use:

/* Spinner */
.lds-dual-ring:after {
  content: " ";
  display: block;
  width: 64px;
  height: 64px;
  margin: auto;
  border-radius: 50%;
  border: 6px solid #fff;
  border-color: #fff transparent #fff transparent;
  animation: lds-dual-ring 1.2s linear infinite;
  opacity: 0.5;
}
@keyframes lds-dual-ring {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

And here is a simple function that toggles the spinner:

    function toggleSpinner(cover) {
        cover.classList.toggle("lds-dual-ring");
    }

I first toggle the spinner when we init the covers:

    const cover = document.querySelector(opts.target);
    toggleSpinner(cover);
    const videoWrapper = cover.nextElementSibling;
    initPlayer(videoWrapper, onPlayerReady);

And toggle the spinner again, thus turning it off, when each player is ready.

    function onPlayerReady() {
        const coverSelector = opts.target;
        manageCover.init(coverSelector);
        const cover = document.querySelector(coverSelector);
        toggleSpinner(cover);
        cover.addEventListener("click", coverClickHandler);
    }

The code is now updated so that a loading spinner is on top of the cover when the video loads with the page. While the spinner is spinning you can’t yet click on the cover.

As soon as a video has loaded which only takes a second, the spinner goes away and you can click on the cover to start playing.

There’s a range of spinners to choose from at https://loading.io/css/
Some of the CSS needs to be adjusted though for it to work with your page. I made those adjustments when getting the dual-ring spinner working.

The code at https://jsitor.com/YVOb8ulZV is updated and ready to go.

2 Likes

When a playlist is being used, audio starts playing before anything is clicked.

https://jsitor.com/YVOb8ulZV

function initPlayers() {
    initPlayer.init({
        target: ".jacket-left",
        playlist: "0dgNc5S8cLI,mnfmQe8Mv1g,-Xgi_way56U,CHahce95B1g",
    });

How is that fixed?

How would you like me to stop helping?

What did I do that was wrong, I added a playlist instead of a single video?

How do I add a playlist to one of them?
https://jsitor.com/5eBjd9wgGe

Audio starts right away before anything is clicked.

How is that fixed so that doesn’t happen?

playlist: "0dgNc5S8cLI,mnfmQe8Mv1g,-Xgi_way56U,CHahce95B1g"

All I added was the playlist line into the code.

function initPlayers() {
  initPlayer.init(".jacket-left");
  initPlayer.init(".jacket-middle", {
    playlist: "0dgNc5S8cLI,mnfmQe8Mv1g,-Xgi_way56U,CHahce95B1g"
  });
  initPlayer.init(".jacket-right");
}

Original code:
https://jsitor.com/YVOb8ulZV

I just wanted to know how a playlist would be able to be used in the code.

My apologies. I said it’s ready to go, and you asked how is how is this fixed? My mistake was thinking your question was about my code update, but instead it was asking about the playlist issue.

I shall return after some rest.

1 Like

With the spinners, it’s not a good idea to have the code calling toggleSpinner embedded directly in the other code that deals with players. There is a Separation of Concerns ideal that should be applied.

Instead of having the toggleSpinner embedded in the code, we want the spinner to start when the player is a added, and to stop spinning when the player is ready.

I achieved that but ended up with awful-looking code, such as:

if (events.onPlayerAdd) {
  events.onPlayerAdd();
}

But there’s a better way. We can use events instead, which is something that JavaScript is really good at.

When we init the players I want to tell it then, that we want to toggle the spinners. An init method is just right for that sort of thing, because the toggleSpinner behaviour is going to remain consistent no matter how many players we add.

function addPlayers() {
    function toggleSpinner(evt) {
        spinner.toggleDualRing(evt.target);
    }
    player.init({
        onAddPlayer: toggleSpinner,
        onPlayerReady: toggleSpinner
    });
    player.add(".jacket-left");
    player.add(".jacket-middle", {
        start: 4
    });
    player.add(".jacket-right");
}

In the player code we create a place where the config can be easily accessed:

const player = (function makePlayer() {
    "use strict";

    const config = {};
...
    function init(initConfig) {
        Object.assign(config, initConfig);
    }
    return {
        add,
        init
    };
}());

When we add a player to a cover, that’s when we want to set up events for that cover.

    const config = {};
    const onAddPlayer = new Event("onAddPlayer");
    const onPlayerReady = new Event("onPlayerReady");
...
    function addEvents(cover) {
        cover.addEventListener("onAddPlayer", config.onAddPlayer);
        cover.addEventListener("onPlayerReady", config.onPlayerReady);
    }

    function add(coverSelector, settings = {}) {
        const cover = document.querySelector(coverSelector);
        addEvents(cover);

That way, we can easily dispatch the desired event whenever the time is right.
With the onAddPlayer event that time is at the end of the add function:

    function add(coverSelector, settings = {}) {
        ...
        cover.dispatchEvent(onAddPlayer);
    }

And with the onPlayerReady event, that time is at the end of the playerReadyCallback function:

    function playerReadyWrapper(cover, onPlayerReady) {
        return function playerReadyCallback() {
            manageCover.init(cover);
            cover.dispatchEvent(onPlayerReady);
        };
    }

The placement of the start or the end of the function is not relevant at this stage. Later on if it does become important that’s when we use more detailed events, such as beforePlayerReady and afterPlayerReady. For now though onPlayerReady does the job perfectly well for us.

Using events is a really good way to fine-tine what happens in a function, without needing to change the function itself. We can just change the configuration of the event handler outside of the function, and the different behaviour that we want is achieved.

    player.init({
        onAddPlayer: toggleSpinner,
        onPlayerReady: toggleSpinner
    });

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

I’ll investigate the playlist issue next.

2 Likes

With the playlist issue, it looks as if something unexpected has happened in the videoPlayer section to the addPlayer and initPlayer code.

I was talking earlier about the need for tests. When there are tests in place, they give you early warning about when things are going wrong, giving you the opportunity to fix them up before they become a problem.

Other than manually checking to see if a video plays, do you have any ways of checking that the code is properly working? And if the code isn’t, have you any way to be informed about what the problem is?

1 Like

This is how the code worked before:
https://jsfiddle.net/heL2x7vd/

   let hasShuffled = false;

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

     if (!hasShuffled) {
       player.setShuffle(shufflePlaylist);
       player.playVideoAt(0);
       hasShuffled = true;
     }
   }

That shuffle part of the code was moved to the onPlayerReady event, because that only happens once, and you don’t need to check for shuffling on every state change of the player.

What issue do you have with tests? Do you think they aren’t needed for your code? Or is it something else?

I tested it here:

Audio starts right away before anything is clicked.

How is that fixed?

You might have to click “Run”
https://jsitor.com/yv_kmUNUcr

  function shufflePlaylist(player) {
    player.setShuffle(true);
    player.playVideoAt(0);
  }

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

  function onPlayerStateChange(event) {
    player = event.target;
  }