
Setup
# Clone repository
git clone github.com/MatthiasPeltzer/vidply.git
cd vidply
# Install dependencies
npm install
# Build production files
npm run build
# Start dev server
npm run dev
The development server runs on localhost
Directory structure
vidply/
├── build/ # Build scripts
│ ├── build.js # TypeScript bundling (esbuild → ESM + IIFE)
│ ├── build-css.js # CSS build (clean-css)
│ ├── watch.js # Watch mode
│ └── clean.js # Clean dist
├── demo/ # Demo pages
│ ├── demo.html # Main demo
│ ├── playlist-audio.html # Audio playlist demo
│ ├── playlist-video.html # Video playlist demo
│ ├── hls-test.html # HLS streaming demo
│ ├── dash-test.html # DASH streaming demo
│ └── single-player-dash.html # Single DASH player demo
├── dist/ # Built files (generated)
│ ├── prod/vidply.esm.min.js # ES Module (production)
│ ├── legacy/vidply.min.js # IIFE (production)
│ ├── types/index.d.ts # TypeScript declarations
│ └── vidply.min.css # Styles (production)
├── docs/ # Documentation
├── src/ # Source code (strict TypeScript)
│ ├── core/
│ │ └── Player.ts # Main Player class
│ ├── controls/
│ │ ├── ControlBar.ts # Control bar UI (incl. download button, buffering spinner)
│ │ ├── CaptionManager.ts # Caption handling
│ │ ├── KeyboardManager.ts # Keyboard shortcuts
│ │ ├── SettingsDialog.ts # Settings menu
│ │ └── TranscriptManager.ts
│ ├── features/
│ │ └── PlaylistManager.ts # Playlist support
│ ├── i18n/
│ │ ├── i18n.ts # i18n system
│ │ ├── translations.ts # Translation loader
│ │ └── languages/ # Built-in languages
│ │ ├── en.ts
│ │ ├── de.ts
│ │ ├── es.ts
│ │ ├── fr.ts
│ │ └── ja.ts
│ ├── icons/
│ │ └── Icons.ts # SVG icon definitions
│ ├── renderers/
│ │ ├── HTML5Renderer.ts # Native HTML5 video/audio
│ │ ├── YouTubeRenderer.ts # YouTube iframe API
│ │ ├── VimeoRenderer.ts # Vimeo Player API
│ │ ├── SoundCloudRenderer.ts# SoundCloud Widget API
│ │ ├── HLSRenderer.ts # hls.js integration + native iOS bridge
│ │ └── DASHRenderer.ts # dash.js integration
│ ├── styles/
│ │ └── vidply.css # Main stylesheet
│ ├── types/ # Shared TypeScript types
│ │ ├── options.ts # PlayerOptions
│ │ └── globals.d.ts # Ambient declarations (Hls, dashjs, …)
│ ├── utils/
│ │ ├── DOMUtils.ts # DOM helpers
│ │ ├── EventEmitter.ts # Event system
│ │ ├── TimeUtils.ts # Time formatting
│ │ ├── FocusUtils.ts # Focus management
│ │ ├── MenuUtils.ts # Menu helpers
│ │ ├── StorageManager.ts # localStorage wrapper
│ │ └── ...
│ └── index.ts # Main entry point
├── index.html # Development page
├── tsconfig.json # Strict TypeScript config
├── package.json
└── server.js # Dev server
NPM scripts
| Script | Description |
|---|---|
npm run build | Build everything (TypeScript bundles + types + CSS) |
npm run build:js | Bundle TypeScript with esbuild (ESM + IIFE) |
npm run build:types | Type declarations .d.ts Type declarations dist/types/ |
npm run build:css | Build CSS only |
npm run typecheck | tsc --noEmit Strict type checking in src/ |
npm run watch | Watch mode (automatic rebuild) |
npm run dev | Start development server |
npm run clean | Clean up the “dist” directory |
npm run start | Build + start development server |
npm run test | Vitest unit tests |
npm run test:e2e | Playwright end-to-end tests |
Build system
TypeScript bundles (esbuild)
build/build.js uses the TypeScript source files directly (esbuild handles the transpilation) and generates:
| File | Format | Usage |
|---|---|---|
dev/vidply.esm.js | ES module | Development |
prod/vidply.esm.min.js | ES module | Production |
legacy/vidply.js | IIFE | Development |
legacy/vidply.min.js | IIFE | Production |
Features:
- Code splitting (renderers are loaded on demand)
- Tree shaking
- Source maps (development)
- Minification (production)
Type declarations (tsc)
npm run build:types Runs tsc --emitDeclarationOnly against tsconfig.build.json and outputs:
| File | Use |
|---|---|
dist/types/index.d.ts | Public type entry, referenced by package.json#types |
dist/types/**/*.d.ts | Module-related declarations for ‘tree-shakable’ named imports |
CSS (clean-css)
build/build-css.js generated:
| File | Usage |
|---|---|
vidply.css | Development |
vidply.min.css | Production |
Architecture
Key components
Player
├── ControlBar # UI controls
│ ├── PlayButton
│ ├── ProgressBar
│ ├── VolumeControl
│ ├── DownloadButton # opt-in via downloadButton/downloadUrl
│ ├── PipButton # native PiP, or pin/unpin custom floating player when floating: true
│ ├── SettingsButton
│ └── FullscreenButton
├── BufferingOverlay # Centered loading spinner (.vidply-loading)
├── CaptionManager # Captions/subtitles
├── KeyboardManager # Keyboard shortcuts
├── TranscriptManager # Interactive transcript
├── FloatingPlayerManager # Custom in-page miniplayer ("own PiP"), opt-in via floating: true
├── Renderer # Media playback
│ ├── HTML5Renderer
│ ├── YouTubeRenderer
│ ├── VimeoRenderer
│ ├── SoundCloudRenderer
│ ├── HLSRenderer # hls.js + native iOS TextTrack bridge
│ └── DASHRenderer
└── PlaylistManager # Playlist handling
Event flow
User Action → KeyboardManager/ControlBar → Player → Renderer → DOM
↓
EventEmitter → External Listeners
Renderer selection
// src/core/Player.ts
selectRenderer(src: string): RendererCtor {
if (isYouTubeUrl(src)) return YouTubeRenderer;
if (isVimeoUrl(src)) return VimeoRenderer;
if (src.includes('soundcloud.com')) return SoundCloudRenderer;
if (isHLSUrl(src)) return HLSRenderer; // hls.js (or native HLS on iOS/iPadOS)
if (isDASHUrl(src)) return DASHRenderer;
return HTML5Renderer;
}
The HLS renderer decides for itself whether
hls.js(Chrome / Firefox / Edge / Desktop Safari) or the native<video>HLS support (iOS / iPadOS, where MSE is not available). On the native path, it integratesTextTrackinto the VidPly user interface for subtitles, transcripts and quality, ensuring that feature parity is maintained.
Adding features
New button
- Add to control bar (
src/controls/ControlBar.ts):
createMyButton(): HTMLButtonElement {
const button = document.createElement('button');
button.className = 'vidply-button vidply-my-button';
button.setAttribute('aria-label', i18n.t('player.myButton'));
button.innerHTML = Icons.myIcon;
button.addEventListener('click', () => this.player.myAction());
return button;
}
- Add icon (
src/icons/Icons.ts):
export const Icons = {
// ...existing icons
myIcon: `<svg>...</svg>`,
} as const;
- Add translation (
src/i18n/languages/en.ts):
export default {
player: {
// ...existing
myButton: 'My Button',
}
};
- Add to Player API (
src/core/Player.ts):
myAction(): void {
this.emit('myaction');
}
New renderer
- Create renderer (
src/renderers/MyRenderer.ts):
import type { Player } from '../core/Player';
export class MyRenderer {
constructor(public player: Player) {}
async init(): Promise<void> { /* ... */ }
async load(src: string): Promise<void> { /* ... */ }
play(): void { /* ... */ }
pause(): void { /* ... */ }
seek(time: number): void { /* ... */ }
setVolume(vol: number): void { /* ... */ }
getCurrentTime(): number { /* ... */ }
getDuration(): number { /* ... */ }
destroy(): void { /* ... */ }
}
- Register in the player:
import { MyRenderer } from '../renderers/MyRenderer';
// In selectRenderer()
if (isMyServiceUrl(src)) return MyRenderer;
New language
- Create language file (
src/i18n/languages/pt.ts):
export default {
player: {
play: 'Reproduzir',
pause: 'Pausar',
// ... all translations
}
};
- Register (
src/i18n/translations.ts):
import pt from './languages/pt';
export const translations = {
en, de, es, fr, ja,
pt,
};
CSS architecture
Variables
:root {
/* 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;
}
BEM naming convention
.vidply-player { } /* Block */
.vidply-player--fullscreen { } /* Modifier */
.vidply-controls { } /* Block */
.vidply-controls__left { } /* Element */
.vidply-button { } /* Block */
.vidply-button--active { } /* Modifier */
Accessibility
/* High contrast mode */
@media (forced-colors: active) {
.vidply-button {
border: 1px solid currentColor;
}
}
/* Focus visible */
.vidply-button:focus-visible {
outline: 2px solid var(--vidply-primary-color);
outline-offset: 2px;
}
/* Touch targets */
.vidply-button {
min-width: 44px;
min-height: 44px;
}
Testing
Manual testing
- Open
localhostbynpm run dev - Test demos:
demo/demo.html- Full feature setdemo/playlist-audio.html- Audio playlistsdemo/playlist-video.html- Video playlistsdemo/hls-test.html- HLS streamingdemo/dash-test.html- DASH streaming
Accessibility tests
- Keyboard: Navigation using the keyboard only
- Screen reader: Testing with NVDA/VoiceOver
- High contrast: Enable Windows High Contrast Mode
- Mobile devices: Testing touch interactions
Browser tests
| Browser | Priority |
|---|---|
| Chrome | High |
| Firefox | High |
| Safari | High |
| Edge | Medium |
| iOS Safari | High |
| Android Chrome | Medium |
Plugin system
EventEmitter
// Player extends EventEmitter
player.on('play', () => console.log('Playing'));
player.on('pause', () => console.log('Paused'));
player.on('timeupdate', (time) => console.log(time));
// Custom events
player.emit('mycustomevent', { data: 'value' });
Available events
| Event | Data | Description |
|---|---|---|
ready | - | Player initialised |
play | - | Playback started |
pause | - | Playback paused |
ended | - | Playback finished |
timeupdate | Time | Current time changed |
volumechange | Volume | Volume changed |
playbackspeedchange | Speed | Speed changed |
fullscreenchange | Full screen | Full screen switched |
hlsmanifestparsed | Data | HLS manifest analysed |
dashqualitychanged | Data | DASH quality changed |
textcuesupdate | - | New text cues available (HLS/DASH) |
captionsenabled | Track | Subtitles enabled |
captionsdisabled | - | Subtitles disabled |
playlisttrackchange | Element | Title changed in playlist |
error | Error | An error has occurred |
Debugging
Enable debug mode
const player = new Player('#video', { debug: true });
Console logging
if (this.options.debug) {
console.log('[VidPly]', message, data);
}
Common problems
| Problem | Troubleshooting steps |
|---|---|
| Player is not initialising | Check the data-vidply attribute; check console errors |
| Renderer is not loading | Check the format of the source URL; check the network requests |
| Subtitles are not displayed | Check VTT syntax; check CORS |
| Keyboard not working | Check keyboard: true; check focus |
Documentation files
| File | Contents |
|---|---|
| GETTING_STARTED.md | Basic setup |
| USAGE.md | Detailed API usage |
| PLAYLIST.md | Playlist functions |
| TRANSCRIPT.md | Transcription system |
| KEYBOARD.md | Keyboard shortcuts |
| BUILD.md | Details on the build system |
| CHANGELOG.md | Version history |
| User manual.md | Integration Guide |
Quick Reference
Important files
| What | Where |
|---|---|
| Entry point | src/index.ts |
| Player class | src/core/Player.ts |
| Type of player options | src/types/options.ts |
| Control bar | src/controls/ControlBar.ts |
| Subtitles | src/controls/CaptionManager.ts |
| Keyboard | src/controls/KeyboardManager.ts |
| i18n | src/i18n/i18n.ts |
| Styles | src/styles/vidply.css |
| Icons | src/icons/Icons.ts |
| TypeScript configuration | tsconfig.json |
Build output
| File | Format | Size |
|---|---|---|
vidply.esm.min.js | ESM | ~45 KB |
vidply.min.js | IIFE | ~50 KB |
vidply.min.css | CSS | ~15 KB |
External dependencies
| Dependency | Purpose | Loaded |
|---|---|---|
| hls.js | HLS streaming (Chrome / Firefox / Edge / Desktop Safari) | On-demand (CDN) |
| dash.js | DASH streaming | On-demand (CDN) |
| YouTube IFrame API | YouTube playback | On-demand |
| Vimeo Player API | Vimeo playback | On demand |
| SoundCloud Widget API | SoundCloud playback | On demand |
Contribute
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make changes
- Test thoroughly
- Create:
npm run build - Commit:
git commit -m 'Add my feature' - Push:
git push origin feature/my-feature - Open pull request
Code style
- Strict TypeScript (
strict: true,noImplicitAny,strictNullChecks) - BEM CSS naming convention
- Meaningful variable names
- Comment on non-obvious intentions (not what the code does)
- Add ARIA attributes to every interactive element
- Execute
npm run typecheckandnpm run testbefore committing
Version: 1.1.7 | Licence: GPL-2.0-or-later