This guide is the architectural and operational reference for Video.js — for the engineers who integrate it deeply into an application, the architects who reason about how it fits a system, and the contributors who modify or extend it. It covers the internal architecture, the complete configuration surface, the playback technology and middleware layers, security considerations, the build and test pipeline, code patterns, and a candid look at known issues and improvement areas.
For installation basics, playback control, captions, and end-user-facing usage, see the companion User Guide. This document deliberately goes deeper: it should be thorough enough that an AI or engineer could reconstruct how the library is organized and behaves.
Note
This guide documents Video.js 8.23.8 (Apache-2.0). "Video.js" is a registered trademark of Brightcove, Inc. The library is a client-side browser library — there is no server component, no database, and no backend to operate. "Operations" here means integration, build, deployment of the static assets, and runtime behavior in the browser.
Table of Contents
- Executive Summary
- System Architecture
- Repository Structure
- The Component Architecture
- The Tech (Playback Technology) Layer
- The Middleware Layer
- The Plugin System
- Event System and Mixins
- Configuration Reference
- The
videojsNamespace API - Player API Reference
- Error Model
- Requirements and Compatibility
- Build, Tooling, and Distribution
- Deployment and Integration
- Security Analysis
- Performance Considerations
- Testing Strategy
- Code Quality and Conventions
- Contributing
- Potential Improvements and Tech Debt
- Quick Reference
1. Executive Summary
Video.js is a framework, not just a player widget. At its core is a small custom UI-component system and a pluggable playback-technology abstraction. Everything visible — the control bar, buttons, progress bar, menus — is a Component in a tree rooted at the Player. Everything that decodes media is a Tech. Between the player and the tech sits an optional middleware pipeline. Behavior is extended through plugins. This separation is what lets a single API and skin work identically across very different playback backends.
| Aspect | Detail |
|---|---|
| Project | Video.js — Web Video Player & Framework |
| Version analyzed | 8.23.8 |
| License | Apache-2.0 |
| Language | JavaScript (ES2015+, transpiled via Babel) |
| Distribution | npm package video.js + CDN builds |
| Module formats | CommonJS (dist/video.cjs.js), ES module (dist/video.es.js), UMD (CDN video.min.js) |
| Types | Bundled TypeScript definitions (dist/types/video.d.ts) |
| Entry point | src/js/index.js → src/js/video.js |
| Core runtime deps | @videojs/http-streaming (HLS/DASH), videojs-contrib-quality-levels, videojs-vtt.js, @videojs/xhr, videojs-font, plus VHS sub-dependencies |
| Has backend | No — entirely client-side |
| Started | May 2010; 900+ contributors |
Key architectural facts an integrator must internalize:
- The default
techOrderis['html5']. Flash and IE8 support were removed in v8. - HLS and DASH are handled in-process by the bundled streaming engine — no separate dependency.
- The global options object (
videojs.options) is literallyPlayer.prototype.options_; per-player options are deep-merged on top of it. - Players are tracked globally by element
id; failing todispose()before reusing anidis the most common integration bug.
2. System Architecture
Your Web Page / Application
│
videojs(id, options, ready)
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Player (Component) │
│ │
│ ┌──────────────┐ options merge ┌──────────────────┐ │
│ │ Component │◄──────────────────│ videojs.options │ │
│ │ tree (UI) │ (defaults + │ (= Player.proto. │ │
│ │ ControlBar, │ user opts) │ options_) │ │
│ │ buttons, │ └──────────────────┘ │
│ │ menus, ... │ │
│ └──────────────┘ │
│ │ events (EventTarget mixin) │
│ ▼ │
│ ┌──────────────┐ middleware pipeline ┌────────────┐ │
│ │ Player core │◄────────────────────────►│ Middleware │ │
│ │ (state, │ (intercept getters/ │ chain │ │
│ │ playback │ setters by source type)└────────────┘ │
│ │ control) │ │
│ └──────────────┘ │
│ │ delegates decode/playback to │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Tech (abstraction) │ │
│ │ Html5 tech ── wraps native <video>/<audio> ─────────┼──► browser
│ │ └── source handler: @videojs/http-streaming │ │ media
│ │ (HLS .m3u8 / DASH .mpd via MSE) │ │ engine
│ └─────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ plugins (basic fn / advanced Plugin subclass) │
│ ┌──────────────┐ │
│ │ Plugins │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
Layers, top to bottom
- Public namespace (
videojs) — the factory function and the static registry/utility API (src/js/video.js). - Player — the root component; owns state, orchestrates the tech, exposes the playback API (
src/js/player.js). - Component tree — the UI; every control is a
Componentsubclass (src/js/component.js,src/js/control-bar/**). - Middleware — optional interceptors between player and tech, selected by source type (
src/js/tech/middleware.js). - Tech — the playback abstraction;
Html5is the only built-in tech in v8 (src/js/tech/**). - Source handlers — register with a tech to handle specific formats; the bundled streaming engine is the source handler for HLS/DASH.
- Tracks — text/audio/video track lists modeled on the W3C spec (
src/js/tracks/**). - Plugins — user-supplied extensions (
src/js/plugin.js). - Utilities — DOM, events, time, URL, object, function, number, string, browser, logging, stylesheet helpers (
src/js/utils/**).
3. Repository Structure
video.js/
├── src/
│ ├── js/
│ │ ├── index.js # Package entry: imports video + VHS + quality-levels
│ │ ├── video.js # The videojs() function + static namespace API
│ │ ├── player.js # Player class (the largest, central module)
│ │ ├── component.js # Base Component class + registry
│ │ ├── clickable-component.js, button.js, big-play-button.js,
│ │ │ # close-button.js, transient-button.js
│ │ ├── modal-dialog.js, error-display.js, title-bar.js,
│ │ │ loading-spinner.js, poster-image.js, resize-manager.js,
│ │ │ live-tracker.js, spatial-navigation.js
│ │ ├── event-target.js # EventTarget base class
│ │ ├── plugin.js # Plugin base class + registry
│ │ ├── media-error.js # MediaError class (W3C error codes)
│ │ ├── fullscreen-api.js # Cross-browser fullscreen mapping
│ │ ├── setup.js # Auto-setup scanning for data-setup
│ │ ├── consts/ # errors.js (error metadata)
│ │ ├── control-bar/ # All control-bar components (LOW triage, many files)
│ │ ├── menu/, slider/, mixins/ # UI building blocks
│ │ ├── tech/ # tech.js, html5.js, middleware.js, loader.js
│ │ ├── tracks/ # text/audio/video track lists, settings, display
│ │ └── utils/ # dom, events, fn, obj, time, url, str, num,
│ │ # browser, log, guid, promise, buffer, stylesheet…
│ ├── css/ # Sass source for the default skin (vjs.scss, components/)
│ └── images/ # icons.svg sprite
├── lang/ # 50+ language JSON dictionaries (en.json is the template)
├── docs/ # Guides, examples, legacy docs
├── test/ # unit (QUnit/karma), api, require (browserify/webpack/node)
├── sandbox/ # Manual test HTML pages (copied from .example files)
├── build/ # Build scripts (minify.js, sandbox.js, translations.js, …)
├── dist/ # Build output (generated; not in source control)
├── package.json # Scripts, deps, version 8.23.8
├── rollup.config.js # JS bundling config
├── postcss.config.js, tsconfig.json, .jsdoc.js
└── composer.json # PHP/Packagist metadata (library listing only)
Note
A triage detail worth knowing: the
src/js/control-bar/andsrc/js/tracks/folders contain many small component files. They are individually small but collectively make up most of the visible UI. The architecturally load-bearing modules areplayer.js,component.js,video.js,tech/, andplugin.js.
4. The Component Architecture
The Component class (src/js/component.js) is the foundation of the entire UI. Understanding it is the key to extending Video.js.
Core ideas
- A component is a JS object paired with a DOM element.
component.el()returns the element;component.createEl()builds it. - Components form a tree. A parent holds children via
addChild()/removeChild(); adding a child inserts its element into the parent's content element (contentEl()). - Components are registered by name.
Component.registerComponent(name, Class)(exposed asvideojs.registerComponent) adds a component to a global registry so it can be referenced by string in thechildrenoption. - Default children are declarative. A component's
options_.childrenarray (or achildrenmap) drivesinitChildren(), which instantiates each child. Setting a child's option tofalseskips it;trueuses defaults.
The class hierarchy
Component
├── ClickableComponent
│ └── Button
│ ├── BigPlayButton
│ ├── CloseButton
│ └── TransientButton
│ └── PosterImage
├── ModalDialog
│ └── ErrorDisplay
├── Player
├── LiveTracker
├── LoadingSpinner
├── ResizeManager
├── TitleBar
└── (control-bar/menu/slider/tracks components…)
EventTarget
└── SpatialNavigation
MediaError (standalone, mimics W3C MediaError)
Plugin (base for advanced plugins)
ClickableComponent adds keyboard/tap activation and ARIA role="button"; Button specializes it into a native <button>. ModalDialog is a focus-trapping overlay (used by ErrorDisplay and player.createModal()).
Key Component methods (for extension authors)
| Method | Purpose |
|---|---|
createEl(tag, props, attrs) |
Build the component's DOM element. Override to customize markup. |
buildCSSClass() |
Return the component's CSS class string. Override and append super.buildCSSClass(). |
addChild(child, opts, index) / removeChild(child) |
Manage children. |
getChild(name) / getChildById(id) / getDescendant(...names) |
Traverse the tree. |
on/off/one/any/trigger |
Events (from the evented mixin). |
show()/hide()/addClass/removeClass/toggleClass |
Visibility and classes. |
dimensions/width/height/currentWidth/currentHeight |
Sizing. |
setTimeout/setInterval/requestAnimationFrame (+ clear*) |
Timers auto-cleaned on dispose(). |
ready(fn) / triggerReady() |
Async readiness. |
localize(string, tokens, default) |
Translate UI text. |
dispose() |
Tear down the component and all children (recursively). |
Important
Always use the component's own
setTimeout/setInterval/requestAnimationFramewrappers rather than thewindowequivalents. They are tracked inSets and cleared automatically ondispose(), preventing leaks and callbacks firing against disposed components. Usingwindow.clearTimeouton an id fromComponent#setTimeoutwill not clean up its dispose listener.
Registering a custom component
const Component = videojs.getComponent('Component');
class TitleOverlay extends Component {
createEl() {
return videojs.dom.createEl('div', {
className: 'vjs-title-overlay',
textContent: this.options_.text || ''
});
}
}
videojs.registerComponent('TitleOverlay', TitleOverlay);
const player = videojs('my-player');
player.addChild('TitleOverlay', { text: 'Now Playing' });
Warning
A
Techmust not be registered withregisterComponent— useTech.registerTech()(orvideojs.registerTech()). Registering a tech as a component logs a warning and is rejected;registerComponentthrowsIllegal component, "<name>"; ...for non-Componentclasses. ThePlayercomponent cannot be re-registered after any player has been created.
5. The Tech (Playback Technology) Layer
A Tech is the abstraction that actually plays media. It presents a uniform interface (play, pause, currentTime, volume, duration, buffered, canPlayType, supportsFullScreen, …) and emits a uniform set of events (loadstart, play, pause, playing, ended, volumechange, durationchange, error, plus optional timeupdate, progress, fullscreen events). The player talks only to the tech interface, never to the browser's media element directly.
The Html5 tech
In Video.js 8 the only built-in tech is Html5 (src/js/tech/html5.js), which wraps the native <video>/<audio> element. The default techOrder is therefore ['html5'].
Note
Flash, Silverlight, and other plugin-based techs from earlier major versions are gone. Documentation, examples, or
techOrderarrays referencing aflashtech or.swffiles are obsolete for v8.
Source handlers and adaptive streaming
A tech can register source handlers that take over playback for specific formats. The bundled @videojs/http-streaming (VHS) registers as a source handler on the Html5 tech and plays HLS (.m3u8) and MPEG-DASH (.mpd) using the browser's Media Source Extensions (MSE). It is imported by src/js/index.js, so the standard build always includes it. videojs-contrib-quality-levels is also imported, giving a player.qualityLevels() API surface for representing available renditions.
Tech-first vs source-first selection
When choosing a (source, tech) pair, Video.js defaults to tech-first: for each tech in techOrder, it tries every source. With only one tech in v8 this simply means "the first source the HTML5 tech (with its source handlers) can play wins." Provide sources in priority order and always include type.
Configuring the Html5 tech
Tech options are passed under the html5 key:
videojs('my-player', {
html5: {
nativeTextTracks: false, // force emulated text tracks
nativeAudioTracks: false,
nativeVideoTracks: false,
vhs: { // options forwarded to @videojs/http-streaming
overrideNative: true
}
}
});
Tip
Forcing emulated text tracks (
nativeTextTracks: false) gives consistent caption styling and behavior across browsers, at the cost of relying onvideojs-vtt.jsfor parsing/rendering. Native tracks behave differently per browser (some allow multiple simultaneous tracks).
6. The Middleware Layer
Middleware (src/js/tech/middleware.js) sits between the player and the tech and can intercept method calls based on the source type. This is an advanced extension point used to adapt or augment playback for particular formats without modifying the player or tech.
videojs.use('video/foo', function(player) {
return {
setSource(srcObj, next) {
// transform the source, then continue the chain
next(null, srcObj);
},
currentTime(ct) {
return ct; // intercept getter results
},
setCurrentTime(ct) {
return ct; // intercept setter values
}
};
});
A middleware can short-circuit the chain by returning videojs.middleware.TERMINATOR. Middleware is keyed by MIME type (use '*' to match all sources). This mechanism powers integrations like ad insertion and DRM coordination that need to wrap time/seek/source semantics.
7. The Plugin System
Plugins (src/js/plugin.js) are the primary extension mechanism for application-level behavior. There are two flavors.
Basic plugins
A function registered by name. When called on a player, it runs with this bound to the player and is marked active. Setup events (beforepluginsetup, pluginsetup, and :name variants) fire around it.
videojs.registerPlugin('myBasic', function(options) {
this.addClass('has-my-basic');
});
Advanced plugins
A subclass of the base Plugin class. It gets the evented and stateful mixins, a scoped logger (this.log), a player reference, default state, and automatic dispose() wiring on player teardown. The base class throws if instantiated directly (Plugin must be sub-classed; not directly instantiated.).
const Plugin = videojs.getPlugin('plugin');
class MyAdvanced extends Plugin {
constructor(player, options) {
super(player);
this.state = { count: 0 };
this.on(player, 'play', () => this.setState({ count: this.state.count + 1 }));
}
}
videojs.registerPlugin('myAdvanced', MyAdvanced);
Registry and lifecycle
| API | Purpose |
|---|---|
videojs.registerPlugin(name, plugin) |
Register (basic fn or Plugin subclass). Throws on illegal names or re-registering core methods. |
videojs.deregisterPlugin(name) |
Remove from the registry (base plugin cannot be de-registered). |
videojs.getPlugin(name) / getPlugins() / getPluginVersion(name) |
Inspect the registry. |
player.hasPlugin(name) |
Whether registered. |
player.usingPlugin(name) |
Whether active on this player. |
Caution
Plugin names become properties on the player prototype. A name that collides with an existing player method/property is rejected (
Illegal plugin name, "<name>", already exists). Namespace plugin names defensively. The legacyvideojs.plugin()is deprecated in favor ofvideojs.registerPlugin().
8. Event System and Mixins
EventTarget
EventTarget (src/js/event-target.js) implements a DOM-like event interface — on/off/one/any/trigger plus standard aliases addEventListener/removeEventListener/dispatchEvent. When a triggered event name is in allowedEvents_, the corresponding on<Event> handler is also invoked.
Mixins
Two mixins are applied to components and plugins:
evented(src/js/mixins/evented.js) — grants event methods and an event bus (using the component'sel_when available, so events bubble through the DOM).stateful(src/js/mixins/stateful.js) — grants astateobject andsetState()that triggersstatechangedevents.
The Component constructor mixes these in conditionally (options.evented !== false). It also binds handleLanguagechange and listens for the player's languagechange event so UI text re-localizes automatically.
Tech event re-triggering
The player re-emits a fixed set of tech events as its own (TECH_EVENTS_RETRIGGER: progress, abort, suspend, emptied, stalled, loadedmetadata, loadeddata, timeupdate, resize, volumechange, texttrackchange). Certain events are queued when the playback rate is zero (canplay, canplaythrough, playing, seeked). This is why you can listen on the player for media events regardless of which tech produced them.
9. Configuration Reference
The complete option surface is documented in the User Guide's Player Options Reference. The table below is the authoritative list of defaults as defined in Player.prototype.options_ (video.js 8.23.8), which is what videojs.options exposes.
| Option | Default |
|---|---|
techOrder |
['html5'] (from Tech.defaultTechOrder_) |
html5 |
{} |
enableSourceset |
true |
inactivityTimeout |
2000 |
playbackRates |
[] |
liveui |
false |
children |
['mediaLoader','posterImage','titleBar','textTrackDisplay','loadingSpinner','bigPlayButton','liveTracker','controlBar','errorDisplay','textTrackSettings','resizeManager'] |
language |
browser preference → 'en' |
languages |
{} |
notSupportedMessage |
'No compatible source was found for this media.' |
normalizeAutoplay |
false |
fullscreen |
{ options: { navigationUI: 'hide' } } |
breakpoints |
{} |
responsive |
false |
audioOnlyMode |
false |
audioPosterMode |
false |
spatialNavigation |
{ enabled: false, horizontalSeek: false } |
enableSmoothSeeking |
false |
disableSeekWhileScrubbingOnMobile |
false |
disableSeekWhileScrubbingOnSTV |
false |
Default breakpoints
| Name | Max width (px) | Class |
|---|---|---|
tiny |
210 | vjs-layout-tiny |
xsmall |
320 | vjs-layout-x-small |
small |
425 | vjs-layout-small |
medium |
768 | vjs-layout-medium |
large |
1440 | vjs-layout-large |
xlarge |
2560 | vjs-layout-x-large |
huge |
Infinity |
vjs-layout-huge |
Global configuration flags
Set these on window before Video.js loads:
| Flag | Effect |
|---|---|
VIDEOJS_NO_DYNAMIC_STYLE = true |
Skip injecting the default-dimensions and per-player dimension <style> elements. Width/height are then applied directly to the media element. |
VIDEOJS_NO_BASE_THEME = true |
Skip injecting base skin styles (you must include them manually). |
HELP_IMPROVE_VIDEOJS = false |
Opt out of the small anonymous analytics pixel in CDN builds. |
Note
The default injected dimensions are
300×150for.video-js, andvjs-fluidusespadding-top: 56.25%(16:9). These constants live insrc/js/video.js.
10. The videojs Namespace API
videojs is both the player factory and the library namespace (src/js/video.js). Below is the complete static surface.
Player lifecycle
| Member | Description |
|---|---|
videojs(id, options, ready) |
Create or fetch a player. |
videojs.getPlayer(id) |
Fetch an existing player without creating one. |
videojs.getPlayers() / videojs.players |
Object of players keyed by id. |
videojs.getAllPlayers() |
Array of all (non-disposed) players. |
Registries
| Member | Description |
|---|---|
videojs.registerComponent(name, comp) / videojs.getComponent(name) |
Component registry. |
videojs.registerTech(name, tech) / videojs.getTech(name) |
Tech registry. |
videojs.registerPlugin(name, plugin) / videojs.deregisterPlugin(name) |
Plugin registry. |
videojs.getPlugin(name) / videojs.getPlugins() / videojs.getPluginVersion(name) |
Plugin lookups. |
videojs.use(type, middleware) |
Register middleware; videojs.middleware.TERMINATOR ends a chain. |
Track classes
videojs.TrackList, videojs.TextTrack, videojs.TextTrackList, videojs.AudioTrack, videojs.AudioTrackList, videojs.VideoTrack, videojs.VideoTrackList.
Hooks, events, and globals
| Member | Description |
|---|---|
videojs.hook(name, fn) / videojs.hookOnce / videojs.removeHook / videojs.hooks(name) |
Setup hooks: beforesetup (transform options) and setup (post-create). |
videojs.on/one/off/any/trigger |
Standalone event helpers. |
videojs.options |
The global default options object (= Player.prototype.options_). |
videojs.VERSION |
The library version string. |
videojs.addLanguage(code, data) |
Register a global translation dictionary. |
videojs.log / videojs.createLogger(name) |
Logging (supports log.level('debug')). |
videojs.EventTarget / videojs.Error |
The EventTarget class / error metadata registry. |
videojs.xhr |
Cross-browser XHR wrapper (@videojs/xhr). |
Utility modules (non-deprecated)
videojs.dom, videojs.obj, videojs.fn, videojs.num, videojs.str, videojs.url, videojs.time, videojs.browser.
Important
Many older top-level helpers are deprecated and slated for removal in 9.0 — see the Deprecated and Removed APIs table in the User Guide. Prefer the namespaced equivalents (
videojs.obj.merge,videojs.time.createTimeRanges,videojs.url.parseUrl,videojs.dom.*, nativeFunction.prototype.bind).
Setup hooks example
// Inject a default option into every player before it's created
videojs.hook('beforesetup', (videoEl, options) => {
return videojs.obj.merge(options, { fluid: true });
});
// Run code on every player after creation
videojs.hook('setup', (player) => {
player.log('player created:', player.id());
});
11. Player API Reference
The Player class (src/js/player.js) extends Component. Its public methods follow the HTML5 media API, with getter/setter semantics (call with no argument to get, with an argument to set). The complete public method set in 8.23.8:
Playback & time
play(), pause(), paused(), played(), ended(), scrubbing(), currentTime(), duration(), remainingTime(), remainingTimeDisplay(), buffered(), bufferedPercent(), bufferedEnd(), seekable(), seeking(), load(), reset().
Volume
volume(), muted(), defaultMuted().
Rate
playbackRate(), defaultPlaybackRate(), playbackRates().
Source & format
src(), currentSource(), currentSources(), currentSrc(), currentType(), canPlayType(), selectSource(), addSourceElement(), removeSourceElement(), preload(), autoplay(), loop(), poster(), playsinline(), crossOrigin().
Sizing & layout
width(), height(), dimension(), videoWidth(), videoHeight(), fluid(), fill(), aspectRatio(), responsive(), breakpoints(), currentBreakpoint(), currentBreakpointClass().
Fullscreen & PiP
requestFullscreen(), exitFullscreen(), isFullscreen(), supportsFullScreen(), enterFullWindow(), exitFullWindow(), fullWindowOnEscKey(), requestPictureInPicture(), exitPictureInPicture(), isInPictureInPicture(), disablePictureInPicture().
State, controls & errors
controls(), usingNativeControls(), error(), readyState(), networkState(), userActive(), reportUserActivity(), hasStarted(), isAudio(), audioOnlyMode(), audioPosterMode(), debug(), getCache(), getVideoPlaybackQuality().
Tracks
addTextTrack(), addRemoteTextTrack(), removeRemoteTextTrack() (plus textTracks(), remoteTextTracks(), remoteTextTrackEls(), audioTracks(), videoTracks() from track support).
Media metadata, language & misc
tech(), version(), language(), languages(), toJSON(), createModal(), loadMedia(), getMedia(), hasPlugin(), usingPlugin(), dispose().
Tip
player.loadMedia(media, ready)/player.getMedia()provide a structured way to set source, poster, text tracks, and metadata together — convenient for playlist-style integrations.player.toJSON()serializes the player's option/state snapshot.
12. Error Model
Video.js uses a MediaError class (src/js/media-error.js) that mirrors the W3C MediaError. player.error() returns one of these. The standard codes — order is fixed by the W3C spec and must not change:
| Code | Constant | Default message |
|---|---|---|
0 |
MEDIA_ERR_CUSTOM |
(custom message) |
1 |
MEDIA_ERR_ABORTED |
You aborted the media playback |
2 |
MEDIA_ERR_NETWORK |
A network error caused the media download to fail part-way. |
3 |
MEDIA_ERR_DECODE |
The media playback was aborted due to a corruption problem or because the media used features your browser did not support. |
4 |
MEDIA_ERR_SRC_NOT_SUPPORTED |
The media could not be loaded, either because the server or network failed or because the format is not supported. |
5 |
MEDIA_ERR_ENCRYPTED |
The media is encrypted and we do not have the keys to decrypt it. |
A MediaError also carries optional message, status (e.g. HTTP status from a plugin), and metadata (an object with an errorType aligned to videojs.Error) for richer diagnostics. The ErrorDisplay component (a ModalDialog) renders the localized message over the player when an error event fires.
Internal error/throw points worth knowing
| Message | Source | Meaning |
|---|---|---|
No techOrder specified. Did you overwrite videojs.options... |
player.js |
techOrder was emptied — usually from replacing videojs.options wholesale instead of overriding properties. |
Improper value supplied for aspect ratio... |
player.js |
aspectRatio not in width:height form. |
The element or ID supplied is not valid. (videojs) |
video.js |
videojs() given a non-element/missing id. |
Component <name> does not exist |
component.js |
addChild referenced an unregistered component. |
Illegal component name/component... |
component.js |
Bad registerComponent usage (e.g. registering a tech). |
Plugin must be sub-classed... / Illegal plugin name... |
plugin.js |
Misused plugin base class / colliding plugin name. |
Tip
When you intentionally customize global defaults, mutate properties of
videojs.options(e.g.videojs.options.autoplay = true) rather than reassigningvideojs.options = {...}, which discardstechOrderand other required defaults and triggers the "No techOrder specified" error.
13. Requirements and Compatibility
Runtime (the browser)
- Modern evergreen browsers (Chrome, Firefox, Safari, Edge) on desktop and mobile, plus many web-based smart-TV browsers.
- HTML5 media support is required (the only built-in tech wraps
<video>/<audio>). - Media Source Extensions (MSE) are required for HLS/DASH via the bundled streaming engine; Safari can also play HLS natively.
- The Fullscreen API and Picture-in-Picture API are used when available, with a CSS "full window" fallback (
preferFullWindow). - IE8 and Flash are not supported in v8.
Build/development (contributors)
- Node.js and npm (the project uses npm scripts and
npm-run-all). - Toolchain: Rollup (JS bundling), Babel (
@babel/preset-env+ runtime transform), Sass + PostCSS/Autoprefixer (CSS), TypeScript (tscfor type emit), JSDoc (API docs), Karma + QUnit + Sinon (tests), vjsstandard (lint), uglify-js/clean-css (minify), husky + lint-staged (pre-commit).
14. Build, Tooling, and Distribution
The build is orchestrated entirely through npm scripts in package.json. The headline targets:
| Script | What it does |
|---|---|
npm run build |
Full production build: build-dev, minify, copy, zip. |
npm run build-dev |
clean, lint errors, then build JS, CSS, languages, tests, and types. |
npm run build:js |
Bundle JS with Rollup (rollup -c). |
npm run build:css |
Compile Sass → CSS (default + CDN variants) and run PostCSS. |
npm run build:lang |
Compile lang/*.json into distributable language scripts (vjslang). |
npm run build:types |
Emit TypeScript definitions (tsc). |
npm run minify |
Minify JS (build/minify.js) and CSS (clean-css). |
npm run lint |
Lint with vjsstandard. |
npm test |
build-dev (pretest) then run unit (karma), node-require, and a11y tests in parallel. |
npm run docs:api |
Generate API docs with JSDoc. |
npm start |
Dev server: build the sandbox, then watch + serve via Karma. |
npm run watch |
Watch and rebuild JS, CSS, types, and languages. |
Distribution artifacts
The dist/ output (declared in package.json) includes:
dist/video.cjs.js— CommonJS (main).dist/video.es.js— ES module (module).dist/video.min.js— minified UMD for the CDN/<script>usage.dist/video-js.css/dist/video-js.min.css— default skin.dist/alt/video-js-cdn.css— CDN skin variant.dist/lang/*.json— language files.dist/font/— icon fonts (fromvideojs-font).dist/types/video.d.ts— TypeScript definitions.
Note
The single environment variable observed in the build is
CI_TEST_TYPE(read inrollup.config.js), which controls coverage instrumentation during CI test runs. There are no runtime environment variables — this is a browser library configured entirely through options and globalwindowflags.
15. Deployment and Integration
"Deployment" for Video.js means shipping its static assets and initializing players in your application. There is nothing to run server-side.
Deployment checklist
- [ ] Serve a version-pinned CSS + JS pair (matching versions).
- [ ] Decide delivery: CDN (simplest), self-hosted
dist/, or bundled via npm. - [ ] If bundling, import both
video.jsand its CSS; tree-shaking is limited because the entry pulls in VHS and quality-levels. - [ ] Ensure media and track servers send correct MIME types and CORS headers if cross-origin.
- [ ] Initialize players after their elements are in the DOM; dispose on teardown.
- [ ] Set the appropriate Content-Security-Policy (see Security).
Framework integration patterns
- React/Vue/Angular/Svelte: create the player in the mount/
useEffecthook against aref, anddispose()in the unmount/cleanup. Prefer the<video-js>element to avoid a flash of native controls. ConsiderVIDEOJS_NO_DYNAMIC_STYLEif the framework manages<head>. - Lazy loading: dynamically
import('video.js')to keep it out of the initial bundle. - Multiple players: unique ids per element; the global registry tracks them.
Rollback / version management
Because the library is a static asset, rollback is simply pointing back to a previously pinned version's CSS+JS URLs (or reverting the npm dependency). Keep the version pin in source control so changes are reviewable.
16. Security Analysis
Video.js runs untrusted-by-nature content (media and text tracks, often from third parties) inside the browser. The relevant surfaces:
Cross-origin content
The crossorigin attribute (anonymous / use-credentials) governs CORS for media and tracks. Cross-origin text tracks require CORS on the server and the attribute; browsers commonly extend crossorigin to the media source itself, so media files also need CORS headers. Use anonymous unless credentials are genuinely required.
Content Security Policy (CSP)
Video.js injects <style> elements at runtime for default and per-player dimensions, and historically uses some inline style attributes. This can conflict with strict CSP:
- To avoid runtime-injected
<style>elements, setwindow.VIDEOJS_NO_DYNAMIC_STYLE = true(and size players yourself) and/orwindow.VIDEOJS_NO_BASE_THEME = true(and include the skin manually). - CSP-friendliness around runtime inline style attributes is an actively tracked area (community issues request
style-src-attr 'none'compatibility — see Potential Improvements).
Warning
If you enforce a strict
style-src(no'unsafe-inline'), test thoroughly. You may need the no-dynamic-style/no-base-theme flags plus a nonce/hash strategy for any remaining injected styles, and you should track upstream CSP fixes.
Text track parsing
Emulated text tracks are parsed by videojs-vtt.js. Adaptive-streaming manifests (DASH) are parsed by VHS's mpd-parser, which historically pulled in an XML parser (@xmldom/xmldom) — a dependency where vulnerabilities have been reported in the ecosystem. Keep Video.js and its streaming dependencies up to date and run npm audit.
Trust model
- Video.js executes no remote code itself; it plays media and renders track text.
- Plugins run with full access to the player and page — only load plugins you trust, the same as any third-party script.
- The CDN build includes a small (~1% sampled) anonymous analytics pixel; disable it with
window.HELP_IMPROVE_VIDEOJS = falseif your policy requires.
Dependency hygiene
Run npm audit regularly. The streaming stack (@videojs/http-streaming, m3u8-parser, mpd-parser, mux.js, aes-decrypter) is the most security-relevant dependency cluster because it parses untrusted manifests and segments.
17. Performance Considerations
preload— use'metadata'or'none'for pages with many players to avoid eager downloads;'auto'is honored only when the browser agrees (mobile often ignores it).inactivityTimeout— controls auto-hide of the control bar; the player polls user activity.0disables auto-hide.- Timers — components batch timer cleanup via
Sets and a single dispose listener (rather than per-timer listeners) for efficiency. - Animation frames — use the named-rAF API (
requestNamedAnimationFrame) to coalesce repeated UI updates (e.g. progress bar) into a single queued frame. - Disposal — always
dispose()unused players; orphaned players keep timers, listeners, and DOM alive. This is both a leak and a correctness issue with streaming techs. - Bundle size — the standard build includes the streaming engine and quality-levels. If you only ever play progressive MP4, a custom build excluding VHS can reduce size (advanced; requires building from source).
enableSmoothSeeking— improves perceived scrubbing but increases update frequency; thedisableSeekWhileScrubbingOnMobile/OnSTVoptions reduce work on constrained devices.
18. Testing Strategy
The project has a substantial test suite under test/:
| Area | Detail |
|---|---|
| Unit tests | QUnit specs run via Karma (test/unit/**), covering components, the player, tracks, plugins, utils, and tech. |
| API tests | test/api/ validates the public API shape. |
| Require tests | test/require/ verifies the package loads correctly under Node (node.js), Browserify, and Webpack. |
| Accessibility | test:a11y (build/test-a11y.js) runs access-sniff over a sandbox page. |
| Coverage | Istanbul via rollup-plugin-istanbul; printed when CI_TEST_TYPE=coverage. |
The unit tests double as living documentation: test/unit/component.test.js, player.test.js, plugin-advanced.test.js, and plugin-static.test.js demonstrate subclassing components/techs/plugins and the expected lifecycle. Run the full suite with npm test (which first does a dev build), or use npm start for an interactive Karma watch server.
Tip
When writing a custom component, tech, or plugin, mirror the patterns in the corresponding
test/unit/*.test.jsfile — they exercise registration, the constructor signature(player, options), and disposal, which are the seams most likely to break.
19. Code Quality and Conventions
- Lint standard:
vjsstandard(astandard-derived ruleset).npm run lintchecks; the pre-commit hook (husky+lint-staged) auto-fixes staged.jsfiles and formats Markdown viaremark. - Language level: authored in modern JS (classes, modules, arrow functions,
Set/Map), transpiled by Babel with@babel/preset-envto broad browser support. - Globals via
global: theglobalpackage is used to accesswindow/document(import window from 'global/window') so the code is safe to load in non-browser/SSR contexts without throwing at import time. - Registration pattern: nearly every module ends with
Component.registerComponent('Name', Name)(orTech.registerTech/registerPlugin), wiring itself into the appropriate registry as a side effect of import. This is whysrc/js/index.jssimply imports modules to "activate" them. - JSDoc-first: the codebase is heavily annotated with JSDoc, used to generate the API docs and inform the emitted TypeScript types.
- Getter/setter methods: the consistent
foo()get /foo(value)set convention pervades the Player and Component APIs. - Defensive DOM/ARIA: components set ARIA roles/labels and manage focus (e.g.
ModalDialogfocus trapping,ClickableComponentkeyboard activation) as first-class concerns.
20. Contributing
- License & DCO: contributions are under Apache-2.0. There is no CLA; by submitting a PR you agree to the Developer's Certificate of Origin 1.1.
- Governance: project decisions are overseen by the Video.js Technical Steering Committee (TSC).
- Code of Conduct: participation is governed by the project's Contributor Code of Conduct.
- Workflow: fork, branch, run
npm install, develop withnpm start, ensurenpm run lintandnpm testpass, and open a PR. Commit messages follow the conventional-changelogvideojspreset (used to auto-generateCHANGELOG.md). - Translations: add or update a
lang/XX.jsonfile usinglang/en.jsonas the template; the build compiles these into distributable language scripts. - Docs: Markdown is linted/validated with
remark(docs:lint); API docs come from JSDoc (docs:api).
Note
A major version is on the horizon: the README announces "Big changes coming in Video.js 10, early 2026." Expect the v9.0 removals (the deprecated helpers in this guide) to land before or alongside it. Plan migrations off deprecated APIs now.
21. Potential Improvements and Tech Debt
Observations drawn from the source and the project's own issue tracker:
- CSP friendliness. Runtime inline style attributes and injected
<style>elements complicate strict CSP deployments. Open community issues request support forstyle-src-attr 'none'by avoiding runtime inline style attributes. Until resolved, strict-CSP integrators must rely on the no-dynamic-style/no-base-theme flags and careful testing. - Deprecated API removal (v9.0). A dozen top-level helpers are deprecated (
mergeOptions,bind, time/url helpers, DOM aliases,videojs.plugin). These remain as compatibility shims and add surface area; their removal in 9.0 will simplify the namespace but requires consumer migration. - Font icons → SVG. The icon font is still the default; SVG icons are behind
experimentalSvgIcons. Source comments note the intent to remove font icons entirely in a future major (v9), which will remove a class of icon-rendering and CSP edge cases. - Null-safety hardening. The tracker reports several defensive-coding gaps (e.g. fullscreen calls after the tech is unloaded, slider element access without null checks,
copyStyleSheetsToWindow()whendocument.headis null,updateSourceCaches_()assuming source shape). These are robustness improvements rather than functional breaks. - Event-listener edge case. A reported bug where
one()with multiple event types removes all listeners on the first trigger indicates a corner of the event system worth auditing for multi-type registrations. - Streaming dependency surveillance. The DASH/HLS parsers (notably the XML parser used for manifests) sit on a recurring vulnerability path in the ecosystem; keeping the streaming stack patched is ongoing maintenance, not a one-time fix.
- Accessibility refinements. Open issues note screen-reader announcements for the progress slider and an invalid-markup case for
menuitemradio— areas where the otherwise strong a11y story can be tightened.
Important
None of the above block production use. Video.js is mature, widely deployed (billions of monthly CDN end-users), and actively maintained. Treat these as the backlog you'd inherit if you forked or deeply customized the library.
22. Quick Reference
Initialize / fetch / dispose
const player = videojs('my-player', { controls: true, fluid: true });
const same = videojs.getPlayer('my-player'); // never creates
player.dispose(); // always on teardown
Most-used options
{
controls: true,
autoplay: 'muted', // browser-safe autoplay
preload: 'auto', // 'auto' | 'metadata' | 'none'
fluid: true, // or fill / aspectRatio: '16:9'
responsive: true,
playbackRates: [0.5, 1, 1.5, 2],
liveui: true, // for live streams
language: 'en',
html5: { nativeTextTracks: false }
}
Source (incl. HLS/DASH — no extra packages)
player.src({ src: '/stream/index.m3u8', type: 'application/x-mpegURL' });
player.src({ src: '/stream/manifest.mpd', type: 'application/dash+xml' });
Registries
videojs.registerComponent('Name', ComponentClass);
videojs.registerTech('Name', TechClass);
videojs.registerPlugin('name', pluginFnOrClass);
videojs.use('video/type', middlewareFactory);
videojs.addLanguage('es', { Play: 'Reproducción' });
Global flags (set before load)
window.VIDEOJS_NO_DYNAMIC_STYLE = true; // strict CSP / framework-managed head
window.VIDEOJS_NO_BASE_THEME = true; // supply skin manually
window.HELP_IMPROVE_VIDEOJS = false; // opt out of CDN analytics
Key facts
| Version | 8.23.8 (Apache-2.0) |
Default techOrder |
['html5'] (no Flash) |
| HLS/DASH | bundled (@videojs/http-streaming) |
| Entry | src/js/index.js → src/js/video.js |
| Player tracking | global, by element id |
| Next major | Video.js 10 (announced, early 2026) |