Video.js removes Flash from core player

In August of 2016, we announced our intention of removing Flash as a part of the core Video.js project.
As Html5 video becomes the standard playback tech and Flash fades into obsolescence, it is time
to remove Flash from the core player and move it to a separate code base. This will give us the ability
to allow developers to continue to support legacy browsers by adding the tech themselves, while allowing
us to minimize legacy code in Video.js and decrease the footprint of the player.

This follows in the footsteps of Chrome, Safari and Firefox which are all taking steps to deprecate Flash.

Chrome: Chrome 56 disables flash by default

Safari: Safari makes Flash a legacy plugin

Firefox: Reducing Adobe Flash Usage in Firefox

As of the Video.js 6.0 release, the dream of a Flashless future will come closer to a reality.

In the meantime, the separate videojs-flash project has been created for Flash tech support.
When the videojs-flash plugin is added to the player, the Flash tech is added to the tech order.

1
2
3
4
5
6
7
8
9
10
11
<link rel="stylesheet" href="path/video.js/dist/video-js.css">
<script src="path/video.js/dist/video.js"></script>
<script src="path/videojs-flash/dist/videojs-flash.js"></script>
<video id='vid' class='video-js' controls height=300 width=600>
<source src="video.mp4" type="video/mp4">
</video>
<script>
var player = videojs('vid');
</script>

Feature Spotlight: Accessibility

Accessibility! The most important feature you never knew about.

In the Video.js organization we try hard to have good accessibility. Like most other software, any change can affect the system in unintended ways. For example MuteToggle and VolumeControl were married into VolumeMenuButton in Video.js 5. While this change did allow these controls to work in tandem visually, it also did something unintended. It broke accessibility. In this post we will go over what broke, what the fix was, what accessibility is, and how to test and make sure it works.

Feel free to skip to the last section if you already know what accessibility is.

Accessibility? What’s that?

Accessible software has support for users with vision, hearing, movement/dexterity, or other impairments. It also helps users that want to use the keyboard to navigate. Out of the box web applications have some accessibility due to the nature of HTML, but this is only the case if you are using native elements in intended ways. If you cannot use native DOM elements, like <button>, and instead must use a <div> for buttons, then you need worry about accessibility in your page.

Supporting users with hearing impairment is not something that we can do directly for the users of Video.js. Instead we must indirectly support these users by adding support for captions in videos. In Video.js we have had support for captions and subtitles for some time, internally they are called TextTracks. In fact Video.js has had support for WebVTT format TextTrack, which is much more accessible, since version 4.

Supporting users with vision impairment is harder, but partly in our control. To support this group of users our player must be accessible to screen readers. A screen reader is an application that reads elements off of the screen to the user (as the name implies). On top of reading from the screen it also allows the user to interact with the page using only the keyboard or specific gestures on a touchscreen (without using a mouse or needing to directly touch visible items). HTML has certain rules that must be followed so that a page can be accessible. We will go over the basics of these rules in the next section. Screen readers are further supported by having description tracks that can be read out during video playback. Description tracks are a sub-type of TextTrack, and as previously stated we cannot automatically add them to videos, we can only have support for them in the Video.js.

See the resources section at the end of this post for a list of screen readers.

How do you make a web application screen reader accessible?

If you use the native elements for the purposes that they were intended, you will already have most of the work done.This is why the use of the native element is the recommended way to make anything accessible for a screen reader. For instance if you use a <button> element you will get the following accessibility attributes (without them actually being on the button):

  • tabIndex which allows users to tab to the button
  • role="button" which tells the screen reader that this is a button
  • The space and the enter key will both press the button

In some cases, such as in Video.js, it will not be possible to use the native <button> element. You will have to mimic the accessible functionality from the list above and use a div. Here is a list of what you will have to add:

  • You have to add the role="button" attribute to classify it as a button.
  • You have to add a tabIndex which will allow the div to be navigated to using the tab key
  • You have to add handling for the space and enter key that press the button

A list of role attribute values can be found on Mozilla Developer Network.

After mimicking or adding native accessibility on the controls and content in your webpage, the next thing to look over are aria attributes. For instance, we use aria-live="polite" for our ProgressBar slider. By default aria-live is set off, which means updates to controls should not be read to the user unless they un-focus and re-focus an element. The value of polite which we use allows us to convey the position of the slider to screen reader without them having to change focus on the control. This is useful because the ProgressBar is always updating while a video is playing. A value of polite will also wait to convey said updates until the screen reader is done reading other information to the user.

For a more complete list of ARIA attributes see the specification.

Finally you need to add an accessible “name” to an element so that it can be referred to. A good example of this is can be seen in the MuteToggle control. Since it is not a simple “button” we include innerHTML/innerText of “Mute” or “Unmute” in a way that is hidden from most users but announced to screen readers. In Video.js we refer to the accessible name and the action that a control performs as “control text”. Control text also updates the title attribute of an element in most cases, which is important for visual accessibility. When the action the a control performs changes so does the control text. This will allow the screen reader to refer to the MuteToggle as “Mute Toggle” rather than “button”. It will also convey the current action of the MuteToggle. In this case that will be either “Mute” or “Unmute” depending on what the button would do when pressed (ie the state of the button).

Here are some examples of accessibility straight from Video.js:

  • The MuteToggle <button>:
    • Has aria-live set to polite, rather than the default value of off. aria-live with any value other than off indicates that innerText/innerHTML updates can be sent to the screen reader without the user needing to move focus off of the control. The value of polite means that the screen reader should wait until it is done speaking to convey these updates to the user.
    • Has control text of “Mute” or “Unmute” which indicates the current status of the button to the use
  • The VolumeBar slider <div>:
    • Has a role attribute with a value of slider. Like this: role="slider"
    • Has a tabIndex attribute as it is not a native control element
    • Has EventHandlers that listen for:
      • The up and right arrow keys to increase the volume and the slider percentage
      • The down and left arrow keys to decrease the volume and the slider percentage
    • Has aria-label of “volume level” which is an accessible label that the screen reader will use to refer to it
    • Has aria-valuenow and aria-valuetext properties that update to indicate the current volume level (so the screen reader can read it)
    • Has aria-live set to polite, rather than the default value of off. aria-live with any value other than off indicates that innerText/innerHTML updates can be sent to the screen reader without the user needing to move focus off of the control. The value of polite means that the screen reader should wait until it is done speaking to convey these updates to the user.

The problem and the solution

Now let’s talk about how screen reader accessibility broke in Video.js 5. First VolumeMenuButton replaced MuteToggle and VolumeControl on the ControlBar. VolumeMenuButton was set to mimic MuteToggle when clicked. It would also show the VolumeControl on mouseover or focus. This was a problem because a VolumeControl was a now a child of a button, and buttons should not contain other controls. To the screen reader and to the DOM there are two MuteToggle button controls. When visually there is a VolumeControl and a MuteToggle. Below you can see a gif of this behavior in action :

macOS `VoiceOver` Before The Fix

The solution to this problem was to use a regular div to house the MuteToggle and VolumeControl. This regular div would have no role or control text so that it would be invisible to a screen reader. From that point forward we just needed to mimic the old UI. For those who are wondering, this new Component is called the VolumePanel. See the new behavior in a gif below:

macOS `VoiceOver` After The Fix

Outlines

Another big accessibility fix for controls comes from the removal of one small css rule:

1
outline: none;

Why did we do it? With feedback from the community and external resources, we learned that outlines should always be on. Without outlines there is no visual indication of keyboard focus on control elements and without that, keyboard users who are not visually impaired have a hard time using the controls.

Wrap up

Hopefully this post has given you some insight into making a web application accessible. If you find any issues or have any suggestions for our accessibility or in general feel free to contribute to Video.js.

If you want to keep up to date on the current state of accessibility work see the a11y label on PRs and issues.

Resources

Here are some popular screen readers that are actually used in the wild:

Resources for learning more about web accessibility:

Feature Spotlight: Advanced Plugins

Note: Advanced plugins are being introduced in Video.js 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 and videojs-spellbook 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:

1
2
3
4
5
6
7
8
9
10
const Plugin = videojs.getPlugin('Plugin');
class HelloWorld extends Plugin {
constructor(player) {
super(player);
this.addClass('hello-world')
}
}
videojs.registerPlugin('helloWorld', HelloWorld);

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:

1
2
3
4
5
const player = videojs('my-player');
const 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:

1
2
3
4
5
6
7
8
9
10
11
const player = videojs('my-player');
const instance = player.helloWorld();
instance.on('some-custom-event', (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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const player = videojs('my-player');
const firstInstance = player.helloWorld();
// Logs: 'true'
videojs.log(firstInstance === player.helloWorld());
firstInstance.on('dispose', () => videojs.log('disposing a helloWorld instance'));
// Logs: 'disposing a helloWorld instance'
firstInstance.dispose();
const 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:

1
2
3
4
5
6
7
8
9
10
const player = videojs('my-player');
player.on('pluginsetup', (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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// A static property of the constructor can be used to pre-populate state
// for all instances.
HelloWorld.defaultState = {color: 'red'};
const player = videojs('my-player');
const instance = player.helloWorld();
instance.on('statechanged', (e) => {
const {color} = e.changes;
if (color) {
videojs.log(`The helloWorld color changed from "${color.from}" to "${color.to}"!`);
}
});
// 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:

1
2
3
4
5
6
7
const 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:

1
2
3
4
5
6
7
8
9
const 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.

Video.js 6.0.0-RC.0: The first Release Candidate

The first Release Candidate for 6.0 has been released

Last week, we began wrapping up months of effort to make Video.js even better with the first Release Candidate (RC) of Video.js 6.0. In order to make it better, however, we had to make a few breaking changes and we also made a lot of improvements under the hood.

How to try it out

The RC is now published on npm under the beta tag with verion 6.0.0-RC.0.

1
npm install video.js@beta

Please try it out and let us know how it is on GitHub.

What to look forward to

  • We’re finally removing Flash from core as outlined in a previous post.
  • Plugins are being updated to a React-inspired component architecture. The old style is staying around.
  • We’re recommitting to accessiblity by fixing the accessibilty of our volume control and bringing back outlines!
  • Middleware. A brand new feature to interface between Video.js’s techs and the player.

Feature Spotlights

Over the coming weeks, we’ll post feature spotlights talking about the big things that are happening.
We might also revisit some old features.

Introducing Thumbcoil

A transmuxer takes media contained in some file format, extracts the raw compressed video and audio from inside (a process called demuxing) and repackages the compressed data into another format (termed remuxing) without performing any re-compression.

In the Beginning

While building Mux.js - the transmuxer at the heart of videojs-contrib-hls - we faced a problem: How do we determine if the output from Mux.js is correct?

Early on we managed to figure out how to coax FFmpeg into creating MP4s from MPEG2-TS segments that would play back in a browser with Media Source Extensions (MSE) which at the time meant only Chrome. However, we needed a simple way to compare the output of our transmuxer with what was produced by FFmpeg. The comparison had to be aware of the MP4 format since the two outputs are extremely unlikely to be byte-identical.

MP4 files are composed of boxes - hierarchical logical units that, conveniently, all start with a 32-bit length and a 32-bit box-type. Boxes will often contain other sub-boxes.

The answer to that problem was to build an “mp4-inspector” - a tool that would parse MP4 and display a sort of JSON-like dump of any relevant boxes and their contents. By generating a dump of the output from Mux.js and comparing it to a known-good fragment generated with FFmpeg, we could see where our transmuxer’s output differed.

The “mp4-inspector” was built as a web page so that we can have a graphical color-coded diff of the two segments. Over time the page gained a video element and we started appending the results of transmuxing segments directly into the video element’s MediaSource to aid in instant feedback and validation of changes to Mux.js.

A Brave New World

A media container such as MP4 encapsulates the video and audio stream. It has metadata describing the streams, timing information for each frame, and the stream data itself.

As development continued, we would sometimes encounter streams that would fail in new and interesting ways. Some of these failures were, admittedly, due to bugs in Mux.js. As Mux.js itself became more robust, failures were increasingly caused by problems with the streams or issues with a particular implementation of the MSE specification.

It eventually dawned on us that we really needed to learn more about what was happening inside of those videos. We needed to see not just what was happening at the media container level but we had to go deeper - we needed to peek into the video data itself. For that purpose we created Thumbcoil.

Inside of a container, video and audio are contained in data called bitstreams. Bitstreams are the data produced by encoders to represent the audio signals or video frames. Some common bitstreams are AAC for audio and H.264 for video.

Thumbcoil

Thumbcoil is a suite of tools designed to give you a peek into the internals of H.264 video bitstreams contained inside either an MP4 or MPEG2-TS container file. Using the tools in Thumbcoil you can get a detailed view of the internal structure of the two supported media container formats.

In addition, the tools have the ability to show you the information contained within the most important NAL-units that make up the H.264 bitstream. Ever wonder what kind of secret information the video encoder has squirreled away for decoders to use? Now, with Thumbcoil, you can finally see for yourself!

Motivation

An H.264 encoded bitstream is composed of what are called NAL, or network abstraction layer, units. NALs are a simple packet format designed to use bits as efficiently as possible.

Believe it or not, there are very few good tools to generate a somewhat graphical display of the structure of media containers and the data that they contain. Debugging problems with video playback is usually a tedious task involving various esoteric FFmpeg and FFprobe incantations. Unfortunately at it’s best, FFprobe is only able to print out a small portion of the data we were interested in.

The exact data inside of the various parameter sets for instance is not available via the command-line. Inside of FFprobe, that data is parsed and stored but there is no easy way to dump that information in a human readable form.

In H.264, there are two special types of NAL-units - the SPS or seq_parameter_set and the PPS or pic_parameter_set. These two NAL units contain a lot of information. The decoders require this information to reconstruct the video.

Thumbcoil not only provides parameter set information in excruciating detail but also keeps the information with its surrounding context - the boxes it was contained by or the frame it was specified along with. This context is often very important to understanding issues or peculiarities in streams.

Built Upon Fancy Stuff

One of the more interesting things about how Thumbcoil parses parameter sets is that is builds what is internally called a “codec” for each NAL unit type. These codecs are specified using what is essentially a fancy parser combinator-type setup.

Much of the data in the two parameter sets are stored using a method called exponential-golomb encoding. This method uses a variable number of bits to store numbers and is particularly suited to values that tends to be small.

Each function used to build the codec returns an object with two functions: decode and encode. This means that we can specify the format of, say, a seq_parameter_set NAL unit just once and then we can both parse from and write to the bitstream for that particular NAL unit.

The “grammar” used to specify NAL unit codecs is very similar to the grammar used by the H.264 specification (ISO/IEC 14496-10). The data-types that the codecs in Thumbcoil understand are, with some extensions, merely the same types defined in the specification such as signed- and unsigned- exponential golomb encoded integers.

In addition to the parameter sets, Thumbcoil provides insight into the structure of the slice layers themselves by parsing the slice_header data though we stop short of parsing any of the actual slice_data because things quickly become more difficult and less useful as you descend into that madness.

But what is the deal with the name?

Thumbcoil“ doesn’t mean anything, really. It’s an inside joke that is funny to exactly 3 people in the world - myself included. The odd name does have one benefit in that it makes for a short and easy to remember domain-name: thumb.co.il.

As with all Video.js projects, Thumbcoil is open-source software and we welcome suggestions, issues, and contributions at https://github.com/videojs/thumbcoil.

Video.js 5.12.0 and 5.11.5 releases

Today, there are two releases of video.js.
The first, is a patch release for the 5.11 branch. With this release, we’re also updating 5.11 to stable and publishing it to the CDN.

The 5.12 release is a pre-release. It’s a pretty big milestone too. Video.js finally outputs non-pre-built files so bundlers like webpack can decide what to do with them.

Notable Changes

This release is mostly a maintenance and build-change release.

  • Lint the entire codebase with our linter and run the linter as a pre-push git hook.
  • Fix CSS issues in IE8.
  • Updated dependencies using Greenkeeper.io
  • Make video.js provide ES5 source files for bundlers like webpack and browserify to use. This also makes video.js requirable in node.
    • Video.js still provides a pre-build dist files for the CSS and JavaScript for those that aren’t using bundlers.
  • We’ve added a grunt task called check-translations that will output a list of translations missing from language files based on the en.json file which serves as a template. If you know another lanugage, this would be an easy and quick way to get started contriburing to video.js!

Known Issues

No new known issues but we have started looking into the known issues from the last release.

Looking forward

Going forward, we’re looking into switching to standard-version. This will allow us to accept PRs more easily and have the changelog generated for us. Also, this will make it easier for other core contributors to accept PRs. We’ve previously been using the contrib tool for accepting PRs and have the CHANGELOG generated automatically at that time but getting new people started with it for merging PRs was not the easiest experience.

Raw Changelog

  • @misteroneill, @BrandonOCasey, and @pagarwal123 updates all the code to pass the linter (view)
  • @misteroneill added ghooks to run linter on git push (view)
  • @BrandonOCasey removed unused base-styles.js file (view)
  • @erikyuzwa, @gkatsev updated CSS build to inlcude the IE8-specific CSS from a separate file instead of it being inside of sass (view) (view2)
  • @gkatsev added null checks around navigator.userAgent (view)
  • greenkeeper updated karma dependencies (view)
  • @BrandonOCasey updated language docs to link to IANA language registry (view)
  • @gkatsev removed unused dependencies (view)
  • @misteroneill enabled and updated videojs-standard and fixed an issue with linting (view)
  • @misteroneill updated tests to qunit 2.0 (view)
  • @gkatsev added slack badge to README (view)
  • @gkatsev reverted back to qunitjs 1.x to unbreak IE8. Added es5-shim to tests (view)
  • @gkatsev updated build system to open es5 folder for bundles and dist folder other users (view)
  • greenkeeper updated uglify (view)
  • greenkeeper updated grunt-concurrent (view)
  • greenkeeper updated karma-chrome-launcher (view)
  • @gkatsev added tests for webpack and browserify bundling and node.js requiring (view)
  • @rlchung fixed tests that weren’t disposing players when they finished (view)

Git diffstats

These are deltas between 5.11.5 and 5.12.0 with the dist folder ignored.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
.babelrc | 4 -
.gitignore | 1 -
.jshintrc | 49 ++
.npmignore | 1 -
.travis.yml | 2 +-
CHANGELOG.md | 26 -
Gruntfile.js | 2 +-
README.md | 2 -
build/grunt.js | 131 ++-
build/tasks/cdn-links.js | 2 +-
build/tasks/languages.js | 35 -
build/tasks/saucelabs.js | 24 +
component.json | 2 +-
docs/guides/languages.md | 164 +++-
docs/translations-needed.md | 363 --------
lang/de.json | 8 +-
lang/en.json | 2 -
lang/fr.json | 19 +-
package.json | 80 +-
src/css/_utilities.scss | 2 +-
src/css/components/_control-bar.scss | 16 +
src/css/components/_fullscreen.scss | 2 +
src/css/components/_play-pause.scss | 2 +
src/css/components/_progress.scss | 2 +-
src/css/components/menu/_menu.scss | 3 +-
src/css/ie8.css | 30 -
src/js/base-styles.js | 18 +
src/js/button.js | 28 +-
src/js/clickable-component.js | 29 +-
src/js/component.js | 79 +-
.../audio-track-controls/audio-track-button.js | 11 +-
.../audio-track-controls/audio-track-menu-item.js | 18 +-
src/js/control-bar/control-bar.js | 39 +-
src/js/control-bar/fullscreen-toggle.js | 2 +-
src/js/control-bar/live-display.js | 2 +-
src/js/control-bar/mute-toggle.js | 20 +-
src/js/control-bar/play-toggle.js | 8 +-
.../playback-rate-menu-button.js | 27 +-
.../playback-rate-menu/playback-rate-menu-item.js | 10 +-
.../progress-control/load-progress-bar.js | 22 +-
.../progress-control/mouse-time-display.js | 22 +-
.../progress-control/play-progress-bar.js | 6 +-
.../progress-control/progress-control.js | 5 +-
src/js/control-bar/progress-control/seek-bar.js | 41 +-
.../progress-control/tooltip-progress-bar.js | 10 +-
.../spacer-controls/custom-control-spacer.js | 4 +-
.../caption-settings-menu-item.js | 18 +-
.../text-track-controls/captions-button.js | 13 +-
.../text-track-controls/chapters-button.js | 57 +-
.../chapters-track-menu-item.js | 18 +-
.../text-track-controls/descriptions-button.js | 15 +-
.../off-text-track-menu-item.js | 25 +-
.../text-track-controls/subtitles-button.js | 4 +-
.../text-track-controls/text-track-button.js | 17 +-
.../text-track-controls/text-track-menu-item.js | 36 +-
.../time-controls/current-time-display.js | 13 +-
.../control-bar/time-controls/duration-display.js | 15 +-
.../time-controls/remaining-time-display.js | 7 +-
src/js/control-bar/track-button.js | 7 +-
src/js/control-bar/volume-control/volume-bar.js | 12 +-
.../control-bar/volume-control/volume-control.js | 10 +-
src/js/control-bar/volume-menu-button.js | 19 +-
src/js/error-display.js | 5 +-
src/js/event-target.js | 17 +-
src/js/extend.js | 7 +-
src/js/fullscreen-api.js | 6 +-
src/js/media-error.js | 57 +-
src/js/menu/menu-button.js | 31 +-
src/js/menu/menu-item.js | 10 +-
src/js/menu/menu.js | 63 +-
src/js/modal-dialog.js | 20 +-
src/js/player.js | 383 ++++-----
src/js/plugins.js | 2 +-
src/js/popup/popup-button.js | 10 +-
src/js/popup/popup.js | 11 +-
src/js/poster-image.js | 7 +-
src/js/setup.js | 40 +-
src/js/slider/slider.js | 34 +-
src/js/tech/flash-rtmp.js | 25 +-
src/js/tech/flash.js | 157 ++--
src/js/tech/html5.js | 347 +++-----
src/js/tech/loader.js | 20 +-
src/js/tech/tech.js | 210 +++--
src/js/tracks/audio-track-list.js | 5 +-
src/js/tracks/audio-track.js | 10 +-
src/js/tracks/html-track-element-list.js | 4 +-
src/js/tracks/html-track-element.js | 8 +-
src/js/tracks/text-track-cue-list.js | 12 +-
src/js/tracks/text-track-display.js | 144 ++--
src/js/tracks/text-track-list-converter.js | 28 +-
src/js/tracks/text-track-list.js | 6 +-
src/js/tracks/text-track-settings.js | 347 ++++----
src/js/tracks/text-track.js | 51 +-
src/js/tracks/track-enums.js | 29 +-
src/js/tracks/track-list.js | 12 +-
src/js/tracks/track.js | 13 +-
src/js/tracks/video-track-list.js | 4 +-
src/js/tracks/video-track.js | 10 +-
src/js/utils/browser.js | 32 +-
src/js/utils/buffer.js | 9 +-
src/js/utils/dom.js | 95 +--
src/js/utils/events.js | 429 +++++-----
src/js/utils/fn.js | 6 +-
src/js/utils/format-time.js | 2 +-
src/js/utils/guid.js | 2 +-
src/js/utils/log.js | 14 +-
src/js/utils/merge-options.js | 14 +-
src/js/utils/stylesheet.js | 7 +-
src/js/utils/time-ranges.js | 68 +-
src/js/utils/to-title-case.js | 2 +-
src/js/utils/url.js | 29 +-
src/js/video.js | 53 +-
test/api/api.js | 421 +++++-----
test/globals-shim.js | 2 -
test/index.html | 2 -
test/karma.conf.js | 17 +-
test/require/browserify.js | 8 -
test/require/node.js | 9 -
test/require/webpack.js | 8 -
test/unit/button.test.js | 32 +-
test/unit/clickable-component.test.js | 31 +-
test/unit/close-button.test.js | 22 +-
test/unit/component.test.js | 639 +++++++--------
test/unit/controls.test.js | 101 ++-
test/unit/events.test.js | 240 +++---
test/unit/extend.test.js | 20 +-
test/unit/media-error.test.js | 69 --
test/unit/menu.test.js | 54 +-
test/unit/modal-dialog.test.js | 138 ++--
test/unit/player.test.js | 912 ++++++++++-----------
test/unit/plugins.test.js | 196 +++--
test/unit/poster.test.js | 68 +-
test/unit/setup.test.js | 20 +-
test/unit/tech/flash-rtmp.test.js | 68 +-
test/unit/tech/flash.test.js | 203 ++---
test/unit/tech/html5.test.js | 491 +++++------
test/unit/tech/tech-faker.js | 86 +-
test/unit/tech/tech.test.js | 438 +++++-----
test/unit/test-helpers.js | 71 +-
test/unit/tracks/audio-track-list.test.js | 108 ++-
test/unit/tracks/audio-track.test.js | 88 +-
test/unit/tracks/audio-tracks.test.js | 70 +-
test/unit/tracks/html-track-element-list.test.js | 50 +-
test/unit/tracks/html-track-element.test.js | 68 +-
test/unit/tracks/text-track-controls.test.js | 202 ++---
test/unit/tracks/text-track-cue-list.test.js | 79 +-
test/unit/tracks/text-track-list-converter.test.js | 67 +-
test/unit/tracks/text-track-list.test.js | 23 +-
test/unit/tracks/text-track-settings.test.js | 178 ++--
test/unit/tracks/text-track.test.js | 203 +++--
test/unit/tracks/text-tracks.test.js | 401 +++++----
test/unit/tracks/track-baseline.js | 37 +-
test/unit/tracks/track-list.test.js | 104 ++-
test/unit/tracks/track.test.js | 16 +-
test/unit/tracks/video-track-list.test.js | 111 ++-
test/unit/tracks/video-track.test.js | 87 +-
test/unit/tracks/video-tracks.test.js | 71 +-
test/unit/utils/dom.test.js | 426 ++++------
test/unit/utils/fn.test.js | 13 +-
test/unit/utils/format-time.test.js | 48 +-
test/unit/utils/log.test.js | 26 +-
test/unit/utils/merge-options.test.js | 16 +-
test/unit/utils/time-ranges.test.js | 49 +-
test/unit/utils/to-title-case.test.js | 10 +-
test/unit/utils/url.test.js | 103 ++-
test/unit/video.test.js | 186 ++---
166 files changed, 5233 insertions(+), 6541 deletions(-)

The End of "HTML-First"

When video.js was first released all the way back in 2010, Flash was the only way to play video in Firefox, IE, and the Android browser. And when you could use HTML video, it was really complicated to get right and broken in all sorts of scenarios (live streaming, anyone?). Those problems were a big part of why we wrote video.js in the first place. The HTML standard provided a simple, powerful, and universal API for video: why use anything else?

The superiority of HTML video is pretty well established these days and browser support for video reflects that. If you have your videos in MP4 format, video.js will play them natively in HTML on every modern desktop and mobile browser out there. In video.js 5.0, we started the process of deprecating the last holdout in our supported browsers: the dreaded Internet Explorer 8. If you’re saying to yourself “Wha?! You still support IE8??”, I share your shock and horror. It’s no fun but we’ve been holding out for the couple folks who want to use video on their sites and still have to support ancient clients. There’s a couple more hoops to jump through however, and you should start emotionally preparing yourself for the end of IE8 support if you’re one of the people using it. With IE8 heading out to pasture, including a Flash fallback by default in video.js is starting to look a little silly.

So here’s what we’re thinking: move Flash support out of the core of video.js and into our legacy-compatiblity shim around the time Chrome begins deprecating Flash this December. We’ll keep it around for awhile to support some more complex usage (say, live streaming in IE10) but the heart of video.js will go from “HTML-first” to “HTML-only.” That should mean more focus from the core committers on some amazing new stuff like improving our plugin framework, enhancing our support for HLS and DASH, and making advanced features like ads easier to integrate and better for viewers.

How does that sound? Let us know in this issue, ping @videojs on Twitter, or come say “hi” in our Slack channel.

Video.js 5.11.0 Prelease

Today sees the prerelease of version 5.11.0. I wanted to take a moment to talk about some of the changes, additions, and known issues.

This is a pre-release only. It’s available on npm under the next tag and also available on the CDN under the fully qualified version number: //vjs.zencdn.net/5.11.0/video.js. It’ll stay in pre-release state for around a week or more to make sure that there aren’t any glaring bugs, so, please give it a shot and open issues if you find anything.

Notable Changes

  • In version 5.0, we wanted to deprecate videojs.players property in favor of of the videojs.getPlayers() getter. However, it’s proved to be very useful and it is now being un-deprecated so it will no longer print deprecations in the console.
  • If the player is created without a source set and a user hits play, video.js will now wait for the a source to be provided before starting playback. This eliminates an error that happens when we try to play an empty source.
  • Our custom captions settings dialog was updated to be more accessible by using more aria attributes and better option names in drop downs.

Known Issues

  • In video.js 5.10, to be able to better respond to the source of the video element changing directly without going through video.js, we disposed SourceHandlers on subsequent loadstarts and cleared out the current source. However, this causes an issue with videojs-contrib-dash. A contributor investigated and found out that the two PRs mentioned before cause the issue. If a video is created with an MPEG-DASH source element, we end up seeing a loadstart event because of the source element and then when Dash.js kicks in and starts playback using MSE, we get another loadstart event. Since we see the second loadstat event we dispose of the SourceHandler, that is, videojs-contrib-dash and Dash.js, and the video doesn’t play. A work around, while we figure out a correct solution, would be not to use source elements with DASH sources and only use the video.js API in the meantime.

Raw Changelog

  • @BrandonOCasey Document audio/video track usage (view)
  • @hartman Correct documentation to refer to nativeTextTracks option (view)
  • @nickygerritsen Also pass tech options to canHandleSource (view)
  • @misteroneill Un-deprecate the videojs.players property (view)
  • @nickygerritsen Add title to all clickable components (view)
  • @nickygerritsen Update Dutch language file (view)
  • @hartman Add descriptions and audio button to adaptive classes (view)
  • @MattiasBuelens Retain details from tech error (view)
  • @nickygerritsen Fix test for tooltips in IE8 (view)
  • @mboles added loadstart event to jsdoc (view)
  • @hartman added default print styling (view)
  • @ldayananda updated videojs to not do anything if no src is set (view)
  • @nickygerritsen removed unused tracks when changing sources. Fixes ##3000 (view)
  • @vit-koumar updated Flash tech to return Infinity from duration instead of -1 (view)
  • @alex-phillips added ontextdata to Flash tech (view)
  • @MattiasBuelens updated components to use durationchange only (view)
  • @misteroneill improved Logging for IE < 11 (view)
  • @vdeshpande updated control text of modal dialog (view)
  • @ldayananda fixed mouse handling on menus by using mouseleave over mouseout (view)
  • @mister-ben updated language to inherit correctly and respect the attribute on the player (view)
  • @sashyro fixed nativeControlsForTouch option (view)
  • @tbasse fixed techCall null check against tech (view)
  • @rbran100 checked src and currentSrc in handleTechReady to work around mixed content issues in chrome (view)
  • @OwenEdwards fixed caption settings dialog labels for accessibility (view)
  • @OwenEdwards removed spurious head tags in the simple-embed example (view)
  • @ntadej added a null check to errorDisplay usage (view)
  • @misteroneill fixed logging issues on IE by separating fn.apply and stringify checks (view)
  • @misteroneill fixed npm test from running coveralls locally (view)
  • @gkatsev added es6-shim to tests. Fixes Flash duration test (view)
  • @misteroneill corrects test assertions for older IEs in the log module (view)
  • @gkatsev fixed setting lang by looping through loop element variable and not constant tag (view)

Git diffstats

These are deltas between 5.11.0 and 5.10.7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
CHANGELOG.md | 33 +
build/grunt.js | 3 +-
component.json | 2 +-
docs/examples/simple-embed/index.html | 3 -
docs/guides/audio-tracks.md | 69 +
docs/guides/languages.md | 12 +-
docs/guides/text-tracks.md | 184 +
docs/guides/tracks.md | 186 +-
docs/guides/video-tracks.md | 70 +
docs/index.md | 2 +-
lang/en.json | 1 +
lang/nl.json | 19 +-
package.json | 7 +-
src/css/_print.scss | 5 +
src/css/components/_adaptive.scss | 9 +-
src/css/components/_captions-settings.scss | 26 +-
src/css/video-js.scss | 2 +
src/js/clickable-component.js | 12 +-
.../control-bar/time-controls/duration-display.js | 8 +-
.../time-controls/remaining-time-display.js | 1 +
src/js/menu/menu-button.js | 4 +-
src/js/modal-dialog.js | 2 +-
src/js/player.js | 68 +-
src/js/tech/flash-rtmp.js | 3 +-
src/js/tech/flash.js | 21 +-
src/js/tech/html5.js | 73 +-
src/js/tech/tech.js | 16 +-
src/js/tracks/text-track-settings.js | 176 +-
src/js/utils/browser.js | 3 +
src/js/utils/create-deprecation-proxy.js | 50 -
src/js/utils/log.js | 124 +-
src/js/video.js | 16 +-
test/globals-shim.js | 1 +
test/unit/button.test.js | 5 +-
test/unit/player.test.js | 22 +-
test/unit/plugins.test.js | 20 +-
test/unit/tech/flash.test.js | 51 +-
test/unit/tech/html5.test.js | 16 +-
test/unit/tech/tech.test.js | 17 +-
test/unit/tracks/text-track-settings.test.js | 51 +-
test/unit/tracks/text-track.test.js | 13 +-
test/unit/utils/create-deprecation-proxy.test.js | 45 -
test/unit/utils/log.test.js | 104 +-
103 files changed, 971 insertions(+), 55554 deletions(-)

Video.js 5's fluid mode and playlist picker

How it works

In video.js 5.0, we added support for truly fluid layouts with video.js.
You can see an example of it on the video.js website.

It is done by using intrinsic ratios. Video.js does the heavy lifting for you.

How to use it in video.js

In video.js, to make a player fluid, you can either set the fluid option

1
2
3
let player = videojs('preview-player', {
fluid: true
});

Or you can add one of the fluid classes to the player: .vjs-fluid, .vjs-4-3, .vjs-16-9:

1
<video id="preview-player" class="video-js vjs-fluid" controls data-setup={}>

.vjs-4-3 maintains a 4:3 aspect ratio for the video and .vjs-16-9 maintains a 16:9 one. .vjs-fluid is a bit more special. It waits for the video metadata to load and then uses the video width and video height to calculate the correct aspect ratio to use for the video.

Playlist picker

This works great if you only have the player by itself. What if you are trying to a attach a playlist to the video element and keep it at the same height Like we did on the advanced example page on the video.js website?

We could calculate how much the padding top should be depending on the width of the playlist picker or the container element but then each time a video changes we would need to recalculate the height of the playlist picker. Instead, we can rely on video.js to do all the work.

Attaching the playlist picker

For this example, We’re using the videojs-playlist-ui and videojs-playlist plugins for the playlist functionality.
We then wrap the player in a container and put the playlist-ui element in there as well.

1
2
3
4
5
6
7
<section class="main-preview-player">
<video id="preview-player" class="video-js vjs-fluid" controls preload="auto" crossorigin="anonymous">
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
</video>
<ol class="vjs-playlist"></ol>
</section>

Now we can relatively quickly make them align together with some CSS:

1
2
3
4
5
6
7
8
.video-js {
width: 70%;
float: left;
}
.vjs-playlist {
width: 30%;
float: right;
}

How to make it fluid

As you can see in the preceeding screenshot, it isn’t aligned correctly with the player like in the screenshot above.
Video.js calculates the aspect ratio and then adds a stylesheet to the page:

1
2
3
.preview-player-dimensions.vjs-fluid {
padding-top: 41.66666666666667%;
}

That percentage results in a 2.4 aspect ratio which matches that of the oceans clip.

So, to make sure that the playlist picker is the same height, we can just add the player dimensions class to it:

1
<ol class="vjs-playlist preview-player-dimensions vjs-fluid"></ol>

How to make it line up

One of the easiest ways of making these two things line up correctly is to use flexbox. It’ll make the player and playlist picker grow to fill up as much space as needed. Also, the playlist picker collapse underneath the player if the width of the page is too small.
Flexbox is available on a lot of platforms. However, some browsers were implementing flexbox as the specification for it was evolving. It’s probably best to run this css through something like autoprefixer. Using autoprefixer won’t make it work on browsers that don’t support flexbox but will significantly increase platform support.

First, we set display to flex and add some properties for wrapping and sizing:

1
2
3
4
5
.main-preview-player {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}

flex-wrap allows playlist picker to wrap to the next line if the width of the container is too small.
Then we want to position the player and playlist picker relative to the container and set some default and minimum sizes:

1
2
3
4
5
6
7
.video-js,
.vjs-playlist {
position: relative;
min-width: 300px;
min-height: 150px;
height: 0;
}

And finally, we want to apply the flex setting to the player and playlist picker:

1
2
3
4
5
6
7
.video-js {
flex: 3 1 70%;
}
.vjs-playlist {
flex: 1 1 30%;
}

This tells the player to grow and take up 3x the space as the playlist picker and defaults to 70% of the width. The playlist picker itself defaults to 30% of the width and is allowed to grow and shrink as necessary.
Now if we load this in a browser we see a problem. The playlist isn’t the right height:

This is because the playlist-ui plugin sets its own padding on the element that ends up overriding the preview-player-dimensions padding-top. We can fix this by forcing the padding-top we want. However, while this solves our height problem, where are our items? Oh, you need to scroll to get them. That seems less than ideal.

This happens because our padding-top is inside the playlist picker; it pushed all the elements down requiring scrolling to get to them.

The final solution

Ultimately, what we need to do is wrap the playlist element in a container that flexes so that the padding-top doesn’t push the playlist items down.

1
2
3
<div class="playlist-container preview-player-dimensions vjs-fluid">
<ol class="vjs-playlist"></ol>
</div>

We also change the vjs-playlist references to playlist-container and absolutely position the playlist picker inside its container:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.playlist-container {
position: relative;
min-width: 300px;
min-height: 150px;
height: 0;
}
.playlist-container {
flex: 1 1 30%;
}
.vjs-playlist {
margin: 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}

Now we have what we were looking for:

All together now


The HTML:

1
2
3
4
5
6
7
8
9
<section class="main-preview-player">
<video id="preview-player" class="video-js vjs-fluid" controls preload="auto" crossorigin="anonymous">
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
</video>
<div class="playlist-container preview-player-dimensions vjs-fluid">
<ol class="vjs-playlist"></ol>
</div>
</section>

And the CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
.main-preview-player {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.video-js,
.playlist-container {
position: relative;
min-width: 300px;
min-height: 150px;
height: 0;
}
.video-js {
flex: 3 1 70%;
}
.playlist-container {
flex: 1 1 30%;
}
.vjs-playlist {
margin: 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}

Video.js 5: The Only Thing That’s Changed Is Everything...except for like 3 things that didn't (including the name).

First and foremost, THANK YOU to the 25 contributors who completed and merged 146 pull requests and updated just about every line of code in the project. And thank you to the hundreds of issue commenters and plugin authors who helped shape this latest version. For a widget, we’ve got a pretty awesome community.

For 5.0 we have some interesting new features, and we made A LOT of new technology choices. This will include an exhaustive dive into those choices, because… why not?

  • Redesigned and rebuilt the UI
    • Using a flex-box-based controls layout for easier add-ons
    • Improved accessibility of the controls
    • Switched from LESS to SASS
    • Switched to Material Icons
  • Rebuilt the library in ES6/Babel/Browserify including Modules and Classes
  • Added support for responsive layouts including auto-sizing to the video content
  • Added support for HLS in desktop browsers without Flash
  • Improved audio-only support
  • Added 6 more language translations bringing the total to 25
  • Switched from Closure Compiler to UglifyJS (we STOPPED mangling object properties)
  • Switched to JSDoc for documentation
  • Switched to BrowserStack for automated browser testing
  • Switched to Fastly for our CDN
  • New definition around plugins
  • New player skin designer on Codepen
  • New definition around playback technologies (“techs”)
  • New project governance model
  • New Website and Logo!

If you’re a Video.js user or lover of semver, you’re probably looking at the changelog and dying inside (or out). You’re probably already thinking about how little you want to do the mega-upgrade dance every time the major version is bumped. To be clear, there will be at least some upgrade cost between major versions because that’s just how things go. However, from 5.0 on, we plan on being a lot more liberal with major versions to avoid stop-the-world mega releases like this.

The fact is, this release cleaned up a lot of technical debt and cruft over years of maintaining a popular open source library. Working with the codebase is fun again (not like it wasn’t before, Judgy McJudgerson) and we think we’ve bought ourselves at least 6 months before we have to upgrade to ES9000. In all seriousness, we plan on being quicker with releases, both breaking and non, in order to make incremental upgrades less painful and, well, quicker. Besides, Chrome and Firefox are going to be in the thousands soon for their releases, and we want some of that fun.

Redesigned and rebuilt UI

Video.js has had an all-CSS skin (including for Flash) since it was created in 2010 (the first version was literally all CSS, no images, fonts, or svgs. And still works today!) Over those years we’ve seen some very creative customizations and learned a lot about what users are hoping to do with player design when they’re able to use native web techologies. For 5.0 we’ve both simplified the default layout and added more flexibility than ever before.

Flex Box Layout

One of our biggest challenges with the layout of the controls is keeping the control bar flexible and able to accomodate any new buttons that a plugin author might want to add. In 4.0 we used CSS floats to allow a new button to be appended to the control bar and flow into space. This however led to a very awkward tab order for anyone navigating the controls with the tab key. In 5.0 we were finally able to take advantage of flex box, which improves on the flexibility of the previous version while also maintaining the right tab order. In IE8 (because yeah, we still support that) we fall back to display:table, which works surprisingly well.

Improved accessibility

Accessibility has been a hot topic as of late, with new regulations helping push the industry forward and defining what accessbility means for a player. The tab-order mentioned previously was an eye sore in our accessiblity support, and we’re all happy to have that fixed in 5.0. Addtionally, after a long debate, we switched our Button elements to use the actual HTML button tag instead of divs. Using divs previously gave us a large degree of safety from external styles clobbering our own styles. This can be a big problem for html-based widgets that are dropped into other frameworks that add styles directly to native elements (ahem Foundation). In the end however the accessibility experts in our community made a strong enough case, pointing out that there’s still a number of devices that do not handle ARIA roles and javascript enhanced divs well enough to fully rely on them.

Switched from LESS to SASS

The original decision to use Less over Sass in version 4 was largely driven by the fact that it could be run in the browser. We knew we wanted to start using a preprocessor, but we still wanted to provide things like the skin designer which meant we needed to be able to do all pre-processing on the client. Less was totally appropriate for the job and allowed us to start modernizing the skin in appearance and tooling.

Since then, thanks to Emscripten, Sass has joined Less in the browser. This meant that we were free to use whichever of the two we liked, so all the contributors put on Less or Sass branded boxing gloves and fought until two gloves were still standing, which turned out to be Sass. Aside from the battle royale, there were a few reasons we decided to go with Sass for the new base skin in version 5.

  • Familiarity. Core contributors that started working on the new base skin were a little more experienced with Sass and simply preferred it.
  • Speed. On top of allowing us to use Sass without requiring Ruby, LibSass is fast.
  • Popularity. Your parents are right, not everything’s a popularity contest… but it is when you want to pick between two basically equivalent* tools. We wanted to pick something that more devs would be familiar with, and that seems to be Sass. On that note, it was also requested fairly often on the issue tracker.
  • Community. This goes hand in hand with popularity, but the Sass community is large and growing, with currently popular projects using it (and switching to it) and new tooling popping up every day along side industry standards such as Compass and Bourbon.

Big changes that we made during the switch that are unrelated to the tooling:

  • We broke apart the source files.
  • As mentioned above, we switched to a Flexbox-based layout. “What about IE8,” you say again? Tables. Srsly.
  • Improved support for responsive layouts.
  • We simplified the amount of customization we explicitly allow in the source to encourage people to build on top of the base skin, not edit it directly.
  • On the simplification note, we tried to simplify the base skin as much as possible. Our goal was to allow designers to build anything on top of the base without having to entirely start from scratch.
  • And more!

Switched to Material Icons

The switch to an icon font in version 4 was a huge win for Video.js. It allowed designers to do things as basic as style component icon colors with just CSS. It simplified the code base by allowing us not not worry about things like image sprites or other image optimizations. The only recurring issue we ran into was the process around adding new icons to the set, which ultimately involved just uploading the font back to IcoMoon and re-exporting.

In version 5, we’ve switched everything about our icon set. First we went with a new set of icons: Google’s Material Icons. This was a big step forward in terms of appearance, but we had the same issue as far as adding new icons. To fix that process we created new Font tooling for the project.

The tooling is really simple, but it allows anyone to write out a JSON configuration file pointing to any SVGs they have access to. The output of that tool is the font files themselves along with a new SCSS partial that gets imported into our Sass workflow in the core project at build.

We primarily use the Material icons, but occasionally we have to pull in a social media icon from another set, which this process greatly simplifies. See icons missing in the set that you’d like to use? Give the tool a try and let us know what you think!

Rebuilt the library in ES6/Babel/Browserify including Modules and Classes

5.0 comes with bit of enlightenment as we ditch our not-built-here syndrome and make a leap to post-modern development practices.

When we started work on the new version we originally planned to just use browserify and CommonJS modules, but when we took a closer look at the great work being done on Babel, it only made sense to jump again to ES6 modules and save ourselves another code transition down the road, while at the same time gaining a lot of great new JavaScript features.

Side-stepping any argument of the validity of JavaScript Classes, Video.js has always used classes for the internal UI framework. We used a custom class implementation that was of course incompatible with other custom implementations, so the move to ES6 opens the door for some potential interoperability with other frameworks as more people use ES6 classes specifically.

With the move to modules we’ve also opened the door to using more of the glorious ecosystem that is npm. We’ve started to toss out some of the non-core internal libraries that required us to be experts in just about everything, and replace them with other community-built modules. So far this includes libraries like lodash and raynos/xhr, and we’re actively looking for opportunies where we can share more code.

What does this mean for the end user?

This should make it easier for end-users of videojs, especially if they use browserify themselves. With this change, users can just
require('video.js') in their app.js or whenever they need it to instantiate players. In addition, plugins that were written
using browserify themselves can be added very easily using browserify and require as well. Of course, you can use Babel and
ES6 modules as well. For example, your app.js could look like the following, assuming browserify and babel for ES6 syntax:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// import videojs
import videojs from 'video.js';
// import several plugins.
// Each requires videojs itself and registers itself accordingly.
import 'videojs-playlist';
import 'videojs-thumbnail';
// this isn't ready yet, unfortunately
import 'videojs-contrib-hls';
// make a player
let player = videojs('my-video');
// initialize some plugins
player.playlist(myplaylist);
player.thumbnail(myThumbnailConfig);

Added support for responsive layouts including auto-sizing to the video content

Check out this video from the SF Vid Tech meetup for quick overview.

Proper support for responsive layouts has been a long time request. We’ve had tips and guides for how to make it work in the old version, but now it’s seamlessly built into the player. First we have two easy CSS classes you can just add to the embed code, vjs-16-9 and vjs-4-3.

<video class="video-js vjs-16-9 vjs-default-skin" ...></video>;

Or you can set the player to a fluid layout in the options and it will automatically update to match the aspect ratio of the content., whether you set a width, height, or neither.

1
var myPlayer = videojs(id, { fluid: true, preload: 'metadata' });

This was very tricky to implement, but well worth the effort. We have a jsbin example page where you can try out different options.

Added support for HTTP Live Streaming in desktop browsers WITHOUT Flash

We’ve been chasing down the last excuses for using Flash for video for awhile now and have reached some major milestones. videojs-contrib-hls 1.0 (it’s about time!) will use Media Source Extensions, an advanced video element API, to provide HLS playback in Chrome and Microsoft Edge. Besides the enhanced debuggability of doing everything in Javascript, most of the computation has been moved to a web worker and is hardware accelerated on platforms that support it. That means even 60fps or 4k streams should play back without a stutter and your battery will get less of a workout, too. As part of that work, we’ve split off the code we use to repackage HLS video into a separate project in the video.js org: mux.js. If you’re interested in how video works down at the bit level, check it out! We’re always looking for new contributors.

Switched from Closure Compiler to Uglify (We STOPPED mangling object properties)

In version 4.0 we introduced Google’s Closure Compiler into our build chain. It’s advanced optimizations mode saved us 25% in filesize over UglifyJS at the time. The downsides were that it required us to write the code in very specific ways and mangled internal object properties, which made contributing, debugging, and writing plugins much more difficult. The reality today is that with gzip and improved bandwidth, video.js users are pushing us less and less to squeeze the last bit of filesize out of the library. So for 5.0 we’ve switched back to UglifyJS. Plugin authors rejoice!

Improved audio-only support

Hey did you know? Video.js also supports the HTML5 audio tag. Try it out and let us know what you think.

<audio id="my_audio_1" class="video-js vjs-default-skin" controls data-setup="{}"><source src="MY_AUDIO_FILE.mp3" type="audio/mp3"></source></audio>

Added 6 more language translations

Ever since we implemented localization in Video.js there’s been a wave of contributions from all over the world. With 5.0 comes Danish, Bosnian, Serbian, Croatian, Finnish, and Turkish. This brings the number of lanugages supported to 25!

New definition around plugins

Plugins mostly have not changed from how they worked in Videojs 4.x. You still register and use them the same way.
The only major change is that if you are instantiating a plugin inside the config of a player, this plugin will get run
before the player is ready. This is so that if plugins are doing some UI work or adding themselves as children of the player
they can do so early on. This means that plugins that require the player to be ready would need to handle that themselves or
else not support instantiation via the player config. Ex:

1
2
3
4
5
videojs('my-video', {
plugins: {
playlists: {}
}
});

As part of the update to videojs 5 and our switch from Google’s Closure Compiler to Uglify,
we’ve been focusing on making the plugin experience better.
We’ve made sure to export some of the utility functions that we use inside our own codebase to make writing plugins easier.
This way plugins don’t need to include extra code themselves if a function, merge, for example, is available from videojs.

Also, we’re encouraging plugin authors to publish their plugins on npm. If the plugins are tagged with videojs-plugin,
they’ll show up on our spiffy new plugins listing page.

New player skin designer on Codepen

We’re taking a slightly different approach with the designer now. Instead of exposing all CSS properties from the default skin, we’ve set up a template starter that has fewer options and allows you to get at the more common customizations more easily.

It’s also using Codepen, which is a great service and much better than hosting the designer ourselves. If you use it to make a cool skin, be sure to let us know. Tweet at @videojs or comment on the designer.

http://codepen.io/heff/pen/EarCt/left/?editors=010

New project governance model

Jumping right on that band wagon, we’re implementing a governance model for the project that’s similar to Node.js/io.js and 0MQ’s C4.1. Along with some better structure around the project it comes with a lower bar for becoming an official maintainer of the project. We’re aiming to have a more diverse set of people and companies representing the leadership of Video.js.

Improved the definition of a playback technology or “tech”

The creator of our most popular plugin (the YouTube plugin) took on the task of improving the relationship between the player and techs. “Tech” is the term we use to describe the translation layer between the player API and the underlying video objects, which can be the HTML5 video element, plugins like Flash and Silverlight, or other players like Youtube and Vimeo. If you’ve built a tech or are intersted in building one, check out the changes in 5.0.

Switched to JSDoc from a home-grown docs parser

In the last version we had a custom built docs generator that used esprima to parse the AST and create a lot of the API docs just from the code, with JSDoc tags filling in the rest. That could still be a good project, but it’s become too much to maintain and too limited for plugin authors.

For 5.0 we’ve switched to jsdoc. Check out the new docs site. Anyone want to help us design it? :)

Switched to BrowserStack for automated browser testing

For 5.0 we have moved from Sauce Labs to BrowserStack as our browser provider for automated testing. BrowserStack is a cross-browser testing tool that provides desktop and mobile browsers and are currently rolling out support for real mobile devices. We moved to BrowserStack to reduce the amount of timeout errors, build time and false positives we were seeing in our builds previously. This stability has allowed us to expand our testing coverage to include iOS, Android and all supported versions of Internet Explorer.

Switched to Fastly for our CDN

The CDN-hosted version of Video.js at vjs.zencdn.net is downloaded over 2 BILLION times every month, even with agressive browser-side caching. Video.js is relatively small in file size but that’s still 34 TB per month. We were previously piggy-backing on Brightcove’s Akamai account, but Fastly offered to host the library for free. We gave it a shot and fell in love. In full disclosure, our testing found that Fastly didn’t perform quite as well as Akamai internationally, but Fastly’s user interface, API, and real-time purge are things of beauty and make our CDN deploy process much simpler.

Video.js Logo

We simplified it a bit. What do you think?

If you don’t feel comfortable contributing to Video.js we can always use help on the website. It gets over 200,000 unique views every month. Contribute at the website repo.

HALP!

It’s our community that keeps Video.js actually decent and truly free (Apache 2), so don’t be shy. Jump in and submit a bug, build a plugin, or design a skin.

In conclusion, it’s been a good year of a lot of good work. Thanks!