
π 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
Dev server runs at 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 | Emit .d.ts type declarations to dist/types/ |
npm run build:css | Build CSS only |
npm run typecheck | tsc --noEmit strict type-check across src/ |
npm run watch | Watch mode (auto-rebuild) |
npm run dev | Start dev server |
npm run clean | Clean dist directory |
npm run start | Build + start dev server |
npm run test | Vitest unit tests |
npm run test:e2e | Playwright end-to-end tests |
ποΈ Build System
TypeScript bundles (esbuild)
build/build.js consumes the TypeScript sources directly (esbuild handles transpilation) and produces:
| File | Format | Use |
|---|---|---|
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 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 emits:
| File | Use |
|---|---|
dist/types/index.d.ts | Public type entry, referenced from package.json#types |
dist/types/**/*.d.ts | Per-module declarations for tree-shakable named imports |
CSS (clean-css)
build/build-css.js produces:
| File | Use |
|---|---|
vidply.css | Development |
vidply.min.css | Production |
π― Architecture
Core 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 self-decides whether to use
hls.js(Chrome / Firefox / Edge / desktop Safari) or the native<video>HLS support (iOS / iPadOS where MSE is unavailable). On the native path it bridges the browser'sTextTrackAPI into VidPly's caption / transcript / quality UI so feature parity is preserved.
π Adding Features
New Control Button
- Add to ControlBar (
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 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
.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
localhostafternpm run dev - Test demos:
demo/demo.html- Full featuresdemo/playlist-audio.html- Audio playlistsdemo/playlist-video.html- Video playlistsdemo/hls-test.html- HLS streamingdemo/dash-test.html- DASH streaming
Accessibility Testing
- Keyboard: Navigate using only keyboard
- Screen reader: Test with NVDA/VoiceOver
- High contrast: Enable Windows HCM
- Mobile: Test touch interactions
Browser Testing
| 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 initialized |
play | - | Playback started |
pause | - | Playback paused |
ended | - | Playback ended |
timeupdate | time | Current time changed |
volumechange | volume | Volume changed |
playbackspeedchange | speed | Speed changed |
fullscreenchange | isFullscreen | Fullscreen toggled |
hlsmanifestparsed | data | HLS manifest parsed |
dashqualitychanged | data | DASH quality changed |
textcuesupdate | - | New text cues available (HLS/DASH) |
captionsenabled | track | Captions enabled |
captionsdisabled | - | Captions disabled |
playlisttrackchange | item | Playlist track changed |
error | error | Error occurred |
π Debugging
Enable Debug Mode
const player = new Player('#video', { debug: true });
Console Logging
if (this.options.debug) {
console.log('[VidPly]', message, data);
}
Common Issues
| Issue | Debug Steps |
|---|---|
| Player not initializing | Check data-vidply attribute; check console errors |
| Renderer not loading | Check source URL format; verify network requests |
| Captions not showing | Validate VTT syntax; check CORS |
| Keyboard not working | Check keyboard: true; verify focus |
π Documentation Files
| File | Content |
|---|---|
| GETTING_STARTED | Basic setup |
| USAGE | Detailed API usage |
| PLAYLIST | Playlist features |
| TRANSCRIPT | Transcript system |
| KEYBOARD | Keyboard shortcuts |
| BUILD | Build system details |
| CHANGELOG | Version history |
| Users-Guide | Integration guide |
π Quick Reference
Key Files
| What | Where |
|---|---|
| Entry point | src/index.ts |
| Player class | src/core/Player.ts |
| Player options type | src/types/options.ts |
| Control bar | src/controls/ControlBar.ts |
| Captions | 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 config | tsconfig.json |
Build Output
| File | Format | Size |
|---|---|---|
vidply.esm.min.js | ESM | ~45KB |
vidply.min.js | IIFE | ~50KB |
vidply.min.css | CSS | ~15KB |
External Dependencies
| Dependency | Pinned version | Purpose | Loaded |
|---|---|---|---|
| hls.js | 1.7.3 | HLS streaming (Chrome / Firefox / Edge / desktop Safari) | On demand (CDN fallback) |
| dash.js | 5.2.1 (modern UMD) | DASH streaming | On demand (CDN fallback) |
| YouTube IFrame API | β | YouTube playback | On demand |
| Vimeo Player API | β | Vimeo playback | On demand |
| SoundCloud Widget API | β | SoundCloud playback | On demand |
Default CDN fallback URLs (override via hlsScriptUrl / dashScriptUrl):
π€ Contributing
- Fork the repository
- Create feature branch:
git checkout -b feature/my-feature - Make changes
- Test thoroughly
- Build:
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
- Meaningful variable names
- Comment non-obvious intent (not what the code does)
- Include ARIA attributes on every interactive element
- Run
npm run typecheckandnpm run testbefore committing
Version: 1.1.7 | License: GPL-2.0-or-later