Thumbnails aus Videos in Browsern mit ffmpeg.wasm extrahieren
Video-Thumbnails sind für moderne Webanwendungen unverzichtbar – sie geben Nutzern eine schnelle visuelle Vorschau und helfen ihnen bei der Entscheidung, ob sie sich mit Ihren Inhalten befassen möchten. Mit dem Aufkommen leistungsstarker WebAssembly-Werkzeuge (Wasm) können Sie diese Thumbnails nun vollständig im Browser erzeugen, sodass die Medien der Nutzer lokal bleiben und gleichzeitig die Serverlast sinkt.
Warum Thumbnails im Browser extrahieren?
Die clientseitige Extraktion von Thumbnails bietet mehrere Vorteile:
- Geringere Serverlast – Die Encoding-Arbeit läuft auf dem Gerät der Nutzer, wodurch Back-End-Ressourcen frei werden.
- Lokales Feedback – Nutzer können sich das Ergebnis ansehen, ohne das Video auf Ihren Server hochzuladen.
- Besserer Datenschutz – Videos verlassen den Browser nie, was besonders bei sensiblen Inhalten oder bei der Einhaltung von Datenschutzvorschriften hilfreich ist.
Lernen Sie ffmpeg.wasm kennen
FFmpeg.wasm ist eine WebAssembly-Portierung des beliebten FFmpeg-Toolkits. Es stellt eine vertraute, kommandozeilenähnliche API in JavaScript bereit und läuft vollständig in modernen Browsern.
Wichtige Funktionen:
- Enthält die FFmpeg-Filter und -Codecs, die in den ausgewählten Core-Build kompiliert wurden.
- Bietet Single-Threaded- und Multithreaded-Cores.
- Der JavaScript-Wrapper ist MIT-lizenziert; der mitgelieferte Core und die Codecs haben eigene Lizenzen.
Installieren und initialisieren
npm install @ffmpeg/ffmpeg@0.12.15 @ffmpeg/util@0.12.2
// Use a browser bundler that supports the package's module worker.
import { FFmpeg } from '@ffmpeg/ffmpeg'
import { fetchFile, toBlobURL } from '@ffmpeg/util'
const ffmpeg = new FFmpeg()
let loading
let busy = false
export async function loadFFmpeg() {
if (ffmpeg.loaded) return
if (!loading) {
loading = (async () => {
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.10/dist/esm'
const coreURL = await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript')
try {
const wasmURL = await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm')
try {
await ffmpeg.load({ coreURL, wasmURL })
} finally {
URL.revokeObjectURL(wasmURL)
}
} finally {
URL.revokeObjectURL(coreURL)
}
})().finally(() => {
loading = undefined
})
}
await loading
}
Rufen Sie loadFFmpeg() verzögert auf, wenn die Nutzer einen Upload-Dialog öffnen. Dadurch wird der
Single-Threaded-Core geladen, der mehrere Dutzend Megabyte groß ist. Der Wrapper führt FFmpeg
bereits in einem Web Worker aus. Liefern Sie die Anwendung über HTTPS oder localhost aus und stellen
Sie sicher, dass ihre Content Security Policy die verwendeten Worker, Wasm- und Asset-URLs zulässt.
Sehen Sie sich die offiziellen
Beispiele zum Laden an.
Browser-Anforderungen erfüllen
Der oben verwendete Single-Threaded-Core @ffmpeg/core benötigt Unterstützung für WebAssembly und
Worker; er erfordert kein SharedArrayBuffer. Wenn Sie explizit zu @ffmpeg/core-mt wechseln, stellen Sie
dessen zusätzliches Worker-Asset bereit und aktivieren Sie die Cross-Origin-Isolation für
WebAssembly-Threads:
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Wenn Sie einen Service Worker oder ein eigenes CDN verwenden, stellen Sie sicher, dass diese Header über jeden Zwischenschritt korrekt weitergereicht werden.
Ein einzelnes Thumbnail extrahieren
Dieses Beispiel erzeugt ein PNG-Thumbnail und gibt die temporären virtuellen Dateien nach jedem Aufruf wieder frei:
export async function extractThumbnail(videoFile, time = '00:00:01') {
if (busy) throw new Error('A thumbnail operation is already running')
busy = true
const id = crypto.randomUUID()
const input = `input-${id}.mp4`
const output = `thumb-${id}.png`
try {
await loadFFmpeg()
await ffmpeg.writeFile(input, await fetchFile(videoFile))
const code = await ffmpeg.exec([
'-ss',
time,
'-i',
input,
'-map',
'0:v:0',
'-frames:v',
'1',
output,
])
if (code !== 0) throw new Error(`FFmpeg failed with exit code ${code}`)
// Seeking past the end may return success without creating an image.
const data = await ffmpeg.readFile(output)
if (!(data instanceof Uint8Array) || data.length === 0) {
throw new Error('No thumbnail was produced')
}
return new Blob([data], { type: 'image/png' })
} finally {
await Promise.allSettled([ffmpeg.deleteFile(input), ffmpeg.deleteFile(output)])
busy = false
}
}
Anwendungsbeispiel mit Fehlerbehandlung:
try {
const blob = await extractThumbnail(file, '00:00:05')
const url = URL.createObjectURL(blob)
thumbnailImg.onload = thumbnailImg.onerror = () => URL.revokeObjectURL(url)
thumbnailImg.src = url
} catch (err) {
console.error('Thumbnail extraction failed', err)
if (err.message && err.message.includes('load() failed')) {
console.error(
'FFmpeg failed to load. This might be due to browser compatibility or network issues.',
)
} else if (err.message && err.message.includes('SharedArrayBuffer')) {
console.error(
'SharedArrayBuffer is not available. Ensure Cross-Origin Isolation headers are set.',
)
}
}
Mehrere Thumbnails nacheinander abrufen
Verwenden Sie dieselbe FFmpeg-Instanz weiter, ohne Wasm neu zu laden. Dieser einfache Helfer wartet jede Extraktion ab; er schreibt und entfernt die Eingabe für jeden Zeitstempel. Deaktivieren Sie überlappende Aktionen, während er läuft:
export async function extractThumbnails(videoFile, marks = ['00:00:01', '00:00:05']) {
const thumbnails = []
for (const mark of marks) thumbnails.push(await extractThumbnail(videoFile, mark))
return thumbnails
}
Die UI mit einem Web Worker reaktionsfähig halten
@ffmpeg/ffmpeg erstellt einen eigenen Worker, daher ist für dieses Beispiel kein zusätzlicher
Wrapper-Worker nötig. Warten Sie dessen asynchrone Methoden ab, deaktivieren Sie wiederholte Klicks
während der Extraktion und zeigen Sie einen Ladezustand an, während der Core heruntergeladen wird
oder eine Datei verarbeitet. Die Ausführung im Worker hält den UI-Thread verfügbar, doch CPU- und
Speicherauslastung können die Reaktionsfähigkeit dennoch beeinträchtigen.
Performance-Tipps
- Asset-Caching – Cachen Sie die versionierten Core-Assets, um die Downloadzeit bei erneuten Aufrufen zu verkürzen.
- Lazy Loading – Importieren Sie die Bibliothek und rufen Sie
loadFFmpeg()erst auf, wenn die Nutzer mit einer Funktion interagieren, die sie benötigt, etwa beim Auswählen einer Videodatei. - Dateigröße begrenzen – Große 4K-Videos können den Browser-Speicher übersteigen oder zu lange für die Verarbeitung brauchen. Begrenzen Sie Uploads auf eine sinnvolle Größe, zum Beispiel 200 MB.
- Eine Instanz weiterverwenden – Mehrere FFmpeg-Instanzen verschwenden Speicher und verlangsamen die Verarbeitung.
- Rückfall auf den Server – Wenn der ausgewählte Core nicht geladen werden kann oder eine Datei die Grenzen des Geräts überschreitet, bieten Sie serverseitige Verarbeitung an, sofern die Nutzer dem Upload des Videos zustimmen.
Verarbeitung im Browser im Vergleich zum Server
| Aspekt | Browser (FFmpeg.wasm) | Server (z. B. Transloadit) |
|---|---|---|
| Latenz | Hängt von Download und Tempo des Geräts ab | Round-Trip + Queue-Zeit |
| Datenschutz | Medien verlassen das Gerät nie | Erfordert Upload und Ablage |
| Skalierbarkeit | Durch Hardware der Nutzer begrenzt | Praktisch unbegrenzt |
| Aufwand der Umsetzung | JS-Bibliothek + Header | API-Aufruf |
| Akkuverbrauch mobil | Hoch | Niedrig |
| Kompatibilität | Moderne Browser mit bestimmten Funktionen | Universell |
Nutzen Sie das Modell, das am besten zu Ihrem Produkt passt – oder kombinieren Sie beide zu einer robusten Lösung.
Häufige Probleme beheben
SharedArrayBufferist mit dem Threaded-Core nicht verfügbar – Prüfen Sie die HeaderCross-Origin-Embedder-Policy: require-corpundCross-Origin-Opener-Policy: same-originnoch einmal. Stellen Sie sicher, dass sie korrekt auf die Seite angewendet werden, die FFmpeg.wasm ausliefert.RangeError: Out of memory– Das Video ist möglicherweise zu groß oder zu komplex für den verfügbaren Speicher des Browsers. Versuchen Sie, das Video vor der Verarbeitung mit FFmpeg.wasm zu kürzen oder herunterzuskalieren, oder weichen Sie auf serverseitige Verarbeitung aus.- Langsamer erster Durchlauf – Der anfängliche Download und die Kompilierung von
ffmpeg-core.wasmkönnen Zeit in Anspruch nehmen. Setzen Sie Caching per Service Worker ein und erwägen Sie einen „Aufwärm“-Aufruf vonloadFFmpeg(), sobald die Seite sichtbar oder untätig ist, anstatt auf eine direkte Interaktion der Nutzer zu warten. - Mobile Browser – Testen Sie das Laden des Cores und repräsentative Dateien auf Ihren unterstützten Geräten. Die Speichergrenzen können sich deutlich unterscheiden; verlassen Sie sich nicht auf die Erkennung des Browsernamens als Ersatz dafür, die benötigten Funktionen zu testen.
Den Produktivbetrieb mit Transloadit vereinfachen
Wenn Ihre App täglich Zehntausende Videos verarbeiten muss – oder jeden Browser unterstützen soll – übernimmt unser 🤖 /video/thumbs Robot die Extraktion der Thumbnails für Sie. Er unterstützt die parallele Extraktion von Thumbnails, kann eine frei wählbare Anzahl von 1-999 Thumbnails pro Video erzeugen, erlaubt eigene Zeitstempel (in Prozent oder Sekunden), bietet mehrere Ausgabeformate (JPEG, JPG, PNG) und enthält fortgeschrittene Strategien zur Größenänderung wie crop, fit, fillcrop, min_fit, pad und stretch. In Kombination mit unserem Video-Encoding-Service skaliert er automatisch und blockiert die UI nie.
Nächste Schritte
Experimentieren Sie lokal mit FFmpeg.wasm, cachen Sie den Wasm-Core für eine flotte UX und
entscheiden Sie, wo der Kompromiss zwischen Browser und Server für Ihr Projekt sinnvoll ist. Wenn
Sie an die Grenzen der clientseitigen Verarbeitung stoßen, ist eine
Assembly, die den Robot /video/thumbs verwendet, nur einen API-Aufruf entfernt.
