Understanding the JavaScript delete operator

How would that be done?

You mean this?


    function combineSettings(oldSettings, newSettings) {
        const props = Object.keys(oldSettings);
        return props.reduce(function combine(combined, prop) {
            if (oldSettings[prop].toString() === "[object Object]") {
                const oldProp = oldSettings[prop] || {};
                const newProp = newSettings[prop] || {};
                combined[prop] = combineSettings(oldProp, newProp);
            } else if (newSettings.hasOwnProperty(prop)) {
                combined[prop] = newSettings[prop];
            }
            return combined;
        }, oldSettings);
    }
    function addVideo(video, settings) {
        const defaultSettings = {
            width: settings.width || 640,
            height: settings.height || 390,
            videoId: video.dataset.id,
            playerVars: {
                start: 0,
                end: 999999,
                loop: false, // custom setting, for onPlayerStateChange to loop video
                autoplay: 1,
                controls: 1,
                showinfo: 1,
                rel: 0,
                iv_load_policy: 3,
                cc_load_policy: 0,
                fs: 0,
                disablekb: 1
            },
            events: {
                "onReady": onPlayerReady,
                "onStateChange": onPlayerStateChange
            }
        };

What code shall we use to do this.

No, not combineSettings. That has no good reason to belong in the videoPlayer code. That videoPlayer code should remain exactly the same for all possible uses that you have of it.

What that means is messing around with the settings and playerVars before the object gets sent to the videoPlayer init function.

This one then:

What should I do first?

 function onPlayerStateChange(event) {
    const player = event.target;
    if (event.data === YT.PlayerState.PLAYING) {
      const otherVideos = (video) => video !== player;
      const pauseVideo = (video) => video.pauseVideo();
      players.filter(otherVideos).forEach(pauseVideo);
    }
    const playerVars = player.b.b.playerVars;
    if (playerVars.loop && event.data === YT.PlayerState.ENDED) {
      player.seekTo(playerVars.start);
    }
  }

  function addVideo(video, settings) {
    players.push(new YT.Player(video, Object.assign({
      videoId: video.dataset.id,
      playerVars: Object.assign({
        autoplay: 1,
        controls: 1,
        rel: 0,
        iv_load_policy: 3,
        cc_load_policy: 0,
        fs: 0,
        disablekb: 1
      }, settings),
      events: {
        "onReady": onPlayerReady,
        "onStateChange": onPlayerStateChange
      }
    }, settings)));
     console.log(settings);
  }

  function init(video, settings) {
    load.js("https://www.youtube.com/player_api").then(function() {
      YT.ready(function() {
        addVideo(video, settings);
      });
    });
  }
  return {
    init
  };
}());

https://jsfiddle.net/g6oaht8f/129/ it is then.

First of all, the videoPlayer function must not automatically play videos. That deserves a smack over the nose with a rolled up newspaper.

When autoplay is required, that is something that should be configured through the settings object instead.

  function addVideo(video, settings) {
    players.push(new YT.Player(video, Object.assign({
      videoId: video.dataset.id,
      playerVars: settings,
      events: {
        "onReady": onPlayerReady,
        "onStateChange": onPlayerStateChange
      }
    }, settings)));
  }
...
  function initPlayer(wrapper) {
    const video = wrapper.querySelector(".video");
    opts.width = opts.width || 198;
    opts.height = opts.height || 198;
    opts.autoplay = 1;
    opts.controls = 1;
    videoPlayer.init(video, opts);
  }

playerVars can then also be removed from the videoPlayer code. The purpose here is that the videoPlayer.init function should expect a properly formatted settings object. We plan to use the initPlayer function to properly format the settings object.

  function addVideo(video, settings) {
    players.push(new YT.Player(video, Object.assign({
      videoId: video.dataset.id,
      // playerVars: settings,
      events: {
        "onReady": onPlayerReady,
        "onStateChange": onPlayerStateChange
      }
    }, settings)));
  }
...
  function initPlayer(wrapper) {
    ...
    opts.playerVars = opts;
    videoPlayer.init(video, opts);
  }

Now that we have constricted the messing around of the settings and playerVars to the initPlayer function, we can go ahead with tidying things up from there.

I added to opts:

And is that it?

Is there anything else that needs to be done to it?

const load = (function makeLoad() {
    "use strict";

    function _load(tag) {
        return function(url) {
            return new Promise(function(resolve) {
                const element = document.createElement(tag);
                const parent = "body";
                const attr = "src";
                element.onload = function() {
                    resolve(url);
                };
                element[attr] = url;
                document[parent].appendChild(element);
            });
        };
    }
    return {
        js: _load("script")
    };
}());
(function manageCover() {
    "use strict";
    const hide = (el) => el.classList.add("hide");

    function coverClickHandler(evt) {
        const cover = evt.currentTarget;
        hide(cover);
    }
    const cover = document.querySelector(".jacketc");
    cover.addEventListener("click", coverClickHandler);
}());
const videoPlayer = (function makeVideoPlayer() {
    "use strict";
    const players = [];

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

    function onPlayerStateChange(event) {
        const player = event.target;
        if (event.data === YT.PlayerState.PLAYING) {
            const otherVideos = (video) => video !== player;
            const pauseVideo = (video) => video.pauseVideo();
            players.filter(otherVideos).forEach(pauseVideo);
        }
        const playerVars = player.b.b.playerVars;
        if (playerVars.loop && event.data === YT.PlayerState.ENDED) {
            player.seekTo(playerVars.start);
        }
    }

    function addVideo(video, settings) {
        players.push(new YT.Player(video, Object.assign({
            videoId: video.dataset.id,
            events: {
                "onReady": onPlayerReady,
                "onStateChange": onPlayerStateChange
            }
        }, settings)));
    }

    function init(video, settings) {
        load.js("https://www.youtube.com/player_api").then(function() {
            YT.ready(function() {
                addVideo(video, settings);
            });
        });
    }
    return {
        init
    };
}());

function loadPlayer(opts) {
    "use strict";
    const show = (el) => el.classList.remove("hide");

    function initPlayer(wrapper) {
        const video = wrapper.querySelector(".video");
        opts.width = opts.width || 198;
        opts.height = opts.height || 198;
        opts.autoplay = 1;
        opts.controls = 1;
        opts.rel = 0;
        opts.iv_load_policy = 3;
        opts.cc_load_policy = 0;
        opts.fs = 0;
        opts.disablekb = 1;
        opts.playerVars = opts;
        videoPlayer.init(video, opts);
    }

    function coverClickHandler(evt) {
        const wrapper = evt.currentTarget.nextElementSibling;
        show(wrapper);
        initPlayer(wrapper);
    }
    const cover = document.querySelector(opts.target);
    cover.addEventListener("click", coverClickHandler);
}
loadPlayer({
    target: ".jacketc",
    width: 600,
    height: 338,
    start: 200,
    end: 205,
    loop: true
});
loadPlayer({
    target: ".alpha",
    start: 0,
    end: 280,
    loop: true
});
loadPlayer({
    target: ".beta",
    start: 0,
    end: 240,
    loop: true
});
loadPlayer({
    target: ".gamma",
    start: 0,
    end: 222,
    loop: true
});
loadPlayer({
    target: ".delta",
    start: 4,
    end: 254,
    loop: true
});
loadPlayer({
    target: ".epsilon",
    start: 0,
    end: 150,
    loop: true
});
loadPlayer({
    target: ".zeta",
    start: 0,
    end: 285,
    loop: true
});
loadPlayer({
    target: ".eta",
    start: 23,
    end: 312,
    loop: true
});
loadPlayer({
    target: ".theta",
    start: 12
});
loadPlayer({
    target: ".iota",
    start: 1,
    end: 50
});

Is this how it should look?
On each video that is clicked, all of this stuff should come up?

Does anything else get done to the code?

Height / Width are twice in there.

The properties that the youtube api supports for its settings are listed at https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player

We can define an array with those supported parameters, and loop through each of them. If each one exists as a property of the opts object, we can then add that to the settings.

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

Similar is done with the playerVars parameters too, for which we have a list of supported parameters at https://developers.google.com/youtube/player_parameters?playerVersion=HTML5#Parameters

    const playerVarsParams = ["autoplay", "cc_lang_pref", "cc_load_policy",
      "color", "controls", "disablekb", "enablejsapi", "end", "fs", "hl",
      "iv_load_policy", "list", "listType", "loop", "modestbranding",
      "origin", "playlist", "playsinline", "rel", "showinfo", "start",
      "widget_referrer"];
    settings.playerVars = playerVarsParams.reduce(function (playerVars, param) {
	    	if (opts[param] !== undefined) {
            playerVars[param] = opts[param];
        }
        return playerVars;
    }, {});

That now gives us a properly formatted settings object, that we can give to the videoPlayer.init function.

https://jsfiddle.net/g6oaht8f/137/

No it’s not, because I said:

and that tidying up was done in the above code.

Last Updated:

Line is longer than 80 characters.

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

Unexpected ‘]’.

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

Line is longer than 80 characters.
settings.playerVars = playerVarsParams.reduce(function(playerVars, param) {

Code:


    function onPlayerStateChange(event) {
        const player = event.target;
        if (event.data === YT.PlayerState.PLAYING) {
            const otherVideos = (video) => video !== player;
            const pauseVideo = (video) => video.pauseVideo();
            players.filter(otherVideos).forEach(pauseVideo);
        }
        const playerVars = player.b.b.playerVars;
        if (playerVars.loop && event.data === YT.PlayerState.ENDED) {
            player.seekTo(playerVars.start);
        }
    }

    function addVideo(video, settings) {
        players.push(new YT.Player(video, Object.assign({
            videoId: video.dataset.id,
            events: {
                "onReady": onPlayerReady,
                "onStateChange": onPlayerStateChange
            }
        }, settings)));
    }

    function init(video, settings) {
        load.js("https://www.youtube.com/player_api").then(function() {
            YT.ready(function() {
                addVideo(video, settings);
            });
        });
    }
    return {
        init
    };
}());

function loadPlayer(opts) {
    "use strict";
    const show = (el) => el.classList.remove("hide");

    function initPlayer(wrapper) {
        const video = wrapper.querySelector(".video");
        opts.width = opts.width || 198;
        opts.height = opts.height || 198;
        opts.autoplay = 1;
        opts.controls = 1;
        opts.rel = 0;
        opts.iv_load_policy = 3;
        opts.cc_load_policy = 0;
        opts.fs = 0;
        opts.disablekb = 1;
        const settingsParams = ["width", "height", "videoid"];
        const settings = settingsParams.reduce(function(settings, param) {
            if (opts[param] !== undefined) {
                settings[param] = opts[param];
            }
            return settings;
        }, {});
        const playerVarsParams = ["autoplay", "cc_load_policy", "controls", "disablekb", "end", "iv_load_policy", "loop", "rel", "start", ];
        settings.playerVars = playerVarsParams.reduce(function(playerVars, param) {
            if (opts[param] !== undefined) {
                playerVars[param] = opts[param];
            }
            return playerVars;
        }, {});
        console.log(settings);
        videoPlayer.init(video, settings);
    }

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

Here;s the updated code, with a happier JSLint.

1 Like

Where would this line go?

host: "https://www.youtube-nocookie.com",

I don’t think host is a YouTube Param, correct?

Then, what exactly is it, and would I keep it inside function addVideo?

I would add it to here:

It wouldn’t go inside here then.
const settingsParams = ["width", "height", /*"host",*/ "videoid"];

Where would it go in here?

Would I keep it here?

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

Yes, it seems that host is an undocumented feature, so you can add “host” to to the array.

What do you mean by that, add it to the array?

Not inside here, right?

Because it’s not a Param.

Host wouldn’t go inside here because it’s not a Param.

or would it go in here?

const settingsParams = ["width", "height", "videoid"];

Whether it’s listed in there or not it works if it’s in here:

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

If you want to allow “host” to be used, then it goes in the array.

Whether it’s listed in there or not it works if it’s in here:

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

That’s a bad place to put it, because it’s best for the videoPlayer function to be identical across all of your code.

Shouldn’t host not work if it’s not listed in the Params?

Works here:

const settingsParams = ["width", "height", "host", "videoid"];

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

Works here:

const settingsParams = ["width", "height", "videoid"];

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

That’s why it’s called an “undocumented feature”