Questions about the YouTube player_api code


#142

Here:
https://jsfiddle.net/hzyrfkwb/432/


#143

Does that achieve the desired width and height now?


#144

What would the shortcuts have looked like?
https://jsfiddle.net/hzyrfkwb/399/


#145

That would have just been to put width and height in playVars, and fish that information out for the addVideo function.

width: playVars.width,
height: playVars.height,

...

loadPlayer(".jacketc", playerVars: {
    width: 600,
    height: 338,
    start: 900,
    end: 1200
});

That breaks many rules though, but it is a shortcut. After getting things working that way, that’s when people normally refactor the code, to make improvements so that they aren’t breaking as many rules.


#146

oh, ok. Thanks for your help again.


#147

Yes.


#148

If I had said no, would there have been a different way to do it?

I just think that that seems like a lot to change in the code for something that seems small.

Getting it to work using just this was what I was thinking.

loadPlayer(".jacketc", {
  start: 900,
  end: 1200,
  width: 600,
  height: 338,
});

#149

Other conflicting factors would have been looked in to, such as CSS setting a width and height instead.


#150

Can I get your thoughts and opinion on doing it this way?

Is this a good way of doing it?
https://jsfiddle.net/hzyrfkwb/446/

 function addVideo(video, desiredPlayerVars) {
        const videoId = video.getAttribute("data-id");
        const defaultPlayerVars = {
            autoplay: 1,
            controls: 1,
            showinfo: 1,
            rel: 0,
            iv_load_policy: 3,
            cc_load_policy: 0,
            fs: 0,
            disablekb: 1,
            width: 198,
            height: 198
        };
        const playerVars = Object.assign(defaultPlayerVars, desiredPlayerVars);
        players.push(new YT.Player(video, {
            videoId: videoId,
            width: playerVars.width,
            height: playerVars.height,
            playerVars,
            events: {
                "onReady": onPlayerReady,
                "onStateChange": onPlayerStateChange
            }
        }));
    }

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

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

    function initPlayer(wrapper) {
        videoPlayer.init({
            video: wrapper.querySelector(".video"),
            playerVars
        });
    }

    function coverClickHandler(evt) {
        const wrapper = evt.currentTarget.nextElementSibling;
        show(wrapper);
        initPlayer(wrapper);
    }
    const cover = document.querySelector(containerSelector);
    cover.addEventListener("click", coverClickHandler);
}
loadPlayer(".jacketc", {
    start: 900,
    end: 1200,
    width: 600,
    height: 338
});

For context on what was done to the code:

Okay, so all fixed. I made a few changes, and it works. The problem is, there were two distinct problems, and one was hiding the other.

First thing, remove the iframe CSS lines. That is being done by your YT API include, get rid of it.

Second thing, you never actually set the width and height on the players themselves. You set width and height in your custom playerVars, which is great, but then when you start a player, you have hard-coded values and completely ignore the custom attributes.

Instead, I moved the width and height attributes into the defaultPlayer object, so when you merge that using Object.assign(…), those are being overridden with the custom settings. Then, when you create a new player, use the player’s values:

players.push(new YT.Player(video, {
  videoId: videoId,
  width: playerVars.width,      // this is coming from the custom settings.
  height: playerVars.height,   //    ... as is this.
  playerVars,
  events: {
    "onReady": onPlayerReady,
    "onStateChange": onPlayerStateChange
  }
}))

https://www.freecodecamp.org/forum/t/how-do-i-assign-width-height-to-only-1-youtube-player/243743/7


#151

That’s the shortcut method that I was speaking of before. It’s not preferred because it goes against youtube api design, and misleads the programmer into believing that they belong in there.


#152

What if that code were to be adjusted, would that be able to be done?

Can it be fixed

So that this can be used?

loadPlayer(".jacketc", {
    start: 900,
    end: 1200,
    width: 600,
    height: 338
});

#153

That is the shortcut method that results in improper mixing of playerVars and video settings. I have decided to not be involved with such improper behaviour.


#154

If instead, the loadPlayer function split up the object into playVars and settings information, then that I can certainly be involved with, as the videoPlayer code then retains a good separation between video settings and playVars too


#155

Yes, we can do that.


#156

It would start with by using this:

loadPlayer(".jacketc", {
    start: 900,
    end: 1200,
    width: 600,
    height: 338
});

Then the loadPlayer function can figure out what belongs to player settings, and what belongs to playerVars.

And loadPlayer then calls videoPlayer with the appropriate information.


#157

Here:
https://jsfiddle.net/hzyrfkwb/449/

function addVideo(video, desiredPlayerVars) {
    const videoId = video.getAttribute("data-id");
    const defaultPlayerVars = {
      autoplay: 1,
      controls: 1,
      showinfo: 1,
      rel: 0,
      iv_load_policy: 3,
      cc_load_policy: 0,
      fs: 0,
      disablekb: 1
    };
    const playerVars = Object.assign(defaultPlayerVars, desiredPlayerVars);
    players.push(new YT.Player(video, {
      width: 198,
      height: 198,
      videoId: videoId,
      playerVars,
      events: {
        "onReady": onPlayerReady,
        "onStateChange": onPlayerStateChange
      }
    }));
  }

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

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

  function initPlayer(wrapper) {
    videoPlayer.init({
      video: wrapper.querySelector(".video"),
      playerVars
    });
  }

  function coverClickHandler(evt) {
    const wrapper = evt.currentTarget.nextElementSibling;
    show(wrapper);
    initPlayer(wrapper);
  }
  const cover = document.querySelector(containerSelector);
  cover.addEventListener("click", coverClickHandler);
}
loadPlayer(".jacketc", {
  start: 900,
  end: 1200,
  width: 600,
  height: 338
});

#158

To do this properly two different things are needed.

One is for the addVideo function to use any valid information that we give it, and the other is for loadPlayer to divide up the settings.

I’ll just explore the addVideo situation for a bit, to ensure that I have a good solution there.


#159

It looks like I won’t have time today to take you your this step by step, so I’ll do some posts about the details instead, ending with a link to the final code.


#160

Thank you and I’ll read through everything.


#161

We want to give the videoPlayer function some settings, all combined together.
This can be done where the initPlayer function puts those settings as both the videoplayer settings, and as the playerVars settings too.

What starts as:

loadPlayer({
    target: ".jacketc",
    width: 600,
    height: 338,
    start: 900,
    end: 1200
});

ends up going to the videoPlayer init function as:

loadPlayer({
    target: ".jacketc",
    width: 600,
    height: 338,
    start: 900,
    end: 1200,
    playerVars: {
      target: ".jacketc",
      width: 600,
      height: 338,
      start: 900,
      end: 1200
    }
});

That way, settings that aren’t relevant are just ignored.

We can have initPlayer behave nicely too, so that if playerVars does exist then it won’t do any fancy doubling up.

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

We can now convert jacketc, and all the players still carry on working.

loadPlayer({
    target: ".jacketc",
    width: 600,
    height: 338,
    playerVars: {
        start: 900,
        end: 1200
    },
});
loadPlayer({
   target: ".jacketc",
   width: 600,
   height: 338,
   start: 900,
   end: 1200
});

The addVideo function should not be setting default width and heights for the videos.

      players.push(new YT.Player(opts.video, {
         width: opts.width || 198,
         height: opts.height || 198,

Instead, the initPlayer function is the much more appropriate place to do that instead. The real default width and height that youtube players have is 640x390 instead.

      players.push(new YT.Player(opts.video, {
         // width: opts.width || 198,
         width: opts.width || 640,
         // height: opts.height || 198,
         height: opts.height || 390,
...
   function initPlayer(wrapper) {
      ...
      // default to 198x198 for multiple video players
      opts.width = opts.width || 198;
      opts.height = opts.height || 198;

And while we’re in initPlayer, it’s more appropriate for video to be the first function parameter, followed by various settings. That playerVars variable wasn’t doing anything either, and can go too.

   // function init(opts) {
   function init(video, settings) {
      // const playerVars = opts.playerVars || {};
      load.js("https://www.youtube.com/player_api").then(function() {
         YT.ready(function() {
            // addVideo(opts);
            addVideo(video, settings);
         });
      });
   }
   function initPlayer(wrapper) {
      // opts.video = wrapper.querySelector(".video");
      const video = wrapper.querySelector(".video");
      ...
      videoPlayer.init(video, opts);
   }

We also should update the addVideo function so that video and settings is used instead of opts.

      // function addVideo(opts) {
      function addVideo(video, settings) {
        // const videoId = opts.video.dataset.id;
        const videoId = settings.video.dataset.id;

        // rename all other opts to be settings instead.
        ...
        // players.push(new YT.Player(opts.video, {
        players.push(new YT.Player(video, {
      }

We want the addVideo function to be capable of setting more than just width and height. As a result, we can use the settings to update everything that has a matching property in the default settings.

So let’s get those default settings as a separate variable:

      // players.push(new YT.Player(opts.video, {
      const defaultSettings = {
        width: opts.width || 640,
        height: opts.height || 390,
        videoId: videoId,
        // playerVars
        playerVars: {
          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
        }
      // }));
      };
        // const playerVars = Object.assign(defaultPlayerVars, opts.playerVars);
        const updatedSettings = Object.assign({}, defaultSettings);
        updatedSettings.playerVars = 
          Object.assign(opts.playerVars, defaultSettings.playerVars);
        // players.push(new YT.Player(video, {
        //   ...
        // }));
        players.push(new YT.Player(video, updatedSettings));

Those updatedSettings lines are just temporary, because we’re going to use a combineSettings function instead, that allows a deeply nested updating of existing values.

        // const updatedSettings = Object.assign({}, defaultSettings);
        // updatedSettings.playerVars =
        //     Object.assign(settings.playerVars, defaultSettings.playerVars);
        const updatedSettings = combineSettings(defaultSettings, settings);

Only properties that already exist in the default settings, should be updated.
So, we can use reduce, to work through the existing properties. If that property is an object, like playerVars or events, then those will be worked through separately too.

This combineSettings function can be placed just above the addVideo function.

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

If you want control some new settings, such as start and end, we only need to add them in to the default settings area so that the combiner knows to update those settings.

            playerVars: {
                start: 0,
                end: 999999,
                autoplay: 1,

And despite start and end being mixed up with width and height, the youtube api gets the information that it needs all in the right places.

The updated code for this is found at https://jsfiddle.net/hzyrfkwb/453/