Single-file audio + video player web app for media libraries served from basic HTTP directory listings (NGINX/Apache/IIS/etc).
It can be used as:
- A web player for folders of media on a server (NAS/home server/shared hosting).
- A local media player by installing it as a PWA (where supported).
- Play videos with external subtitles (
.srt/.vtt). - Customize subtitle display (font, size, position hint, text color, background color, and optional text-shadow mode).
- Play audio (not just video) and use cover art thumbnails when available.
- Build playlists from folders/albums, reorder them, and loop playback.
- Import/export
.m3uplaylists and play.m3uplaylists from the web (when CORS allows). - Generate video thumbnails (pre-rendered server-side, or on-the-fly in the browser).
- Load default behavior from
player.html.jsonand export your current configuration from the Settings modal. - Upload a custom player poster image (or set one in
player.html.json) with sensible precedence and export support. - Install as a PWA so it can behave like a local media player (launchable like an app; local file handling where supported).
- Play media from OneDrive / Google Drive (HTTPS + app keys required).
- Share a URL that resumes at the same folder + playlist + media + timestamp + subtitle selection.
- Quick Start
- Features
- Feature requests
- Pre-rendered server-side thumbnails
- Configuration file (
player.html.json) - Installing ffmpeg
- Supported browsers
- Supported web servers
player.html is designed to be a drop-in audio and video player that does not require any configuration or other files.
- Copy
./dist/player.htmlinto a folder that is served over HTTP with directory listing enabled. - Browse to
player.htmlin your browser.
player.html treats the directory listing HTML as an API for enumerating files/folders, so it works with simple web servers (see Supported web servers).
Serve player.html from https:// (or http://localhost) and use your browser's "Install" option to install it as an app.
Once installed, it can be launched like a local media player. Some platforms also support opening local media files directly into the app (PWA file handlers).
- Single file (
dist/player.html) with zero runtime dependencies. - All CSS/JS/SVG/assets are inlined (portable drop-in file).
- Video playback in the browser media engine (
MP4,M4V,MOV,MKV,WEBM,OGG, etc). - External subtitle support (
.srtand.vtt). - Subtitle display settings in the subtitles modal: font, size, fallback vertical position, text color, background color, and optional text-shadow background mode.
- Authored subtitle cue positioning is preserved. The subtitle position setting is only used as a fallback for cues without authored position.
- Picture-in-picture support.
- Playback controls: play/pause, seek, stop, volume, playback rate, fullscreen, PiP.
- Progress bar with timestamp preview thumbnail on hover/click/drag.
- Audio playback in the browser media engine (
MP3,WAV,AAC,M4A,MKA,OGG, etc). - Audio cover art thumbnails (sidecar art and embedded art, when available).
- Uses folder listing pages as an API (no backend required).
- Folder + file tiles for fast navigation through large media libraries.
- Playlist panel: view, add, reorder, previous/next, and looping playback.
- Import/export
.m3uplaylists. - Shareable state in the URL hash (restores folder, playlist, media, timestamp, and subtitle selection).
- Video thumbnails use pre-rendered server-side thumbnails when present (recommended for large libraries).
- Video thumbnails fall back to in-browser thumbnail generation otherwise.
- Animated thumbnails (optional).
- Thumbnail caching using
localStorage(view cache size, clear cache).
- Installable as a PWA (inline generated manifest).
- Shareable URL that resumes
player.htmlin the same folder, playlist, media item, timestamp, and subtitle selection. - Standard page metadata (
title,description) plus favicon and PWA manifest support.
- Select your own theme color with hue+saturation theming (including grayscale themes at
saturation: 0). - Set a custom player poster image from Settings (
Upload) or by config (settings.poster-image). - Media file metadata (bitrate, resolution, etc).
- Settings can be configured by file (
player.html.json) and exported from the UI. See Configuration file (player.html.json).
Use player.html with your own look and branding:
- Theme color via hue+saturation controls
- Custom poster/icon image via upload or config (
settings.poster-image)
- Keyboard shortcuts (press
?in the app to see the list). - Paste and Play:
CTRL+Vto play the URL currently in your clipboard. - Screenshot capture:
Shift+S, or useFile metadatamodal ->Actions->Take screenshot.
- Play media directly from OneDrive and Google Drive (requires HTTPS and valid app keys in
app.options.cloud).
Open the playlist panel using the playlist button next to the file sources. From there you can:
- Add media from the file tiles using the add button
- Reorder items with the up/down controls
- Jump to previous/next track (buttons in the main controls or Page Up/Page Down)
- Toggle looping playback for the entire playlist
- Import
.m3uplaylists from a URL (requires CORS) or from a local file - Export the current playlist as a
.m3ufile - Save the playlist to browser localStorage and restore it later
Notes:
- The current folder/playlist/media/timestamp/subtitle selection are encoded in the URL hash for shareable links and refresh restore.
- Looping is enabled by default and repeats the playlist.
- Imported
.m3ufiles can include relative URLs; they are resolved against the playlist URL (or the current page for local files). - Saved playlists in localStorage are optional and restored only on demand.
* Be careful with concurrency. Increasing the setting above 1 does make it generate thumbnails much faster. But it is very easy for HTTP requests for generating thumbnails to saturate a connection enough that the main video gets starved for bandwidth. Especially if you browse into a folder with many dozens of videos in it. Thumbnails are generated lazily as tiles scroll into view to reduce bandwidth.
** Animated thumbnails can consume a lot of data. The experience may degrade on slower network connections
player.html can use server-side thumbnails for any video that has one available. It will fall back to generating thumbnails in the browser otherwise. The server-side thumbnail files must follow the common filename convention of using the video file name, with the extension replaced with an image extension, in the same folder as the video. Note: The image files must be shown by your web server's directory browsing (may require mime-type adjustments on some servers) feature to show up in player.html.
- Media filename:
myMedia.mp4 - Matching thumbnail filename:
myMedia.jpg
- JPEG
- JPG
- PNG
- WEBP
- GIF
This repo includes prerender-video-thumbnails.py, which recursively generates missing thumbnails for all video files under a root folder. It never overwrites existing thumbnails.
What it does:
- Looks for videos by extension (e.g.
mp4,mkv,webm,mov, etc). - If a matching thumbnail already exists, it skips the video.
- Otherwise it generates a new image using
ffmpeg.
Requires ffmpeg on your PATH.
Run it from the repo root:
uv run prerender-video-thumbnails.py [ROOT]
Examples:
uv run prerender-video-thumbnails.py
uv run prerender-video-thumbnails.py "C:\Media\Videos" --ext webp --seek 2.5 --max-dim 640
Options:
ROOT- Root folder to scan (default: current directory)--ext- Thumbnail extension to create:jpg,jpeg,png,gif,webp(default:jpg)--seek- Seek time in seconds for the thumbnail frame (default:5.0)--max-dim- Maximum output dimension in pixels for the largest side (default:512)
This repo includes prerender-audio-thumbnails.py, which recursively creates missing folder.jpg sidecar files for audio folders. It scans each folder for embedded artwork and uses the first one it finds.
What it does:
- If a folder already has
folder.jpg, it skips the folder. - Otherwise it checks each audio file in the folder for embedded art.
- The first image it can extract becomes
folder.jpg.
Requires ffmpeg on your PATH.
Run it from the repo root:
uv run prerender-audio-thumbnails.py [ROOT]
Examples:
uv run prerender-audio-thumbnails.py
uv run prerender-audio-thumbnails.py "C:\Media\Audio" --debug
Options:
ROOT- Root folder to scan (default: current directory)--debug- Print ffmpeg debug output
player.html can load a configuration file named player.html.json from the same folder as player.html.
Priority order (highest to lowest):
- User settings saved in
localStorage(setting-*keys) player.html.json- Built-in defaults from
src/js/config.js
player.html.json uses the same shape as app.options. You can create one by:
- Opening the Settings modal
- Clicking
Export settings file - Saving the downloaded
player.html.jsonnext toplayer.html
{
"settings": {
"hue": 323,
"saturation": 100,
"auto-subtitles": false,
"subtitle-font": "sans",
"subtitle-size": "100%",
"subtitle-position": "author",
"subtitle-color": "#ffffff",
"subtitle-background": "#000000",
"subtitle-shadow": false,
"thumbnailing": true,
"animate": true
},
"thumbnails": {
"timestamps": [0.005, 0.01, 0.015],
"size": 320,
"mime": { "type": "image/webp", "quality": 0.2 },
"cache": true,
"resizeQuality": "high",
"concurrency": 1
},
"volumeExponent": 1.8
}User-facing display and subtitle defaults (most commonly customized):
settings.hue: Theme hue (number)settings.saturation: Theme saturation from0to100(use0for grayscale themes)- The Theme color picker in Settings updates
settings.hueandsettings.saturationtogether. settings.auto-subtitles: Auto-load matching subtitle file (true/false)settings.subtitle-font:sans,serif,mono,casualsettings.subtitle-size: Percentage string. Presets in UI:50%,75%,100%,150%,200%. Arbitrary percentages are accepted (clamped to25%-400%).settings.subtitle-position:author,90,75,60,35,20settings.subtitle-color: Hex color (for example#ffffff)settings.subtitle-background: Hex color (for example#000000)settings.subtitle-shadow:trueuses transparent cue backgrounds with offset text shadow based onsettings.subtitle-background;falseuses solid cue backgroundsettings.poster-image: Optional custom player poster image URL. Supports relative URL, absolute URL, or data URI.settings.blur: Enable UI blur effects (true/false)settings.transitions: Enable UI transitions (true/false)settings.thumbnailing: Enable video thumbnail generation (true/false)settings.animate: Enable animated thumbnails (true/false)settings.playlist-depth: Folder depth used when adding folders to playlists (1to3)
Poster image behavior:
- Precedence is:
- user
localStoragevalue (setting-poster-image) from uploaded custom poster player.html.jsonvalue (settings.poster-image)- built-in generated fallback poster
- user
- Uploaded raster images are resized to fit within
1920x1080and encoded as WebP when supported (PNG fallback). - Uploaded SVG files are preserved as SVG data URIs (not rasterized).
- Exported
player.html.jsonincludessettings.poster-image(including uploaded data URI values).
Thumbnail timing and generation:
thumbnails.timestamps: Relative capture positions for generated thumbnails (0.0to1.0)thumbnails.size: Max thumbnail width in pxthumbnails.mime.type: Output image mime type (for exampleimage/webp)thumbnails.mime.quality: Output quality (0.0to1.0)thumbnails.cache: Enable thumbnail cache inlocalStorage(true/false)thumbnails.resizeQuality: Canvas resize quality (low,medium,high)thumbnails.concurrency: Concurrent video thumbnail generation jobs
Audio thumbnail options:
audioThumbnails.concurrency: Concurrent cover-art lookupsaudioThumbnails.sidecarConcurrency: Concurrent sidecar checks per audio file
Playback and UI timing:
volumeExponent: Volume curve exponentupdateRate.timeupdate: UI update throttle for mediatimeupdateevents (ms)updateRate.trickHover: UI update throttle for trick-hover previews (ms)
Cloud source integration:
cloud.onedrive.clientIdcloud.gdrive.developerKeycloud.gdrive.clientIdcloud.gdrive.appId
Diagnostics:
debug: Enable non-error console logging (true/false)
Compatibility notes:
subtitles.*keys are still accepted on load for compatibility, butsettings.*is the canonical configuration surface for user-facing defaults.- Runtime/browser-detected fields may appear in exported files. They are optional and are not required for a hand-authored config file.
If you have an idea for player.html, please open a GitHub issue and label it as an enhancement.
Feature requests are preferred over pull requests.
The more detail you include, the easier it is to implement quickly and correctly. Helpful details include:
- What problem you are trying to solve
- What behavior you want
- Any UI/UX expectations
- Edge cases or compatibility concerns
- Example files/URLs/screenshots (if relevant)
A useful quality check before posting:
- Paste your issue draft into an AI/LLM and ask:
Does this feature request for player.html have enough detail to clearly implement it? What other details should I consider adding?
- Then paste your enhancement draft after that prompt and refine based on the feedback.
The thumbnail scripts require ffmpeg to be available on your PATH.
Windows (winget):
winget install --id=Gyan.FFmpeg --exact
macOS (Homebrew):
brew install ffmpeg
Linux (package manager examples):
sudo apt install ffmpeg
If your source is already H.264 video + AC3 audio, you can often remux to MP4 for broad playback compatibility while keeping the original quality.
Remuxing (-c copy) is a container change, not a re-encode. For copied streams it is lossless and typically much faster than transcoding.
This command:
- Copies video/audio/subtitle streams without re-encoding
- Copies chapters
- Moves MP4 metadata to the front (
faststart) for better web playback
ffmpeg -i "input.mkv" -map 0 -c copy -map_chapters 0 -movflags +faststart "output.mp4"
If your source audio is something less broadly supported in browsers/devices (for example DTS or EAC3), you can keep the original H.264 video stream and only transcode audio to AC3:
This keeps video remuxed/lossless (-c:v copy) and only re-encodes audio.
ffmpeg -i "input.mkv" -map 0 -c:v copy -c:a ac3 -b:a 640k -map_chapters 0 -movflags +faststart "output.mp4"
src/player.htmlis the dev template (it referencessrc/styles.css,src/js/*, andsrc/svg/*).dist/player.htmlis generated output; do not edit it by hand.
The build inlines CSS, JS, SVGs, and assets into a single portable file.
uv run build.py
For continuous rebuilds while editing:
uv run build.py --watch
The latest version of these browsers is supported:
- Edge (Chromium)
- Firefox
- Safari (Mac, iPadOS, iOS)
- Chrome
Modern browser/device media stacks have improved significantly. In practice, many current platforms now handle 5.1 AC3 / Dolby Digital playback out of the box.
Real-world compatibility can still vary by OS version, hardware decoder, browser version, and how audio is routed, but MP4 + H.264 + AC3 has worked well across iPhone/iOS Safari, Chrome/Edge, Roku devices, and Xbox in testing.
The latest version of these web servers (others may work as well):
- NGINX (
autoindexon) - Apache (
mod_autoindex) - IIS (enable
Directory Browsing)
player.htmluses folder.api to consume HTTP directory listings like an APIplayer.htmluses video-thumbnail.js to render thumbnails from video file URLsplayer.htmluses audio-thumbnail.js to render thumbnails from audio file URLs
© 2026 Paul Ellis


