
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 widget API with standardised controls |
| HLS | Adaptive bitrate streaming (hls.js in most browsers, native on iOS / iPadOS) |
| DASH | MPEG-DASH streaming via dash.js |
| Buffer spinner | 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 sources + 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 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 soundcloud.com. The widget is loaded on demand and can be integrated with the standard VidPly 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 is automatically loaded from the CDN as soon as .m3u8 URLs are detected. Behaviour varies by platform:
| Platform | HLS engine | Subtitles / Transcript / Quality |
|---|---|---|
| Chrome / Firefox / Edge | hls.js | Full VidPly user interface |
| Desktop macOS Safari | 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, interactive transcript and quality menu into the browser’s native HLS text tracks – you lose no UI controls.
DASH streaming
<video data-vidply src="https://example.com/manifest.mpd"></video>
dash.js is automatically loaded from the CDN as soon as .mpd URLs are detected. DASH streams support:
- Adaptive bitrate selection
- 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 on by default
<track kind="subtitles" src="captions.vtt" srclang="en" label="English" default>
Chapter
Add chapter navigation with 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
});
Reference of all options
| Option | Type | Default | Description |
|---|---|---|---|
width | Number | 800 | Player 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 fading (ms) |
keyboard | bool | true | Enable keyboard shortcut |
language | String | 'en' | UI 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 plays the content / explicit loading |
initialDuration | Number | 0 | Initial duration in seconds (UI only, before media metadata is loaded) |
requirePlaybackForAccessibilityToggles | bool | false | If true: AD/SL displays a prompt before playback, rather than loading/playing implicitly |
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 can be operated via the keyboard, is fully internationalised (player.download translation key) 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’) that appears in a configurable corner of the viewing area, can be moved and resized, and retains VidPly’s own subtitle display, transport controls and full-screen function within the floating shell.
As long as floating it is enabled, the browser’s native Picture-in-Picture API is
automatically suppressed (disablepictureinpicture + disableremoteplayback),
ensuring the user receives a consistent user experience across all browsers.
Behaviour
- Automatic display on scrolling out – When the original player is scrolled out of the viewport, the video appears in the floating shell. When it is scrolled back in, the video re-docks to its original container.
- Manual docking/undocking – The PiP button in the control bar manually docks the floating shell. Manual docking/undocking overrides scroll-based automatic floating until the next user-triggered
play. - Close button – The X button in the floating shell pauses the video, returns it to its original container and suppresses automatic floating for the remainder of the playback session.
- Drag & Resize – The floating shell can be dragged by the header and resized using the corner/edge handles. The geometry is retained per player via local storage.
- Reduced control bar – Only the essential controls are displayed within the floating shell: Play/Pause, Rewind, Fast-forward, Volume, Subtitles, PiP and Full Screen. Tooltips open 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 feature is disabled and the floating PiP 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
channels as TYPO3 / mpc_vidply is used):
<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
role="dialog"with a translatedaria-label(player.floatingPlayerDialog). - The PiP switch uses
aria-pressedto indicate the pinned status. - The focus returns to the original button when the floating shell 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
A centred loading spinner appears automatically whilst the media is being buffered (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 */
}
The container provides a .vidply-buffering class that you can use for styling or integration:
.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 next track |
autoPlayFirst | true | Automatically play 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
Provide an alternative video with audio description:
const player = new Player('#my-video', {
audioDescription: true,
audioDescriptionSrc: 'video-with-description.mp4'
});
Users can switch using the AD button.
Sign language
Add a sign language interpretation overlay:
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
- Window can be moved 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 forward/backward ±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 transcript |
| D | Drag mode (transcript/characters) |
| 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
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
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 whether 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 | Autoplay muted: true for autoplay |
Subtitles are not displayed
| Problem | Solution |
|---|---|
| VTT is not loading | Check the file URL; validate the VTT syntax |
| CORS error | Serve VTT from the same domain or enable CORS |
| Incorrect encoding | Save the VTT as UTF-8 |
YouTube/Vimeo not working
| Problem | Solution |
|---|---|
| Does not load | Check video URL format |
| API error | Ensure that the videos are embeddable |
| 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 whether 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 tracks 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 appropriate video resolutions
- ✅ Compress videos for web delivery
Accessibility
- ✅ Always provide subtitles
- ✅ Use meaningful titles
- ✅ Test using only the keyboard
- ✅ Test with screen readers
Cross-browser
- ✅ Provide MP4 and WebM sources
- ✅ Test on mobile devices
- ✅ Take into account differences in full-screen mode on iOS