Skip to content

Video thumbnails

Show a preview image for a video without playing it: pick a moment, get a file. @symbiote-native/video-thumbnails wraps expo-video-thumbnails so every SymbioteNative adapter can reach it, not just React. The single export is a free function with no per-instance state, so the React, Vue, Angular, Svelte, and Solid entry points are plain re-exports of the same core.

OS platform Support
iOS live
Android live
Framework adapter Support
React live
Vue live
Angular live
Svelte live
Solid live
Terminal window
npm install @symbiote-native/video-thumbnails

Scaffolding or extending a SymbioteNative app? npx @symbiote-native/cli new --video-thumbnails (or add --video-thumbnails in an existing app) installs and wires this for you - see @symbiote-native/cli.

expo-video-thumbnails and expo-modules-core come along as regular dependencies, pinned to exact versions. Never install either yourself, and never add the expo meta-package to your project (it bundles its own Metro/Babel pipeline, which conflicts with this project’s own).

No permission, manifest edit or config plugin is involved.

All five adapters re-export the same function; there is no per-adapter hook, composable or service, since nothing here holds live state.

import { useState } from 'react';
import { getThumbnailAsync } from '@symbiote-native/video-thumbnails/react';
export default function Preview({ video }: { video: string }) {
const [uri, setUri] = useState<string | null>(null);
async function onPress() {
const thumbnail = await getThumbnailAsync(video, { time: 2000 });
setUri(thumbnail.uri);
}
return (
<view>
{uri ? <image source={{ uri }} /> : null}
<button title="Make thumbnail" onPress={onPress} />
</view>
);
}

For a remote video that needs authentication, pass headers:

const { uri } = await getThumbnailAsync('https://example.com/clip.mp4', {
time: 15000,
headers: { Authorization: `Bearer ${token}` },
});

getThumbnailAsync(sourceFilename, options?)

Section titled “getThumbnailAsync(sourceFilename, options?)”
Signature Description
getThumbnailAsync(sourceFilename: string, options?): Promise<IVideoThumbnailsResult> Grabs one frame from a local or remote video and saves it as an image
Field Type Description
time number | undefined Milliseconds into the video to take the frame from
quality number | undefined Compression from 0 (smallest) to 1 (largest)
headers Record<string, string> | undefined Sent with the network request when sourceFilename is a remote URI
Field Type Description
uri string Location of the generated image
width number Width of the image in pixels
height number Height of the image in pixels
  • The package is deprecated upstream. Expo points to generateThumbnailsAsync in expo-video and ships no patches. This wrapper tracks the final upstream behavior.
  • Generation can only be verified on a device or simulator. The headless tests fake the native module, so they prove option defaulting and argument forwarding, not frame extraction.

Should I use this package in new code? No. Upstream deprecated it in favor of generateThumbnailsAsync in expo-video and ships no patches. Use it only to keep existing code running.

It never resolves, or fails on some Android devices or file types. This is the most reported problem upstream, mostly with certain codecs and .mov files on older Android versions. It is not something this wrapper can fix. Test with your real videos, and handle the rejection or a timeout.

Two thumbnails a fraction of a second apart look identical. Frame lookup is not exact: nearby time values (within about a second) can return the same frame. Space the times further apart.

How do I get a thumbnail of a remote video? Pass the URL, and headers if it needs authentication. Long downloads can be slow, so a local file is more reliable.

Sources: Expo docs: VideoThumbnails (deprecated), expo/expo#7832 getThumbnailAsync fails on Android, expo/expo#19165 does not generate images on Android 12, expo/expo#12429 could not generate thumbnail for a .mov on Android 7.

expo-video-thumbnails’s JS is hand-ported into this package’s core/, resolving the native module through expo-modules-core rather than the expo meta-package. The five adapter entry points are plain re-exports of core (Angular stays a physical subpath for its separate ngc/AOT build). The native code is never vendored: expo-modules-autolinking resolves it from node_modules (see the native setup guide).