Adding new website support to H5VEW
With the recent rewrite of HTML5 Video EveryWhere extension to support the new WebExtension API. I decided to write a quick guide on how to add support for a new video streaming website. In this tutorial, I will document the steps followed to add support for Lego.com.
First, let's create a new module for this website. A HTML5 Video EveryWhere
module is a subclass of Module
class and it's injected into the web page
containing the video player to replace. A module should at least override two
functions:
- The
constructor()
function to specify the module alias that it's used to identify different modules. The alias is hyphenated lowercased website name. onInterative()
function that is invoked when DOM is loaded (equivalent to theDOMContentLoaded
event). This is the right place to write the needed code to change the site video player. Alternative functions that are available to override areonLoading
andonComplete
.onLoading
will be invoked before parsing the DOM whileonComplete
will be invoked when the DOM and all the resources (images, subframes, ...) have been loaded.
So let's start with a minimal code that will print "Hello World" when injected
into Lego.com
website. The module path will be content/Lego.js
.
import Module from './Module.js';
class Lego extends Module {
constructor() {
// We overwrite the constructor to set the module name.
super("lego");
}
onInteractive() {
// Here we are using the Module `log` method.
this.log("Hello World");
}
}
// Needed to execute the module on file injection
new Lego().start();
You will notice we are using ES6 Modules syntax. Even it's still not supported
by the Firefox WebExtensions, it will transcompiled into Firefox v52+ valid
code using babel at a later step. You can use all the fancy latest ES syntax
without worring about the bowser support. You will notice the use of log
function defined on Module class, it's a wrapper of console.log
with
namespacing the log message to identify the extension own messages, you should
use it instead of direct using console
API.
Now, we need this module to be injected when opening Lego.com
video player
page. After inspecting the site, we notice that its videos are opened within an
iFrame and are hosted under https://www.lego.com/en-US/mediaplayer/video/
path where en-US
could be changed to the user locale code. We need to match
this URL and inject the required files when opened. In manifest.json
file,
we add the following object into content_scripts
field:
{
"matches": ["https://www.lego.com/*/mediaplayer/video/*"],
"js": [
"content/Lego.js"
],
"all_frames": true,
"run_at": "document_start"
}
First thing we notice is that besides content/Leog.js
, we inject other
files that are needed for the module to execute. These files are:
content/Modules.js
: DefinesModule
class which is the parent class of all modules and contains common code and it's responsible to communique with the extension background script.content/video-player.js
: DefinesVP
class which creates the video player widget and add to it custom styles, properties based on the extension settings and also it adds a context menu to the video player.content/statics.js
: Tracks how often this module is used, browser/extension version and the browser default language.content/common.js
: Contains common functions used by more than when script.content/Options.js
: DefinesOptions
class that contains all logic related to defining, retrieving and updating extension options. An instance of this class is created byModule
class under theoptions
attribute.
We can also notice that Lego video player is always hosted under the specified
URL. All http://
requests are redirected to https://
protocol and all
requests not containing the www
resource are redirected to
www.lego.com
. So our matching pattern is just one simple pattern.
Next, we need to define a new option to disable this module. In
content/Options.js
, add to defaults
attribute in the constructor the
new option which is of type boolean
and with default value false
.
this.defaults = {
// ...
disablelego: ["boolean", false],
};
To test our code, open about:debugging
URL in Firefox and load the
extension. You should see the "Hello World" message when you visit a web page
with Lego.comm video player embed in.
Now, we can move forward by updating onIntercative
function code to extract
video URLs and replace the video player with an instance of VP. Generally, the
module code logic follows these steps:
- (Optional) Validate the URL of the document in case the matches patterns are not enough to eliminate pages URL which are known to not include the video player. Or to invoke different code for different URLs patterns. e.g: YouTube watch page vs. channels/users page.
- Extract video data including video files path and poster URL. These data can
be included inside the HTML document as JavaScript variable or as embed JSON
document or as tag attributes. Or these data can be downloaded from other
URL. In this case, you have to add the URL pattern of the resource to
permissions
field in themanifest.json
file if it is hosted on a different domain. - Create an instance of
VP
class with the container element of the video player as first argument andthis.options
as second option. - Add video URLs using either
VP.srcs()
orVP.addSrc()
methods. The first expects an object with the video quality/format as key and the video URL as value. The second method expects the video URL as first argument, the video quality as second argument (possible values:"low"
,"medium"
,"high"
or"higher"
) and the video format as third argument (possible values:"mp4"
or"webm"
). - Set the poster URL using
VP.props()
function which expects an object of the HTMLVideoElement attributes and its values. In this case, setposter
attribute to the poster URL. - Invoke
VP.setup()
method to replace the website video player with the browser video player, to add video sources and to apply all provided styles and properties.
We resume. To add new website support. We implement a subclass of Module with
overwriting one of its abstract methods but mostly onInterative
. We add a new
entry to the module with its matching URL patterns to content_scripts
on
manifest.json
. We add module specific options definitions into defaults
property of content/Options.js
module.
The final implementation of Lego.com
module can be found on HTML5 Video
EveryWhere repository. I welcome patches for new websites support, for
new language translation or for issue fixing.