Creating a videojs plugin

At Dotsub we work with a lot of video. For the last couple years we have been using videojs as our sites main video player. This week I’ve been updating some of our plugins to the latest videojs version (5.9.2) and also open sourcing them!

This week lets walk through how to create a videojs plugin. We’ll make a simple watermark plugin that displays the company logo on a video.

Getting Started

The folks at videojs do provide a yeoman generator as an initial starting point. You can check out that project here: https://github.com/videojs/generator-videojs-plugin

To create the initial framework just install and run the generator.

$ npm install -g yo generator-videojs-plugin
$ yo videojs-plugin

I enabled the following for the watermark plugin.

{
  "generator-videojs-plugin": 
  {
    "bcov": false,
    "scope": "",
    "name": "watermark",
    "description": "Adds a watermark image the video player",
    "author": "Brooks Lyrette ",
    "license": "apache2",
    "changelog": true,
    "sass": true,
    "docs": false,
    "lang": false,
    "bower": false
  }
}

You now have a ready to go project that uses `grunt` to build, `karma` and `qunit` to test. The plugin code is under `src/plugin.js` and `src/plugin.scss`.

You can make sure everything is working fine by typing `npm test` in your console.

Note: If you need to be able to use ES6 imports check this out: https://github.com/videojs/generator-videojs-plugin/pull/69#discussion_r62665092

### Making the plugin do something

Now comes the fun part, making the plugin do something useful. The requirements are pretty simple for this plugin:

  • Show a configured image in the video player.
  • When the video is played for the first time wait the pre configured number of seconds to fade the logo out.
  • After the initial fade out, the logo should be shown when the player’s controls are shown.
  • The logo can be a clickable link.
  • The logo position should be configurable (the four corners).

Defaults, Options and configuration.

Videojs provides a nice way deal with configuration. As the plugin developer you can provide a set of `defaults`, the user will input their settings using `options` and you can merge the two using a function the provide called: `videojs.mergeOptions(defaults, options);`. Based on the requirements our plugin will use the following settings with these defaults:

const defaults = {
  position: 'top-right',
  fadeTime: 3000,
  url: undefined,
  image: undefined
};

image: The URL to the image to be used as the watermark.

position: The location to place the watermark (top-left, top-right, bottom-left, bottom-right). Defaults to ‘top-right’.

fadeTime: The amount of time in milliseconds for the initial watermark fade. Defaults to 3000.

url: A url to be linked to from the watermark. If the user clicks the watermark the video will be paused and the link will open in a new window.

Coding the plugin

We’ll need some DOM elements added to the video player. If you look at the generated code in `src/plugin.js` you’ll see there is already a function listing for `onPlayerReady`. This is where we will add the needed DOM elements. `onPlayerReady` is an event videojs throws when the player is setup and ready to go.

const watermark = function(options) {
  this.ready(() => {
    onPlayerReady(this, videojs.mergeOptions(defaults, options));
  });
};

Let’s add a call to a new function `setupWatermark(player, options);`. In that function lets setup the required DOM.

const onPlayerReady = (player, options) => {
  player.addClass('vjs-watermark');

  // if there is no image set just exit
  if (!options.image) {
    return;
  }
  setupWatermark(player, options);
};

As you see above we exit if there is not image configured. If there is one we continue on and set up the DOM. The setup ends up looking like:

const setupWatermark = (player, options) => {
  // Add a div and img tag
  const videoEl = player.el();
  const div = document.createElement('div');
  const img = document.createElement('img');

  div.id = 'vjs-watermark';
  div.classList.add('vjs-watermark-content');
  div.classList.add(`vjs-watermark-${options.position}`);
  img.src = options.image;

  // if a url is provided make the image link to that URL.
  if (options.url) {
    const a = document.createElement('a');

    a.href = options.url;
    // if the user clicks the link pause and open a new window
    a.onclick = (e) => {
      e.preventDefault();
      player.pause();
      window.open(options.url);
    };
    a.appendChild(img);
    div.appendChild(a);
  } else {
    div.appendChild(img);
  }
  videoEl.appendChild(div);
};

We only add a `a` tag if there was a url provided in the configuration. To handle the logo positions we have a CSS class for each location. Let’s write the SCSS for the plugin. We will us absolute positioning for the image container.

// Sass for videojs-watermark
.video-js {
  &.vjs-watermark {
    display: block;
  }
  .vjs-watermark-content {
    opacity: 0.99;
    position: absolute;
    padding: 5px;
  }

  // pre-defined positions
  .vjs-watermark-top-right {
    right: 0;
  }
  .vjs-watermark-top-left {
    left: 0;
  }
  .vjs-watermark-bottom-right {
    right: 0;
    bottom: 30px;
  }
  .vjs-watermark-bottom-left {
    left: 0;
    bottom: 30px;
  }
}

For the bottom logo positions the `30px` of padding is to account for the height of the controlbar.

We’ve covered almost all of the requirements, you can configure the image, its location and an optional URL to link to.

Implementing fade out

We will want the logo to fade in and out. This will look smoother than having it just disappear. We will do this using CSS transitions. To do this we will animate the opacity from 1 to 0 over 1 second.

Since one of the requirements is that the logo remains visible on the initial play we can use a timeout to wait the correct amount of time.

const onPlayerReady = (player, options) => {
  ...
  player.on('play', () => fadeWatermark(options));
};

const fadeWatermark = (options) => {
  setTimeout(
    () => document.getElementById('vjs-watermark').classList.add('vjs-watermark-fade'),
  options.fadeTime);
};

Once the `vjs-watermark-fade` fade class is attached to the element it will start fading in and out. For the SCSS I recommend using a mixin that covers all the browser prefixes.

// cross browser mixin
@mixin transition($args...) {
  -webkit-transition: $args;
  -moz-transition: $args;
  -ms-transition: $args;
  -o-transition: $args;
  transition: $args;
}

// Sass for videojs-watermark
.video-js {
  &.vjs-watermark {
    display: block;
  }
  .vjs-watermark-content {
    opacity: 0.99;
    position: absolute;
    padding: 5px;
    @include transition(visibility 1.0s, opacity 1.0s);
  }
  ...
  //fade out when the user is not active and the video is playing.
  &.vjs-user-inactive.vjs-playing .vjs-watermark-fade {
   opacity: 0;
  }
}

There you have it. A working simple videojs watermark plugin. You can check out the completed project here: https://github.com/dotsub/videojs-watermark (Apache 2.0 licenced)

The final version includes Qunit tests and ES6 support.

Building a Universal Translator

It’s absolutely amazing how fast technology is advancing. Unbeknownst to most users, Google Chrome has the built in capabilities to create a universal translator using nothing but HTML and Javascript.

Chrome implements the Web Speech API which allows you to use speech synthesis and speech recognition from Javascript. In this post I’ll outline how we created https://universal-translator.dotsub.com.

universal translator

First let’s decompose the steps required. The universal translator needs to do three things:

  1. Recognise what the user is saying.
  2. Translate the spoken phrase.
  3. Speak the result.

Speech Recognition

As I mentioned before Google Chrome has a built in speech recognition engine. Using this engine is pretty simple.

It is important to properly set recognition.lang to the language the user is speaking. In the full source code this is driven from the spoken language select. Now we have the spoken input from the user. This is all we need to complete step one.

Machine Translation

We will use Google’s Translation API to translate our text.

Speech Synthesis

The Web Speech API also includes a speech synthesis engine. It only takes a few lines to get the browser to speak any line of text.

Here is the full speech synthesis part of our universal translator. It takes input from the user translates it to the target language and speaks the result.

Conclusion

There you have it, less than 150 lines of Javascript that makes a universal translator. The finished demo here: https://universal-translator.dotsub.com/. You can look over the code here: https://github.com/dotsub/universal-translator