Jump to content

Documentation

Universal, accessible video and audio player

VidPly Logo

A modern, feature-rich media player, written entirely in TypeScript and provided as an ES module with no dependencies. It combines the best accessibility features of AblePlayer with the streaming capabilities of MediaElement.js. Fully internationalised with support for 5 languages and full WCAG 2.2 AA compliance.

 

Live demos

Try out VidPly in action:

Why VidPly?

  • No dependencies – Pure vanilla JavaScript/TypeScript, no frameworks required
  • TypeScript-native – Built using strict TypeScript with built-in type declarations (dist/types/index.d.ts)
  • Accessibility first – WCAG 2.2 AA compliant with full support for keyboard navigation and screen readers
  • Multilingual – Built-in translations for 5 languages with easy extensibility
  • Fully customisable – CSS variables and comprehensive API
  • Modern build structure – ES modules with tree-shaking, code splitting and source maps
  • Production-ready – Thoroughly tested with real-world media content

Features

Core media support

  • Audio and video playback – Native HTML5 support for both media types
  • Multiple formats – MP3, OGG, WAV (audio) / MP4, WebM (video)
  • YouTube integration – Embedding YouTube videos with standardised controls
  • Vimeo integration – Seamless integration of the Vimeo player
  • SoundCloud integration – Playback of SoundCloud tracks and sets via the widget API with standardised controls
  • HLS streaming – Adaptive bitrate streaming with quality selection and dynamic subtitle detection
    • Uses hls.js 1.6.16 in Chrome / Firefox / Edge / Desktop Safari for full feature parity (quality menu, subtitles, transcript)
    • On iOS / iPadOS, where MSE is not available, falls back to native HLS; native text tracks continue to be displayed via the VidPly user interface for subtitles and transcripts
  • DASH streaming – MPEG-DASH support via dash.js 5.2.0 (modern UMD) with adaptive quality, TTML and WebVTT subtitles
  • Buffer spinner – Centred loading spinner that appears automatically whilst media is buffering (HTML5, HLS, DASH)
  • Download button – Optional download control with support for custom URLs (downloadButton + downloadUrl)
  • Preview thumbnails – Video preview thumbnails when hovering over the progress bar
  • OS media controls – Integration of the Media Session API: metadata for the currently playing track (title/artist/album/cover art) as well as controls for the lock screen, notifications and headphones, including ‘Previous/Next track’ for playlists
  • Playlists – Full support for playlists with automatic playback and navigation
    • Audio playlists with track information
    • Video playlists with thumbnails
    • Mixed playlists – Combination of audio and video in a single playlist
    • ‘Previous/Next track’ controls
    • Visual playlist window
    • Full-screen mode: YouTube-style horizontal carousel
      • Automatic show/hide depending on playback status
      • Swipe-enabled touch interface
      • Responsive card layout

Accessibility features (compliant with WCAG 2.2 AA)

  • Full keyboard navigation – All functions are accessible via the keyboard, custom keyboard shortcuts and menu navigation using the arrow keys (non-interactive/disabled menu items are excluded from the wandering focus)
  • Support for screen readers – Full ARIA labels (aria-controls, aria-expanded, aria-haspopup), live regions; menus with single-selection options (speed/quality/subtitles) role="menuitemradio" + aria-checked, and playback/pause, subtitle and volume controls – whether operated by mouse or via the keyboard – are announced
  • Interactive transcripts – click to jump and automatic scrolling with correct semantic HTML and a focus trap whilst the transcript is open
  • Sign language overlay – picture-in-picture with drag-and-resize functionality, operable via the keyboard
  • Audio description – Alternative audio track with descriptions of the visual content
  • Subtitle styling – Fully customisable (font, size, colour, opacity, border style)
  • High-contrast mode – Support for Windows high-contrast mode, colour-independent design
  • Reduced motion – Smooth scrolling and animations comply with prefers-reduced-motion (SC 2.3.3)
  • Zoom & reordering – Pinch-to-zoom remains available even in (pseudo-)full-screen mode; no fixed maximum-scale lock (SC 1.4.4 / 1.4.10)
  • Focus management – Logical focus order, programmatic focus management, visible indicators, focus traps in the transcript window and in the settings dialogue
  • Touch accessibility – Buttons, resize handles and slider sliders with a size of at least 24×24 CSS pixels in accordance with WCAG 2.2 AA (SC 2.5.8); swipable surfaces

Captions & subtitles

  • WebVTT support – standard subtitle format
  • Multiple languages – support for multiple audio tracks
  • Subtitle selection – Easily switch between tracks using the CC button
  • Subtitle styling – Dedicated styling dialogue (font, size, colour, opacity)
  • Chapter navigation – Jump to video chapters
  • Interactive transcripts – Transcript window with click-and-search function (the browser’s ‘Find on this page’ search function works for searching)

Playback functions

  • Adjustable playback speed – 0.25x to 2x playback
  • Navigation controls – Forward/backward navigation
  • Volume control – 0–100 per cent with mute function
  • Repeat – Single file or entire playlist
  • Full-screen mode – Native full-screen API with intelligent playlist overlay
  • Picture-in-picture – Native PiP support in the browser (can be activated via the standard PiP button)
  • Custom floating player (mini-player) – Optional window that floats on the page, which
    • automatically floats when the original player is scrolled out of view, and docks again when it is scrolled back into view
    • can be manually pinned or unpinned using the PiP button (manual pinning takes precedence over the scrolling behaviour)
    • is fully movable and resizable, whilst maintaining the geometry for each player
    • ensures that VidPly subtitles, controls and full-screen mode continue to function within the floating overlay
    • automatically suppresses the browser’s native PiP whilst it is active
    • is only available on desktop (disabled by default when the viewport width is less than 768 px)

Internationalisation

Built-in support for 5 languages:

  • English (en)
  • Spanish (es) – Español
  • French (fr) – Français
  • German (de) – Deutsch
  • Japanese (ja) – 日本語

All elements of the user interface are fully translated, including:

  • Control buttons and menus
  • Time display and duration formatting
  • Keyboard shortcuts
  • Error messages and notifications
  • ARIA labels for screen readers

Custom translations: Easily add your own languages by loading JSON or YAML translation files via data attributes or JavaScript options. The player automatically detects the HTML lang attribute and loads the appropriate translations.

Installation

Compiling from source code

First, build the player:

 
# Install dependencies
npm install

# Build production files
npm run build
 

This will place the minimised files in the dist/ folder.

 
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="dist/vidply.min.css">
</head>
<body>
  <video data-vidply src="video.mp4" width="800" height="450">
    <track kind="subtitles" src="captions.vtt" srclang="en" label="English">
  </video>

  <script type="module">
    import Player from './dist/prod/vidply.esm.min.js';
    // Auto-initialized via data-vidply attribute
  </script>
</body>
</html>
 

Option 2: Traditional script tag (IIFE)

 
<link rel="stylesheet" href="dist/vidply.min.css">
<script src="dist/legacy/vidply.min.js"></script>

<video id="my-video" src="video.mp4"></video>

<script>
  const player = new VidPly.Player('#my-video', {
    controls: true,
    autoplay: false,
    volume: 0.8,
    language: 'en'
  });
</script>
 

Option 3: Development (TypeScript source files)

 
import Player from './src/index';

const player = new Player('#my-video', {
  controls: true,
  autoplay: false,
  volume: 0.8,
  language: 'en'
});
 

The library is written in strict TypeScript. Type declarations are applied dist/types/index.d.ts , so that users who tsc or vite receive full IntelliSense without any additional @types package.

 

Quick Start

1. Create the player

 
npm install
npm run build
 

2. Add it to your page

 
<link rel="stylesheet" href="dist/vidply.min.css">
<script type="module">
  import Player from './dist/prod/vidply.esm.min.js';
</script>
 

3. Create a video player

 
<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-es.vtt" srclang="es" label="Español">
</video>
 

That’s it! The player will be initialised automatically.

YouTube player

 
<video data-vidply src="https://www.youtube.com/watch?v=VIDEO_ID"></video>
 

Vimeo player

 
<video data-vidply src="https://vimeo.com/VIDEO_ID"></video>
 

Audio player

 
<audio data-vidply>
  <source src="audio.mp3" type="audio/mpeg">
</audio>
 

HLS streaming

 
<video data-vidply src="https://example.com/stream.m3u8"></video>
 

DASH streaming

 
<video data-vidply src="https://example.com/manifest.mpd"></video>
 

SoundCloud

 
<audio data-vidply src="https://soundcloud.com/artist/track"></audio>
 

Download button

Enable the download button and (optionally) specify a custom URL:

 
<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('#my-video', {
  downloadButton: true,
  downloadUrl: '/files/lecture.mp4' // optional, falls back to current src
});
 

Custom floating player (mini-player)

Enable the floating player on the page (‘custom PiP’). If the original video is scrolled out of view, VidPly displays a movable and resizable floating overlay in the selected corner; when the original is scrolled back into view, it is docked again. Users can also manually dock or undock the floating player using the PiP button in the control bar. The browser’s native Picture-in-Picture API is automatically suppressed whilst floating mode is enabled, ensuring a consistent experience for users.

Enable via the data-vidply-options JSON blob:

 
<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>
 

Or programmatically:

 
const player = new Player('#my-video', {
  floating: true,
  floatingPosition: 'bottom-right', // or 'bottom-left' | 'top-right' | 'top-left'
  floatingMinViewportWidth: 768     // disable feature below this viewport width
});
 

Notes:

  • Audio-only players (<audio>) ignore the ‘floating’ option.
  • Closing the floating window pauses playback and prevents it from automatically reappearing until the next user-initiated play.
  • The floating window stores its size and position for each player in local storage.
  • Below floatingMinViewportWidth (default: 768 px), the PiP button is hidden and the floating function is disabled.

DASH + HLS + MP4 fallback

To ensure maximum device compatibility, serve all three formats:

 
<video data-vidply width="800" height="450" poster="preview.jpg">
  <source src="video/dash/manifest.mpd" type="application/dash+xml">
  <source src="video/hls/master.m3u8" type="application/x-mpegURL">
  <source src="video/fallback.mp4" type="video/mp4">
  <track kind="subtitles" src="video/vtt/subtitles.de.vtt" srclang="de" label="Deutsch" default>
  <track kind="subtitles" src="video/vtt/subtitles.en.vtt" srclang="en" label="English">
  <track kind="chapters" src="video/vtt/chapters.de.vtt" srclang="de" label="Kapitel">
</video>
 

VidPly automatically detects the source type based on the file extension (.mpd / .m3u8 / .mp4) and selects the appropriate renderer.

Configuration options

 
const player = new Player('#video', {
  // Display
  width: 800,
  height: 450,
  poster: 'poster.jpg',
  responsive: true,

  // Media metadata + OS media controls (Media Session API)
  title: null,                  // Now-playing title (lock-screen / notification)
  artist: null,                 // Now-playing artist/author
  album: null,                  // Now-playing album/collection
  mediaSession: true,           // Expose OS media controls + metadata (set false to opt out)

  // Playback
  autoplay: false,
  loop: false,
  muted: false,
  volume: 0.8,
  playbackSpeed: 1.0,
  startTime: 0,
  
  // Controls
  controls: true,
  hideControlsDelay: 3000,
  playPauseButton: true,
  progressBar: true,
  currentTime: true,
  duration: true,
  volumeControl: true,
  muteButton: true,
  chaptersButton: true,
  qualityButton: true,
  captionStyleButton: true,
  speedButton: true,
  captionsButton: true,
  transcriptButton: true,
  audioDescriptionButton: true,
  signLanguageButton: true,
  fullscreenButton: true,
  helpButton: true,             // Show a keyboard-shortcuts help button (requires keyboard: true)
  pipButton: false,
  downloadButton: false,        // Show a download button in the control bar
  downloadUrl: null,            // Optional explicit download URL (falls back to current src)
  downloadFormat: null,         // Optional override for the displayed download format (e.g. "MP4")
  downloadFileSize: null,       // Optional override for the displayed file size (bytes)
  downloadFetchSize: true,      // Issue a HEAD request to detect file size when not provided

  // 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)

  // Seeking
  seekInterval: 10,
  seekIntervalLarge: 30,

  // Captions
  captions: true,
  captionsDefault: false,
  captionsFontSize: '100%',
  captionsFontFamily: 'sans-serif',
  captionsColor: '#FFFFFF',
  captionsBackgroundColor: '#000000',
  captionsOpacity: 0.8,

  // Audio Description
  audioDescription: true,
  audioDescriptionSrc: null, // URL to audio-described version

  // Sign Language
  signLanguage: true,
  signLanguageSrc: null, // URL to sign language video
  signLanguagePosition: 'bottom-right', // 'bottom-right', 'bottom-left', 'top-right', 'top-left'
  signLanguageDisplayMode: 'both',      // 'pip' (overlay) | 'main' (source swap) | 'both'
  signLanguageSources: undefined,       // { en: '/sl/en.mp4', de: '/sl/de.mp4', ... }

  // Transcripts
  transcript: false,
  transcriptPosition: 'external',
  transcriptContainer: null,
  
  // Keyboard (defaults; do not assign the same key to two actions)
  keyboard: true,
  keyboardShortcuts: {
    'play-pause': [' ', 'p', 'k'],
    'seek-forward': ['ArrowRight'],
    'seek-backward': ['ArrowLeft'],
    'volume-up': ['ArrowUp'],
    'volume-down': ['ArrowDown'],
    'mute': ['m'],
    'fullscreen': ['f'],
    'captions': ['c'],
    'caption-style-menu': ['a'],
    'speed-up': ['>'],
    'speed-down': ['<'],
    'speed-menu': ['s'],
    'quality-menu': ['q'],
    'chapters-menu': ['j'],
    'transcript-toggle': ['t'],
    'help': ['?']
  },
  
  // Accessibility
  screenReaderAnnouncements: true,
  focusHighlight: true,
  highContrast: false,
  ariaLabels: {},                 // Override individual ARIA labels by i18n key
  metadataAlerts: {},             // Map of metadata-cue keys to alert handlers (opt-in)
  metadataHashtags: {},           // Map of metadata-cue hashtags to handler config (opt-in)

  // Internationalization
  language: 'en',
  languages: ['en'],
  languageFiles: undefined,       // { pt: '/i18n/pt.json', it: '/i18n/it.json' }
  languageFile: undefined,        // Single language code to load
  languageFileUrl: undefined,     // URL for the single language file

  // Resume Playback
  resumePlayback: true,           // Save and offer to resume playback position
  resumeThreshold: 10,            // Seconds; do not offer resume if less than this was watched
  resumePrompt: true,             // false = silently auto-resume

  // Thumbnail Preview
  thumbnailPreview: true,
  thumbnailCacheSize: 50,
  thumbnailPregenerate: true,
  thumbnailInterval: 10,
  thumbnailWidth: 160,
  thumbnailHeight: 90,
  thumbnailQuality: 0.8,

  // Lazy Loading
  lazyInit: true,
  lazyMargin: '200px',

  // Theming
  theme: 'dark',                  // 'dark' | 'light' | 'minimal' | 'high-contrast'
  themeVariables: {},             // Custom --vidply-* CSS variable overrides

  // Callbacks
  onReady: () => console.log('Ready!'),
  onPlay: () => console.log('Playing!'),
  onPause: () => console.log('Paused!'),
  onEnded: () => console.log('Ended!'),
  onTimeUpdate: (t) => {},
  onVolumeChange: (v) => {},
  onError: (err) => console.error(err),

  // Streaming
  hideSpeedForHls: false,         // Hide speed control for ALL HLS streams
  hideSpeedForHlsVideo: false,    // Hide speed control only for HLS video (e.g. live streams)
  hideSpeedForDash: false,        // Hide speed control for ALL DASH streams
  hideSpeedForDashVideo: false,   // Hide speed control only for DASH video

  // Advanced
  debug: false,
  pauseOthersOnPlay: true,
  classPrefix: 'vidply',          // CSS class prefix and event/storage namespace
  iconType: 'svg',
  initialDuration: 0,             // Optional duration shown before metadata is loaded
  requirePlaybackForAccessibilityToggles: false, // If true, AD/SL toggles before play show a notice instead of starting playback
  fillContainer: false,
  playsInline: true,              // Inline playback on iOS

  // Performance
  preload: 'metadata',            // 'none', 'metadata', or 'auto'
  deferLoad: false                // Delay loading until user plays (good for many players)
});
 

Keyboard shortcuts

KeyAction
Space bar / P / KPlay/Pause
FToggle full screen
MMute/Unmute
/ Increase/decrease volume
/ Skip: -10 s / +10 s
CTurn subtitles on/off (or open the menu if there are several options)
AOpen subtitle style menu
< / >Decrease/increase speed
SOpen the speed menu
QOpen the ‘Quality’ menu
JOpen the ‘Chapter’ menu
TShow/hide subtitles
?Show help on keyboard shortcuts
DSwitch access mode (transcript/sign language)
RSwitch between zoom modes (transcript/sign language)
EscExit drag/resize mode or close menus
HomeReset the position of subtitles/sign language

API Reference

Playback Controls

 
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 10 seconds
player.seekBackward(10) // Skip backward 10 seconds
 

Volume Control

 
player.setVolume(0.5)   // Set volume (0.0-1.0)
player.getVolume()      // Get current volume
player.mute()           // Mute audio
player.unmute()         // Unmute audio
player.toggleMute()     // Toggle mute state
 

Note on mobile devices: On touch devices (iOS, Android, tablets), instead of the volume slider, only a mute/unmute button is displayed. Mobile browsers control the volume of HTML5 videos using the device’s volume buttons – this is standard behaviour that cannot be overridden by web apps for security reasons. The mute button is used to mute the audio quickly , whilst the hardware buttons control the actual volume.

Playback speed

 
player.setPlaybackSpeed(1.5)  // Set speed (0.25-2.0)
player.getPlaybackSpeed()     // Get current speed
 

Full screen

 
player.enterFullscreen()  // Enter fullscreen
player.exitFullscreen()   // Exit fullscreen
player.toggleFullscreen() // Toggle fullscreen
 

Note on iOS/Mobile Safari: As iOS does not support the full-screen API for container elements, VidPly automatically switches to a ‘pseudo-full-screen’ mode, in which the player is positioned using CSS to fill the entire viewport. This provides a near-full-screen experience on iOS devices, whilst retaining all the player’s functions.

Subtitles

 
player.enableCaptions()   // Enable captions
player.disableCaptions()  // Disable captions
player.toggleCaptions()   // Toggle captions

// Switch between caption tracks
player.captionManager.switchTrack(0)  // Switch to first track
player.captionManager.getAvailableTracks()  // Get all tracks
 

Transcript

 
// Show/Hide Transcript
player.transcriptManager.showTranscript()     // Show transcript window
player.transcriptManager.hideTranscript()     // Hide transcript window
player.transcriptManager.toggleTranscript()   // Toggle transcript visibility

// Drag & Resize Modes (Desktop only, mobile breakpoint: 768px)
player.transcriptManager.toggleKeyboardDragMode()   // Toggle drag mode (D key)
player.transcriptManager.toggleResizeMode()         // Toggle resize mode (R key)

// Settings Menu
player.transcriptManager.showSettingsMenu()    // Show settings dropdown
player.transcriptManager.hideSettingsMenu()    // Hide settings dropdown

// Check State
if (player.transcriptManager.isVisible) {
  console.log('Transcript is visible');
}
 

Audio description

 
player.enableAudioDescription()   // Switch to described version
player.disableAudioDescription()  // Switch back to original
player.toggleAudioDescription()   // Toggle audio description
 

Sign language

 
// Show/Hide Sign Language Video
player.enableSignLanguage()   // Show sign language overlay
player.disableSignLanguage()  // Hide sign language overlay
player.toggleSignLanguage()   // Toggle sign language

// Multi-Language Support
player.switchSignLanguage('de')  // Switch to German sign language

// Drag & Resize (available via settings menu or keyboard)
// D key - Toggle drag mode with arrow keys
// R key - Toggle resize mode (shows resize handles)
// Home key - Reset position
// Escape - Exit drag/resize mode
 

Playlists

 
import { Player, PlaylistManager } from './dist/prod/vidply.esm.min.js';

// Create player
const player = new Player('#my-player');

// Create playlist manager
const playlist = new PlaylistManager(player, {
  autoAdvance: true,   // Auto-play next track
  loop: false,         // Loop back to start
  showPanel: true      // Show playlist UI
});

// Load tracks
playlist.loadPlaylist([
  {
    src: 'track1.mp3',
    title: 'Track 1',
    artist: 'Artist Name',
    poster: 'thumb1.jpg'
  },
  {
    src: 'track2.mp3',
    title: 'Track 2',
    artist: 'Artist Name',
    tracks: [
      { src: 'captions.vtt', kind: 'captions', srclang: 'en' }
    ]
  }
]);

// Control playlist
playlist.next()         // Go to next track
playlist.previous()     // Go to previous track
playlist.goToTrack(2)   // Jump to specific track
playlist.hasNext()      // Check if next track exists
playlist.hasPrevious()  // Check if previous track exists

// Listen for track changes
player.on('playlisttrackchange', (e) => {
  // e: { index: number, item: PlaylistTrack, total: number, previousIndex?: number }
  console.log(`Now playing track ${e.index + 1} / ${e.total}:`, e.item.title);
});
 

Settings

 
player.showSettings()  // Open settings dialog
player.hideSettings()  // Close settings dialog
 

Keyboard shortcuts help

 
player.toggleKeyboardHelp()  // Toggle the keyboard-shortcuts help dialog
player.showKeyboardHelp()    // Open the help dialog
player.hideKeyboardHelp()    // Close the help dialog
 

The dialogue is derived from the current keyboardShortcuts key assignments (including any overrides) and can also be accessed via the Help button in the control bar and via the keyboard shortcut ‘?’. Only keyboard shortcuts relevant to the current player are listed: Keyboard shortcuts for functions (subtitles, subtitle style, playback speed, quality, chapters, transcript, full screen) are only displayed if the respective function is actually available; therefore, for a pure audio player or a video without subtitles, no keyboard shortcuts that cannot be executed will not be displayed. The list is regenerated every time the dialogue box is opened, so that features loaded later (e.g. HLS subtitles or quality levels) appear as soon as they are ready.

Status information

 
player.getCurrentTime()  // Get current time
player.getDuration()     // Get duration
player.isPlaying()       // Check if playing
player.isPaused()        // Check if paused
player.isEnded()         // Check if ended
player.isMuted()         // Check if muted
player.isFullscreen()    // Check if fullscreen
 

Event listeners

 
player.on('ready', () => {})
player.on('play', () => {})
player.on('pause', () => {})
player.on('ended', () => {})
player.on('timeupdate', (time) => {})
player.on('volumechange', (volume) => {})
player.on('playbackspeedchange', (speed) => {})
player.on('fullscreenchange', (isFullscreen) => {})
player.on('hlsmanifestparsed', (data) => {})
player.on('dashqualitychanged', (data) => {})
player.on('textcuesupdate', () => {})
player.on('captionsenabled', (track) => {})
player.on('captionsdisabled', () => {})
player.on('error', (error) => {})
 

Clean-up

 
player.destroy()  // Remove player and cleanup
 

Customisation

Custom styling

VidPly offers a wide range of CSS variables for easy customisation:

 
/* Override default colors and sizing */
.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);
}
 

Add a custom language

 
<video 
  data-vidply 
  data-vidply-language-files='{"pt": "languages/pt.json", "it": "languages/it.json"}'
  src="video.mp4"
></video>
 

Option 2: JavaScript API

 
import { i18n } from './src/i18n/i18n';

// Load language file from URL
await i18n.loadLanguageFromUrl('pt', 'languages/pt.json');

// Or load multiple languages
await i18n.loadLanguagesFromUrls({
  'pt': 'languages/pt.json',
  'it': 'languages/it.json'
});

// Set the language
i18n.setLanguage('pt');
 

Option 3: Add translations programmatically

 
import { i18n } from './src/i18n/i18n';

i18n.addTranslation('pt', {
  player: {
    play: 'Reproduzir',
    pause: 'Pausar',
    mute: 'Silenciar',
    unmute: 'Ativar som'
  }
});

i18n.setLanguage('pt');
 

Language file format

Create languages/pt.json:

 
{
  "player": {
    "play": "Reproduzir",
    "pause": "Pausar",
    "mute": "Silenciar",
    "unmute": "Ativar som",
    "fullscreen": "Tela cheia",
    "captions": "Legendas"
  },
  "time": {
    "currentTime": "Tempo atual",
    "duration": "Duração"
  }
}
 

The player supports both the JSON and YAML formats for language files.

Build process

VidPly uses a modern build system with esbuild for TypeScript bundling, the TypeScript compiler for .d.ts declarations, and ‘clean-css’ for CSS.

Available scripts

 
npm run build        # Build everything (JS + types + CSS)
npm run build:js     # Bundle TypeScript with esbuild (ESM + IIFE)
npm run build:types  # Emit type declarations to dist/types/
npm run build:css    # Build CSS only
npm run typecheck    # Run tsc --noEmit
npm run watch        # Watch mode for development
npm run clean        # Clean dist directory
npm run dev          # Start dev server
npm run test         # Run unit tests (Vitest)
npm run test:e2e     # Run end-to-end tests (Playwright)
npm run test:all     # Run all tests
 

Output files

  • dist/dev/vidply.esm.js - ES module (development)
  • dist/prod/vidply.esm.min.js - ES module (production)
  • dist/legacy/vidply.js - IIFE (development)
  • dist/legacy/vidply.min.js - IIFE (production environment)
  • dist/types/index.d.ts - TypeScript declarations
  • dist/vidply.css - Styles (unminified)
  • dist/vidply.min.css - Styles (minified)

Detailed information on the build documentation can be found in the ‘BUILD.md’ file.

Browser support

The library is provided in two bundles. Choose the one that suits your target audience:

Modern ESM bundle (dist/prod/vidply.esm.min.js) – recommended.

  • Chrome 100+
  • Firefox 100+
  • Safari 15+
  • Edge 100+
  • iOS Safari 15+
  • Android Chrome 100+

Legacy IIFE package (dist/legacy/vidply.min.js) – to support older browsers.

  • Chrome 80+
  • Firefox 78+
  • Safari 14+
  • Edge 88+

The TypeScript declarations are based on ES2022; both bundles are built using esbuild + Terser. You can find the exact targets in the Build Instructions file.

Licence

GNU General Public Licence v2.0 or later

Copyright (C) 2026 Matthias Peltzer

This programme is free software; you may redistribute it and/or modify it under the terms of the GNU General Public Licence, as published by the Free Software Foundation; either version 2 of the licence or (at your option) any later version.

The full text of the licence can be found in the LICENSE file.

Get involved

Contributions are welcome! Please don’t hesitate to submit a pull request.

Documentation

Guides

Reference

Acknowledgements

Inspired by:

Built with Vanilla JavaScript by Matthias Peltzer

Share page