译 文 : Hiding and Showing Video Player Controls

Last week I decided to resolve some outstanding issues surrounding the control bar and get into the process of updating the relevant player. I’m lucky to have some time now, and I’ll be writing some updates about it.

One of the expected behaviors of the player control bar is that it fades out after a few seconds when the user is inactive while watching a video. Previously, the way we did this with video.js was with some CSS tricks. When the user mouse moves out of the video player area, the control bar is given a class named VJS-fade-out. This class has a conversion effect after a 2 second delay.

.vjs-fade-out {
  display: block;
  visibility: hidden;
  opacity: 0;

  -webkit-transition: visibility 1.5 s, opacity 1.5 s;
     -moz-transition: visibility 1.5 s, opacity 1.5 s;
      -ms-transition: visibility 1.5 s, opacity 1.5 s;
       -o-transition: visibility 1.5 s, opacity 1.5 s;
          transition: visibility 1.5 s, opacity 1.5 s;

  /* Wait a moment, then fade out of the control bar */
  -webkit-transition-delay: 2s;
     -moz-transition-delay: 2s;
      -ms-transition-delay: 2s;
       -o-transition-delay: 2s;
          transition-delay: 2s;
}
Copy the code

When the user moves the mouse back over the player, the class is deleted, cancelling all latency mitigation effects. This provides a simple example of how control fade-out works the way you expect it to, with just a few lines of javascript to add/remove classes.

player.on('mouseout'.function(){ 
  controlBar.addClass('vjs-fade-out'); 
});

player.on('mouseover'.function(){ 
  controlBar.removeClass('vjs-fade-out'); 
});
Copy the code

Although there are some disadvantages, it is still necessary to get rid of this method.

  • The control bar does not fade out in full screen mode because the mouse may never move out of the player area.
  • There is no mouse on mobile devices, so different events and interactions are required to show/hide controls.

In addition to these issues, we want it to be possible to hook any player component or plug-in into the same trigger as the hidden control. Components like social sharing ICONS should fade out with the control bar.

User state

One of the first things to add is the player’s userActive property, which can be true or false. This abstracts the control into what we actually care about, namely whether the user is currently interacting with the player or passively watching a video. This also removes the control bar from tracking user activity per se and allows other components to more easily align with the control bar through player-level state.

This property is called player.userActive() and returns true or false. When this value is changed, it triggers an event on the player.

player.userActive(true)
    // -> the 'userActive' event is triggered
player.userActive(false)
    // -> the 'userinactive' event is triggered
Copy the code

The player element also adds the CSS class name of JS-user-Active or JS-user-Inactive. The class name is actually used to hide and show the contents of the control bar.

.vjs-default-skin.vjs-user-inactive .vjs-control-bar {
  display: block;
  visibility: hidden;
  opacity: 0;

  -webkit-transition: visibility 1.5 s, opacity 1.5 s;
     -moz-transition: visibility 1.5 s, opacity 1.5 s;
      -ms-transition: visibility 1.5 s, opacity 1.5 s;
       -o-transition: visibility 1.5 s, opacity 1.5 s;
          transition: visibility 1.5 s, opacity 1.5 s;
}
Copy the code

The 2-second delay has been removed from CSS and will instead be built into the process of setting the userActive state to false via JavaScript timeout. This timeout is reset whenever a mouse event occurs in the player. Such as:

var resetDelay, inactivityTimeout;

resetDelay = function(){
    clearTimeout(inactivityTimeout);
    inactivityTimeout = setTimeout(function(){
        player.userActive(false);
    }, 2000);
};

player.on('mousemove'.function(){
    resetDelay();
})
Copy the code

The Mousemove event is invoked very quickly when the mouse moves, and we want to block the player process as little as possible during this operation, so we use a technique written by John Resig.

Instead of resetting the timeout for each Mousemove, a variable is set through the Mousemove event, which can be retrieved through a controlled JavaScript time interval.

var userActivity, activityCheck;

player.on('mousemove'.function(){
    userActivity = true;
});

activityCheck = setInterval(function() {

  // Check whether the mouse is moved
  if (userActivity) {

    // Reset the activity tracker
    userActivity = false;

    // If the user is inactive, set the state to active
    if (player.userActive() === false) {
      player.userActive(true);
    }

    // Clear any existing inactive timeouts to start the timer
    clearTimeout(inactivityTimeout);

    // Within X seconds, if no more activity occurs, the user is considered inactive
    inactivityTimeout = setTimeout(function() {
      // Prevents an inactive timeout from being triggered before the activityCheck loop picks up the next user activity.
      if(! userActivity) {this.userActive(false); }},2000); }},250);
Copy the code

This may have a lot to follow and is slightly simpler than what’s actually in the player today, but essentially it allows us to get some processing weights from the browser as the mouse moves.

Hide the control bar in full screen

Thanks to the new userActive state and delayed javascript timeout, controls no longer need to move the mouse outside of the player area to be hidden, and can now be hidden in full-screen mode, just like when the player enters the game. This also means that we can now hide the mouse cursor as if it were a control, so that the mouse does not appear on the player when viewed in full screen.

.vjs-fullscreen.vjs-user-inactive {
  cursor: none;
}
Copy the code

Hide the control bar on the touch device

The expected behavior on a touch device is different from that of a desktop browser. There are no Mousemove events to help determine whether a user is active or inactive, so a long delay is typically added before the control fades out. Also, while clicking on the video itself in a desktop browser typically toggles between play and pause, clicking on the video on a mobile device only toggles the visibility of the control.

Fortunately, the framework we built around userActive makes this last part easy to set up.

video.on('tap'.function(){
  if (player.userActive() === true) {
    player.userActive(false);
  } else {
    player.userActive(true); }});Copy the code

Manually switching userActive between True and false will apply the appropriate class name and trigger the events required to show and hide the control, as expected on mobile devices.

The Tap event is actually a custom event, similar to the tap event you’ll find in jQuery Mobile, Hammer.js, and other mobile touch libraries. The TAP event is emitted whenever the TouchStart event is emitted and the associated Touchend event is emitted within 250 milliseconds. If the TouchEnd event takes longer to fire, or if a TouchMove event occurs in between, it will not be considered a tap.

conclusion

I hope this article provides insight into how these controls work in Video.js and how to build your own plug-ins for Video.js to mimic the same interactions.

Cheers,

-heff