
Quick Overview
VidPly is a universal, accessible media player supporting:
| Feature | Support |
|---|---|
| Video | MP4, WebM, OGG |
| Audio | MP3, OGG, WAV |
| YouTube | Embedded with unified controls |
| Vimeo | Embedded with unified controls |
| SoundCloud | Embedded via Widget API with unified controls |
| HLS | Adaptive bitrate streaming (hls.js on most browsers, native on iOS / iPadOS) |
| DASH | MPEG-DASH streaming via dash.js |
| Buffering Spinner | Centered loading spinner during waiting/seeking |
| Download Button | Optional control with custom URL support |
| Playlists | Audio, video & mixed media with thumbnails |
| Accessibility | WCAG 2.2 AA compliant |
| TypeScript | Strict TS sources + bundled .d.ts declarations |
| Languages | EN, ES, FR, DE, JA + custom |
Installation
1. Build the Player
npm install
npm run build
This creates files in dist/:
prod/vidply.esm.min.js- ES Module (recommended)legacy/vidply.min.js- IIFE for script tag (globalVidPly)vidply.min.css- Styles
2. Include in Your Page
ES Module (Recommended):
<link rel="stylesheet" href="dist/vidply.min.css">
<script type="module">
import Player from './dist/prod/vidply.esm.min.js';
</script>
Traditional Script Tag:
<link rel="stylesheet" href="dist/vidply.min.css">
<script src="dist/legacy/vidply.min.js"></script>
Basic Usage
Video Player
<video data-vidply width="800" height="450">
<source src="video.mp4" type="video/mp4">
<source src="video.webm" type="video/webm">
</video>
The data-vidply attribute auto-initializes the player.
Audio Player
<audio data-vidply>
<source src="audio.mp3" type="audio/mpeg">
<source src="audio.ogg" type="audio/ogg">
</audio>
With Poster Image
<video data-vidply poster="thumbnail.jpg" width="800" height="450">
<source src="video.mp4" type="video/mp4">
</video>
External Services
YouTube
<video data-vidply src="https://www.youtube.com/watch?v=VIDEO_ID"></video>
Or with full URL:
<video data-vidply src="https://youtu.be/VIDEO_ID"></video>
Vimeo
<video data-vidply src="https://vimeo.com/VIDEO_ID"></video>
VidPly or Native Embed Controls
YouTube and Vimeo play in a chromeless iframe with the VidPly control bar by default (embedChrome: 'vidply').
To keep only the provider's own player UI, set embedChrome: 'provider':
<video data-vidply data-embed-chrome="provider" src="https://www.youtube.com/watch?v=VIDEO_ID"></video>
With 'provider' the VidPly control bar and play overlay are hidden and the iframe handles playback and keyboard
input, so VidPly-only controls (captions menu, transcript, download, "Open on YouTube/Vimeo" link) are not available.
YouTube captions are never shown in either mode; add sidecar WebVTT via playlist tracks with embedChrome: 'vidply'
when captions are needed.
SoundCloud
<audio data-vidply src="https://soundcloud.com/artist/track-name"></audio>
SoundCloud keeps its widget UI by default (embedChromeSoundcloud: 'provider'); set
data-embed-chrome-soundcloud="vidply" to show the VidPly bar below the widget.
The SoundCloud Widget API is auto-detected for any URL containing soundcloud.com. The widget is loaded on demand and integrates with the standard VidPly play / pause / seek / volume controls.
When using mpc-vidply in TYPO3, the Privacy Layer displays a GDPR consent overlay before the SoundCloud iframe is loaded — visitors must accept loading external content first.
HLS Streaming
<video data-vidply src="https://example.com/stream.m3u8"></video>
hls.js 1.7.3 is loaded on demand when .m3u8 URLs are detected (CDN fallback if not already on the page). Behavior per platform:
| Platform | HLS engine | Captions / Transcript / Quality |
|---|---|---|
| Chrome / Firefox / Edge | hls.js | Full VidPly UI |
| Desktop macOS Safari | hls.js (for parity with other browsers) | Full VidPly UI |
| iOS / iPadOS Safari | Native <video> HLS | Full VidPly UI via the native TextTrack API bridge |
Even on iOS where
hls.jscannot run (no MSE), VidPly still wires the captions menu, interactive transcript and quality menu into the browser's native HLS text tracks — you don't lose any UI controls.
DASH Streaming
<video data-vidply src="https://example.com/manifest.mpd"></video>
dash.js 5.2.1 (modern UMD) is loaded on demand when .mpd URLs are detected. DASH streams support:
- Adaptive bitrate quality selection
- TTML subtitles (rendered natively by dash.js)
- WebVTT subtitles (handled by VidPly's caption system with transcript support)
DASH + HLS + MP4 Fallback
<video data-vidply width="800" height="450">
<source src="dash/manifest.mpd" type="application/dash+xml">
<source src="hls/master.m3u8" type="application/x-mpegURL">
<source src="fallback.mp4" type="video/mp4">
<track kind="subtitles" src="subtitles.en.vtt" srclang="en" label="English">
</video>
Adding Captions & Subtitles
Basic Captions
<video data-vidply width="800" height="450">
<source src="video.mp4" type="video/mp4">
<track kind="subtitles" src="captions-en.vtt" srclang="en" label="English">
<track kind="subtitles" src="captions-de.vtt" srclang="de" label="Deutsch">
</video>
Track Types
| Kind | Purpose |
|---|---|
subtitles | Translation of dialogue |
captions | Dialogue + sound effects (deaf/hard of hearing) |
descriptions | Text descriptions of visual content |
chapters | Chapter markers for navigation |
Default Captions On
<track kind="subtitles" src="captions.vtt" srclang="en" label="English" default>
Chapters
Add chapter navigation with a chapters track:
<video data-vidply>
<source src="video.mp4" type="video/mp4">
<track kind="chapters" src="chapters.vtt" srclang="en">
</video>
chapters.vtt:
WEBVTT
00:00:00.000 --> 00:02:30.000
Introduction
00:02:30.000 --> 00:08:00.000
Main Topic
00:08:00.000 --> 00:12:00.000
Examples
00:12:00.000 --> 00:15:00.000
Conclusion
Configuration Options
Via Data Attributes
<video
data-vidply
data-vidply-autoplay="false"
data-vidply-loop="false"
data-vidply-muted="false"
data-vidply-volume="0.8"
data-vidply-language="en"
data-vidply-keyboard="true"
data-vidply-responsive="true"
src="video.mp4">
</video>
Via JavaScript
const player = new Player('#my-video', {
// Display
width: 800,
height: 450,
poster: 'poster.jpg',
responsive: true,
// Playback
autoplay: false,
loop: false,
muted: false,
volume: 0.8,
playbackSpeed: 1.0,
// Controls
controls: true,
hideControlsDelay: 3000,
// Buttons (show/hide individual controls)
playPauseButton: true,
progressBar: true,
volumeControl: true,
speedButton: true,
captionsButton: true,
fullscreenButton: true,
pipButton: true,
downloadButton: false, // Show a download button in the control bar
downloadUrl: null, // Optional explicit download URL (defaults to current src)
// Custom Floating Player (in-page miniplayer / "own PiP")
floating: false, // Enable the custom floating player; also disables native browser PiP
floatingPosition: 'bottom-right', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
floatingMinViewportWidth: 768, // Floating feature is hidden below this viewport width (px)
// Language
language: 'en',
// Keyboard
keyboard: true,
// Accessibility
screenReaderAnnouncements: true,
focusHighlight: true
});
All Options Reference
| Option | Type | Default | Description |
|---|---|---|---|
width | number | 800 | Player width |
height | number | 450 | Player height |
poster | string | null | Poster image URL |
responsive | bool | true | Responsive sizing |
autoplay | bool | false | Auto-start playback |
loop | bool | false | Loop playback |
muted | bool | false | Start muted |
volume | number | 0.8 | Default volume (0-1) |
playbackSpeed | number | 1.0 | Default speed |
controls | bool | true | Show controls |
hideControlsDelay | number | 3000 | Auto-hide delay (ms) |
keyboard | bool | true | Enable keyboard shortcuts |
language | string | 'en' | UI language |
captions | bool | true | Enable captions support |
captionsDefault | bool | false | Show captions by default |
transcript | bool | false | Show transcript panel |
debug | bool | false | Debug logging |
preload | string | 'metadata' | 'none', 'metadata', 'auto' |
deferLoad | bool | false | Avoid starting network loading during init; load on user play / explicit load |
fillContainer | bool | false | Fill the player container |
showTrackInfo | bool | true | Render .vidply-track-info for standalone players (playlists use their own header) |
initialDuration | number | 0 | Initial duration in seconds (UI only, before media metadata is loaded) |
requirePlaybackForAccessibilityToggles | bool | false | If true: AD/SL before playback shows a notice instead of implicitly loading/playing |
Download Button
Show an opt-in download button in the control bar:
<video
data-vidply
data-vidply-download-button="true"
data-vidply-download-url="/files/lecture.mp4"
src="/streams/lecture/manifest.mpd">
</video>
const player = new Player('#video', {
downloadButton: true,
downloadUrl: '/files/lecture.mp4' // optional; falls back to current src
});
The button is keyboard accessible, fully internationalized (player.download translation key) and uses aria-label so screen readers announce it correctly.
Hint for streaming sources:
downloadUrlis normally the URL of an MP4/MP3 fallback because users typically can't download a.mpdor.m3u8manifest as a single file.
In a playlist each track can bring its own file (downloadUrl, downloadFormat,
downloadFileSize). The button then offers whatever is selected and hides on
tracks that are not downloadable — see PLAYLIST.
Custom Floating Player (Miniplayer)
VidPly ships an optional in-page floating player ("own PiP") that pops up in a configurable corner of the viewport, can be dragged and resized, and keeps VidPly's own caption rendering, transport controls and fullscreen working inside the floating shell.
While floating is enabled, the native browser Picture-in-Picture API is
suppressed automatically (disablepictureinpicture + disableremoteplayback)
so the user gets a single, consistent experience across browsers.
Behavior
- Auto-float on scroll-out - When the original player scrolls out of the viewport, the video pops into the floating shell. When it scrolls back in, the video docks back to its original container.
- Manual pin/unpin - The PiP button in the control bar manually pins the floating shell. Manual pin/unpin overrides scroll-based auto-floating until the next user-initiated
play. - Close button - The X button in the floating shell pauses the video, returns it to its original container and suppresses auto-float for the rest of the play session.
- Drag & resize - The floating shell can be dragged by its header and resized via the corner/edge handles. Geometry is persisted per player via local storage.
- Reduced control bar - Inside the floating shell only essential controls are shown: play/pause, rewind, forward, volume, captions, PiP and fullscreen. Tooltips open above the buttons; captions are sized at
90%and capped to95%width. - Audio players are skipped - The floating feature only applies to
<video>players. - Desktop only by default - Below
floatingMinViewportWidth(default 768 px) the feature is disabled and the floating PiP button is hidden in the main control bar (it never falls into the overflow menu).
Enable via HTML (data-vidply-options JSON)
Floating options are passed via the data-vidply-options JSON blob (the same
channel TYPO3 / mpc_vidply uses):
<video
data-vidply
data-vidply-options='{"floating": true, "floatingPosition": "bottom-right", "floatingMinViewportWidth": 768}'
src="video.mp4"
width="800" height="450">
<track kind="subtitles" src="captions.vtt" srclang="en" label="English">
</video>
Enable via JavaScript
const player = new Player('#video', {
floating: true,
floatingPosition: 'bottom-right', // or 'bottom-left' | 'top-right' | 'top-left'
floatingMinViewportWidth: 768
});
Accessibility
- The floating shell is announced as a
role="dialog"with a translatedaria-label(player.floatingPlayerDialog). - The PiP toggle uses
aria-pressedto expose the pinned state. - Focus is returned to the originating button when the floating shell is closed via keyboard.
- All built-in languages (en, de, es, fr, ja) include translations for
player.floatingPlayer,player.floatingPlayerClose,player.floatingPlayerEnter,player.floatingPlayerExitandplayer.floatingPlayerDialog.
Buffering Spinner
A centered loading spinner appears automatically while the media is buffering (waiting / seeking / initial loadstart) and disappears on canplay / playing. It works for HTML5, HLS and DASH renderers and respects prefers-reduced-motion.
You can theme it via CSS variables:
.vidply-player {
--vidply-spinner-color: #ffffff;
--vidply-spinner-size: 56px;
--vidply-z-buffering: 1; /* sits below the play overlay */
}
The container exposes a .vidply-buffering class while loading, which you can use to style or hook into:
.vidply-player.vidply-buffering .my-skin-overlay {
opacity: 0.3;
}
Playlists
Audio Playlist
<div id="audio-player"></div>
<script type="module">
import { Player, PlaylistManager } from './dist/prod/vidply.esm.min.js';
// When you pass a non-media element, VidPly will create the media element for you.
const player = new Player('#audio-player', { mediaType: 'audio' });
const playlist = new PlaylistManager(player, {
autoAdvance: true,
loop: false,
showPanel: true
});
playlist.loadPlaylist([
{
src: 'track1.mp3',
title: 'Track 1',
artist: 'Artist Name',
poster: 'cover1.jpg'
},
{
src: 'track2.mp3',
title: 'Track 2',
artist: 'Artist Name',
poster: 'cover2.jpg'
}
]);
</script>
Video Playlist
<div id="video-player"></div>
<script type="module">
import { Player, PlaylistManager } from './dist/prod/vidply.esm.min.js';
const player = new Player('#video-player');
const playlist = new PlaylistManager(player, {
autoAdvance: true,
showPanel: true
});
playlist.loadPlaylist([
{
src: 'video1.mp4',
title: 'Video 1',
poster: 'thumb1.jpg',
tracks: [
{ src: 'captions1.vtt', kind: 'captions', srclang: 'en', label: 'English' }
]
},
{
src: 'video2.mp4',
title: 'Video 2',
poster: 'thumb2.jpg'
}
]);
</script>
Playlist Options
| Option | Default | Description |
|---|---|---|
autoAdvance | true | Auto-play next track |
autoPlayFirst | true | Auto-play first track on loadPlaylist() (if false: first track is loaded/selected but not played) |
loop | false | Loop playlist |
showPanel | true | Show playlist panel |
Playlist Controls
playlist.next() // Next track
playlist.previous() // Previous track
playlist.goToTrack(2) // Jump to track index
playlist.hasNext() // Check if next exists
playlist.hasPrevious() // Check if previous exists
Accessibility Features
Audio Description
VidPly supports spoken audio description in two ways:
Described video (recommended for production) — upload or link a pre-mixed MP4/WebM where narration is already embedded in the audio track. VidPly swaps the entire video source when the user toggles AD.
VTT speech (extended AD fallback) — when no described video is available, a
kind="descriptions"WebVTT track can drive browser text-to-speech: the player pauses at each cue, speaks the description, then resumes playback. Quality depends on the browser voice; use described video when broadcast-grade narration is required.
Text description cues are always available in the transcript panel for reading, regardless of which spoken path
is active. Set audioDescriptionSpeech: false to use text-only descriptions (no TTS).
Mode (audioDescriptionMode, default auto):
| Value | Behavior |
|---|---|
auto | Described video if configured, else VTT speech if descriptions VTT present |
swap | Described video swap only |
vtt_speech | VTT speech only |
const player = new Player('#my-video', {
audioDescription: true,
audioDescriptionSrc: 'video-with-description.mp4', // optional
audioDescriptionMode: 'auto',
audioDescriptionSpeech: true,
audioDescriptionExtended: true
});
Users toggle AD via the AD button. Demo: `demo/single-player-vtt-speech.html`.
Sign Language
Add sign language interpretation overlay:
const player = new Player('#my-video', {
signLanguage: true,
signLanguageSrc: 'sign-language.mp4',
signLanguagePosition: 'bottom-right'
});
Position options: bottom-right, bottom-left, top-right, top-left
Interactive Transcripts
const player = new Player('#my-video', {
transcript: true,
transcriptPosition: 'external',
transcriptContainer: '#transcript-panel'
});
Features:
- Click any line to seek to that point
- Auto-scrolls during playback
- Searchable text
- Draggable/resizable window
Keyboard Shortcuts
VidPly includes comprehensive keyboard controls:
| Key | Action |
|---|---|
| Space / P / K | Play/Pause |
| F | Toggle fullscreen |
| M | Mute/Unmute |
| ↑ / ↓ | Volume up/down |
| ← / → | Seek ±10 seconds |
| C | Toggle captions |
| A | Caption style menu |
| <** / **> | Decrease/increase speed |
| S | Speed menu |
| Q | Quality menu |
| J | Chapters menu |
| T | Toggle transcript |
| D | Drag mode (transcript/sign) |
| R | Resize mode |
| Home | Reset position |
| Escape | Exit mode/close menu |
Custom Keyboard Shortcuts
const player = new Player('#my-video', {
keyboardShortcuts: {
'play-pause': [' ', 'p', 'k'],
'seek-forward': ['ArrowRight', 'l'],
'seek-backward': ['ArrowLeft', 'j'],
'volume-up': ['ArrowUp'],
'volume-down': ['ArrowDown'],
'mute': ['m'],
'fullscreen': ['f'],
'captions': ['c']
}
});
Styling & Theming
CSS Variables
.vidply-player {
/* Colors */
--vidply-primary-color: #3b82f6;
--vidply-background: rgba(0, 0, 0, 0.8);
--vidply-text-color: #ffffff;
/* Sizing */
--vidply-button-size: 40px;
--vidply-icon-size: 20px;
/* Spacing */
--vidply-gap-sm: 4px;
--vidply-gap-md: 8px;
--vidply-gap-lg: 12px;
/* Border radius */
--vidply-radius-sm: 4px;
--vidply-radius-md: 8px;
--vidply-radius-lg: 12px;
/* Transitions */
--vidply-transition-fast: 150ms;
--vidply-transition-normal: 300ms;
}
Custom Progress Bar
.vidply-progress-played {
background: linear-gradient(90deg, #667eea, #764ba2);
}
Custom Buttons
.vidply-button:hover {
background: rgba(59, 130, 246, 0.2);
}
.vidply-button:focus {
outline: 2px solid var(--vidply-primary-color);
outline-offset: 2px;
}
Internationalization
Built-in Languages
- English (en)
- Spanish (es) - Español
- French (fr) - Français
- German (de) - Deutsch
- Japanese (ja) - 日本語
Set Language
const player = new Player('#my-video', {
language: 'de' // German
});
Auto-detect from HTML
<html lang="de">
<!-- Player auto-detects German -->
</html>
Load Custom Language
Via data attribute:
<video
data-vidply
data-vidply-language-files='{"pt": "languages/pt.json"}'
src="video.mp4">
</video>
Via JavaScript (options):
import Player from './dist/prod/vidply.esm.min.js';
const player = new Player('#my-video', {
language: 'pt',
languageFiles: { pt: 'languages/pt.json' }
});
Language File Format
languages/pt.json:
{
"player": {
"play": "Reproduzir",
"pause": "Pausar",
"mute": "Silenciar",
"unmute": "Ativar som",
"fullscreen": "Tela cheia",
"captions": "Legendas",
"settings": "Configurações"
},
"time": {
"currentTime": "Tempo atual",
"duration": "Duração"
}
}
API Reference
Playback
player.play() // Start playback
player.pause() // Pause playback
player.stop() // Stop and reset
player.toggle() // Toggle play/pause
player.seek(30) // Seek to 30 seconds
player.seekForward(10) // Skip forward 10s
player.seekBackward(10) // Skip backward 10s
Volume
player.setVolume(0.5) // Set volume (0-1)
player.getVolume() // Get volume
player.mute() // Mute
player.unmute() // Unmute
player.toggleMute() // Toggle mute
Speed
player.setPlaybackSpeed(1.5) // Set speed (0.25-2.0)
player.getPlaybackSpeed() // Get speed
Fullscreen
player.enterFullscreen() // Enter fullscreen
player.exitFullscreen() // Exit fullscreen
player.toggleFullscreen() // Toggle fullscreen
Captions
player.enableCaptions() // Enable captions
player.disableCaptions() // Disable captions
player.toggleCaptions() // Toggle captions
State
player.getCurrentTime() // Current time in seconds
player.getDuration() // Total duration
player.isPlaying() // Is playing?
player.isPaused() // Is paused?
player.isEnded() // Has ended?
player.isMuted() // Is muted?
player.isFullscreen() // Is fullscreen?
Events
player.on('ready', () => {})
player.on('play', () => {})
player.on('pause', () => {})
player.on('ended', () => {})
player.on('timeupdate', (time) => {})
player.on('volumechange', (volume) => {})
player.on('fullscreenchange', (isFullscreen) => {})
player.on('captionsenabled', (track) => {})
player.on('error', (error) => {})
Cleanup
player.destroy() // Remove player
Troubleshooting
Video Not Playing
| Issue | Solution |
|---|---|
| Black screen | Check video URL is accessible |
| CORS error | Ensure proper CORS headers on server |
| Format unsupported | Provide MP4 fallback |
| Autoplay blocked | Set muted: true for autoplay |
Captions Not Showing
| Issue | Solution |
|---|---|
| VTT not loading | Check file URL; validate VTT syntax |
| CORS error | Serve VTT from same domain or enable CORS |
| Wrong encoding | Save VTT as UTF-8 |
YouTube/Vimeo Not Working
| Issue | Solution |
|---|---|
| Not loading | Check video URL format |
| API errors | Ensure videos are embeddable |
| Controls missing | VidPly uses native service players |
HLS Not Playing
| Issue | Solution |
|---|---|
| Stream not loading | Verify .m3u8 URL is accessible |
| CORS issues | Configure CORS on streaming server |
| Segments failing | Check segment URLs in manifest |
DASH Not Playing
| Issue | Solution |
|---|---|
| Stream not loading | Verify .mpd URL is accessible |
| CORS issues | Configure CORS on streaming server |
| No quality levels | Check MPD has multiple representations |
| TTML captions missing | dash.js renders TTML natively; ensure tracks are in the manifest |
| Transcript not available | TTML tracks don't support transcript; use WebVTT for transcript |
Best Practices
Performance
- ✅ Use
responsive: truefor fluid layouts - ✅ Provide poster images
- ✅ Use appropriate video resolutions
- ✅ Compress videos for web delivery
Accessibility
- ✅ Always provide captions
- ✅ Use meaningful track labels
- ✅ Test with keyboard only
- ✅ Test with screen readers
Cross-browser
- ✅ Provide MP4 + WebM sources
- ✅ Test on mobile devices
- ✅ Handle fullscreen differences on iOS