Note: Advanced plugins are being introduced in Video.js 6.0 / Brightcove Player 6.0 and are only supported from that version forward.
If you've been a Video.js user for a while, you're likely familiar with the concept of plugins: functions that become methods of any player you create. If you're not familiar with Video.js plugins, we have a comprehensive plugins guide available.
These plugins - which we'll call basic plugins - are lightweight and offer complete control of the player. That's really useful and it isn't changing - existing plugins should continue to work!
But what if you want a richer set of features? Or more guidance on how to structure your plugin? Or more tools out of the box that help manage complex plugin-rich players?
Well, until Video.js 6.0, you had to figure things out on your own.
Introducing Advanced Plugins
One of Video.js' strengths is its rich ecosystem of plugins; so, in the last few months, we wanted to focus our efforts on improving the plugin author experience.
While projects like the plugin generator make becoming a plugin author easier than ever, the Video.js team thought it was important to provide a foundational API and set of conventions on which the future of Video.js plugins could be built.
Our solution is advanced plugins.
Advanced Plugins are Component-Like
One of the design goals for advanced plugins was to provide an API that was reminiscent of the existing components system. We achieved this in a number of ways.
At the lowest level, this included a name change for the plugin registration function from videojs.plugin
to videojs.registerPlugin
(taking a naming cue from videojs.registerComponent
and videojs.registerTech
).
Beyond a simple registration method name change, advanced plugins are class-based. A trivial example of an advanced plugin might look something like this:
var Plugin = videojs.getPlugin('plugin');
var HelloWorld = videojs.extend(Plugin, {
constructor(player) {
Plugin.call(this, player);
this.player.addClass('hello-world');
}
});
videojs.registerPlugin('helloWorld', HelloWorld);
If you're using an ES6 transpiler, you can use ES6 classes in a similar fashion!
This plugin can be initialized in the same way as a basic plugin - via a player method whose name matches the registered name of the plugin.
In the case of advanced plugins, this method is a factory function, which instantiates the plugin class and returns an instance.
It's useful to know that the player method that is created will always be a function. If a player already has an instance of an advanced plugin, its associated method will simply return the pre-existing instance rather than re-initialize it:
var player = videojs('my-player');
var instance = player.helloWorld();
// Logs: 'true'
videojs.log(instance === player.helloWorld());
The helloWorld
method will return this plugin object until it is disposed - after which it will create a new plugin instance again.
Events
Similar to components, advanced plugins can listen to and trigger events via the on
, one
, off
, and trigger
methods.
This provides a loosely coupled communication channel for plugins and other objects (components, players, etc) to manage their own state and respond to changes in the state of one another.
Additional Event Data
The Video.js event system allows additional data to be passed to listeners as a second argument when triggering events (the first argument is the event object itself).
Plugin events pass a consistent set of properties in this object (including any custom properties passed to trigger
):
instance
: The plugin instance, which triggered the event.name
: The name of the plugin as a string (e.g.'helloWorld'
).plugin
: The plugin class/constructor function (e.g.HelloWorld
).
For example, a listener for an event on a plugin can expect something like this:
var player = videojs('my-player');
var instance = player.helloWorld();
instance.on('some-custom-event', function(e, data) {
videojs.log(data.instance === instance); // true
videojs.log(data.name === 'helloWorld'); // true
videojs.log(data.plugin === videojs.getPlugin('helloWorld')); // true
videojs.log(data.foo); // "bar"
});
instance.trigger('some-custom-event', {foo: 'bar'});
Lifecycle
Another similarity between plugins and components is the concept of a lifecycle - more specifically, setup and teardown processes.
We get the setup feature as a side effect of normal object creation in JavaScript, but we are left to our own devices when it comes to object destruction and ensuring that references between objects are cleaned up to avoid leaking memory.
Video.js components have long had a dispose
method and event that deal with removing a component from the DOM and memory. Advanced plugins have the same feature:
var player = videojs('my-player');
var firstInstance = player.helloWorld();
// Logs: 'true'
videojs.log(firstInstance === player.helloWorld());
firstInstance.on('dispose', function() {
videojs.log('disposing a helloWorld instance');
});
// Logs: 'disposing a helloWorld instance'
firstInstance.dispose();
var secondInstance = player.helloWorld();
// Logs: 'false'
videojs.log(firstInstance === secondInstance);
The pluginsetup
Event
Plugins do have one lifecycle feature that components do not: the pluginsetup
event.
This event is triggered on a player when a plugin is initialized on it:
var player = videojs('my-player');
player.on('pluginsetup', function(e, hash) {
if (hash.name === 'helloWorld') {
videojs.log('A helloWorld instance was created!');
}
});
// Logs: 'A helloWorld instance was created!'
player.helloWorld();
React-inspired Statefulness
One of the exciting additions in Video.js for both advanced plugins and components is React-inspired statefulness. Essentially, this means that all plugin objects and component objects have a state
property, which is a plain object that can be used to store variable state for that object. Then, there is a setState
method that updates this object and triggers a statechanged
event.
This system allows plugins and components to use their evented nature to communicate in-memory state changes through a consistent API:
// A static property of the constructor can be used to pre-populate state
// for all instances.
HelloWorld.defaultState = {color: 'red'};
var player = videojs('my-player');
var instance = player.helloWorld();
instance.on('statechanged', function(e) {
var color = e.changes.color;
if (color) {
videojs.log('The helloWorld color changed from "' + color.from + '" to "' + color.from + '"!');
}
});
// Logs: 'The helloWorld color changed from "red" to "blue"!'
instance.setState({color: 'blue'});
Player Plugin Awareness
Finally, we couldn't add new plugin infrastructure without working on one of the more pernicious problems of managing complex combinations of plugins: the player can't report which plugins it has initialized - or not. To this end, the player has two new methods: hasPlugin
and usingPlugin
. These methods work for both types of plugins.
The hasPlugin
Method
This method reports whether a plugin matching a given name is available on the player:
var player = videojs('my-player');
// Logs: 'true'
videojs.log(player.hasPlugin('helloWorld'));
// Logs: 'false'
videojs.log(player.hasPlugin('fooBar'));
This method ignores whether or not the plugin has been initialized and merely reports whether or not it has been registered.
The usingPlugin
Method
This method reports not only whether a plugin is available on a player, but whether it is currently active on the player:
var player = videojs('my-player');
// Logs: 'false'
videojs.log(player.usingPlugin('helloWorld'));
player.helloWorld();
// Logs: 'true'
videojs.log(player.usingPlugin('helloWorld'));
One caveat to note here. While this works for both types of plugins, only advanced plugins can change this value more than once. A basic plugin has no built-in lifecycle or events; so, it's not possible to determine whether one has been "disposed".
Go Forth and Code
We hope these additions and improvements to the plugin architecture will make writing Video.js plugins more pleasurable and remove some of the low-level legwork involved in ensuring plugins aren't creating memory leaks and other problems.
The design of advanced plugins is such that we can add features as 6.0 matures and we get more community feedback. As always, we strongly encourage our users to give back to the Video.js project in whatever way they can.
For a more complete discussion of plugins generally, visit the Video.js plugins guide.
This was originally posted on the Video.js blog.