This package provides a customizable, modular web-based media player built on top of Video.js. It supports advanced features such as DRM, adaptive streaming (DASH, HLS), Shaka Player integration, and robust subtitle and audio-only handling. The code is designed to quickly integrate secure video playback into web applications, with options for custom controls and extensibility.
video.js)
shaka-player) - optional, for advanced DASH/HLS
streaming
videojs-contrib-eme - for
EME (Encrypted Media Extensions) support
/path/to/videojs-package).
cd /path/to/videojs-package
npm install
npm run build
npm link
cd /path/to/your-frontend-project
npm link videojs-drm-player
cd /path/to/videojs-package
npm run build
cd /path/to/your-frontend-project
npm install /path/to/videojs-package
Example:
npm install ../videojs-package
# or with absolute path
npm install /Users/username/Desktop/videojs-package
Note: This method copies the package files into
your project's node_modules directory (not a
symlink). The package will be tracked in your
package.json with a file: protocol
path.
cd /path/to/videojs-package
npm run build
cd /path/to/your-frontend-project
npm install /path/to/videojs-package
Note: Unlike npm link, changes do
not automatically reflect. You must reinstall after each change.
The final product is a JS package that initializes a video player that supports:
videojs-package/
├── src/
│ ├── index.js # Main player logic and class
│ ├── components/
│ │ ├── CustomControlBar.js # Custom UI controls logic
│ │ └── control-bar/
│ │ └── components/ # Individual control components
│ ├── shaka/
│ │ ├── shaka.js # Shaka Player tech integration
│ │ ├── shaka-drm-config.js # Shaka DRM configuration
│ │ ├── setup-quality-tracks.js
│ │ ├── setup-text-tracks.js
│ │ └── setup-audio-tracks.js
│ ├── drm-config.js # DRM configuration templates (for non-Shaka)
│ ├── helpers/
│ │ ├── helper.js # Utility functions
│ │ └── renditionHelper.js
│ ├── constants/
│ │ └── displayFormat.js # Audio-only display format constants
│ └── styles/ # CSS files for styling
├── package.json # Project metadata and dependencies
├── webpack.config.js # Webpack build configuration
└── README.md # Project documentation
File Roles:
index.js: Main entry point, player class, and
orchestration.
components/CustomControlBar.js: Handles custom UI
controls (play, pause, subtitles, etc.).
shaka/shaka.js: Shaka Player tech integration for
Video.js.
shaka/shaka-drm-config.js: DRM setup and configuration
for Shaka Player.
drm-config.js: DRM setup and configuration for standard
Video.js (non-Shaka).
helpers/helper.js: Utility functions for source detection
and audio-only detection.
constants/displayFormat.js: Constants for audio-only
display formats.
styles/: Styling for the player and controls.Create and configure a Video.js player instance, attach it to the DOM, and set up controls.
Example Initialization:const player = new VideoJSDRMPlayer({
containerId: "video-container", // (required) id of the container div
source: "https://example.com/video.mpd", // (required) video source URL
totalWidth: "100%", // (optional) total container width
totalHeight: "auto", // (optional) total container height
isDRM: true, // (optional) enable DRM
useShakaTech: false, // (optional) use Shaka Player tech
displayFormat: 1, // (optional) audio-only display format (1-4)
drmConfig: { // (optional) DRM config object
widevine: {
url: "https://lic.staging.drmtoday.com/license-proxy-widevine/cenc/",
licenseHeaders: {
"x-dt-custom-data": "your-custom-data"
}
},
fairplay: {
certificateUrl: "https://lic.staging.drmtoday.com/license-server-fairplay/cert/orca",
url: "https://lic.staging.drmtoday.com/license-server-fairplay/",
licenseHeaders: {
"x-dt-custom-data": "your-custom-data"
}
},
playready: {
url: "https://lic.staging.drmtoday.com/license-proxy-headerauth/drmtoday/RightsManager.asmx",
licenseHeaders: {
"x-dt-custom-data": "your-custom-data"
}
}
},
controls: false, // (optional) use built-in controls
autoplay: false, // (optional)
loop: false, // (optional)
muted: false, // (optional)
fluid: false, // (optional) responsive fluid mode
poster: "https://example.com/poster.jpg" // (optional) poster image
});
Required Parameters:
containerId: The id of the HTML element to attach the
player to.
source: The video source URL (DASH, HLS, MP4, etc.).
totalWidth, totalHeight: Container
dimensions.
isDRM: Enable DRM (default: false).useShakaTech: Use Shaka Player as the tech (default:
false). Recommended for DASH/HLS streaming.
displayFormat: Audio-only display format (1-4, see
section 6).
drmConfig: DRM configuration object (see example above).
controls: Use built-in controls (default: false, uses
custom controls).
autoplay, loop, muted: Standard
video options.
containerId or
source will cause initialization to fail.
isDRM is true, drmConfig must be
provided.
Configure DRM protection when initializing the player. The package supports two approaches: standard Video.js DRM (via videojs-contrib-eme) and Shaka Player DRM. The DRM configuration is passed during player initialization.
When useShakaTech is false or not specified,
DRM is handled via the videojs-contrib-eme plugin.
When useShakaTech is true, DRM is handled
through Shaka Player's built-in DRM system.
const player = new VideoJSDRMPlayer({
containerId: "video-container",
source: "https://example.com/video.mpd",
isDRM: true, // Enable DRM
useShakaTech: false, // Use standard Video.js (default)
drmConfig: {
widevine: {
url: "https://lic.staging.drmtoday.com/license-proxy-widevine/cenc/",
licenseHeaders: {
"x-dt-custom-data": "your-custom-data-here"
}
},
fairplay: {
certificateUrl: "https://lic.staging.drmtoday.com/license-server-fairplay/cert/orca",
url: "https://lic.staging.drmtoday.com/license-server-fairplay/",
licenseHeaders: {
"x-dt-custom-data": "your-custom-data-here"
}
},
playready: {
url: "https://lic.staging.drmtoday.com/license-proxy-headerauth/drmtoday/RightsManager.asmx",
licenseHeaders: {
"x-dt-custom-data": "your-custom-data-here"
}
}
}
});
Pitfall:
certificateUrl must be accessible and
return a valid certificate.
x-dt-custom-data header is commonly used for DRMtoday.
isDRM is true,
drmConfig must be provided, otherwise initialization will
fail.
Understand how the controls option affects player behavior
and how audioOnlyMode is automatically set when audio-only
content is detected.
controls Option
The controls option determines which control system the
player uses:
controls: true (or truthy): Uses Video.js
built-in controls. The player will display the standard Video.js control
bar with all native features.
controls: false (or falsy, default): Uses
custom controls provided by the package. The player will display the
custom control bar with play/pause, volume, time display, subtitles,
settings, and fullscreen buttons.
// Using built-in Video.js controls
const player1 = new VideoJSDRMPlayer({
containerId: "player-1",
source: "https://example.com/video.mp4",
controls: true // Use Video.js built-in controls
});
// Using custom controls (default)
const player2 = new VideoJSDRMPlayer({
containerId: "player-2",
source: "https://example.com/video.mp4",
controls: false // Use custom controls (or omit this option)
});
How It Works:
controls: false (default), the package automatically creates and attaches a CustomControlBar component to the player after initialization.
controls: true, Video.js uses its native control bar, and the package does not create custom controls.
audioOnlyMode Option
When the library automatically detects audio-only content (based on MIME
type or video dimensions), it handles the UI differently depending on the
controls option:
controls: true (Built-in Controls)
If audio-only content is detected and controls: true, the
library automatically sets audioOnlyMode: true in the player
options. This tells Video.js to handle audio-only content using its
built-in audio-only mode, which provides appropriate UI adjustments.
// Audio-only with built-in controls
const player = new VideoJSDRMPlayer({
containerId: "audio-player",
source: "https://example.com/audio.mp3",
controls: true // Built-in controls
});
// When audio-only is detected, the library automatically sets:
// this.options.audioOnlyMode = true
// Video.js will handle the audio-only UI automatically
Case 2: When controls: false (Custom Controls)
If audio-only content is detected and controls: false, the
library applies the custom audio-only display format (based on the
displayFormat option) instead of setting
audioOnlyMode. This allows you to have full control over how
audio-only content is displayed using the custom control system.
// Audio-only with custom controls
const player = new VideoJSDRMPlayer({
containerId: "audio-player",
source: "https://example.com/audio.mp3",
controls: false, // Custom controls
displayFormat: 1 // Use display format 1 (control bar only)
});
// When audio-only is detected, the library calls:
// this.updateAudioOnlyBeforeInitialization(true)
// This applies the selected displayFormat CSS classes
// audioOnlyMode is NOT set in this case
Summary:
| Content Type | controls Value |
audioOnlyMode Behavior |
UI Handling |
|---|---|---|---|
| Audio-only | true |
✅ Automatically set to true |
Video.js built-in audio-only UI |
| Audio-only | false |
❌ Not set (remains undefined) |
Custom displayFormat CSS classes applied |
| Video | true |
❌ Not set | Video.js built-in controls with standard video UI |
| Video | false |
❌ Not set | Custom controls with custom video UI |
audioOnlyMode option is automatically set by the
library and should not be manually set in your initialization options. It only works when controls: true.
controls: true and want to customize audio-only
display, you cannot use displayFormat options. Video.js
will handle it automatically via audioOnlyMode.
controls: false, you must specify
displayFormat to control how audio-only content is
displayed (see section 6 for display format options).
Detect and adjust UI for audio-only content based on the selected display format.
Code Snippet:const srcType = detectSourceType(options.source);
this.isAudioOnly = isAudioMime(srcType);
// After metadata loads
this.player.on("loadedmetadata", () => {
setTimeout(() => {
if (!this.options.controls) {
this.updateAudioOnlyAfterInitialization();
}
}, 500);
});
Explanation:
displayFormat option.
Shaka Player is integrated as a custom Video.js tech, providing advanced streaming capabilities for DASH and HLS content. It offers better control over adaptive bitrate streaming, DRM, and track management compared to the standard Video.js HTTP Streaming (VHS) tech.
To use Shaka Player, set useShakaTech: true in the player
options:
const player = new VideoJSDRMPlayer({
containerId: "video-container",
source: "https://example.com/video.mpd",
useShakaTech: true, // Enable Shaka Player
isDRM: true,
drmConfig: {
// ... DRM configuration
}
});
The Shaka Player is registered as a custom tech in Video.js:
// Register tech with Video.js
videojs.registerTech("shaka", Shaka);
// In player options
techOrder: this.options.useShakaTech ? ["shaka", "html5"] : ["html5"]
Key Features:
When using Shaka Player, DRM is configured through Shaka's DRM system:
// In shaka-drm-config.js
export async function setupShakaDRM(shakaPlayer, drmConfig = {}) {
const config = {
drm: {
servers: {},
},
};
// Widevine
if (drmConfig.widevine?.url) {
config.drm.servers["com.widevine.alpha"] = drmConfig.widevine.url;
}
// FairPlay
if (drmConfig.fairplay?.certificateUrl) {
const cert = await fetch(drmConfig.fairplay.certificateUrl);
config.drm.servers["com.apple.fps"] = drmConfig.fairplay.url;
config.drm.advanced = {
"com.apple.fps": {
serverCertificate: new Uint8Array(await cert.arrayBuffer()),
},
};
}
// PlayReady
if (drmConfig.playready?.url) {
config.drm.servers["com.microsoft.playready"] = drmConfig.playready.url;
}
shakaPlayer.configure(config);
}
Shaka Player automatically sets up quality tracks, text tracks, and audio tracks:
initShakaMenus() {
// Setup quality tracks (renditions)
setupQualityTracks(this, this.shaka_);
// Setup text tracks (subtitles)
setupTextTracks(this, this.shaka_);
// Setup audio tracks
setupAudioTracks(this, this.shaka_);
}
Benefits:
| Scenario | Use Shaka Player | Use Standard Video.js |
|---|---|---|
| DASH Streaming | ✅ Recommended | ⚠️ Limited support |
| HLS Streaming | ✅ Better control | ✅ Native support |
| Advanced DRM | ✅ More flexible | ✅ Works with EME plugin |
| Quality Selection | ✅ Automatic | ⚠️ Manual setup |
| Simple MP4 | ❌ Overkill | ✅ Recommended |
The package automatically detects audio-only content and provides four
different display formats to suit various use cases. The display format
is controlled by the displayFormat option.
The displayFormat option accepts values from 1 to 4:
const DISPLAY_FORMAT = {
DEFAULT: 1, // Control bar only (hidden video)
VIDEO_AREA: 2, // Show video area with black background
PLAY_PAUSE_ONLY: 3, // Minimal UI - play/pause button only
HIDDEN: 4 // Completely hidden
};
Value: 1 or
DISPLAY_FORMAT.DEFAULT
Description: Hides the video element completely and shows only the control bar. This is the default behavior for audio-only content.
const player = new VideoJSDRMPlayer({
containerId: "audio-player",
source: "https://example.com/audio.mp3",
displayFormat: 1 // or DISPLAY_FORMAT.DEFAULT
});
CSS Applied:
.video-js.audio-only.display-format-1 {
height: auto !important;
background-color: transparent;
}
.video-js.audio-only.display-format-1 .vjs-tech,
.video-js.audio-only.display-format-1 video {
display: none !important;
}
Use Case: Podcast players, music players, audio streaming
services.
Value: 2 or
DISPLAY_FORMAT.VIDEO_AREA
Description: Shows a video area with a black background, maintaining the aspect ratio of a video player while playing audio.
const player = new VideoJSDRMPlayer({
containerId: "audio-player",
source: "https://example.com/audio.mp3",
displayFormat: 2 // or DISPLAY_FORMAT.VIDEO_AREA
});
CSS Applied:
.video-js.audio-only.display-format-2 {
height: auto;
}
.video-js.audio-only.display-format-2 .vjs-tech {
display: block;
background-color: #000;
}
Use Case: When you want to maintain a consistent video
player layout even for audio content.
Value: 3 or
DISPLAY_FORMAT.PLAY_PAUSE_ONLY
Description: Shows only a circular play/pause button. All other controls are hidden. Perfect for minimal, embedded audio players.
const player = new VideoJSDRMPlayer({
containerId: "audio-player",
source: "https://example.com/audio.mp3",
displayFormat: 3, // or DISPLAY_FORMAT.PLAY_PAUSE_ONLY
poster: "https://example.com/album-cover.jpg" // Optional: background image
});
CSS Applied:
.video-js.audio-only.display-format-3 {
display: inline-block;
padding: 5px;
background-color: transparent;
}
.video-js.audio-only.display-format-3 .vjs-tech,
.video-js.audio-only.display-format-3 video {
display: none !important;
}
.video-js.audio-only.display-format-3 .custom-progress-container,
.video-js.audio-only.display-format-3 .custom-controls-right {
display: none;
}
.video-js.audio-only.display-format-3 .videojs-drm-play-button {
border-radius: 50%;
background: linear-gradient(135deg, #b9bfde 0%, #89838e 100%);
}
Features:
Value: 4 or
DISPLAY_FORMAT.HIDDEN
Description: Completely hides the player. Useful when you want to control playback programmatically without showing any UI.
const player = new VideoJSDRMPlayer({
containerId: "audio-player",
source: "https://example.com/audio.mp3",
displayFormat: 4 // or DISPLAY_FORMAT.HIDDEN
});
// Control programmatically
player.play();
player.setVolume(0.5);
CSS Applied:
.video-js.audio-only.display-format-4 {
display: none;
}
Use Case: Background audio, programmatic control, hidden
players for automation.
| Format | Video Area | Control Bar | Play Button | Best For |
|---|---|---|---|---|
| 1 - DEFAULT | ❌ Hidden | ✅ Full | ✅ Visible | Podcasts, music players |
| 2 - VIDEO_AREA | ✅ Black background | ✅ Full | ✅ Visible | Consistent video layout |
| 3 - PLAY_PAUSE_ONLY | ❌ Hidden | ❌ Hidden | ✅ Only button | Minimal embedded players |
| 4 - HIDDEN | ❌ Hidden | ❌ Hidden | ❌ Hidden | Programmatic control |
The player automatically detects audio-only content in two ways:
.mp3, .m4a,
.aac).
// Automatic detection
const srcType = detectSourceType(options.source);
this.isAudioOnly = isAudioMime(srcType);
// Or check dimensions after load
if (this.player.videoWidth() === 0 && this.player.videoHeight() === 0) {
this.isAudioOnly = true;
}
The package supports per-instance CSS customization, allowing you to style each player instance independently. This is achieved through container-specific class names and CSS selectors.
Each player instance creates a video element with a unique ID based on the container ID:
// Player creates element with ID: {containerId}-video
const player1 = new VideoJSDRMPlayer({
containerId: "player-1",
source: "https://example.com/video1.mp4"
});
// Creates: <video id="player-1-video" class="video-js">
const player2 = new VideoJSDRMPlayer({
containerId: "player-2",
source: "https://example.com/video2.mp4"
});
// Creates: <video id="player-2-video" class="video-js">
Target specific players using their container ID:
/* Style player-1 specifically */
#player-1-video.video-js {
border: 2px solid #007acc;
border-radius: 12px;
}
#player-1-video .custom-video-control-bar {
background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
}
/* Style player-2 differently */
#player-2-video.video-js {
border: 2px solid #ff6b6b;
border-radius: 8px;
}
Add classes to the container element and use them for styling:
<div id="player-1" class="premium-player"></div>
<div id="player-2" class="basic-player"></div>
/* Premium player styling */
.premium-player #player-1-video.video-js {
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
}
.premium-player .custom-video-control-bar {
background: rgba(0, 122, 204, 0.9);
}
/* Basic player styling */
.basic-player #player-2-video.video-js {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
Use data attributes for more flexible targeting:
<div id="player-1" data-theme="dark" data-size="large"></div>
<div id="player-2" data-theme="light" data-size="small"></div>
/* Dark theme */
[data-theme="dark"] .video-js {
background-color: #1a1a1a;
}
[data-theme="dark"] .custom-video-control-bar {
background: rgba(0, 0, 0, 0.8);
color: #fff;
}
/* Light theme */
[data-theme="light"] .video-js {
background-color: #f5f5f5;
}
[data-theme="light"] .custom-video-control-bar {
background: rgba(255, 255, 255, 0.9);
color: #000;
}
/* Size variations */
[data-size="large"] .video-js {
width: 100%;
max-width: 1200px;
}
[data-size="small"] .video-js {
width: 100%;
max-width: 480px;
}
You can customize individual control components per instance:
/* Custom play button for player-1 */
#player-1-video .videojs-drm-play-button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
width: 60px;
height: 60px;
}
/* Different play button for player-2 */
#player-2-video .videojs-drm-play-button {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
border-radius: 8px;
width: 50px;
height: 50px;
}
/* Custom volume button */
#player-1-video .videojs-drm-volume-button {
background: rgba(102, 126, 234, 0.2);
border-radius: 6px;
}
/* Custom progress bar */
#player-1-video .custom-progress-bar {
height: 6px;
background: rgba(102, 126, 234, 0.3);
}
#player-1-video .custom-progress-bar .custom-progress-filled {
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
}
Customize audio-only display formats per instance:
/* Player-1: Custom play/pause only style */
#player-1-video.audio-only.display-format-3 .videojs-drm-play-button {
background: url('album-cover.jpg') center/cover;
border: 3px solid #fff;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
}
/* Player-2: Different style for same format */
#player-2-video.audio-only.display-format-3 .videojs-drm-play-button {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
border: none;
box-shadow: 0 2px 10px rgba(238, 90, 111, 0.4);
}
Apply different styles based on screen size for specific instances:
/* Desktop: Large player-1 */
#player-1-video.video-js {
width: 100%;
max-width: 1200px;
}
/* Tablet: Medium player-1 */
@media (max-width: 768px) {
#player-1-video.video-js {
max-width: 768px;
}
#player-1-video .videojs-drm-play-button {
width: 50px;
height: 50px;
}
}
/* Mobile: Compact player-1 */
@media (max-width: 480px) {
#player-1-video.video-js {
max-width: 100%;
}
#player-1-video .videojs-drm-play-button {
width: 40px;
height: 40px;
}
#player-1-video .custom-video-control-bar {
padding: 8px;
}
}
<!-- HTML -->
<div id="premium-player" class="player-container premium"></div>
<div id="standard-player" class="player-container standard"></div>
<div id="minimal-player" class="player-container minimal"></div>
/* Premium Theme */
.premium #premium-player-video.video-js {
border: 3px solid #007acc;
border-radius: 16px;
box-shadow: 0 12px 48px rgba(0, 122, 204, 0.3);
overflow: hidden;
}
.premium .custom-video-control-bar {
background: linear-gradient(to top,
rgba(0, 122, 204, 0.95) 0%,
rgba(0, 122, 204, 0.7) 50%,
transparent 100%);
padding: 20px;
}
.premium .videojs-drm-play-button {
background: linear-gradient(135deg, #007acc 0%, #005a9e 100%);
box-shadow: 0 4px 16px rgba(0, 122, 204, 0.4);
}
/* Standard Theme */
.standard #standard-player-video.video-js {
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.standard .custom-video-control-bar {
background: rgba(0, 0, 0, 0.7);
}
/* Minimal Theme */
.minimal #minimal-player-video.video-js {
border: none;
box-shadow: none;
}
.minimal .custom-video-control-bar {
background: transparent;
padding: 10px;
}
.minimal .videojs-drm-play-button {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(10px);
}
Use CSS variables for easy theme switching:
/* Define theme variables on container */
#player-1 {
--player-primary-color: #007acc;
--player-secondary-color: #005a9e;
--player-control-bg: rgba(0, 122, 204, 0.9);
--player-border-radius: 12px;
}
#player-2 {
--player-primary-color: #ff6b6b;
--player-secondary-color: #ee5a6f;
--player-control-bg: rgba(255, 107, 107, 0.9);
--player-border-radius: 8px;
}
/* Use variables in styles */
.video-js {
border: 2px solid var(--player-primary-color, #007acc);
border-radius: var(--player-border-radius, 8px);
}
.custom-video-control-bar {
background: var(--player-control-bg, rgba(0, 0, 0, 0.8));
}
.videojs-drm-play-button {
background: linear-gradient(135deg,
var(--player-primary-color, #007acc) 0%,
var(--player-secondary-color, #005a9e) 100%);
}
You can change themes dynamically via JavaScript:
// Change theme dynamically
document.getElementById('player-1').style.setProperty('--player-primary-color', '#ff6b6b');
document.getElementById('player-1').style.setProperty('--player-secondary-color', '#ee5a6f');
isDRM is set to
true.
videojs.log.level('debug')
shaka.log.setLevel(shaka.log.Level.DEBUG)
player.on('ready', () => console.log('Player ready'));
player.on('error', (error) => console.error('Player error:', error));
navigator.requestMediaKeySystemAccess
x-dt-custom-data for DRMtoday)
For further questions or advanced customization, refer to the official Video.js documentation, Shaka Player documentation, or contact your system administrator.