Video.js Media Player Package

Table of Contents

1. Preface / Introduction

a. Background and Purpose

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.

b. Scope and Learning Objectives


2. Environment and Prerequisites

a. Programming Language, Libraries, and Frameworks

b. Execution Environment

c. Installation and Setup

  1. Download the zipped package at: https://dr.orca.jp/players/videojs/{uploaded-date}/videojs-package.zip
  2. Unzip the package to your desired location (e.g., /path/to/videojs-package).
  3. Navigate to the unzipped package directory and install dependencies:
    cd /path/to/videojs-package
    npm install
    npm run build
  4. Choose one of the following installation methods:

    Method 1: Using npm link (Recommended for Development)

    1. In the package directory, create a global link:
      npm link
    2. Navigate to your frontend project source directory and link the package:
      cd /path/to/your-frontend-project
      npm link videojs-drm-player
    3. Note: If you make changes to the package source code, rebuild the package:
      cd /path/to/videojs-package
      npm run build

    Method 2: Using npm install with Source Path

    1. Navigate to your frontend project source directory and install the package using the local path:
      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.

    2. Important: If you make changes to the package source code, you must:
      1. Rebuild the package:
        cd /path/to/videojs-package
        npm run build
      2. Reinstall the package in your project:
        cd /path/to/your-frontend-project
        npm install /path/to/videojs-package
      3. Restart your development server or rebuild your application.

      Note: Unlike npm link, changes do not automatically reflect. You must reinstall after each change.


3. Overview of the Code

a. Final Output

The final product is a JS package that initializes a video player that supports:

b. Directory Structure

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:


4. Step-by-Step Code Explanation

4.1. Player Initialization

Purpose:

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: Optional Parameters: Initialization Validations: Pitfall:

4.2. DRM Configuration at Initialization

Purpose:

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.

Case 1: Using Standard Video.js (without Shaka Player)

When useShakaTech is false or not specified, DRM is handled via the videojs-contrib-eme plugin.

Case 2: Using Shaka Player

When useShakaTech is true, DRM is handled through Shaka Player's built-in DRM system.

Example Initialization:
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:

4.3. Controls Option and Audio-Only Mode

Purpose:

Understand how the controls option affects player behavior and how audioOnlyMode is automatically set when audio-only content is detected.

The controls Option

The controls option determines which control system the player uses:

Example:
// 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:

The 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:

Case 1: When 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
Pitfall:

4.4. Audio-Only Content Detection

Purpose:

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: Pitfall:

5. Shaka Player Integration

5.1. Overview

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.

5.2. Enabling Shaka Player

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
  }
});

5.3. Shaka Player Tech Implementation

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:

5.4. Shaka Player DRM Configuration

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);
}

5.5. Track Management with Shaka Player

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:

5.6. When to Use Shaka Player

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

6. Audio-Only Content Handling

6.1. Overview

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.

6.2. Display Format Options

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
};

6.3. Display Format 1: DEFAULT (Control Bar Only)

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.

6.4. Display Format 2: VIDEO_AREA (Black Background)

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.

6.5. Display Format 3: PLAY_PAUSE_ONLY (Minimal UI)

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: Use Case: Embedded audio players in articles, minimal music players, background audio controls.

6.6. Display Format 4: HIDDEN

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.

6.7. Display Format Comparison

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

6.8. Automatic Detection

The player automatically detects audio-only content in two ways:

  1. MIME Type Detection: Checks if the source URL ends with audio MIME types (e.g., .mp3, .m4a, .aac).
  2. Video Dimension Detection: After metadata loads, checks if video dimensions are 0x0.
// 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;
}

7. CSS Customization for Multiple Instances

7.1. Overview

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.

7.2. Container-Specific Styling

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">

7.3. CSS Selector Strategies

Strategy 1: Container ID Selector

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;
}

Strategy 2: Container Class Selector

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);
}

Strategy 3: Data Attributes

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;
}

7.4. Customizing Control Bar Components

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%);
}

7.5. Audio-Only Format Customization

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);
}

7.6. Responsive Customization

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;
  }
}

7.7. Complete Example: Multiple Themed Players

<!-- 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);
}

7.8. CSS Variables for Dynamic Theming

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');

7.9. Best Practices


8. Supplementary Notes and Advanced Topics

a. Better Design Practices

b. Advanced Considerations


9. Error Handling and Debugging Tips

a. Common Error Messages

b. Debugging Tools

c. DRM Debugging


For further questions or advanced customization, refer to the official Video.js documentation, Shaka Player documentation, or contact your system administrator.