Video.js Plugins

One of the great strengths of Video.js is its ecosystem of plugins that allow authors from all over the world to share their video player customizations. This includes everything from the simplest UI tweaks to new playback technologies and source handlers!

Because we view plugins as such an important part of Video.js, the organization is committed to maintaining a robust set of tools for plugin authorship:

  • generator-videojs-plugin

    A Yeoman generator for scaffolding a Video.js plugin project. Additionally, it offers a set of conventions for plugin authorship that, if followed, make authorship, contribution, and usage consistent and predictable.

    In short, the generator sets up plugin authors to focus on writing their plugin - not messing with tools.

Writing a Basic Plugin

If you’ve written a Video.js plugin before, the basic plugin concept should be familiar. It’s similar to a jQuery plugin in that the core idea is that you’re adding a method to the player.

Write a JavaScript Function

A basic plugin is a plain JavaScript function:

  1. function examplePlugin(options) {
  2. if (options.customClass) {
  3. this.addClass(options.customClass);
  4. }
  5. this.on('playing', function() {
  6. videojs.log('playback began!');
  7. });
  8. }

By convention, plugins are passed an options object; however, you can realistically accept whatever arguments you want. This example plugin will add a custom class (whatever is passed in as options.customClass) and, whenever playback begins, it will log a message to the browser console.

Note: The value of this in the plugin function is the player instance; so, you have access to its complete API.

Register a Basic Plugin

Now that we have a function that does something with a player, all that’s left is to register the plugin with Video.js:

  1. videojs.registerPlugin('examplePlugin', examplePlugin);

After that, any player will automatically have an examplePlugin method on its prototype!

Note: The only stipulation with the name of the plugin is that it cannot conflict with any existing plugin or player method.

Writing an Advanced Plugin

Video.js 6 introduced advanced plugins: these are plugins that share a similar API with basic plugins, but are class-based and offer a range of extra features out of the box.

While reading the following sections, you may want to refer to the Plugin API docs for more detail.

Write a JavaScript Class/Constructor

If you’re familiar with creating components, this process is similar. An advanced plugin starts with a JavaScript class (a.k.a. a constructor function).

If you’re using ES6 already, you can use that syntax with your transpiler/language of choice (Babel, TypeScript, etc):

  1. const Plugin = videojs.getPlugin('plugin');
  2. class ExamplePlugin extends Plugin {
  3. constructor(player, options) {
  4. super(player, options);
  5. if (options.customClass) {
  6. player.addClass(options.customClass);
  7. }
  8. player.on('playing', function() {
  9. videojs.log('playback began!');
  10. });
  11. }
  12. }

Or with ES5:

  1. var Plugin = videojs.getPlugin('plugin');
  2. var ExamplePlugin = videojs.extend(Plugin, {
  3. constructor: function(player, options) {
  4. Plugin.call(this, player, options);
  5. if (options.customClass) {
  6. player.addClass(options.customClass);
  7. }
  8. player.on('playing', function() {
  9. videojs.log('playback began!');
  10. });
  11. }
  12. });

For now, this example advanced plugin does the exact same thing as the basic plugin described above - not to worry, we will make it more interesting as we continue!

Register an Advanced Plugin

The registration process for advanced plugins is identical to the process for basic plugins.

  1. videojs.registerPlugin('examplePlugin', ExamplePlugin);

Note: Because ES6 classes are syntactic sugar on top of existing constructor function and prototype architecture in JavaScript, in all cases registerPlugin‘s second argument is a function.

Key Differences from Basic Plugins

Advanced plugins have two key differences from basic plugins that are important to understand before describing their advanced features.

The Value of this

With basic plugins, the value of this in the plugin function will be the player.

With advanced plugins, the value of this is the instance of the plugin class. The player is passed to the plugin constructor as its first argument (and is automatically applied to the plugin instance as the player property) and any further arguments are passed after that.

The Player Plugin Name Property

Both basic plugins and advanced plugins are set up by calling a method on a player with a name matching the plugin (e.g., player.examplePlugin()).

However, with advanced plugins, this method acts like a factory function and it is replaced for the current player by a new function which returns the plugin instance:

  1. // `examplePlugin` has not been called, so it is a factory function.
  2. player.examplePlugin();
  3. // `examplePlugin` is now a function that returns the same instance of
  4. // `ExamplePlugin` that was generated by the previous call.
  5. player.examplePlugin().someMethodName();

With basic plugins, the method does not change - it is always the same function. It is up to the authors of basic plugins to deal with multiple calls to their plugin function.

Features of Advanced Plugins

Up to this point, our example advanced plugin is functionally identical to our example basic plugin. However, advanced plugins bring with them a great deal of benefit that is not built into basic plugins.

Events

Like components, advanced plugins offer an implementation of events. This includes:

  • The ability to listen for events on the plugin instance using on or one:

    1. player.examplePlugin().on('example-event', function() {
    2. videojs.log('example plugin received an example-event');
    3. });
  • The ability to trigger custom events on a plugin instance:

    1. player.examplePlugin().trigger('example-event');
  • The ability to stop listening to custom events on a plugin instance using off:

    1. player.examplePlugin().off('example-event');

By offering a built-in events system, advanced plugins offer a wider range of options for code structure with a pattern familiar to most web developers.

Extra Event Data

All events triggered by plugins include an additional data object as a second argument. This object has three properties:

  • name: The name of the plugin (e.g. "examplePlugin") as a string.
  • plugin: The plugin constructor (e.g. ExamplePlugin).
  • instance: The plugin constructor instance.

Statefulness

A new concept introduced for advanced plugins is statefulness. This is similar to React components’ state property and setState method.

Advanced plugin instances each have a state property, which is a plain JavaScript object - it can contain any keys and values the plugin author wants.

A default state can be provided by adding a static property to a plugin constructor:

  1. ExamplePlugin.defaultState = {
  2. customClass: 'default-custom-class'
  3. };

When the state is updated via the setState method, the plugin instance fires a "statechanged" event, but only if something changed! This event can be used as a signal to update the DOM or perform some other action. The event object passed to listeners for this event includes, an object describing the changes that occurred on the state property:

  1. player.examplePlugin().on('statechanged', function(e) {
  2. if (e.changes && e.changes.customClass) {
  3. this.player
  4. .removeClass(e.changes.customClass.from)
  5. .addClass(e.changes.customClass.to);
  6. }
  7. });
  8. player.examplePlugin().setState({customClass: 'another-custom-class'});

Lifecycle

Like components, advanced plugins have a lifecycle. They can be created with their factory function and they can be destroyed using their dispose method:

  1. // set up a example plugin instance
  2. player.examplePlugin();
  3. // dispose of it anytime thereafter
  4. player.examplePlugin().dispose();

The dispose method has several effects:

  • Triggers a "dispose" event on the plugin instance.
  • Cleans up all event listeners on the plugin instance, which helps avoid errors caused by events being triggered after an object is cleaned up.
  • Removes plugin state and references to the player to avoid memory leaks.
  • Reverts the player’s named property (e.g. player.examplePlugin) back to the original factory function, so the plugin can be set up again.

In addition, if the player is disposed, the disposal of all its advanced plugin instances will be triggered as well.

Version

Adding a version number to a plugin is done by defining a VERSION property on the plugin before registering it:

  1. ExamplePlugin.VERSION = '1.0.1';
  2. videojs.registerPlugin('examplePlugin', ExamplePlugin);

Retrieve it using videojs.getPluginVersion:

  1. var version = videojs.getPluginVersion('examplePlugin');
  2. console.log(version); // 1.0.1

Note that the plugin generator already takes care of adding a version number for you.

Logging

By default, each advanced plugin instance has its own log property much like videojs and Player instances do. The log messages will be prefixed with the player’s ID and the plugin’s name:

  1. player.examplePlugin().log('hello world!');

The above will log the following:

  1. VIDEOJS: $PLAYER_ID: examplePlugin: hello world!

The log function will also have all the methods/properties of the default videojs.log; such as, error(), warn(), level(), etc.

NOTE: This method is added in the constructor and it will not override any predefined log property of the plugin’s prototype.

Advanced Example Advanced Plugin

What follows is a complete ES6 advanced plugin that logs a custom message when the player’s state changes between playing and pause. It uses all the described advanced features:

  1. import videojs from 'video.js';
  2. const Plugin = videojs.getPlugin('plugin');
  3. class Advanced extends Plugin {
  4. constructor(player, options) {
  5. super(player, options);
  6. // Whenever the player emits a playing or pause event, we update the
  7. // state if necessary.
  8. this.on(player, ['playing', 'pause'], this.updateState);
  9. this.on('statechanged', this.logState);
  10. }
  11. dispose() {
  12. super.dispose();
  13. videojs.log('the advanced plugin is being disposed');
  14. }
  15. updateState() {
  16. this.setState({playing: !this.player.paused()});
  17. }
  18. logState(changed) {
  19. videojs.log(`the player is now ${this.state.playing ? 'playing' : 'paused'}`);
  20. }
  21. }
  22. videojs.registerPlugin('advanced', Advanced);
  23. const player = videojs('example-player');
  24. player.advanced();
  25. // This will begin playback, which will trigger a "playing" event, which will
  26. // update the state of the plugin, which will cause a message to be logged.
  27. player.play();
  28. // This will pause playback, which will trigger a "paused" event, which will
  29. // update the state of the plugin, which will cause a message to be logged.
  30. player.pause();
  31. player.advanced().dispose();
  32. // This will begin playback, but the plugin has been disposed, so it will not
  33. // log any messages.
  34. player.play();

This example may be a bit pointless in reality, but it demonstrates the sort of flexibility offered by advanced plugins over basic plugins.

Setting up a Plugin

There are two ways to set up (or initialize) a plugin on a player. Both ways work identically for both basic and advanced plugins.

The first way is during creation of the player. Using the plugins option, a plugin can be automatically set up on a player:

  1. videojs('example-player', {
  2. plugins: {
  3. examplePlugin: {
  4. customClass: 'example-class'
  5. }
  6. }
  7. });

Otherwise, a plugin can be manually set up:

  1. var player = videojs('example-player');
  2. player.examplePlugin({customClass: 'example-class'});

These two methods are functionally identical - use whichever you prefer!

Plugin Setup Events

Occasionally, a use-case arises where some code needs to wait for a plugin to be initialized. As of Video.js 6, this can be achieved by listening for pluginsetup events on the player.

For any given plugin initialization, there are four events to be aware of:

  • beforepluginsetup: Triggered immediately before any plugin is initialized.
  • beforepluginsetup:examplePlugin Triggered immediately before the examplePlugin is initialized.
  • pluginsetup: Triggered after any plugin is initialized.
  • pluginsetup:examplePlugin: Triggered after he examplePlugin is initialized.

These events work for both basic and advanced plugins. They are triggered on the player and each includes an object of extra event data as a second argument to its listeners.

References