svelte enhanced video

v0.0.1

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.

SvelteVideoJavaScriptFrontendViteFFmpeg

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
npm install -D svelte-enhanced-video
yarn
yarn add -D svelte-enhanced-video
pnpm
pnpm add -D svelte-enhanced-video

FFmpeg Setup

FFmpeg is required. The plugin resolves it in this order:

  1. ffmpegPath / ffprobePath options (explicit)
  2. ffmpeg-ffprobe-static package
  3. ffmpeg-static + ffprobe-static packages
  4. System PATH

Option A: install static binaries

pnpm
pnpm add -D ffmpeg-ffprobe-static
npm
npm install -D ffmpeg-ffprobe-static
yarn
yarn add -D ffmpeg-ffprobe-static
bun
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.

Option B: use system FFmpeg (must be on 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

ts logo
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:

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

OptionTypeDefaultDescription
formatsVideoFormat[]['mp4', 'webm']Output formats: mp4, webm, mp4_hevc
resolutionsnumber[][2160, 1440, 1080, 720, 480, 360]Target heights in px
fpsnumbersource fpsFPS cap (min(fps, source_fps))
cacheDirectorystring./.video-cache/videosDisk cache location
maxJobsnumbercpus - 1Concurrent FFmpeg processes
ffmpegPathstringauto-resolvedCustom FFmpeg binary
ffprobePathstringauto-resolvedCustom ffprobe binary
lockMaxAgeMsnumber7200000 (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:

null logo
# Video Cache
.video-cache/

Usage

Use <video:enhanced> in any .svelte file:

svelte logo
<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:

html logo
<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:

ts logo
import hero from './assets/hero.mp4?enhanced';
// hero.mp4["1080p"]  → "/assets/hero_1080p.mp4"
// hero.webm["720p"]  → "/_enhanced-video/hero_720p.webm"

Type shape:

ts logo
interface EnhancedVideo {
  mp4: Record<string, string>;
  webm: Record<string, string>;
  mp4_hevc: Record<string, string>;
}

Encoding Formats

FormatCodecAudioContainerNotes
mp4H.264AACMP4Universal, CRF 23
webmVP9OpusWebMSmaller, CRF 32
mp4_hevcH.265AACMP4Smallest, 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):

svelte logo
<!-- 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 the fps option 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.4
  • mp4_hevc (H.265) — limited support outside Safari/iOS; requires hvc1 tag
  • mp4 (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
npm install plyr
yarn
yarn add plyr
pnpm
pnpm add plyr

Usage

svelte logo
<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.

Result