0
# Feature System
1
2
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.
3
4
## Capabilities
5
6
### Feature Interface
7
8
All features follow a standardized interface for consistent integration with the player.
9
10
```javascript { .api }
11
interface Feature {
12
/**
13
* Build and initialize the feature
14
* @param player - MediaElementPlayer instance
15
* @param controls - Controls container element
16
* @param layers - Layers container for overlays
17
* @param media - MediaElement instance
18
*/
19
build(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
20
21
/**
22
* Clean up and destroy the feature (optional)
23
* @param player - MediaElementPlayer instance
24
* @param layers - Layers container
25
* @param controls - Controls container
26
* @param media - MediaElement instance
27
*/
28
clean?(player: MediaElementPlayer, layers: HTMLElement, controls: HTMLElement, media: MediaElement): void;
29
}
30
31
// Features are added to MediaElementPlayer prototype
32
interface MediaElementPlayer {
33
// Feature methods follow naming pattern: build{FeatureName} and clean{FeatureName}
34
buildplaypause(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
35
cleanplaypause?(player: MediaElementPlayer, layers: HTMLElement, controls: HTMLElement, media: MediaElement): void;
36
}
37
```
38
39
### Built-in Features
40
41
MediaElement.js includes several built-in features for common player functionality.
42
43
```javascript { .api }
44
/** Available built-in features */
45
const builtInFeatures = [
46
'playpause', // Play/pause toggle button
47
'current', // Current time display
48
'progress', // Progress bar with scrubbing
49
'duration', // Total duration display
50
'tracks', // Caption/subtitle controls
51
'volume', // Volume control with slider
52
'fullscreen' // Fullscreen toggle button
53
];
54
55
// Default feature configuration
56
const defaultFeatures = ['playpause', 'current', 'progress', 'duration', 'tracks', 'volume', 'fullscreen'];
57
```
58
59
### Play/Pause Feature
60
61
Toggle button for controlling media playback.
62
63
```javascript { .api }
64
interface PlayPauseFeature extends Feature {
65
/**
66
* Build play/pause button control
67
*/
68
buildplaypause(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
69
}
70
71
// CSS classes generated
72
interface PlayPauseClasses {
73
button: 'mejs__button mejs__playpause-button';
74
play: 'mejs__playpause-button > button.mejs__play';
75
pause: 'mejs__playpause-button > button.mejs__pause';
76
}
77
```
78
79
**Usage Examples:**
80
81
```javascript
82
// Enable play/pause feature
83
const player = new MediaElementPlayer('video', {
84
features: ['playpause']
85
});
86
87
// Access play/pause button after initialization
88
const playButton = player.controls.querySelector('.mejs__playpause-button button');
89
console.log('Play button:', playButton);
90
91
// Custom play/pause styling
92
player.container.addEventListener('controlsready', () => {
93
const playPauseBtn = player.controls.querySelector('.mejs__playpause-button');
94
playPauseBtn.style.order = '1'; // Position in flex layout
95
});
96
```
97
98
### Progress Feature
99
100
Interactive progress bar with scrubbing capabilities.
101
102
```javascript { .api }
103
interface ProgressFeature extends Feature {
104
/**
105
* Build progress bar control
106
*/
107
buildprogress(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
108
}
109
110
// CSS classes generated
111
interface ProgressClasses {
112
rail: 'mejs__time-rail';
113
total: 'mejs__time-total';
114
loaded: 'mejs__time-loaded';
115
current: 'mejs__time-current';
116
handle: 'mejs__time-handle';
117
float: 'mejs__time-float';
118
floatCurrent: 'mejs__time-float-current';
119
floatCorner: 'mejs__time-float-corner';
120
marker: 'mejs__time-marker';
121
}
122
```
123
124
**Usage Examples:**
125
126
```javascript
127
// Enable progress bar
128
const player = new MediaElementPlayer('video', {
129
features: ['progress']
130
});
131
132
// Access progress elements
133
player.container.addEventListener('controlsready', () => {
134
const progressRail = player.controls.querySelector('.mejs__time-rail');
135
const currentProgress = player.controls.querySelector('.mejs__time-current');
136
137
console.log('Progress rail:', progressRail);
138
console.log('Current progress:', currentProgress);
139
});
140
141
// Listen for progress events
142
player.media.addEventListener('progress', () => {
143
const buffered = player.media.buffered;
144
if (buffered.length > 0) {
145
const loadedPercent = (buffered.end(0) / player.media.duration) * 100;
146
console.log(`Loaded: ${loadedPercent}%`);
147
}
148
});
149
```
150
151
### Time Display Features
152
153
Current time and duration display components.
154
155
```javascript { .api }
156
interface CurrentTimeFeature extends Feature {
157
/**
158
* Build current time display
159
*/
160
buildcurrent(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
161
}
162
163
interface DurationFeature extends Feature {
164
/**
165
* Build duration display
166
*/
167
buildduration(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
168
}
169
170
// CSS classes generated
171
interface TimeClasses {
172
time: 'mejs__time';
173
timeCurrent: 'mejs__time mejs__currenttime-container';
174
timeDuration: 'mejs__time mejs__duration-container';
175
}
176
```
177
178
**Usage Examples:**
179
180
```javascript
181
// Enable time displays
182
const player = new MediaElementPlayer('video', {
183
features: ['current', 'duration'],
184
// Time format options
185
alwaysShowHours: false,
186
showTimecodeFrameCount: false,
187
timeFormat: 'mm:ss'
188
});
189
190
// Access time display elements
191
player.container.addEventListener('controlsready', () => {
192
const currentTime = player.controls.querySelector('.mejs__currenttime-container');
193
const duration = player.controls.querySelector('.mejs__duration-container');
194
195
console.log('Current time element:', currentTime);
196
console.log('Duration element:', duration);
197
});
198
199
// Custom time format
200
const customTimePlayer = new MediaElementPlayer('video', {
201
features: ['current', 'duration'],
202
alwaysShowHours: true,
203
timeFormat: 'hh:mm:ss'
204
});
205
```
206
207
### Volume Feature
208
209
Volume control with mute toggle and volume slider.
210
211
```javascript { .api }
212
interface VolumeFeature extends Feature {
213
/**
214
* Build volume control
215
*/
216
buildvolume(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
217
}
218
219
// CSS classes generated
220
interface VolumeClasses {
221
button: 'mejs__button mejs__volume-button';
222
slider: 'mejs__volume-slider';
223
rail: 'mejs__volume-rail';
224
handle: 'mejs__volume-handle';
225
current: 'mejs__volume-current';
226
icon: 'mejs__volume-icon';
227
}
228
```
229
230
**Usage Examples:**
231
232
```javascript
233
// Enable volume control
234
const player = new MediaElementPlayer('video', {
235
features: ['volume']
236
});
237
238
// Access volume elements
239
player.container.addEventListener('controlsready', () => {
240
const volumeButton = player.controls.querySelector('.mejs__volume-button');
241
const volumeSlider = player.controls.querySelector('.mejs__volume-slider');
242
243
console.log('Volume button:', volumeButton);
244
console.log('Volume slider:', volumeSlider);
245
});
246
247
// Listen for volume changes
248
player.media.addEventListener('volumechange', () => {
249
console.log(`Volume: ${player.media.volume}, Muted: ${player.media.muted}`);
250
});
251
252
// Set initial volume
253
player.container.addEventListener('controlsready', () => {
254
player.setVolume(0.8); // 80% volume
255
});
256
```
257
258
### Fullscreen Feature
259
260
Fullscreen toggle functionality with browser API integration.
261
262
```javascript { .api }
263
interface FullscreenFeature extends Feature {
264
/**
265
* Build fullscreen control
266
*/
267
buildfullscreen(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
268
}
269
270
// CSS classes generated
271
interface FullscreenClasses {
272
button: 'mejs__button mejs__fullscreen-button';
273
unfullscreen: 'mejs__unfullscreen';
274
isFullscreen: 'mejs__container-fullscreen';
275
}
276
```
277
278
**Usage Examples:**
279
280
```javascript
281
// Enable fullscreen control
282
const player = new MediaElementPlayer('video', {
283
features: ['fullscreen']
284
});
285
286
// Listen for fullscreen changes
287
player.container.addEventListener('controlsready', () => {
288
player.media.addEventListener('enterfullscreen', () => {
289
console.log('Entered fullscreen');
290
});
291
292
player.media.addEventListener('exitfullscreen', () => {
293
console.log('Exited fullscreen');
294
});
295
});
296
297
// Programmatic fullscreen control
298
player.enterFullScreen();
299
player.exitFullScreen();
300
301
// Check fullscreen state
302
if (player.isFullScreen) {
303
console.log('Currently in fullscreen');
304
}
305
```
306
307
### Tracks Feature
308
309
Caption and subtitle track management.
310
311
```javascript { .api }
312
interface TracksFeature extends Feature {
313
/**
314
* Build tracks/captions control
315
*/
316
buildtracks(player: MediaElementPlayer, controls: HTMLElement, layers: HTMLElement, media: MediaElement): void;
317
}
318
319
// CSS classes generated
320
interface TracksClasses {
321
button: 'mejs__button mejs__captions-button';
322
selector: 'mejs__captions-selector';
323
translations: 'mejs__captions-translations';
324
layer: 'mejs__captions-layer';
325
text: 'mejs__captions-text';
326
}
327
```
328
329
**Usage Examples:**
330
331
```javascript
332
// Enable tracks/captions
333
const player = new MediaElementPlayer('video', {
334
features: ['tracks']
335
});
336
337
// Add tracks programmatically
338
player.container.addEventListener('controlsready', () => {
339
// Tracks are typically added via HTML track elements
340
const track = document.createElement('track');
341
track.kind = 'subtitles';
342
track.src = 'captions.vtt';
343
track.srclang = 'en';
344
track.label = 'English';
345
track.default = true;
346
347
player.media.appendChild(track);
348
});
349
350
// Listen for track changes
351
player.media.addEventListener('loadedmetadata', () => {
352
const tracks = player.media.textTracks;
353
console.log(`Found ${tracks.length} text tracks`);
354
355
for (let i = 0; i < tracks.length; i++) {
356
const track = tracks[i];
357
console.log(`Track ${i}: ${track.label} (${track.language})`);
358
}
359
});
360
```
361
362
### Custom Feature Development
363
364
Create custom features using the standardized interface.
365
366
```javascript { .api }
367
/**
368
* Custom feature implementation example
369
*/
370
Object.assign(MediaElementPlayer.prototype, {
371
/**
372
* Build custom speed control feature
373
*/
374
buildspeed(player, controls, layers, media) {
375
const speedButton = document.createElement('div');
376
speedButton.className = `${player.options.classPrefix}button ${player.options.classPrefix}speed-button`;
377
speedButton.innerHTML = `
378
<button type="button">1x</button>
379
<div class="${player.options.classPrefix}speed-selector">
380
<ul>
381
<li><button data-speed="0.5">0.5x</button></li>
382
<li><button data-speed="1" class="selected">1x</button></li>
383
<li><button data-speed="1.25">1.25x</button></li>
384
<li><button data-speed="1.5">1.5x</button></li>
385
<li><button data-speed="2">2x</button></li>
386
</ul>
387
</div>
388
`;
389
390
// Add to controls
391
controls.appendChild(speedButton);
392
393
// Event handlers
394
const mainButton = speedButton.querySelector('button');
395
const selector = speedButton.querySelector(`.${player.options.classPrefix}speed-selector`);
396
const speedButtons = selector.querySelectorAll('button');
397
398
mainButton.addEventListener('click', () => {
399
selector.style.display = selector.style.display === 'block' ? 'none' : 'block';
400
});
401
402
speedButtons.forEach(btn => {
403
btn.addEventListener('click', (e) => {
404
const speed = parseFloat(e.target.dataset.speed);
405
media.playbackRate = speed;
406
mainButton.textContent = `${speed}x`;
407
408
// Update selected state
409
speedButtons.forEach(b => b.classList.remove('selected'));
410
e.target.classList.add('selected');
411
412
selector.style.display = 'none';
413
});
414
});
415
416
// Store reference for cleanup
417
player.speedButton = speedButton;
418
},
419
420
/**
421
* Clean up custom speed feature
422
*/
423
cleanspeed(player, layers, controls, media) {
424
if (player.speedButton) {
425
player.speedButton.remove();
426
delete player.speedButton;
427
}
428
}
429
});
430
```
431
432
**Usage Examples:**
433
434
```javascript
435
// Use custom speed feature
436
const player = new MediaElementPlayer('video', {
437
features: ['playpause', 'progress', 'speed', 'volume'] // Include custom 'speed' feature
438
});
439
440
// Custom feature with configuration
441
Object.assign(MediaElementPlayer.prototype, {
442
buildquality(player, controls, layers, media) {
443
// Check if HLS renderer is active
444
if (player.media.rendererName !== 'hls') {
445
return; // Only show for HLS streams
446
}
447
448
const qualityButton = document.createElement('div');
449
qualityButton.className = `${player.options.classPrefix}button ${player.options.classPrefix}quality-button`;
450
451
// Implementation details...
452
controls.appendChild(qualityButton);
453
454
player.qualityButton = qualityButton;
455
}
456
});
457
```
458
459
### Feature Configuration
460
461
Control feature behavior through player options.
462
463
```javascript { .api }
464
interface FeatureConfiguration {
465
/** List of features to enable */
466
features?: string[];
467
468
/** Use default feature set */
469
useDefaultControls?: boolean;
470
471
/** CSS class prefix for feature elements */
472
classPrefix?: string;
473
474
/** Feature-specific options */
475
[featureName: string]: any;
476
}
477
```
478
479
**Usage Examples:**
480
481
```javascript
482
// Minimal feature set
483
const minimalPlayer = new MediaElementPlayer('video', {
484
features: ['playpause', 'progress']
485
});
486
487
// Custom feature order (affects display order)
488
const customOrderPlayer = new MediaElementPlayer('video', {
489
features: ['volume', 'playpause', 'progress', 'current', 'duration', 'fullscreen']
490
});
491
492
// All features with custom prefix
493
const styledPlayer = new MediaElementPlayer('video', {
494
features: ['playpause', 'current', 'progress', 'duration', 'tracks', 'volume', 'fullscreen'],
495
classPrefix: 'myplayer-',
496
useDefaultControls: false
497
});
498
499
// Feature-specific configuration
500
const configuredPlayer = new MediaElementPlayer('video', {
501
features: ['playpause', 'progress', 'volume'],
502
// Volume-specific options could be added here
503
volumeStep: 0.1, // Custom option for volume feature
504
progressHover: 'time' // Custom option for progress feature
505
});
506
```
507
508
### Feature Events
509
510
Features can dispatch and listen for custom events.
511
512
```javascript { .api }
513
// Common feature events
514
const featureEvents = [
515
'controlsready', // All features built
516
'controlsshown', // Controls became visible
517
'controlshidden', // Controls hidden
518
'featurebuilt', // Individual feature built
519
'featureclean' // Individual feature cleaned
520
];
521
```
522
523
**Usage Examples:**
524
525
```javascript
526
const player = new MediaElementPlayer('video', {
527
features: ['playpause', 'progress', 'volume'],
528
success: (mediaElement, originalNode, instance) => {
529
530
// Listen for feature events
531
instance.addEventListener('controlsready', () => {
532
console.log('All controls ready');
533
534
// Customize controls after they're built
535
const progressRail = instance.controls.querySelector('.mejs__time-rail');
536
progressRail.style.height = '8px';
537
});
538
539
instance.addEventListener('controlsshown', () => {
540
console.log('Controls shown');
541
});
542
543
instance.addEventListener('controlshidden', () => {
544
console.log('Controls hidden');
545
});
546
547
// Feature-specific events (if implemented by features)
548
mediaElement.addEventListener('volumechanged', (e) => {
549
console.log('Volume changed via feature:', e.detail.volume);
550
});
551
}
552
});
553
```
554
555
### Feature Lifecycle
556
557
Understanding the feature lifecycle helps in custom feature development.
558
559
```javascript { .api }
560
// Feature lifecycle phases
561
const lifecycle = {
562
1: 'Player initialization',
563
2: 'MediaElement creation',
564
3: 'Feature building (build* methods called)',
565
4: 'Controls ready event',
566
5: 'Player active',
567
6: 'Feature cleanup (clean* methods called)',
568
7: 'Player destruction'
569
};
570
```
571
572
The feature system provides a flexible architecture for extending MediaElement.js with custom functionality while maintaining consistency with built-in features.