svelte enhanced video
v0.0.1A Vite plugin that automatically generates multi-resolution, multi-format video variants from <video:enhanced> tags in Svelte components. Videos are encoded at build time (or lazily in dev) using FFmpeg, cached on disk, and served with responsive <source> tags.
A Vite plugin that automatically generates multi-resolution, multi-format video variants from <video:enhanced> tags in Svelte components. Videos are encoded at build time (or lazily in dev) using FFmpeg, cached on disk, and served with responsive <source> tags.
Install
npm install -D svelte-enhanced-video
yarn add -D svelte-enhanced-video
pnpm add -D svelte-enhanced-video
FFmpeg Setup
FFmpeg is required. The plugin resolves it in this order:
ffmpegPath/ffprobePathoptions (explicit)ffmpeg-ffprobe-staticpackageffmpeg-static+ffprobe-staticpackages- System
PATH
Option A: install static binaries
pnpm add -D ffmpeg-ffprobe-static
npm install -D ffmpeg-ffprobe-static
yarn add -D ffmpeg-ffprobe-static
bun add -D ffmpeg-ffprobe-static
Option B: use system FFmpeg (must be on PATH)
Go to FFmpeg.org and download the static build for your OS. then add it to your system PATH.
ffmpeg -version
Setup
Add the plugin to vite.config.ts before the Svelte plugin. The plugin works with both SvelteKit and plain Vite + Svelte setups.
SvelteKit
import { enhancedVideo } from 'svelte-enhanced-video';
import { sveltekit } from '@sveltejs/kit/vite';
export default {
plugins: [
enhancedVideo({
formats: ['mp4', 'webm'],
resolutions: [1080, 720, 480],
fps: 30
}),
sveltekit()
]
};
Vite + Svelte (no SvelteKit)
The plugin does not depend on SvelteKit — it hooks into Vite’s transform and load lifecycle, so it works with the standalone @sveltejs/vite-plugin-svelte as well:
import { enhancedVideo } from 'svelte-enhanced-video';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
base: './',
plugins: [enhancedVideo({ formats: ['mp4'], fps: 30 }), svelte()]
});
Full Options
| Option | Type | Default | Description |
|---|---|---|---|
formats | VideoFormat[] | ['mp4', 'webm'] | Output formats: mp4, webm, mp4_hevc |
resolutions | number[] | [2160, 1440, 1080, 720, 480, 360] | Target heights in px |
fps | number | source fps | FPS cap (min(fps, source_fps)) |
cacheDirectory | string | ./.video-cache/videos | Disk cache location |
maxJobs | number | cpus - 1 | Concurrent FFmpeg processes |
ffmpegPath | string | auto-resolved | Custom FFmpeg binary |
ffprobePath | string | auto-resolved | Custom ffprobe binary |
lockMaxAgeMs | number | 7200000 (2h) | Stale lock cleanup threshold |
Ignore cache folder
Make sure to add the cache folder .video-cache/ (by default) to your .gitignore to avoid committing generated videos:
# Video Cache
.video-cache/
Usage
Use <video:enhanced> in any .svelte file:
<video:enhanced src="./assets/hero.mp4" controls muted autoplay loop class="player" />
The plugin transforms this at compile time into a standard <video> with multiple <source> tags per format/resolution:
<video id="video_0" controls muted autoplay loop class="player">
<source src="/assets/hero_1080p.mp4" type="video/mp4" size="1080" />
<source src="/assets/hero_720p.mp4" type="video/mp4" size="720" />
<source src="/assets/hero_1080p.webm" type='video/webm; codecs="vp9"' size="1080" />
<source src="/assets/hero_720p.webm" type='video/webm; codecs="vp9"' size="720" />
Your browser does not support the video tag.
</video>
All standard <video> attributes (controls, autoplay, muted, loop, preload, playsinline, class, style, id, data-*, etc.) are passed through.
Programmatic Import
You can also import video metadata directly:
import hero from './assets/hero.mp4?enhanced';
// hero.mp4["1080p"] → "/assets/hero_1080p.mp4"
// hero.webm["720p"] → "/_enhanced-video/hero_720p.webm"
Type shape:
interface EnhancedVideo {
mp4: Record<string, string>;
webm: Record<string, string>;
mp4_hevc: Record<string, string>;
}
Encoding Formats
| Format | Codec | Audio | Container | Notes |
|---|---|---|---|---|
mp4 | H.264 | AAC | MP4 | Universal, CRF 23 |
webm | VP9 | Opus | WebM | Smaller, CRF 32 |
mp4_hevc | H.265 | AAC | MP4 | Smallest, CRF 28, hvc1 tag |
Dev vs Build Behavior
Development: Videos encode in the background. The original file is served immediately as a fallback. When encoding finishes, a full HMR reload swaps in optimized sources. A polling interval (2s) checks for completion.
Build: All variants are encoded synchronously before the build completes. Missing FFmpeg or encoding errors will fail the build.
Assumptions, Limitations & Gotchas
src must be a static string
Only literal string paths work. These are skipped (with a console warning):
<!-- Won't work -->
<video:enhanced src={videoPath} />
<video:enhanced src={`./assets/${name}.mp4`} />
Resolution filtering
Only resolutions smaller than or equal to the source video height are generated. A 720p source with default config produces only 720p, 480p, and 360p variants. If the source is smaller than all configured resolutions, no <source> tags are generated.
Cache management
- Cache lives at
.video-cache/videos/by default — add it to.gitignore - Cache is not auto-pruned; it grows over time as videos change
- Cache key =
SHA256(mtime + filesize + fps_cap)— changing thefpsoption invalidates the entire cache - Delete
.video-cache/to force re-encode
First dev server start can be slow
Large videos or those with multiple resolutions cause noticeable encoding delays on the first run. Consider pre-building your cache or reducing resolutions/formats during development.
FFmpeg codec parameters are hardcoded
CRF values, presets, and codec flags cannot be customized per-format or per-resolution.
Only works in .svelte files
The <video:enhanced> transform runs on the Svelte file AST. It does not work in plain HTML, dynamic string templates, or programmatically constructed markup.
Browser support considerations
webm(VP9) — not supported in Safari lower than 16.4mp4_hevc(H.265) — limited support outside Safari/iOS; requireshvc1tagmp4(H.264) — universal support
Plugin order matters
enhancedVideo() must be registered before sveltekit() in the Vite plugins array so the transform runs first.
Use With Plyr.js
With this plugin you will have the easiers way to create a multi resolution video player by adding plyrjs
install plyr
npm install plyr
yarn add plyr
pnpm add plyr
Usage
<script lang="ts">
import type Plyr from 'plyr';
import 'plyr/dist/plyr.css';
import { onMount } from 'svelte';
let videoElement = $state<HTMLVideoElement>(null!); // ! is used to assert that this will actually in fact be assigneda value.
onMount(() => {
let player: Plyr | null = null;
async function load() {
// Dynamic import (no ssr support by plyr)
const module = await import('plyr');
const Plyr = module.default;
player = new Plyr(videoElement, {
controls: [
'play-large',
'play',
'progress',
'current-time',
'duration',
'mute',
'volume',
'settings',
'fullscreen'
]
});
}
load();
return () => {
player?.destroy();
};
});
</script>
<div>
<video:enhanced
bind:this={videoElement}
class="max-h-[calc(100vh-128px)] w-full rounded-lg border"
src="$lib/assets/video-plugin/demo.mp4"
></video:enhanced>
</div>
you will have a professional video player with multiple resolution support, all without any additional configuration or setup.
