
Brief overview
VidPly is a universal, accessible media player that supports the following:
| Features | Support |
|---|---|
| Video | MP4, WebM, OGG |
| Audio | MP3, OGG, WAV |
| YouTube | Embedded with standardised controls |
| Vimeo | Embedded with standardised controls |
| SoundCloud | Embedded via the Widget API with standardised controls |
| HLS | Adaptive streaming with variable bitrate (hls.js in most browsers, natively on iOS / iPadOS) |
| DASH | MPEG-DASH streaming via dash.js |
| Buffer loading indicator | Centred loading spinner whilst waiting/searching |
| Download button | Optional controls with support for custom URLs |
| Playlists | Audio, video and mixed media with thumbnails |
| Accessibility | WCAG 2.2 AA compliant |
| TypeScript | Strict TS source files + bundled .d.ts declarations |
| Languages | EN, ES, FR, DE, JA + custom |
Installation
1. Create the player
npm install
npm run build
This will create files in the dist/:
prod/vidply.esm.min.js- ES module (recommended)legacy/vidply.min.js- IIFE for the script tag (globalVidPly)vidply.min.css- Styles
2. Embed it into 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 automatically initialises 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 the full URL:
<video data-vidply src="https://youtu.be/VIDEO_ID"></video>
Vimeo
<video data-vidply src="https://vimeo.com/VIDEO_ID"></video>
SoundCloud
<audio data-vidply src="https://soundcloud.com/artist/track-name"></audio>
The SoundCloud widget API is automatically detected for any URL that soundcloud.com. The widget is loaded on demand and can be integrated with VidPly’s standard controls for play, pause, seek and volume.
When used mpc-vidply in TYPO3, the privacy layer displays a GDPR consent overlay before the SoundCloud iframe is loaded – visitors must first consent to the loading of external content.
HLS streaming
<video data-vidply src="https://example.com/stream.m3u8"></video>
hls.js 1.6.16 is loaded on demand when .m3u8 URLs are detected (CDN fallback if not already present on the page). Behaviour varies by platform:
| Platform | HLS engine | Subtitles / Transcript / Quality |
|---|---|---|
| Chrome / Firefox / Edge | hls.js | Full VidPly user interface |
| Desktop version of Safari on macOS | hls.js (for compatibility with other browsers) | Full VidPly user interface |
| iOS / iPadOS Safari | Native <video> HLS | Full VidPly user interface via the native TextTrack API bridge |
Even on iOS, where
hls.jscannot be run (no MSE), VidPly still integrates the subtitle menu, the interactive transcript and the quality menu into the browser’s native HLS text tracks – you won’t lose any UI controls.
DASH streaming
<video data-vidply src="https://example.com/manifest.mpd"></video>
dash.js 5.2.0 (modern UMD) is loaded on demand as soon as .mpd URLs are detected. DASH streams support:
- Quality selection with adaptive bitrate
- TTML subtitles (rendered natively by dash.js)
- WebVTT subtitles (processed by VidPly’s subtitle 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 subtitles
Simple subtitles
<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
| Type | Purpose |
|---|---|
subtitles | Translation of dialogue |
captions | Dialogue + sound effects (for the deaf/hard of hearing) |
descriptions | Text descriptions of visual content |
chapters | Chapter markers for navigation |
Subtitles enabled by default
<track kind="subtitles" src="captions.vtt" srclang="en" label="English" default>
Chapter
Add chapter navigation using a chapter 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
About 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>
About 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
});
Overview of all options
| Option | Type | Default | Description |
|---|---|---|---|
width | Number | 800 | Player’s width |
height | Number | 450 | Player height |
poster | String | null | URL of the poster image |
responsive | bool | true | Responsive resizing |
autoplay | bool | false | Start playback automatically |
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 | Delay before automatic hiding (ms) |
keyboard | bool | true | Enable keyboard shortcut |
language | String | 'en' | User interface language |
captions | bool | true | Enable subtitle support |
captionsDefault | bool | false | Show subtitles by default |
transcript | bool | false | Show subtitle window |
debug | bool | false | Debug logging |
preload | String | 'metadata' | 'none', 'metadata', 'auto' |
deferLoad | bool | false | Avoid starting the network loading process during initialisation; only load when the user launches the application or upon an explicit load command |
initialDuration | Number | 0 | Initial duration in seconds (UI only, before the media metadata is loaded) |
requirePlaybackForAccessibilityToggles | bool | false | If ‘true’: AD/SL displays a prompt before playback, rather than implicitly starting the download and playing it |
Download button
Displays an optional 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 accessible via the keyboard, is fully internationalised (player.download translation keys) and uses aria-label so that screen readers announce it correctly.
Note for streaming sources:
downloadUrlis usually the URL of an MP4/MP3 fallback, as users generally do not.mpdor.m3u8manifest as a single file.
Custom floating player (mini-player)
VidPly includes an optional, in-page floating player (‘custom PiP’) which appears in a configurable corner of the viewport, can be dragged and resized, and retains VidPly’s own subtitle display, controls and full-screen mode within the floating container.
As long as floating it is enabled, the browser’s native Picture-in-Picture API is
automatically suppressed (disablepictureinpicture + disableremoteplayback)
, ensuring a consistent experience for the user across browsers.
Behaviour
- Automatic display on scrolling out – When the original player is scrolled out of the viewport, the video appears in the floating container. When it is scrolled back in, the video is docked back to its original container.
- Manual docking/undocking – The PiP button in the control bar can be used to manually dock the floating container. Manual docking/undocking takes precedence over scroll-based automatic docking until the next user-triggered
play. - Close button – The X button in the floating window pauses the video, returns it to its original container and disables automatic floating for the remainder of the playback session.
- Drag and resize – The floating window can be dragged by its header and resized using the corner or edge handles. The geometry is retained per player via local storage.
- Streamlined control bar – Only the essential controls are displayed within the floating window: Play/Pause, Rewind, Fast-forward, Volume, Subtitles, PiP and Full Screen. Tooltips appear above the buttons; the size of the subtitles is
90%and limited to95%width. - Audio players are skipped – The floating function applies only to
<video>players. - By default, only on the desktop – Below
floatingMinViewportWidth(default 768 px), the function is disabled and the PiP floating button is hidden in the main control bar (it never appears in the overflow menu).
Enabling via HTML (data-vidply-options JSON)
The floating options are controlled via the data-vidply-options JSON blob (the same
channel used by 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>
Enabling 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 displayed as
role="dialog"with a translatedaria-label(player.floatingPlayerDialog). - The PiP toggle uses
aria-pressedto indicate the pinned status. - The focus returns to the original button when the floating button is closed using the keyboard.
- All built-in languages (en, de, es, fr, ja) contain translations for
player.floatingPlayer,player.floatingPlayerClose,player.floatingPlayerEnter,player.floatingPlayerExitandplayer.floatingPlayerDialog.
Buffering Spinner
Whilst buffering the media (waiting / seeking / initial loadstart) and disappears when canplay / playing. It works for HTML5, HLS and DASH renderers and takes into account prefers-reduced-motion.
You can customise the design using CSS variables:
.vidply-player {
--vidply-spinner-color: #ffffff;
--vidply-spinner-size: 56px;
--vidply-z-buffering: 1; /* sits below the play overlay */
}
During the loading process, the container provides a .vidply-buffering class that you can use for styling or to integrate hooks:
.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 | Automatically play the next track |
autoPlayFirst | true | Automatically play the first track loadPlaylist() (if ‘false’: the first track is loaded/selected but not played) |
loop | false | Repeat playlist |
showPanel | true | Show playlist window |
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 descriptions in two ways:
Described video (recommended for production) – Upload or link to a pre-mixed MP4/WebM file in which the commentary is already embedded in the audio track. VidPly replaces the entire video source when the user switches the audio description on or off.
VTT track (advanced fallback for audio description) – if no described video is available, a
kind="descriptions"WebVTT track can control the browser’s text-to-speech function: the player pauses at each cue, reads out the description, and then resumes playback. The quality depends on the browser’s voice; use described videos if broadcast-quality narration is required.
Text description markers are always available to read in the transcript window, regardless of which audio track
is active. Set audioDescriptionSpeech: false to use text-only descriptions (without TTS).
Mode (audioDescriptionMode, Default auto):
| Value | Behaviour |
|---|---|
auto | Video being described, if configured; otherwise, VTT language, provided VTT descriptions are available |
swap | Replaces the described video only |
vtt_speech | VTT language only |
const player = new Player('#my-video', {
audioDescription: true,
audioDescriptionSrc: 'video-with-description.mp4', // optional
audioDescriptionMode: 'auto',
audioDescriptionSpeech: true,
audioDescriptionExtended: true
});
Users can toggle the subtitles using the AD button. Demo: `demo/single-player-vtt-speech.html`.
Sign language
Add an overlay for sign language interpretation:
const player = new Player('#my-video', {
signLanguage: true,
signLanguageSrc: 'sign-language.mp4',
signLanguagePosition: 'bottom-right'
});
Positioning 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 on any line to jump to that point
- Automatic scrolling during playback
- Searchable text
- The window can be dragged and resized
Keyboard shortcuts
VidPly offers comprehensive keyboard control:
| Key | Action |
|---|---|
| Space bar / P / K | Play/Pause |
| F | Toggle full screen |
| M | Mute/Unmute |
| ↑ / ↓ | Increase/decrease volume |
| ← / → | Skip forwards and backwards ±10 seconds |
| C | Turn subtitles on/off |
| A | ‘Subtitle Style’ menu |
| <** / **> | Decrease/increase speed |
| S | Speed menu |
| Q | ‘Quality’ menu |
| J | Chapter menu |
| T | Show/hide subtitles |
| D | Drag mode (transcript/sign language) |
| R | Resize mode |
| Home | Reset position |
| Esc | 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']
}
});
Design & Themes
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;
}
Internationalisation
Integrated 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
});
Detect automatically from HTML
<html lang="de">
<!-- Player auto-detects German -->
</html>
Load custom language
About 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
Full screen
player.enterFullscreen() // Enter fullscreen
player.exitFullscreen() // Exit fullscreen
player.toggleFullscreen() // Toggle fullscreen
Subtitles
player.enableCaptions() // Enable captions
player.disableCaptions() // Disable captions
player.toggleCaptions() // Toggle captions
Status
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) => {})
Clean-up
player.destroy() // Remove player
Troubleshooting
Video won’t play
| Problem | Solution |
|---|---|
| Black screen | Check that the video URL is accessible |
| CORS error | Ensure that the CORS headers on the server are correct |
| Format not supported | Provide an MP4 fallback |
| Autoplay blocked | Set up muted: true autoplay |
Subtitles are not displayed
| Problem | Solution |
|---|---|
| VTT is not loading | Check the file URL; validate the VTT syntax |
| CORS error | Serve the VTT from the same domain or enable CORS |
| Incorrect encoding | Save the VTT as UTF-8 |
YouTube/Vimeo not working
| Problem | Solution |
|---|---|
| Not loading | Check the format of the video URL |
| API error | Ensure that the videos can be embedded |
| Controls missing | VidPly uses native service players |
HLS is not playing
| Problem | Solution |
|---|---|
| Stream is not loading | Check whether the .m3u8 URL is accessible |
| CORS issues | Configure CORS on the streaming server |
| Segments are failing | Check the segment URLs in the manifest |
DASH is not playing
| Problem | Solution |
|---|---|
| Stream is not loading | Check that the .mpd URL is accessible |
| CORS issues | Configure CORS on the streaming server |
| No quality levels | Check whether MPD has multiple renditions |
| TTML subtitles are missing | dash.js renders TTML natively; ensure that the titles are included in the manifest |
| Transcript not available | TTML tracks do not support transcripts; use WebVTT for transcripts |
Best practice
Performance
- ✅ Use
responsive: truefor fluid layouts - ✅ Provide poster images
- ✅ Use suitable video resolutions
- ✅ Compress videos for web delivery
Accessibility
- ✅ Always provide subtitles
- ✅ Use descriptive titles for audio tracks
- ✅ Test usability using only the keyboard
- ✅ Test with screen readers
Across browsers
- ✅ Provide MP4 and WebM sources
- ✅ Test on mobile devices
- ✅ Take into account differences in full-screen mode on iOS