Why is playVideo() not in the code?

Updated: Custom properties now work:
https://jsfiddle.net/zkxj1tv9/1/

start, end needed to be added to here.

       playerParamDefaults.playerVars = {
            autoplay: 0,
            cc_load_policy: 0,
            controls: 1,
            disablekb: 1,
            enablejsapi: 1,
            end: 0,
            fs: 0,
            iv_load_policy: 3,
            loop: 0,
            rel: 0,
            start: 0
        };

Next, getting the middle player to work. Any ideas?

It works in the old code: https://jsfiddle.net/84jcb2xa/

It should be able to work like this:

 initPlayer({
        height: 338,
        loop: true,
        playlist: "0dgNc5S8cLI,mnfmQe8Mv1g,-Xgi_way56U,CHahce95B1g",
        target: ".jacketc",
        width: 600
    });

or like this:

initPlayer({
    height: 338,
    loop: true,
    listType: "playlist",
    list: "TLGG73CXTCcjc1kxMzA4MjAyMQ"
    target: ".jacketc",
    width: 600
});

These would need to be added

playlist,
listType: “playlist”,
list:

To here somehow.

 playerParamDefaults.playerVars = {
            autoplay: 0,
            cc_load_policy: 0,
            controls: 1,
            disablekb: 1,
            enablejsapi: 1,
            end: 0,
            fs: 0,
            iv_load_policy: 3,
            loop: 0,
            rel: 0,
            start: 0
        };

Old code:

This works:
https://jsfiddle.net/c51rgawb/

 loadPlayer({
        height: 338,
        loop: true,
        listType: "playlist",
        list: "TLGG73CXTCcjc1kxMzA4MjAyMQ",
        target: ".jacketc",
        width: 600
    });

This works:
https://jsfiddle.net/c51rgawb/2/

  loadPlayer({
        height: 338,
        loop: true,
        playlist: "0dgNc5S8cLI,mnfmQe8Mv1g,-Xgi_way56U,CHahce95B1g",
        target: ".jacketc",
        width: 600
    });

In the new code, when I do this I get:
https://jsfiddle.net/p8fa0vzo/1/

playlist is not defined

       playerParamDefaults.playerVars = {
            autoplay: 0,
            cc_load_policy: 0,
            controls: 1,
            disablekb: 1,
            enablejsapi: 1,
            end: 0,
            fs: 0,
            iv_load_policy: 3,
            loop: 0,
            rel: 0,
            playlist,
            listType,
            list,
            start: 0
        };

I did this and now I’m getting:
https://jsfiddle.net/03qrmfas/1/

list is not defined
or playlist is not defined

What is supposed to go next to list, a 0, something else?

            playlist,
            listType: "playlist",
            list,
            start: 0
        };

Here’s the code in question:

    function addPlayer(video, settings) {
        playerVars = Object.assign({
            host: "https://www.youtube-nocookie.com",
            videoId: video.dataset.id,
            events: {
                "onReady": onPlayerReady,
                "onStateChange": onPlayerStateChange
            }
        }, settings);
        players.push(new YT.Player(video, playerVars));
    }

And, here’s what I would do there.

The properties are expected to be in alphabetical order, but I don’t want events in alphabetical order because I think they are of less importance.
Those events can be removed from the object and added on afterwards, but we can’t easily do that while the object is inside the function parameter.

First extract the object.

        const playerVarDefaults = {
            host: "https://www.youtube-nocookie.com",
            videoId: video.dataset.id,
            events: {
                "onReady": onPlayerReady,
                "onStateChange": onPlayerStateChange
            }
        };
        playerVars = Object.assign(playerVarDefaults, settings);

Then we can remove events from the object and add it on later.

        const playerVarDefaults = {
            host: "https://www.youtube-nocookie.com",
            videoId: video.dataset.id
        };
        playerVarDefaults.events = {
            "onReady": onPlayerReady,
            "onStateChange": onPlayerStateChange
        };
        playerVars = Object.assign(playerVarDefaults, settings);

Or, we can move the events property to its alphabetical location:

        const playerVarDefaults = {
            events: {
                "onReady": onPlayerReady,
                "onStateChange": onPlayerStateChange
            },
            host: "https://www.youtube-nocookie.com",
            videoId: video.dataset.id
        };
        playerVars = Object.assign(playerVarDefaults, settings);
1 Like

This is the next error. I got stuck on this one the other day.

How do I fix this?

I can’t figure this out, why is jslint saying this is wrong?

How should those be positioned?

https://jsfiddle.net/7cLf8r1d/

   const settingsParams = ["width", "height", "videoid", "host"];
        const settings = settingsParams.reduce(paramInOpts, {});
        const playerVarsParams = ["autoplay", "cc_load_policy",
            "controls", "disablekb", "end", "fs", "iv_load_policy",
            "list", "listType", "loop", "playlist", "rel", "start"];
        settings.playerVars = playerVarsParams.reduce(paramInOpts, {});
        videoPlayer.addPlayer(video, settings);
    }

Fixed:
https://jsfiddle.net/dLh324f9/

   const playerVarsParams = [
            "autoplay",
            "cc_load_policy",
            "controls",
            "disablekb",
            "end",
            "fs",
            "iv_load_policy",
            "list",
            "listType",
            "loop",
            "playlist",
            "rel",
            "start"
        ];
        settings.playerVars = playerVarsParams.reduce(paramInOpts, {});
        videoPlayer.addPlayer(video, settings);
    }

Since there are no more jslint errors, next is getting this to work in the code.
https://jsfiddle.net/dLh324f9/

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

Videos should start when they are clicked on.

We might want to change our approach there. With a large list like that JSLint wants them listed one by one on different lines.

        const playerVarsParams = [
            "autoplay",
            "cc_load_policy",
            "controls",
            "disablekb",
            "end",
            "fs",
            "iv_load_policy",
            "list",
            "listType",
            "loop",
            "playlist",
            "rel",
            "start"
        ];

That is awkward and unwieldy. There can be better solutions out there.

A different solution is to realize that opts is both settings and playerVars mixed together. We can remove the settings properties from opts, which leaves us with what we need for playerVars. That way we don’t need any of the above long list of properties.

That gives us the following code, where two main things happen:

  • parameters are moved from opts to settings, by updating settings and deleting from opts.
  • the remaining opts are assigned to settings.playerVars
        const settingsParams = ["width", "height", "videoid", "host"];
        const settings = settingsParams.reduce(paramInOpts, {});
        settingsParams.forEach(function (param) {
            delete opts[param];
        });
        settings.playerVars = opts;
        videoPlayer.addPlayer(video, settings);
1 Like

Next is getting the videos to play when they are clicked on.

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

https://jsfiddle.net/dLh324f9/3/

function loadPlayer(opts) {
    "use strict";

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

    function initPlayer(wrapper) {
        const video = wrapper.querySelector(".video");

        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, {});
        settingsParams.forEach(function (param) {
            delete opts[param];
        });
        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);
}

There’s a lot happening with that code. Simplifying what happens is a good way to make it easier to understand what’s going on.

Simplify onYouTubeIframeAPIReady

For example, that onYouTubeIframeAPIReady function shouldn’t be out on its lonesome where it is. It should be in the videoPlayer function with the other youtube-related code. But, we don’t want the loadPlayer code to also go in the videoPlayer code.

Instead, we can have the onYouTubeIframeAPIReady function run a videoPlayer function, that we name onIframeReady. That way we can add that function from the end of the code with the loadPlayer stuff, and the onYouTubeIframeAPIReady can then invoke that function when the iframe is ready to go.

How do we do that in a simple way? We rename the onYouTubeIframeAPIReady line so that it adds a function to videoPlayer instead.

videoPlayer.onIframeReady = function loadPlayers() {
    loadPlayer({
        height: 207,
        target: ".jacket-left",
        width: 277
    });

Then in the videoPlayer, we can have onYouTubeIframeAPIReady invoke that videoPlayer.onIframeReady code.

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

That is a simplification that JSLint is okay with us doing, and helps to ensure that the configuration is down at the end of the code. https://jsfiddle.net/143bq6zw/

More configuration can be moved to the end of the code too. When all configuration is moved to the end of the code, it then becomes easier to understand what the code is configured to do, which makes it easier for us to update that configuration to achieve our needs.

The videoPlayer code currently automatically runs code to load the iframe script. It’s a better practice to put that into an init function, and use that to initialize the videoPlayer code afterwards.

Simplify videoPlayer.onIframeReady

Continuing on with the simplifying, the videoPlayer.onIframeReady calls the loadPlayer function many times with different parameters. Those different parameters should all be put into a single array, so that we successfully separate the config from what is done with it.

const playerConfig = [
    {
      height: 207,
      target: ".jacket-left",
      width: 277
    },
    {
        height: 207,
        start: 4,
        target: ".jacket-middle",
        width: 277
    },
    {
        height: 207,
        target: ".jacket-right",
        width: 277
    },
    {
        height: 338,
        loop: true,
        playlist: "0dgNc5S8cLI,mnfmQe8Mv1g,-Xgi_way56U,CHahce95B1g",
        target: ".jacketc",
        width: 600
    },
    {
        end: 280,
        loop: true,
        start: 0,
        target: ".alpha"
    },
    {
        end: 240,
        loop: true,
        start: 0,
        target: ".beta"
    },
    {
        end: 265,
        loop: true,
        start: 0,
        target: ".gamma"
    },
    {
        end: 254,
        loop: true,
        start: 4,
        target: ".delta"
    },
    {
        end: 242,
        loop: true,
        start: 0,
        target: ".epsilon"
    },
    {
        end: 285,
        loop: true,
        start: 0,
        target: ".zeta"
    },
    {
        end: 312,
        loop: true,
        start: 23,
        target: ".eta"
    },
    {
        start: 2,
        target: ".theta"
    },
    {
        target: ".iota"
    }
];

That way when we are trying to understand what the code does, we only have the following small part after it to worry about:

videoPlayer.onIframeReady = function loadPlayers() {
    playerConfig.forEach(function (config) {
        loadPlayer(config);
    });
};

The updated code is found at: https://jsfiddle.net/143bq6zw/1/

Simplify videoPlayer

The videoPlayer function has code that is automatically run when the page loads. That is a complication that is best avoided by moving the code into an init function so that the videoPlayer can be initialized using that init function.

We can move that code down to an init function at the end of the videoPlayer code, so that we can call the init later on at the end of everything.

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

    return {
        addPlayer,
        init,
        play
    };

That way, after setting up the videoPlayer.onIframeReady function, we init the videoPlayer too.

videoPlayer.onIframeReady = function loadPlayers() {
    ...
};
videoPlayer.init();

The updated code is at: https://jsfiddle.net/143bq6zw/2/

Simplify manageCover

There are several manageCover functions that the same init thing can be done with, moving initialization code into an init function, so that we can separately init the code.

We might also find a major simplification comes from that too.

With manageCovere we assign it to a constant, so that we can call it later with the init command.

const manageCovere = (function makeManageCovere() {

We move the code that automatically runs into an init function, so that we can init it later on.

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

    return {
        init
    };
}());
manageCovere.init();

And this is where the magic happens, we invoke the init method with a selector for cover as the argument.

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

    return {
        init
    };
}());
manageCovere.init(".jacketd");

I could remove most of those manageCover functions now, but surprizes can occur so I’ll make those same updates to the manageCovera, manageCoverb, manageCoverc, and manageCoverd functions too.

You had five sets of duplication there with the manageCover functions. Normally three sets of smaller duplication, or in this case two sets of larger duplication, is the signal to remove that duplication.

Those manageCover init statements can now all be grouped together and moved to the end of the code:

manageCovera.init(".jacket-left");
manageCoverb.init(".jacket-middle");
manageCoverc.init(".jacket-right");
manageCoverd.init(".jacketc");
manageCovere.init(".jacketd");

Those manageCover functions are all nearly identical. It’s only the last one with show functions that’s different. We can update that last manageCovere function so that it checks if the element exists before showing it, and we can then remove all of the other manageCover functions.

const manageCovere = (function makeManageCovere() {
    ...
        const thewrap = cover.parentElement.querySelector(".wraph");
        if (thewrap) {
            show(thewrap);
        }

I’ll start by using manageCovere for the other init methods:

manageCovere.init(".jacket-left");
manageCovere.init(".jacket-middle");
manageCovere.init(".jacket-right");
manageCovere.init(".jacketc");
manageCovere.init(".jacketd");

Everything still works fine, so the other manageCover functions can be removed, and mamageCovere gets renamed to only manageCover.

const manageCover = (function makeManageCover() {

As a reminder, the make prefix tells us that the manageCover variable is not a function, but is an object with methods instead, in this case the init method.

We end up with the following configuration at the end of the code, from which we can now start investigating about getting videos to play when they are clicked on.

manageCover.init(".jacket-left");
manageCover.init(".jacket-middle");
manageCover.init(".jacket-right");
manageCover.init(".jacketc");
manageCover.init(".jacketd");
videoPlayer.onIframeReady = function loadPlayers() {
    playerConfig.forEach(function (config) {
        loadPlayer(config);
    });
};
videoPlayer.init();

The updated code is at: https://jsfiddle.net/143bq6zw/3/

Next up is to investigate the video play issue.

1 Like

Now that we’ve simplified some things, it’s easier to see the wood for the trees.

After we’ve clicked on a cover, on most of the, we want it to play the video, and on jacketd we don’t as that one contains multiple videos.

That is something that we could make automatic, where if the jacket only has one video then it automatically plays it. Or we could make it configurable so that only on some manageCover do we tell it to play the video.

Either way, the manageCover function needs some way to play the video, so I’ll start with automatic, and we can later on move to separate configuration if that’s needed.

Currently there’s no way to play a video, so let’s start there.

Playing a video

The videoPlayer code has a players array with access to all of the videos, but the play function only uses the single player variable, which is a leftover from previous code.

Right now the only code that needs access to a single player, already receives that player via event variables. We can remove the player variable completely, and make the other player variables local to their functions.

    // let player = null;
...
    function onPlayerReady(event) {
        // player = event.target;
        const player = event.target;
...
    function onPlayerStateChange(event) {
        // player = event.target;
        const player = event.target;

That way, we now only have the play function to worry about.

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

That play function needs to be given an index number, of which video to play.

    function play(playerIndex) {
        players[playerIndex].playVideo();
    }

Now how do we get that index number to play?

We can start with looking at where the player is added to players. The addPlayers function is where a player is added to the players list. We can return the index number of the most recently added player from that function.

    function addPlayer(video, settings) {
        ...
        players.push(new YT.Player(video, playerVars));
        return players.length - 1;
    }

That was from the addPlayer function. Where is that called? That’s called from the initPlayer function. That initPlayer function doesn’t get called until the cover is clicked. Here’s the order of things:

  1. loadPlayer is invoked with player config info
  2. loadPlayer adds a click handler to cover
  3. later on when that click handler is clicked, initPlayer is invoked
  4. we then want the player to automatically play.

When we get the playerIndex from addPlayer, we can then give it to the play method.

        const playerIndex = videoPlayer.addPlayer(video, settings);
        videoPlayer.play(playerIndex);

That would be great to do, but it takes time for the player to be added. More time is needed before the player is ready.

So instead, we don’t need the index stuff, yet. Instead, we can have the onPlayerReady function play the video.

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

That works for the single videos, but we need to check for what happens when there are multiple videos.

Currently there is no problem with the bottom videos section, but we are ready to take further action to improve the code when that problem occurs.

The updated code is at: https://jsfiddle.net/143bq6zw/4/

An important idea is not to just make changes to the code so that it works for the most recent situation. Instead, it is to make changes so that the code works for previous situations too, as well as with the current thing we are dealing with.

That way the code ends up becoming more general to handle a wider range of situations, and the configuration becomes more specific.

Having the onPlayerReady function play the video is only a temporary solution. That won’t be helpful when a video is intended to be loaded in paused state behind a cover.

When we get to those other types of situations, the playVideo must then be removed from the onPlayerReady code to have a more general solution applied instead. That more general solution could be to call a different function such as playCheck() to decide if the video should be played or not.

I was wondering why it was placed on there.

Would it help if I adjusted things so that one of the videos is loaded first in a paused state, before the cover is clicked? That would help to drive out a more beneficial solution.

1 Like

With a single player it works like this:
https://jsfiddle.net/182tL0f5/

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

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

(function iife() {
  "use strict";

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

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

Currently the loadPlayer function doesn’t actually load any players.

videoPlayer.onIframeReady = function loadPlayers() {
    playerConfig.forEach(function (config) {
        loadPlayer(config);
    });
};

Instead, it configures the cover so that when it is clicked, a video player is added to that cover area. Instead of loading the player, it just initializes the cover so that eventually the cover will load the player. We should rename a few functions so that they more clearly express what is happening there.

Instead of loadPlayer, we can rename that to be initCover instead.

function initCover(opts) {
    ...
}
...
    playerConfig.forEach(function (config) {
        initCover(config);
    });

With the first player, I’ll add config parameters called autoload and autoplay, to indicate that the player should be loaded immediately but not played.

    {
        autoplay: false,
        height: 207,
        target: ".jacket-left",
        width: 277
    },

It’s important that where us humans are configuring things frequently, that true and false be used to clearly indicate things.

Later on down in the code of initPlayer, that true/false can then be converted to what the computer needs instead.

        if (opts.hasOwnProperty("autoplay")) {
            opts.autoplay = (
                opts.autoplay
                ? 1
                : 0
            );
        }
        settings.playerVars = opts;
        videoPlayer.addPlayer(video, settings);

Removing playVideo from the onPlayerReady function:

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

we should find that we can configure the first video to autoplay.

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

and it does autoplay when we click the cover. Setting autoplay to false results in the clicked cover not autoplaying.

We might not even need to configure autoload in this code to make further beneficial progress.

The player config where we initialize each player is where we should be focusing on instead.

    playerConfig.forEach(function (config) {
        initCover(config);
    });

We can add information to initCover so that it does or doesn’t autoplay, when no parameter is provided.

        initCover(config, {
            autoplay: true
        });

Removing those auto properties from the first player now lets us carry on with this alternative solution:

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

In the initCover code, we can check for that autoplay config setting if one isn’t found on the player config.

function initCover(opts, config) {
...
        if (opts.hasOwnProperty("autoplay")) {
            opts.autoplay = (
                opts.autoplay
                ? 1
                : 0
            );
        } else if (config.hasOwnProperty("autoplay")) {
            opts.autoplay = (
                config.autoplay
                ? 1
                : 0
            );
        }

There is though some obvious duplication in the above code, that we need to take care of. We can have a setAutoplay function that uses a state condition to set autoplay. Currently with the code, the videos all default to no autoplay, so we don’t need to worry about setting autoplay to 0.

        function setAutoplay(obj, state) {
            if (state) {
                obj.autoplay = 1;
            }
            return obj;
        }
...
        if (opts.hasOwnProperty("autoplay")) {
            setAutoplay(opts, opts.autoplay);
        } else if (config.hasOwnProperty("autoplay")) {
            setAutoplay(opts, config.autoplay);
        }

Because the player config has three states (true, false, not present) and the initCover config has three states (true, false, not present) we need to make sure that all nine combinations of states work properly with this code.

I’ll use a nice visible checkmark to indicate whether each state works as expected.

Those states are:

playerConfig -> | autoplay: true | autoplay: false | no autoplay
initCover Config
autoplay: true    autoplay ✔️      no autoplay ✔️   autoplay ✔️
autoplay: false   autoplay ✔️      no autoplay ✔️   no autoplay ✔️
no autoplay       autoplay ✔️      no autoplay ✔️   no autoplay ✔️

I’ve left the code with there being no autoplay on the individual player config, and autoplay being set on the defaultConfig when initCover is run at the end of the code.

The updated code is found at https://jsfiddle.net/143bq6zw/6/

That code can now be configured to handle any combination of autoplay states that you desire.

The way it works is, autoplay is supposed to be set to 0.

Are you wanting the video to start playing when the cover is clicked?

That’s how it works here.
https://jsfiddle.net/182tL0f5/

autoplay is set to 0.

It is only after the cover is clicked that the video is initialized. Because the video is only being initialized after the cover is clicked, those videos must autoplay so that they start playing after the cover is clicked.

That video has a different situation where the video is being autoloaded behind the cover. In that situation I agree, the video must not autoplay when it is loaded.

The videos at https://jsfiddle.net/143bq6zw/5/ are very different from that, because none of them autoload behind the cover.