HTML5 video and audio player with unified cross-browser interface and extensive customization options
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
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;
}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'];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
});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}%`);
}
});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 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 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');
}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})`);
}
});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;
}
});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
});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);
});
}
});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.