# Video.js Media Player Package

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

- Understand the structure and flow of the player code.
- Learn how to configure and modify variables (such as video source, DRM credentials, and UI options) to achieve the desired playback.
- Gain insight into extending or customizing the player for your own needs.
- Learn how to use Shaka Player for advanced streaming scenarios.
- Understand audio-only content display options and CSS customization techniques.

## 2. Environment and Prerequisites

### a. Programming Language, Libraries, and Frameworks

- Language: JavaScript (ES6+)
- Main Library: Video.js (`video.js`)
- Streaming Engine: Shaka Player (`shaka-player`) - optional, for advanced DASH/HLS streaming
- DRM Plugin: `videojs-contrib-eme` - for EME (Encrypted Media Extensions) support
- Bundler: Webpack

### b. Execution Environment

- OS: macOS, Windows, iOS, Android
- Browser: Modern browsers (Chrome, Edge, Safari)

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

- DRM-protected and non-protected video playback
- Shaka Player integration for advanced streaming (DASH, HLS)
- Customizable controls (play, pause, subtitles, quality, etc.)
- Subtitle selection and rendering
- Responsive UI
- Audio-only content detection and multiple display format options
- Per-instance CSS customization

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

- `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.

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

- `containerId`: The id of the HTML element to attach the player to.
- `source`: The video source URL (DASH, HLS, MP4, etc.).

Optional Parameters:

- `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.
- Other options of Video.js can also be used here.

Initialization Validations:

- The constructor validates required options and sets up the player container.
- Throws clear errors if required parameters are missing.
- Automatically detects if source is audio-only based on MIME type.

Pitfall:

- Forgetting to provide `containerId` or `source` will cause initialization to fail.
- If `isDRM` is true, `drmConfig` must be provided.

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

- DRM misconfiguration will result in playback errors. Ensure all required fields are provided.
- For FairPlay, the `certificateUrl` must be accessible and return a valid certificate.
- License headers must be correctly formatted. The `x-dt-custom-data` header is commonly used for DRMtoday.
- If `isDRM` is `true`, `drmConfig` must be provided, otherwise initialization will fail.

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

- `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.

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:

- When `controls: false` (default), the package automatically creates and attaches a `CustomControlBar` component to the player after initialization.
- When `controls: true`, Video.js uses its native control bar, and the package does not create custom controls.
- Custom controls are modular and can be extended, while built-in controls provide standard Video.js functionality.

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

- The `audioOnlyMode` option is automatically set by the library and should not be manually set in your initialization options. It only works when `controls: true`.
- If you set `controls: true` and want to customize audio-only display, you cannot use `displayFormat` options. Video.js will handle it automatically via `audioOnlyMode`.
- If you set `controls: false`, you must specify `displayFormat` to control how audio-only content is displayed (see section 6 for display format options).

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

- The function checks if the loaded media is audio-only based on MIME type and video dimensions.
- Applies CSS classes based on the selected `displayFormat` option.
- Uses the instance context to access and update state.

Pitfall:

- If you customize the UI, ensure you handle the audio-only classes correctly (see section 6).

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

- Automatic DASH and HLS support
- Advanced adaptive bitrate (ABR) streaming
- Built-in DRM support for Widevine, FairPlay, and PlayReady
- Quality track management
- Text track (subtitle) management
- Audio track management

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

- Automatic track detection from manifest
- Quality selection menu in settings
- Subtitle selection and styling
- Audio language selection

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

- Circular play/pause button with gradient background
- Can use poster image as button background
- Minimal footprint - perfect for inline audio

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

- Use Specific Selectors: Always target by container ID or class to avoid style conflicts.
- Namespace Your Styles: Prefix custom classes to avoid conflicts with other stylesheets.
- Use CSS Variables: For themes that need to change dynamically.
- Test Responsive: Ensure custom styles work across different screen sizes.
- Avoid !important: Use specificity instead of !important when possible.
- Document Your Themes: Keep a style guide for your custom themes.

## 8. Supplementary Notes and Advanced Topics

### a. Better Design Practices

- Separation of Concerns:
  Logic for controls, DRM, audio-only, and subtitle handling are split into separate modules for maintainability.
- Extensibility:
  You can add new controls or features by extending the relevant modules.
- Context Passing:
  All handler modules receive the player instance as context, making state management explicit and modular.
- Tech Selection:
  Choose between standard Video.js and Shaka Player based on your streaming requirements.

### b. Advanced Considerations

- Security:
  Never expose DRM credentials in client-side code for production.
  Use HTTPS for all media and license requests.
- Performance:
  Shaka Player provides better performance for DASH streaming compared to standard Video.js VHS.
- Browser Compatibility:
  Test DRM support across different browsers. Widevine works on Chrome/Edge, FairPlay on Safari, PlayReady on Edge.
- Multiple Instances:
  Each player instance is independent. You can have multiple players on the same page with different configurations.

## 9. Error Handling and Debugging Tips

### a. Common Error Messages

- "Container with id ... not found":
  Check your HTML and ensure the container exists before initializing the player.
- "source parameter is required":
  Provide a valid video source URL.
- "drmConfig parameter is required if isDRM is true":
  Provide DRM configuration when `isDRM` is set to `true`.
- DRM playback errors:
  Check your license server, network, browser DRM support, and license headers format.
- Shaka Player initialization errors:
  Ensure Shaka Player is properly loaded and browser supports MediaSource API.

### b. Debugging Tools

- Browser DevTools:
  Use the Console and Network tabs to inspect errors and license requests.
- Video.js Logs:
  Enable verbose logging: `videojs.log.level('debug')`
- Shaka Player Logs:
  Enable debug logging: `shaka.log.setLevel(shaka.log.Level.DEBUG)`
- Custom Event Listeners:
  The player emits custom events that you can listen to:

      player.on('ready', () => console.log('Player ready'));
      player.on('error', (error) => console.error('Player error:', error));

### c. DRM Debugging

- Check browser DRM support using `navigator.requestMediaKeySystemAccess`
- Verify license server URLs are accessible
- Check license headers format (especially `x-dt-custom-data` for DRMtoday)
- Use browser DevTools Network tab to inspect license requests
- For Shaka Player, check the Shaka debug logs for detailed error messages

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