CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-mediaelement

HTML5 video and audio player with unified cross-browser interface and extensive customization options

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

feature-system.mddocs/

Feature System

MediaElement.js uses a modular feature system that allows customization of player controls and functionality. Features are individual components that can be enabled, disabled, or customized to create tailored player experiences.

Capabilities

Feature Interface

All features follow a standardized interface for consistent integration with the player.

interface Feature {
  /**
   * Build and initialize the feature
   * @param player - MediaElementPlayer instance
   * @param controls - Controls container element
   * @param layers - Layers container for overlays
   * @param media - MediaElement instance
   */
  build(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
  
  /**
   * Clean up and destroy the feature (optional)
   * @param player - MediaElementPlayer instance  
   * @param layers - Layers container
   * @param controls - Controls container
   * @param media - MediaElement instance
   */
  clean?(player: MediaElementPlayer, layers: HTMLElement, controls: HTMLElement, media: MediaElement): void;
}

// Features are added to MediaElementPlayer prototype
interface MediaElementPlayer {
  // Feature methods follow naming pattern: build{FeatureName} and clean{FeatureName}
  buildplaypause(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
  cleanplaypause?(player: MediaElementPlayer, layers: HTMLElement, controls: HTMLElement, media: MediaElement): void;
}

Built-in Features

MediaElement.js includes several built-in features for common player functionality.

/** Available built-in features */
const builtInFeatures = [
  'playpause',    // Play/pause toggle button
  'current',      // Current time display
  'progress',     // Progress bar with scrubbing
  'duration',     // Total duration display
  'tracks',       // Caption/subtitle controls
  'volume',       // Volume control with slider
  'fullscreen'    // Fullscreen toggle button
];

// Default feature configuration
const defaultFeatures = ['playpause', 'current', 'progress', 'duration', 'tracks', 'volume', 'fullscreen'];

Play/Pause Feature

Toggle button for controlling media playback.

interface PlayPauseFeature extends Feature {
  /**
   * Build play/pause button control
   */
  buildplaypause(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
}

// CSS classes generated
interface PlayPauseClasses {
  button: 'mejs__button mejs__playpause-button';
  play: 'mejs__playpause-button > button.mejs__play';
  pause: 'mejs__playpause-button > button.mejs__pause';
}

Usage Examples:

// Enable play/pause feature
const player = new MediaElementPlayer('video', {
  features: ['playpause']
});

// Access play/pause button after initialization
const playButton = player.controls.querySelector('.mejs__playpause-button button');
console.log('Play button:', playButton);

// Custom play/pause styling
player.container.addEventListener('controlsready', () => {
  const playPauseBtn = player.controls.querySelector('.mejs__playpause-button');
  playPauseBtn.style.order = '1'; // Position in flex layout
});

Progress Feature

Interactive progress bar with scrubbing capabilities.

interface ProgressFeature extends Feature {
  /**
   * Build progress bar control
   */
  buildprogress(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
}

// CSS classes generated
interface ProgressClasses {
  rail: 'mejs__time-rail';
  total: 'mejs__time-total';
  loaded: 'mejs__time-loaded'; 
  current: 'mejs__time-current';
  handle: 'mejs__time-handle';
  float: 'mejs__time-float';
  floatCurrent: 'mejs__time-float-current';
  floatCorner: 'mejs__time-float-corner';
  marker: 'mejs__time-marker';
}

Usage Examples:

// Enable progress bar
const player = new MediaElementPlayer('video', {
  features: ['progress']
});

// Access progress elements
player.container.addEventListener('controlsready', () => {
  const progressRail = player.controls.querySelector('.mejs__time-rail');
  const currentProgress = player.controls.querySelector('.mejs__time-current');
  
  console.log('Progress rail:', progressRail);
  console.log('Current progress:', currentProgress);
});

// Listen for progress events
player.media.addEventListener('progress', () => {
  const buffered = player.media.buffered;
  if (buffered.length > 0) {
    const loadedPercent = (buffered.end(0) / player.media.duration) * 100;
    console.log(`Loaded: ${loadedPercent}%`);
  }
});

Time Display Features

Current time and duration display components.

interface CurrentTimeFeature extends Feature {
  /**
   * Build current time display
   */
  buildcurrent(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
}

interface DurationFeature extends Feature {
  /**
   * Build duration display
   */
  buildduration(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
}

// CSS classes generated
interface TimeClasses {
  time: 'mejs__time';
  timeCurrent: 'mejs__time mejs__currenttime-container';
  timeDuration: 'mejs__time mejs__duration-container';
}

Usage Examples:

// Enable time displays
const player = new MediaElementPlayer('video', {
  features: ['current', 'duration'],
  // Time format options
  alwaysShowHours: false,
  showTimecodeFrameCount: false,
  timeFormat: 'mm:ss'
});

// Access time display elements
player.container.addEventListener('controlsready', () => {
  const currentTime = player.controls.querySelector('.mejs__currenttime-container');
  const duration = player.controls.querySelector('.mejs__duration-container');
  
  console.log('Current time element:', currentTime);
  console.log('Duration element:', duration);
});

// Custom time format
const customTimePlayer = new MediaElementPlayer('video', {
  features: ['current', 'duration'],
  alwaysShowHours: true,
  timeFormat: 'hh:mm:ss'
});

Volume Feature

Volume control with mute toggle and volume slider.

interface VolumeFeature extends Feature {
  /**
   * Build volume control
   */
  buildvolume(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
}

// CSS classes generated
interface VolumeClasses {
  button: 'mejs__button mejs__volume-button';
  slider: 'mejs__volume-slider';
  rail: 'mejs__volume-rail';
  handle: 'mejs__volume-handle';
  current: 'mejs__volume-current';
  icon: 'mejs__volume-icon';
}

Usage Examples:

// Enable volume control
const player = new MediaElementPlayer('video', {
  features: ['volume']
});

// Access volume elements
player.container.addEventListener('controlsready', () => {
  const volumeButton = player.controls.querySelector('.mejs__volume-button');
  const volumeSlider = player.controls.querySelector('.mejs__volume-slider');
  
  console.log('Volume button:', volumeButton);
  console.log('Volume slider:', volumeSlider);
});

// Listen for volume changes
player.media.addEventListener('volumechange', () => {
  console.log(`Volume: ${player.media.volume}, Muted: ${player.media.muted}`);
});

// Set initial volume
player.container.addEventListener('controlsready', () => {
  player.setVolume(0.8); // 80% volume
});

Fullscreen Feature

Fullscreen toggle functionality with browser API integration.

interface FullscreenFeature extends Feature {
  /**
   * Build fullscreen control
   */
  buildfullscreen(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
}

// CSS classes generated
interface FullscreenClasses {
  button: 'mejs__button mejs__fullscreen-button';
  unfullscreen: 'mejs__unfullscreen';
  isFullscreen: 'mejs__container-fullscreen';
}

Usage Examples:

// Enable fullscreen control
const player = new MediaElementPlayer('video', {
  features: ['fullscreen']
});

// Listen for fullscreen changes
player.container.addEventListener('controlsready', () => {
  player.media.addEventListener('enterfullscreen', () => {
    console.log('Entered fullscreen');
  });
  
  player.media.addEventListener('exitfullscreen', () => {
    console.log('Exited fullscreen');
  });
});

// Programmatic fullscreen control
player.enterFullScreen();
player.exitFullScreen();

// Check fullscreen state
if (player.isFullScreen) {
  console.log('Currently in fullscreen');
}

Tracks Feature

Caption and subtitle track management.

interface TracksFeature extends Feature {
  /**
   * Build tracks/captions control
   */
  buildtracks(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
}

// CSS classes generated
interface TracksClasses {
  button: 'mejs__button mejs__captions-button';
  selector: 'mejs__captions-selector';
  translations: 'mejs__captions-translations';
  layer: 'mejs__captions-layer';
  text: 'mejs__captions-text';
}

Usage Examples:

// Enable tracks/captions
const player = new MediaElementPlayer('video', {
  features: ['tracks']
});

// Add tracks programmatically
player.container.addEventListener('controlsready', () => {
  // Tracks are typically added via HTML track elements
  const track = document.createElement('track');
  track.kind = 'subtitles';
  track.src = 'captions.vtt';
  track.srclang = 'en';
  track.label = 'English';
  track.default = true;
  
  player.media.appendChild(track);
});

// Listen for track changes
player.media.addEventListener('loadedmetadata', () => {
  const tracks = player.media.textTracks;
  console.log(`Found ${tracks.length} text tracks`);
  
  for (let i = 0; i < tracks.length; i++) {
    const track = tracks[i];
    console.log(`Track ${i}: ${track.label} (${track.language})`);
  }
});

Custom Feature Development

Create custom features using the standardized interface.

/**
 * Custom feature implementation example
 */
Object.assign(MediaElementPlayer.prototype, {
  /**
   * Build custom speed control feature
   */
  buildspeed(player, controls, layers, media) {
    const speedButton = document.createElement('div');
    speedButton.className = `${player.options.classPrefix}button ${player.options.classPrefix}speed-button`;
    speedButton.innerHTML = `
      <button type="button">1x</button>
      <div class="${player.options.classPrefix}speed-selector">
        <ul>
          <li><button data-speed="0.5">0.5x</button></li>
          <li><button data-speed="1" class="selected">1x</button></li>
          <li><button data-speed="1.25">1.25x</button></li>
          <li><button data-speed="1.5">1.5x</button></li>
          <li><button data-speed="2">2x</button></li>
        </ul>
      </div>
    `;
    
    // Add to controls
    controls.appendChild(speedButton);
    
    // Event handlers
    const mainButton = speedButton.querySelector('button');
    const selector = speedButton.querySelector(`.${player.options.classPrefix}speed-selector`);
    const speedButtons = selector.querySelectorAll('button');
    
    mainButton.addEventListener('click', () => {
      selector.style.display = selector.style.display === 'block' ? 'none' : 'block';
    });
    
    speedButtons.forEach(btn => {
      btn.addEventListener('click', (e) => {
        const speed = parseFloat(e.target.dataset.speed);
        media.playbackRate = speed;
        mainButton.textContent = `${speed}x`;
        
        // Update selected state
        speedButtons.forEach(b => b.classList.remove('selected'));
        e.target.classList.add('selected');
        
        selector.style.display = 'none';
      });
    });
    
    // Store reference for cleanup
    player.speedButton = speedButton;
  },
  
  /**
   * Clean up custom speed feature
   */
  cleanspeed(player, layers, controls, media) {
    if (player.speedButton) {
      player.speedButton.remove();
      delete player.speedButton;
    }
  }
});

Usage Examples:

// Use custom speed feature
const player = new MediaElementPlayer('video', {
  features: ['playpause', 'progress', 'speed', 'volume'] // Include custom 'speed' feature
});

// Custom feature with configuration
Object.assign(MediaElementPlayer.prototype, {
  buildquality(player, controls, layers, media) {
    // Check if HLS renderer is active
    if (player.media.rendererName !== 'hls') {
      return; // Only show for HLS streams
    }
    
    const qualityButton = document.createElement('div');
    qualityButton.className = `${player.options.classPrefix}button ${player.options.classPrefix}quality-button`;
    
    // Implementation details...
    controls.appendChild(qualityButton);
    
    player.qualityButton = qualityButton;
  }
});

Feature Configuration

Control feature behavior through player options.

interface FeatureConfiguration {
  /** List of features to enable */
  features?: string[];
  
  /** Use default feature set */
  useDefaultControls?: boolean;
  
  /** CSS class prefix for feature elements */
  classPrefix?: string;
  
  /** Feature-specific options */
  [featureName: string]: any;
}

Usage Examples:

// Minimal feature set
const minimalPlayer = new MediaElementPlayer('video', {
  features: ['playpause', 'progress']
});

// Custom feature order (affects display order)
const customOrderPlayer = new MediaElementPlayer('video', {
  features: ['volume', 'playpause', 'progress', 'current', 'duration', 'fullscreen']
});

// All features with custom prefix
const styledPlayer = new MediaElementPlayer('video', {
  features: ['playpause', 'current', 'progress', 'duration', 'tracks', 'volume', 'fullscreen'],
  classPrefix: 'myplayer-',
  useDefaultControls: false
});

// Feature-specific configuration
const configuredPlayer = new MediaElementPlayer('video', {
  features: ['playpause', 'progress', 'volume'],
  // Volume-specific options could be added here
  volumeStep: 0.1, // Custom option for volume feature
  progressHover: 'time' // Custom option for progress feature
});

Feature Events

Features can dispatch and listen for custom events.

// Common feature events
const featureEvents = [
  'controlsready',     // All features built
  'controlsshown',     // Controls became visible
  'controlshidden',    // Controls hidden
  'featurebuilt',      // Individual feature built
  'featureclean'       // Individual feature cleaned
];

Usage Examples:

const player = new MediaElementPlayer('video', {
  features: ['playpause', 'progress', 'volume'],
  success: (mediaElement, originalNode, instance) => {
    
    // Listen for feature events
    instance.addEventListener('controlsready', () => {
      console.log('All controls ready');
      
      // Customize controls after they're built
      const progressRail = instance.controls.querySelector('.mejs__time-rail');
      progressRail.style.height = '8px';
    });
    
    instance.addEventListener('controlsshown', () => {
      console.log('Controls shown');
    });
    
    instance.addEventListener('controlshidden', () => {
      console.log('Controls hidden');
    });
    
    // Feature-specific events (if implemented by features)
    mediaElement.addEventListener('volumechanged', (e) => {
      console.log('Volume changed via feature:', e.detail.volume);
    });
  }
});

Feature Lifecycle

Understanding the feature lifecycle helps in custom feature development.

// Feature lifecycle phases
const lifecycle = {
  1: 'Player initialization',
  2: 'MediaElement creation', 
  3: 'Feature building (build* methods called)',
  4: 'Controls ready event',
  5: 'Player active',
  6: 'Feature cleanup (clean* methods called)',
  7: 'Player destruction'
};

The feature system provides a flexible architecture for extending MediaElement.js with custom functionality while maintaining consistency with built-in features.

docs

configuration.md

core-media-element.md

feature-system.md

index.md

media-player.md

renderer-system.md

utility-functions.md

tile.json