Video JS Blog

Matthew McClure2019-09-13

Announcing the new Videojs.com

If you visit the Video.js website today, you'll (hopefully) notice it looks a little different. You may also notice that you're reading this on the root domain instead of blog.videojs.com. If you've noticed either/both those things then this is redundant, but today I'm incredibly excited to announce a new website for the Video.js project(s).

The plan was for this website to take a month to build and ship and we're going on...well more than that, but we think it's worth it. Not just because I'm the one responsible for the ~6 years we've spent with the previous website, but because the new design shifts the focus to what we think is so important about Video.js: you, the builders, using Video.js to create.

Video.js in 2019

When Heff first created Video.js back in 2010, the reasons for needing and using a video player were different than they are today. I asked him, “What was the biggest motivation for using a player in 2010, and why did you make Video.js?”

Well, interestingly, it wasn't because Flash was dead. That wouldn't happen for years. And it wasn't to support iPhones, because you couldn't build video controls there yet. It was simply to use native web technologies to build video rather than a third-party plugin. It was fun.

I don't know if that answers your question. Those other reasons would be why most people eventually used Video.js.

For what it’s worth, my answer to why I would have used Video.js in the early 2010s would be a little different: it was a nice looking player that had a Flash fallback to support older browsers. Today, however, Flash is dead, and unnecessary anyway because browser support for modern codecs has gotten so much better. Sure, things have gotten a little more complicated with adaptive-bitrate streams becoming commonplace, but even then, simply playing back video in the browser has become table stakes.

That’s one of the reasons why we like to look at Video.js as a player framework instead of a player. Video.js is a well-tested, extensible, and extremely customizable starting point for you to create your own custom experiences for your viewers. We’ve tried to make sure you get a great player right out of the box when you start using Video.js, but, if you want, it can be the starting point for the perfect player for your application.

New Themes

All four Video.js themes
All four Video.js themes

To that end, we've launched the website with four new CSS-only themes. Each is named after the website theme that contains them, but the point is to show off just how easy it is to customize your players while giving you some new starting points for creating your own custom themes. If you're interested in learning more about the process of using/building the themes, there's a high level overview on the Mux blog.

New LogoNew Logo

The new logo will slowly filter out across the different projects over the next few days (let's face it, maybe months), but we've got new stickers on the way! If you're interested in slapping some Video.js on your laptop or bumper, if that's still a thing, shoot us a tweet.

Credit where credit's due!

This redesign and buildout was funded (and built) by Mux and, as usual, hosted by Netlify. Browserstack allows us to test each player version in different browsers and devices, and Fastly provides our CDN-hosted versions. Last, but certainly not least, the Video.js corporate shepherd and primary sponsor is Brightcove.

Go build awesome players

We'd love to see the players you create!

💝 @matt_mcclure

Lahiru Dayananda2019-01-16

In-band Captions Support with videojs-http-streaming

With the release of videojs-http-streaming (VHS) v1.2.0 on July 16th 2018, Video.js has built-in support for CEA/CTA-608 captions carried in FMP4 segments. This means that closed captions are automatically parsed out and made available to Video.js players for MPEG-DASH content and HLS streams using FMP4 segments. Here's a sample player that shows the captions (English with the "CC" button is the track with CEA-608 captions).

If you are curious about CEA-608 captions and the approach we used to parse them out of fmp4s, or a general overivew, you can watch my talk from Demuxed 2018.

Caption Parsing is handled by the mux.js library and interacts with VHS to feed parsed captions back to Video.js.

Usage

Create a CaptionParser:

  import { CaptionParser } from 'mux.js/lib/mp4';

  const captionParser = new CaptionParser();

  // initalize the CaptionParser to ensure that it is ready for data
  if (!captionParser.isInitialized()) {
    captionParser.init();
  }

When working with HLS and MPEG-DASH with fmp4 segments, it's likely that not all the information needed to parse out captions are included in the media segments themselves, and metadata from the init segment needs to be passed to the CaptionParser. For this reason, the video trackIds and timescales defined in the init segment should be passed into the CaptionParser.

  import mp4probe from 'mux.js/lib/mp4/probe';

  // Typed array containing video and caption data
  const data = new Uint8Array();
  // Timescale = 90000
  const timescales = mp4probe.timescale(data);
  // trackId = 1
  const videoTrackIds = mp4probe.videoTrackIds(data);

  // Parsed captions are returned
  const parsed = captionParser.parse(
    data,
    videoTrackIds,
    timescales
  );

Calling captionParser.parse with data containing CEA-608 captions will result in an object with this structure:

  {
    captions: [
      {
        // You can ignore the startPts and endPts values here
        startPts: 90000,
        endPts: 99000,
        // startTime and endTime can be used for caption times in the TextTrack API
        startTime: 1,
        endTime: 1.1,
        text: 'This is a test caption',
        // This is the CEA-608 "channel" the caption belongs to
        stream: 'CC1'
      }
    ],
    // Includes any (or none) of: CC1, CC2, CC3, CC4
    // This should match the captions returned in the above caption array
    captionStreams: {
      CC1: true
    }
  };

You can then take one caption and create a VTTCue to add to a TextTrack with Video.js APIs

  const player = videojs('video');
  const track = player.addRemoteTextTrack({
    kind: 'captions',
    label: parsed.captions[0].stream,
    default: true,
    language: 'en'
  }, false);

  track.addCue(parsed.captions[0]);

VHS is included by default with videojs v7.x.

Gary Katsevman2019-01-11

Video.js 7.4

It's time to have an overview of Video.js 7.4, first released early December. The big new feature for this release is a UI that allows you to seek during live streams. We updated focus-visible to work with our Menus, added more translations, added a replay option to the Play/Pause button, and many, many fixes, multiple of which are accessibility related.

We also dropped our usage of Grunt from our build process. We owe a lot to Grunt as it has served us well but it was time to move on.

Video.js 7.4.1 is currently the latest release with 7.4.2 out as a pre-release until next week.

Thanks

Before continuing, I'd like to thank everyone that was involved, we really appreciate your contributions! There were a total of 14 first time contributors, I think this is a historic high for us and I hope this continues!

Live UI

Video.js has supported live streams for a while, either natively, or via videojs-http-streaming (VHS). However, this UI was very minimal, it disabled the progress bar and basically only allowed pausing, though, there was an indicator that this was a live stream.

Video.js, with the new Live UI, playing a live stream
Video.js, with the new Live UI, playing a live stream

The new UI looks pretty similar to the previous live UI and the regular control bar. The "live" indicator moves to the right side of the progress bar and also indicates whether we are playing back live or we are behind live. Clicking this button will seek to the "live point". The time displays and tooltips will show time with respect to the live point; so, current time at the live point will be 0 and if you seek back 30 seconds it'll show -00:30.

This feature is still somewhat experimental, so, it is behind an option that is off by default. We hope that you try it out and give us feedback and that we can enable it by default in a future release.

To enable the feature, pass in liveui: true to the player: In JavaScript:

var player = videojs('my-id', {
  liveui: true
  }
);

Or HTML:

<video-js id="my-id" data-setup='{"liveui": true}'}>
  <source src="https://example.com/live.m3u8" type="application/x-mpegurl">
</video-js>

Languages

We've had a bunch of language additions and updates in the 7.4 release line. In addition, we now copy the JSON files into the dist/lang folder for easier inclusion your projects.

Languages added and updated

  • Occitan (oc)
  • Russian (ru)
  • Welsh/Cymraeg (cy)
  • Ukrainian (uk)
  • Serbian (sr)
  • Swedish (sv)

Accessibility

As always, we aim to make Video.js as accessible and usable as we can. To that end, we've had a bunch of accessibility related fixes in these releases.

  • Time displays accessibility with VoiceOver
  • remove hidden control text in progress bar
  • make the seek-to-live button announce itself to screen readers properly
  • Make the - in the remaining time display be visual only and not readable by screen readers

Other Features Updates

Responsive Captions Settings Dialog

This uses the new responsive setting and breakpoints to make the dialog respond to the size of the player and improves the user experience.

replay option for PlayToggle

The PlayToggle button changes the icon to a replay icon when the video ends, most users don't mind it and it was a highly requested feature previously. This adds an option to turn it off.

focus-visisble menu backgrounds

Like outlines for other buttons, we use a different background color to represent the focus in menus. We should respect focus-visible there like we do for outlines in buttons.

playerreset event

When the player is reset with the reset() method, it'll now trigger a playerreset event to let components and users know.

Other Fixes

  • Fix fullscreen event triggering twice (7.4.2)
  • PlayToggle cursor pointer
  • select default subtitles on loadedmetadata not loadstart
  • don't apply user preference to subtitles if no language is set
  • make sure that vjs-waiting is only removed if we started playback again
  • allow duration to be set to NaN, making Video.js more spec compliant
  • fix locking menus when the menu button is pressed
  • remove child component from old parent when moving the component to a new parent
  • remove vjs-ended when seeking after video has ended
  • don't autohide the control bar when hovering with the mouse

Full CHANGELOG for 7.4.0, 7.4.1, and 7.4.2

7.4.2 (2019-01-08)

Bug Fixes

  • Control-bar autohide when cursor placed over it #5258 (#5692) (6ebc772)
  • css animation shorthand property order (#5687) (0e69ce9)
  • fs: make sure there's only one fullscreenchange event (#5686) (2bc90a1), closes #5685
  • lang: adds sv translation used by liveui component (#5704) (f38726e)
  • package: update @videojs/http-streaming to version 1.6.0 🚀 (#5705) (3d093ed)
  • player: remove vjs-ended class on seeked (#5728) (f1637cd), closes #5654
  • remaining-time-display: make the '-' be visual and not readable by screen readers (#5671) (05513f8), closes #5168
  • seekbar: don't disable if live tracker's seekable is infinity (#5721) (7f507df)
  • remove child from old parent when moving to new parent via addChild (#5702) (8a3e2a7)

Chores

Documentation

  • liveui: Add a guide for the live ui and live api (#5677) (c147581)

7.4.1 (2018-12-11)

Bug Fixes

Chores

  • package: update autoprefixer to version 9.4.2 (#5647) (19f3465)
  • package: update rollup-plugin-node-resolve to version 4.0.0 🚀 (#5666) (d07b6c2)

Documentation

  • remove grunt and update usage of build scripts (#5656) (62f9e78)

Tests

  • verify null-checks with player and control bar children set to false (#5670) (13b42ad)

7.4.0 (2018-12-03)

Features

Bug Fixes

Chores

Documentation

Garrett Singer2018-12-17

Bugpost: Disconnects and Reconnects

In my old apartment, whenever I nuked a bowl of oats in the microwave, my WIFI cut out. It was an annoyance, but I was hungry, the apartment was too small to move the router further from the microwave, and I didn’t want to buy anything that operated on a different frequency. So I’d start the microwave, watch whatever video was streaming on my laptop until the buffer ran out, then watch the loading spinner until the microwave gave the triple ding.

Thankfully, video players are usually pretty robust against such issues as network connectivity (and rogue microwaves), and the video resumed after the network recovered. But recently, we found that for some content played in Video.js, the video didn’t resume. Even worse, the loading spinner simply disappeared, leaving a frozen frame on screen.

You can see this behavior yourself by checking out Video.js 7.3.0, loading https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd as the source, and using Chrome Dev Tools to disconnect and reconnect the network.

Here’s a quick tour of what happened and how we fixed it.

The Loading Spinner

To approach the bug, we started with the most glaring problem, the disappearance of the loading spinner. The spinner is managed by Video.js. In the code, the vjs-waiting class is added to the HTML element when the tech triggers a waiting event, and is removed on the next timeupdate event.

/**
 * Retrigger the `waiting` event that was triggered by the {@link Tech}.
 *
 * @fires Player#waiting
 * @listens Tech#waiting
 * @private
 */
handleTechWaiting_() {
  this.addClass('vjs-waiting');
  /**
   * A readyState change on the DOM element has caused playback to stop.
   *
   * @event Player#waiting
   * @type {EventTarget~Event}
   */
  this.trigger('waiting');
  this.one('timeupdate', () => this.removeClass('vjs-waiting'));
}

After logging the events emitted by the player, the issue became clear. Some browsers emitted a timeupdate event immediatley after the waiting event.

So timeupdate events themselves were not reliable, but what else could be used to make a better determination of if we're waiting? We checked player.currentTime() on the timeupdate events, and found that when we caught the last timeupdate event, the one after the waiting event, the player was at the same time as the timeupdate event prior to the waiting event (and the same time as when the waiting event was triggered).

`timeupdate` triggered, player.currentTime = 11
`timeupdate` triggered, player.currentTime = 11.250
`waiting` triggered, player.currentTime = 11.250
`timeupdate` triggered, player.currentTime = 11.250

The fix was pretty easy. Simply record the player's time when it gets the waiting event, and instead of removing the vjs-waiting class on the next timeupdate, remove the vjs-waiting class on the next timeupdate that has a different time from the one we recorded.

You can see the PR for that here, and it is available in Video.js 7.4.0.

Resuming Playback after Disconnecting

With the loading spinner back, the next issue was to determine why, when the network reconnected, the video did not resume. First, we tested a few different pieces of content. The HLS source we tested did reconnect, but the DASH source didn't. Worse, when using the DASH source, Chrome's debug console froze, as it racked up request and console errors as fast as the computer could process them.

Console Crash

Imagine your console filled with the following two messages repeated thousands of times:

GET https://dash.akamaized.net/akamai/bbb_30fps/bbb_a64k/bbb_a64k_8.m4a net::ERR_INTERNET_DISCONNECTED
VIDEOJS: WARN: Problem encountered with the current HLS playlist. Trying again since it is the final playlist.

When your console freezes, debugging can be tough. It's like trying to watch a video when the microwave is on. So we tackled that issue first.

Looking at the error message, we were repeatedly retrying the last rendition. That is expected behavior, as we added it a long time ago to our HLS playlist loader. However, when we added DASH support, we created a different playlist loader, and we never added the same logic for reloading the final DASH playlist.

After adding the throttle, debugging became much easier, and we were able to use the console once again and dig into why DASH didn't reconnect while HLS did.

Multiple Requests Multiple Problems

The last problem proved to be the most difficult to debug. Initially, the thought was that demuxed content was the problem, as having a separate audio segment loader to load from the audio playlist could cause trouble when experiencing errors at the same time as the main segment loader tried to load from the video playlist. However, HLS can also have demuxed content, and after an example source proved to resume just fine, that approach was ruled out.

By examining Chrome's network log, the DASH content, after a certain point, stopped requesting video segments, and only requested audio segments. But there were more than just two requests for DASH. The video segments themselves included two requests: one for the init segment, and the other for the video data.

Requests in VHS are managed by a module called media-segment-request, which is responsible for requesting everything required for a segment, whether it be the key, the init segment, the media data, or all of them, before letting the segment loader know that its requests are done. And for the video requests, the media-segment-request never called the done callback after the disconnect.

It turned out that if we received one error, we aborted the other requests in that group, and waited to call the done callback until those requests reported that they were aborted. However, according to the XHR standard, the abort algorithm may not be run if the request was unsent. In this case, when the network disconnected, the first request reported an error before the second had a chance to be sent. After we aborted the second request, we were stuck waiting for an error that would never come.

We fixed that by immediately calling back with an error on the first error we encounter instead of waiting for each subsequent request to finish.

The problem should also have existed in HLS, but only in cases where the segments had an EXT-X-KEY or EXT-X-MAP tag to go along with the media request. In the content we tried, neither of those was present, so it appeared to be isolated to DASH content.

Bug Solved

We hope that was somewhat useful in describing some of the bugs we encounter, how we go about investigation, and what the resolutions look like. We'd be happy to hear from you as well. You can find us on the #playback channel in the Video.js Slack, or navigating Issues and PRs at https://github.com/videojs/http-streaming.

Now to enjoy my oats and video, without any concerns about the playback resuming.

Garrett Singer2018-11-08

Introducing Video.js HTTP Streaming (VHS)

"How do I get my video to play with Video.js?"

This is one of the most frequent questions we get when working on Video.js. And it's a good question.

If someone checks out a copy of Video.js, their content may play on one browser and not another. To get cross browser support for a specific type of content, they must stumble upon a relevant source handler (for instance, videojs-contrib-hls for HLS content). Only after including the source handler will their video play across all major browsers.

We want Video.js to be easy to use, and that isn't the easiest setup. So we decided that it's time to address the issue.

This was the motivation behind integrating Video.js HTTP Streaming (nicknamed VHS) inside of Video.js 7 by default.

What is VHS?

VHS is the successor to videojs-contrib-hls. It is a source handler forked from the videojs-contrib-hls repository. While videojs-contrib-hls was originally designed to add HLS playback on all browsers, we realized that the engine could also play other formats just as well.

To prove this, we added a DASH manifest parser, and with a few minor changes, VHS played DASH content with the same codebase and API as it used for HLS.

Even with the changing landscape of video technologies, Video.js will be ready. If a new streaming format gains popularity, only a few code changes will be necessary to add support to VHS. One engine and API for all formats.

Why is VHS included by default in Video.js?

By including VHS by default in Video.js, you no longer have to worry about what browser supports what streaming technology.

Video.js was built to abstract away differences in video APIs and features between web browsers, creating one simple API as close to the web standards as possible, and filling in feature gaps where possible. Over time, one area where browsers have diverged is in their support of different media formats. Some browsers may support native playback of DASH, others of HLS, and some support neither.

Previously, this would be managed by including source handlers and plugins, but we understand the importance of having a simple setup. By including VHS by default in Video.js, you no longer have to worry about what browser supports what streaming technology. It should just work.

What if I want to opt-out of using VHS?

If you would rather include a different source handler for HLS or DASH playback, Video.js still allows you to do that. Video.js core is the Video.js you know and love, without the inclusion of VHS. Everything should work as it used to.

Contributions and Feedback Welcome

As always, we’d love your contributions and feedback. The best way to reach us for comments, questions, requests, contributions, or just to say hello are the VHS GitHub page and the #playback channel on the Video.js slack

Gary Katsevman2018-10-29

Video.js 7.3: Responsive Layout, Fill Mode, createLogger

Another month, another Video.js release: v7.3.0. This release brings a long wanted feature: Responsive Layout. In addition, Fill Mode has been promoted to a first-class feature and createLogger was added to make debugging and logging easier.

This is currently out as a pre-release and will be promoted to latest in about a week. Please try it out and report any issues you find.

Thanks

I'd like to thank everyone that was involved in making changes that landed in this release. Any and all changes are really appreciated.

Responsive Mode

Responsive Mode will make the player adjust UI components to the size of the player. It uses the playerresize event that was added in v6.7.0 of Video.js, which allows us to know when the player changes sizes.

Responsive mode will set and change certain breakpoint classes like vjs-layout-small when the player size changed. These can be configured. Depending on which class is currently on the player, the control bar and other UI elements can adapt. For example, with vjs-layout-small, the time controls will not show because the time tooltips on the progress bar are available and the captions button is more important. At a larger size, both can be shown without a problem.

You can find out how to enable Responsive Mode and more in our docs page. There is also an example playground in the sandbox folder in the repo.

Fill Mode

Fill Mode allows the Video.js player to resize dynamically, but stay contained within the bounds of the parent container. This is analogous to Fluid Mode, but sometimes you the container is already being sized properly.

Fill Mode is not a brand new mode, the class vjs-fill has been available in Video.js for quite a while. This finally makes it a first-class feature to go along with Fluid Mode.

createLogger

This is a new method on videojs.log that allows you to create a new logger with a specific name. It then creates a chain of names to make it easier to track which component logged this particular message. In particular, this can help plugin authors to log messages and then filter out only the messages that are associated with their plugin.

createLogger returns a function with the same API as videojs.log. You can see it in action below.

Examples

A new method, player.log was added which uses createLogger behind the scenes. It logs the player ID in addition to VIDEOJS:

var player = videojs('myid');

player.log('foo');
// VIDEOJS: myid: foo

You can also go further and create a sub logger:

var player = videojs('myid');
var mylog = player.log.createLogger('my-plugin');

mylog.log('foo');
// VIDEOJS: myid: my-plugin: foo

If you want to log a warning or error for your custom plugin:

var player = videojs('myid');
var mylog = player.log.createLogger('my-plugin');

mylog.log.warn('foo');
// VIDEOJS: myid: my-plugin: WARN: foo

mylog.log.error('bar');
// VIDEOJS: vid1: my-plugin: ERROR: bar
Gary Katsevman2018-07-10

Video.js 7.1 and 6.11: Autoplay and Fullscreen changes

It's been a while since the last release post. The 7.1.0 and 6.11.0 releases of Video.js are full of great features from contributors and maintainers. The two big changes are related to autoplay -- when will we stop talking about autoplay? -- and fullscreen. Though, there's plenty more in there.

Video.js 7.1.0 and 6.11.0 are out as next and next-6 respectively on npm.

To get 7.1.0 via npm:

npm install video.js@next

And 6.11.0 via npm:

npm install video.js@next-6

Autoplay

We've been keeping track of changes happening around autoplay in browser vendors. We've also done a lot of work to make Video.js work as well as possible with the new autoplay policies. However, there were still some use cases that weren't accounted for. To address those, we decided to extend the existing Video.js autoplay option to include a few new values: muted, play, any. The current boolean options will work as they do now and unknown options will be treated as they do now.

  • muted - this will mute the video element before calling play() on loadstart.
  • play - this will call play() on loadstart. This is similar to the autoplay attribute but with a call to play instead of using the attribute.
  • any - this will call play() on loadstart but if it fails, it will try and mute the player and try play()ing again.

Fullscreen

There are two fullscreen related changes in these releases.

The first one is one that was often request and it's finally here: double click to toggle fullscreen. Double clicking inside the player area -- outside of the control bar and modal dialogs -- will enter or exit fullscreen playback.

The second change is to disable the fullscreen toggle if fullscreen is not available, for example, inside of an iframe without allowfullscreen attribute

Fullscreen available
Fullscreen available
Fullscreen unavailable
Fullscreen unavailable

Notable Changes

  • Make audio tracks that are of "main-desc" kind show up with an Audio Description icon
  • Run our CSS through autoprefixer which reduces the output size a bit.
  • Make the mute toggle show up if muting is possible even if changing volume programmatically isn't. For example, on iOS.
  • Make setSource be option for middleware.

Thanks

I'd like to thank everyone that contributed to this release!

Gary Katsevman2018-05-11

Video.js 7 is here! 🎉

I'm happy to announce that Video.js 7 is now out! It brings to Video.js support for HLS and experimental DASH support via the Video.js HTTP Streaming (VHS) project. Video.js 7 also drops support for some older Internet Explorer browsers only keeping support for IE11.

While it's out, it's still in pre-release for a short while and only available under the next tag on npm. Some time next week, we'll flip the switch and make it latest.

changes for removing IE8 code
changes for removing IE8 code

This release is pretty exciting because it means we were able to remove a bunch of code for old browsers and we can now start using newer web platform features that we had previously been avoiding like the :not() css selector. It'll also free us to look forward at new features and not be held back by needing to support old browsers like IE8.

Getting Video.js 7

You can get it from npm via

npm install video.js@next

or

npm install video.js@7.0.0

From GitHub Releases,

Or from our or another CDN

https://vjs.zencdn.net/7.0.0/video.min.js
https://vjs.zencdn.net/7.0.0/video-js.css
https://unpkg.com/video.js@7.0.0/dist/video.min.js
https://unpkg.com/video.js@7.0.0/dist/video-js.css
https://cdnjs.cloudflare.com/ajax/libs/video.js/7.0.0/video.min.js
https://cdnjs.cloudflare.com/ajax/libs/video.js/7.0.0/video-js.css

VHS and Video.js

Video.js will be bundling VHS by default for ease of use for new users. However, some people don't want VHS or are using another plugin. For this, we have a separate build of Video.js which doesn't include VHS.

You can get it from our CDN or another CDN

https://vjs.zencdn.net/7.0.0/alt/video.core.min.js
https://unpkg.com/video.js@7.0.0/dist/alt/video.core.min.js
https://cdnjs.cloudflare.com/ajax/libs/video.js/7.0.0/alt/video.core.min.js

Finally, expect a more detailed post about VHS in the weeks to come.

What the future holds

Video.js hasn't really had a roadmap for a long time. With this release, we think it's time to take a step back and create a roadmap.

The roadmap will take into account currently open issues and internal and external feature requests. In addition, we're going to look at currently available features and see if it's worth refactoring them with the more modern web in-mind.

Later down the line, we'd also want to make a roadmap for a Video.js 8. Given that this release only adds VHS and removes old browsers, there're plenty of already deprecated features that it's time to remove.

Updates from the Video.js 7 roadmap

Video.js 5.x

As of this release, Video.js 5.x is now officially deprecated. This means that we will no longer work on it or back-port fixes to it but it's still available for usage if you're still using it. We do urge you to update to the latest 5.x, or better yet, upgrade to 6.x or 7.

Build Tools

In the roadmap post, we talked about potentially switching to webpack from Rollup for better support of importing VHS into Video.js. However, while doing this work, we found out that the filesize of Video.js will increase by about 20%, which isn't a trivial amount, not to mention that the filesize is already going to increase from the default inclusion of VHS.

Eventually, after much work, we were able to figure out how to bundle VHS using Rollup as well and we are continuing to use Rollup for our builds.

Google Analytics

Video.js 7's CDN builds will no longer send any data to Google Analytics via our stripped down pixel tracking.

Video.js 6 and below CDN builds will continue sending data, unless you opt out with window.HELP_IMPROVE_VIDEOJS = false, but versions 6.8 and above will honor the Do Not Track option that users can set in their headers before sending the data.

We are continuing to investigate our options for collecting our CDN logs in an open manner and will post updates when we have any.

Pat O'Neill2018-04-17

Autoplay Best Practices with Video.js

tl;dr

  • Never assume autoplay will work.
  • Using the muted attribute/option will improve the chances that autoplay will succeed.
  • Prefer programmatic autoplay via the player.play() method, avoiding the autoplay attribute/option.

Introduction

There has been a lot of chatter about autoplay lately and we wanted to take a few minutes to share some best practices when using autoplay with Video.js.

This post is meant to be a source of information - not an editorial or positional statement on autoplaying video. A lot of users hate autoplay because it's annoying or it consumes precious bandwidth. And a lot of publishers providing content free of charge rely on autoplay for the preroll ads that finance their businesses. Browser vendors (and open source library authors) have to weigh these concerns based on the interests of their users, publishers across the web, and their own business. And users have to choose the browser that best reflects their preferences.

This post is not about ways to circumvent autoplay policies. That's not possible and no one should waste their time attempting it. We think it's uncontroversial to say that actively circumventing the browser's built-in user experience or user preferences is harmful.

Autoplay Policies in the Big Browsers

I'm not going to go into exhaustive detail on each browser's specific algorithms because they are subject to change and it would defeat the core point of this post: never assume autoplay will work.

Safari

As of Safari 11, which shipped in September 2017, Apple has updated their autoplay policies to prevent autoplay with sound on most websites.

Chrome

Back in September 2017, Google announced that Chrome's autoplay policy would change in April 2018 with Chrome 66, subject to a series of rules you can read about in the linked article.

Firefox

With Firefox, Mozilla has taken the position of not having a firm autoplay policy for the time being. That may change in the future. That said, Firefox today does allow users to disable autoplay with a few configuration changes.

IE11 and Edge

Microsoft's browsers have no specific/known autoplay policy - at the time of writing, autoplay just works in IE11 and Edge.

Refresher on How to Request Autoplay in Video.js

Note how the title of this section uses the word "request." That's intentional because it should be thought of as a request. Remember, never assume autoplay will work.

One of Video.js' strengths is that it is designed around the native <video> element interface; so, it will defer to the underlying playback technology (in most cases, HTML5 video) and the browser. In other words, if the browser allows it, Video.js allows it.

There are two ways to enable autoplay when using Video.js:

  1. Use the autoplay attribute on either the <video> element or the new <video-js> element.

    <video autoplay src="https://path/to/source.mp4"></video>
    <video-js autoplay src="https://path/to/source.mp4"></video-js>
  2. Use the autoplay option:

    videojs('player', {autoplay: true});

By default, you can defer to the default behavior in Video.js. If autoplay succeeds, the Video.js player will begin playing. If autoplay fails, the Video.js player will behave as if autoplay were off - i.e. it will display the "big play button" component over the poster image (or a black box).

If that works for you, your job is done!

TIP: If you want to use autoplay and improve the chances of it working, use the muted attribute (or the muted option, with Video.js).

Beyond that, there are some general practices that may be useful when dealing with autoplay: detecting whether autoplay is supported at all, or detecting whether autoplay succeeds or fails.

Detecting Autoplay Support

Similar to detecting successful or failed autoplay requests, it may be useful to perform autoplay feature detection on the browser before creating a player at all. In these cases, the can-autoplay library is the best solution. It provides a similar Promise-based API to the native player.play() method:

canAutoplay.video().then(function(obj) {
  if (obj.result === true) {
    // Can autoplay
  } else {
    // Can not autoplay
  }
});

Programmatic Autoplay and Success/Failure Detection

For those who care whether or not an autoplay request was successful, both Google and Apple have recommended the same practice for detecting autoplay success or rejection: listen to the Promise returned by the player.play() method (in browsers that support it) to determine if autoplay was successful or not.

This can be coupled with the autoplay attribute/option or performed programmatically by calling player.play(), but we recommend avoiding the autoplay attribute/option altogether and requesting autoplay programmatically:

<video-js id="player" src="https://path/to/source.mp4"></video-js>
var player = videojs('player');

player.ready(function() {
  var promise = player.play();

  if (promise !== undefined) {
    promise.then(function() {
      // Autoplay started!
    }).catch(function(error) {
      // Autoplay was prevented.
    });
  }
});

NOTE: It's important to understand that using this approach, the Video.js player.autoplay() method will return undefined or false. If you expect your users or integrators to rely on this method, consider the following section.

Programmatic Autoplay with the autoplay Attribute/Option

When building a player for others to use, you may not always have control over whether your users include the autoplay attribute/option on their instance of your player. Thankfully, combining this with programmatic autoplay doesn't seem to have a significant effect on the behavior of playback.

Based on our experimentation, even if the browser handles the actual autoplay operation, calling player.play() after it's begun playing (or failed to play) does not seem to cause current browsers to trigger an extra "play" event in either Chrome or Firefox. It does seem that Safari 11.1 triggers "playing" and "pause" events each time player.play() is called and autoplay fails.

Still, if you have full control, we recommend avoiding the autoplay attribute/option altogether and requesting autoplay programmatically.

NOTE: Even if using the autoplay attribute/option with programmatic autoplay, the player.autoplay() method will return undefined until the player is ready.

Example Use-Case

At Brightcove, one thing we've done to improve the user experience of our Video.js-based player is to hide the "big play button" for players requesting autoplay until the autoplay either succeeds or fails. This prevents a periodic situation where the "big play button" flashes on the player for a moment before autoplay kicks in.

While our actual implementation has more complexity due to specific circumstances with our player, the gist of it is the same as this functional JSBin demonstration.

Conclusion

There may be decision points in your code related to whether autoplay succeeds or not; however, the practical reality is that all the paragraphs before this boil down to a single, essential concept:

Never assume that your request to autoplay will succeed.

Keep that in mind and you're golden. Even if the browsers stop allowing autoplay entirely, our hope is that recommending this approach will be somewhat future-proof.

Greg Smith2018-03-13

videojs-contrib-ads 6

A major update to videojs-contrib-ads has been released. In this post, we'll take a look at what videojs-contrib-ads offers and what has improved in version 6.

What is contrib-ads?

Videojs-contrib-ads is a framework for creating Video.js ad plugins. Seamlessly integrating ad support into a video player can be a daunting task, especially if you have other plugins that may be effected. For example, playing ads may result in additional media events. Imagine an analytics system that listens to "loadstart" events: it would start seeing double when extra loadstart events result from preroll ads. contrib-ads has a feature called Redispatch that makes sure ads do not trigger extra media events. This keeps things as simple as possible without breaking other plugins. Additional benefits of using contrib-ads are listed in the readme.

Version 6

Version 6 of contrib-ads is a maintenance release that includes a major code refactor. The core of the project's behavior is handled by a state machine that advances through various states as the player plays content, preroll ads, midroll ads, and postroll ads. The initial state machine was designed to implement a specific set of functionality when the project was first conceptualized. Over the years, the project became much more fully-featured and many new scenarios and edge-cases arose. In this refactor, the state machine has been updated to precisely match the modern feature-set. This has resulted in a more maintainable and reliable codebase.

How it works

Ad mode is strictly defined as if content playback is blocked by the ad plugin.

contrib-ads has a concept called ad mode. Ad mode is strictly defined as if content playback is blocked by the ad plugin. This does not necessarily mean that an ad is playing. For example, if there is a preroll ad, after a play request we show a loading spinner until ad playback begins. This is considered part of ad mode. The public method player.ads.isInAdMode() can be used to check if we are in ad mode.

In version 6, the state machine was refactored to match this strict definition of ad mode. There are two types of states: content states and ad states. If contrib-ads is in an ad state, it is in ad mode.

Here is a diagram of the new states. Blue states are content states and yellow states are ad states:

For a history of how the state machine has evolved over time, there is detailed information in this Github issue. In this article we're going to focus on how it works in version 6.

Let's walk through the states for a simple preroll scenario. contrib-ads begins in the BeforePreroll state. This is considered a content state because content playback hasn't been requested yet and so ads are not blocking playback. The ad plugin can asynchronously request ads from an ad server during this time, even though the plugin is in content mode. The ad plugin triggers the adsready event when it's ready. Contrib-ads knows that once the play button is pressed, ads are already prepared.

Now the user clicks play. At this point ad mode begins and contrib-ads moves forward to the Preroll state. The Preroll state shows a loading spinner until ad playback begins. The Preroll state knows that the ad plugin is ready and that play was clicked, so triggers readyforpreroll to inform the ad plugin that it's time to play an ad. Once ad playback begins, the control bar turns yellow and will not allow seeking. If the ad takes too long to begin playing, a timeout will occur and content will resume without further adieu. When the ad is complete, contrib-ads restores the content video. Only when content playback results in a playing event does ad mode end.

Now we're in the ContentPlayback state. At this point, the ad plugin could play a midroll ad, causing a brief foray into the Midroll state, or content could continue without interruption.

When content ends, normally we would see an ended event right away. Instead, contrib-ads sends a contentended event, which is the ad plugin's chance to play postroll ads. Perhaps the ad plugin has decided not to play postroll ad, so it responds by triggering nopostroll. contrib-ads knows that now we're really done, so an ended event is triggered.

Finally, since content has ended for the first time, we now transition to the AdsDone state. There will be no more ads, even if the user seeks backwards and plays through the content again.

Conclusion

Ads may not be your favorite part of watching video online, but contrib-ads makes sure that they don't break your site and that content plays even if ads fail. If you're interested in learning more or contributing to the project, check it out on Github!

Next page >