This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

Expo Video (expo-video) iconExpo Video (expo-video)

A library that provides an API to implement video playback in apps.

Android
iOS
tvOS
Web
Included in Expo Go
Recommended version:
~55.0.21

expo-video is a cross-platform, performant video component for React Native and Expo with Web support.

Known issues 
Android

When two VideoView components are overlapping and have the contentFit prop set to cover, one of the videos may be displayed out of bounds. This is a known upstream issue. To work around this issue, use the surfaceType prop and set it to textureView.

Installation

Terminal
- npx expo install expo-video
- yarn expo install expo-video
- pnpm expo install expo-video
- bun expo install expo-video

If you are installing this in an existing React Native app, make sure to install expo in your project.

Configuration in app config

You can configure expo-video using its built-in config plugin if you use config plugins in your project (Continuous Native Generation (CNG)). The plugin allows you to configure various properties that cannot be set at runtime and require building a new app binary to take effect. If your app does not use CNG, then you'll need to manually configure the library.

Example app.json with config plugin

app.json
{ "expo": { "plugins": [ [ "expo-video", { "supportsBackgroundPlayback": true, "supportsPictureInPicture": true } ] ], } }

Configurable properties

NameDefaultDescription
supportsBackgroundPlaybackundefined

A boolean value to enable background playback support. If true, on iOS, the audio key is added to the UIBackgroundModes array in the Info.plist file. If false, the key is removed. When undefined, the key is not modified. On Android, when true adds foreground service permissions and creates a expo-video foreground service in AndroidManifest.xml.

supportsPictureInPictureundefined

A boolean value to enable Picture-in-Picture on Android and iOS. If true, enables the android:supportsPictureInPicture property on Android and adds the audio key to the UIBackgroundModes array in the Info.plist file on iOS. If false, the key is removed. When undefined, the configuration is not modified.

Usage

Here's a simple example of a video with a play and pause button.

Video
import { useEvent } from 'expo'; import { useVideoPlayer, VideoView } from 'expo-video'; import { StyleSheet, View, Button } from 'react-native'; const videoSource = 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'; export default function VideoScreen() { const player = useVideoPlayer(videoSource, player => { player.loop = true; player.play(); }); const { isPlaying } = useEvent(player, 'playingChange', { isPlaying: player.playing }); return ( <View style={styles.contentContainer}> <VideoView style={styles.video} player={player} fullscreenOptions={{ enable: true }} allowsPictureInPicture /> <View style={styles.controlsContainer}> <Button title={isPlaying ? 'Pause' : 'Play'} onPress={() => { if (isPlaying) { player.pause(); } else { player.play(); } }} /> </View> </View> ); } const styles = StyleSheet.create({ contentContainer: { flex: 1, padding: 10, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 50, }, video: { width: 350, height: 275, }, controlsContainer: { padding: 10, }, });

Receiving events

The changes in properties of the VideoPlayer do not update the React state. Therefore, to display the information about the current state of the VideoPlayer, it is necessary to listen to the events it emits. The event system is based on the EventEmitter class and hooks from the expo package. There are a few ways to listen to events:

useEvent hook

Creates a listener that will return a stateful value that can be used in a component. It also cleans up automatically when the component unmounts.

useEvent
import { useEvent } from 'expo'; // ... Other imports, definition of the component, creating the player etc. const { status, error } = useEvent(player, 'statusChange', { status: player.status }); // Rest of the component...

useEventListener hook

Built around the Player.addListener and Player.removeListener methods, creates an event listener with automatic cleanup.

useEventListener
import { useEventListener } from 'expo'; // ...Other imports, definition of the component, creating the player etc. useEventListener(player, 'statusChange', ({ status, error }) => { setPlayerStatus(status); setPlayerError(error); console.log('Player status changed: ', status); }); // Rest of the component...

Player.addListener method

Most flexible way to listen to events, but requires manual cleanup and more boilerplate code.

Player.addListener
// ...Imports, definition of the component, creating the player etc. useEffect(() => { const subscription = player.addListener('statusChange', ({ status, error }) => { setPlayerStatus(status); setPlayerError(error); console.log('Player status changed: ', status); }); return () => { subscription.remove(); }; }, []); // Rest of the component...

Playing local media from the assets directory

expo-video supports playing local media loaded using the require function. You can use the result as a source directly, or assign it to the assetId parameter of a VideoSource if you also want to configure other properties.

Playing local media
import { VideoSource } from 'expo-video'; const assetId = require('./assets/bigbuckbunny.mp4'); const videoSource: VideoSource = { assetId, metadata: { title: 'Big Buck Bunny', artist: 'The Open Movie Project', }, }; const player1 = useVideoPlayer(assetId); // You can use the `asset` directly as a video source const player2 = useVideoPlayer(videoSource);

Playing media from the media library

expo-video supports playing videos picked from user's media library using expo-media-library or any valid PHAsset URI with appropriate permissions.

To play a video from the media library, you should obtain an Asset object with MediaLibrary.getAssetsAsync() and use its uri property as the uri of the video source. Before playing, make sure to request the necessary permissions using MediaLibrary.requestPermissionsAsync().

On iOS make sure not to use the localUri property of the asset info, as it does not contain the necessary permissions to read the asset.

Playing media from media library
import * as MediaLibrary from 'expo-media-library'; import { VideoSource, useVideoPlayer, VideoView } from 'expo-video'; // ...Definition of the component, creating the player etc. const loadAssetAndReplace = async () => { const { granted } = await MediaLibrary.requestPermissionsAsync(false, ['video']); if (!granted) { return; } const pagedAssets = await MediaLibrary.getAssetsAsync({ mediaType: 'video', }); if (pagedAssets.assets.length > 0) { const [asset] = pagedAssets.assets; const videoSource: VideoSource = { uri: asset.uri, metadata: { title: asset.filename, }, }; await player.replaceAsync(videoSource); await player.replaceAsync(asset.uri); // Alternatively you can use the asset uri directly player.play(); } }; // You can now use loadAssetAndReplace to load and play the first video from the media library

Preloading videos

While another video is playing, a video can be loaded before showing it in the view. This allows for quicker transitions between subsequent videos and a better user experience.

To preload a video, you have to create a VideoPlayer with a video source. Even when the player is not connected to a VideoView, it will fill the buffers. Once it is connected to the VideoView, it will be able to start playing without buffering.

In some cases, it is beneficial to preload a video later in the screen lifecycle. In that case, a VideoPlayer with a null source should be created. To start preloading, replace the player source with a video source using the replace() function.

Here is an example of how to preload a video:

Preloading videos
import { useVideoPlayer, VideoView, VideoSource } from 'expo-video'; import { useState, useCallback } from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; const bigBuckBunnySource: VideoSource = 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'; const elephantsDreamSource: VideoSource = 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4'; export default function PreloadingVideoPlayerScreen() { const player1 = useVideoPlayer(bigBuckBunnySource, player => { player.play(); }); const player2 = useVideoPlayer(elephantsDreamSource, player => { player.currentTime = 20; }); const [currentPlayer, setCurrentPlayer] = useState(player1); const replacePlayer = useCallback(async () => { currentPlayer.pause(); if (currentPlayer === player1) { setCurrentPlayer(player2); player1.pause(); player2.play(); } else { setCurrentPlayer(player1); player2.pause(); player1.play(); } }, [player1, currentPlayer]); return ( <View style={styles.contentContainer}> <VideoView player={currentPlayer} style={styles.video} nativeControls={false} /> <TouchableOpacity style={styles.button} onPress={replacePlayer}> <Text style={styles.buttonText}>Replace Player</Text> </TouchableOpacity> </View> ); } const styles = StyleSheet.create({ contentContainer: { flex: 1, padding: 10, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 50, }, button: { alignItems: 'center', justifyContent: 'center', borderRadius: 3, paddingVertical: 8, paddingHorizontal: 12, backgroundColor: '#4630ec', }, buttonText: { fontSize: 12, fontWeight: 'bold', color: '#eeeeee', textAlign: 'center', }, video: { width: 300, height: 168.75, marginVertical: 20, }, });

Using the VideoPlayer directly

In most cases, the useVideoPlayer hook should be used to create a VideoPlayer instance. It manages the player's lifecycle and ensures that it is properly disposed of when the component is unmounted. However, in some advanced use cases, it might be necessary to create a VideoPlayer that does not get automatically destroyed when the component is unmounted. In those cases, the VideoPlayer can be created using the createVideoPlayer function. You need be aware of the risks that come with this approach, as it is your responsibility to call the release() method when the player is no longer needed. If not handled properly, this approach may lead to memory leaks.

Creating player instance
import { createVideoPlayer } from 'expo-video'; const player = createVideoPlayer(videoSource);

Caching videos

If your app frequently replays the same video, caching can be utilized to minimize network usage and enhance user experience, albeit at the cost of increased device storage usage. expo-video supports video caching on Android and iOS platforms. This feature can be activated by setting the useCaching property of a VideoSource object to true.

The cache is persistent and will be cleared on a least-recently-used basis once the preferred size is exceeded. Furthermore, the system can clear the cache due to low storage availability, so it's not advisable to depend on the cache to store critical data.

The cache functions offline. If a portion or the entirety of a video is cached, it can be played from the cache even when the device is offline until the cached data is exhausted.

Managing the cache

API

import { VideoView, useVideoPlayer } from 'expo-video';

No API data file found, sorry!