Despite their significance and popularity, videos have a significant setback in their usage on the internet in the sense that they consume data and bandwidth more than other kinds of media. And dealing with raw video files can quickly lead to performance issues, high bandwidth consumption, and poor user experience for users on low end-devices.
For mobile developers, trying to balance speed and file size can be a serious challenge. Those using React Native to compress video might not know the best strategies, or how to even approach it.
In this guide, we’ll walk through how to compress videos in React Native apps using both local and cloud-based approaches like Cloudinary, and provide guidance on best practices you can adopt to achieve a scalable and smooth user experience.
In this article:
- Why Video Compression Matters
- Using React Native to Compress Video with react-native-compressor
- Using Cloudinary’s React Native SDK
- Best Practices for React Native Video Compression
Why Video Compression Matters
A typical video file usually contains two types of data: visual and audio data. These two components make videos to be fairly larger compared to other media, like images or text. Likewise, these characteristics of video files often lead to several issues, including:
- Slow uploads: Users with slower network connections will experience significant delays when attempting to upload large video files. This can negatively impact the user experience and lead to frustration and even abandoned uploads.
- Increased data usage: Big videos use a lot of internet data to download. If your users have a limited data plan, watching or sending just a few large videos can quickly use their plan up, possibly leading to extra charges or a throttled internet connection.
- Higher storage costs: If you’re building apps where videos are saved on the server-side or cloud services, larger files will require more storage space. This extra space costs money, so storing many big videos can become expensive.
- Poor app performance: Video files that are not appropriately optimized can negatively impact the performance of applications. For instance, apps that play or handle large, unoptimized videos might run poorly. This can look like the video skipping or freezing, the app suddenly closing, or device battery draining much faster than usual.
Video compression solves many of these problems by removing redundant or unnecessary information from a video, making it smaller and easier to store, transmit, and play. By compressing videos before uploading or serving them to users, you can significantly improve the user experience and provide better playback across devices.
Using React Native to Compress Video with react-native-compressor
Compressing videos in React Native can take different forms, and the best approach often depends on your specific needs, the desired level of compression, the target platform, implementation complexity, and so on.
For instance, you can use client-side compression using native modules such as the AVFoundation Framework for iOS apps, MediaCodec and MediaMuxer APIs in Android.
You can also use third-party native libraries that wrap the native video processing capabilities, offering a more convenient API for compression. A common example of this is react-native-compressor
, a popular library that provides functionalities for image and video compression on both iOS and Android.
Set up React Native Application
If you’re new to React Native, follow the instructions in this guide to set up the environment for your OS.
For this tutorial, we’ll use a React Native project bootstrapped without a framework. To create a React Native app, run the following command:
npx @react-native-community/cli@latest init MyApp
This will create a new project called MyApp
.
Install required packages
Next, run the command below to install the necessary packages for the application:
npm install react-native-compressor react-native-image-picker react-native-video
react-native-compressor
: This is the main package that provides functionalities to compress images and videos directly within your React Native application.react-native-image-picker
: A React Native module that allows you to select a photo/video from the device library or camera.react-native-video
: An open-source video player component for React Native with support for DRM, offline playback, HLS/DASH streaming, and more.
For Android, add permissions to android/app/src/main/AndroidManifest.xml
:
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
For iOS, add permissions to ios/VideoCompressorApp/Info.plist
:
<key>NSPhotoLibraryUsageDescription</key> <string>Allow access to select videos</string> <key>NSCameraUsageDescription</key> <string>Allow access to record videos</string>
Next, in the root of the project, start the app using Metro, React Native’s JavaScript bundler:
npm start
Then open another terminal to start the Android or iOS application:
npx react-native run-ios # or npx react-native run-android
If everything works correctly, you should see the app loaded in the emulator as shown below:
If you run into any problems during the set up, run npx react-native doctor
to troubleshoot and fix the issues.
Step 1 – Set up imports and app state
First, let’s import the necessary dependencies for building the application. We’ll also use the useState
hook to manage URIs for the original and compressed videos, as well as compression status and progress tracking.
Delete the content of App.tsx
and add the following code to it:
import React, { useState } from 'react'; import { View, Button, Text, StyleSheet, ActivityIndicator, Alert, ScrollView, Platform } from 'react-native'; import Video from 'react-native-video'; import * as ImagePicker from 'react-native-image-picker'; import { Video as VideoCompressor } from 'react-native-compressor'; export default function App() { const [selectedVideoUri, setSelectedVideoUri] = useState<string | null>(null); const [compressedVideoUri, setCompressedVideoUri] = useState<string | null>(null); const [isCompressing, setIsCompressing] = useState(false); const [compressionProgress, setCompressionProgress] = useState(0); }
Step 2 – Define Video Picker Function
Now let’s create a function that allows users to select a video from their device’s media library. Add the following below the last useState
in the previous code:
const pickVideo = async () => { try { const result = await ImagePicker.launchImageLibrary({ mediaType: 'video', }); if (result.didCancel) { console.log('User cancelled video picker'); } else if (result.errorCode) { console.log('ImagePicker Error: ', result.errorCode); Alert.alert('Error picking video', result.errorMessage); } else if (result.assets && result.assets.length > 0) { setSelectedVideoUri(result.assets[0].uri ?? null); setCompressedVideoUri(null); setCompressionProgress(0); } } catch (error) { console.error('Error picking video:', error); Alert.alert('Error', 'Failed to pick video.'); } };
Step 3 – Compress the Selected Video
Now let’s define a function to compress the selected video with react-native-compressor
. Add the following code beneath the previous code:
const compressVideo = async () => { if (!selectedVideoUri) { Alert.alert('No video selected', 'Please select a video first.'); return; } setIsCompressing(true); setCompressionProgress(0); try { const fileUri = Platform.OS === 'android' ? selectedVideoUri : selectedVideoUri; const result = await VideoCompressor.compress( fileUri, { compressionMethod: 'auto', }, (progress) => { setCompressionProgress(Math.round(progress * 100)); console.log('Compression progress:', progress); } ); setCompressedVideoUri(result); Alert.alert('Compression Successful', 'Video compressed successfully!'); } catch (error) { console.error('An error occurred:', error); Alert.alert('Compression Failed'); } finally { setIsCompressing(false); } };
Step 4 – Define the UI and Styling
Finally let’s create the app’s interface:
return ( <ScrollView contentContainerStyle={styles.container}> <Text style={styles.title}>Video Compression App</Text> <Button title="Pick a Video" onPress={pickVideo} /> {selectedVideoUri && ( <View style={styles.videoContainer}> <Text style={styles.label}>Original Video:</Text> <Video source={{ uri: selectedVideoUri }} style={styles.videoPlayer} controls={true} resizeMode="contain" paused={true} /> <Text style={styles.uriText}>Original URI: {selectedVideoUri.substring(0, 40)}...</Text> </View> )} {selectedVideoUri && ( <Button title={isCompressing ? `Compressing (${compressionProgress}%)` : "Compress Video"} onPress={compressVideo} disabled={isCompressing} /> )} {isCompressing && ( <View style={styles.progressContainer}> <ActivityIndicator size="large" color="#0000ff" /> <Text>Compressing: {compressionProgress}%</Text> </View> )} {compressedVideoUri && ( <View style={styles.videoContainer}> <Text style={styles.label}>Compressed Video:</Text> <Video source={{ uri: compressedVideoUri }} style={styles.videoPlayer} controls={true} resizeMode="contain" paused={true} /> <Text style={styles.uriText}>Compressed URI: {compressedVideoUri.substring(0, 40)}...</Text> </View> )} </ScrollView> ); } const styles = StyleSheet.create({ container: { flexGrow: 1, justifyContent: 'center', alignItems: 'center', padding: 20, marginTop: 50, }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 30, }, videoContainer: { marginTop: 20, alignItems: 'center', width: '100%', }, label: { fontSize: 18, marginBottom: 10, fontWeight: 'bold', }, videoPlayer: { width: 300, height: 200, backgroundColor: '#eee', marginBottom: 10, }, uriText: { fontSize: 12, color: '#666', textAlign: 'center', paddingHorizontal: 10, }, progressContainer: { flexDirection: 'row', alignItems: 'center', marginTop: 20, }, });
Great! Here’s what the app looks like now:
To upload a test video to the emulator for compression, use adb:
adb push /your-computer/videos/video.mp4 /sdcard/Download/
Then verify if the file has been copied by running:
adb shell ls /sdcard/Download/
Finally, open the Files app inside the emulator to check if the video has been indexed.
Using Cloudinary’s React Native SDK
For compressing a handful of videos, libraries like react-native-compressor
works fine. However, for large-scale video compression, a robust and scalable solution for a seamless performance and workflow is essential. Cloudinary is an Image and Video API platform that offers powerful video compression capabilities through Cloudinary Video. In this section, we’ll walk you through how to use the Cloudinary React Native SDK to compress videos in your React Native app.
To compress videos with Cloudinary, the quality
parameter of the transformation object allows you to control the video quality using an integer value between 1 (lowest quality) and 100 (highest quality). You can also pass in auto
to let Cloudinary use its intelligent algorithms to automatically determine the best quality to deliver the video to each user based on their device, connectivity, and other factors.
To learn more about video transformation and optimization with Cloudinary, see the following articles: Video optimization and Video transformations.
Configuring Cloudinary in Your React Native Application
Before you can use Cloudinary in your React Native app, you need to sign up for an account to get your cloud name, API key, and API secret. You can then copy the credentials from your Cloudinary console.
Since we’ll be uploading the video to Cloudinary using unsigned/unauthenticated requests from the client side, you’ll need to create a upload preset in your Cloudinary dashboard. An upload preset is used to define which upload options will be applied to assets that are uploaded unsigned with that preset specified.
Only two parameters are required for unauthenticated requests:
file
: The file to upload.upload_preset
: The name of an unsigned upload preset that you defined for unsigned uploading.
Step 1 – Install Packages
Run the following command to install the Cloudinary React Native SDK:
npm install cloudinary-react-native @cloudinary/url-gen
cloudinary-react-native
: This is the official React Native SDK for Cloudinary. It provides components and utilities specifically designed for integrating Cloudinary services into React Native applications.@cloudinary/url-gen
: Cloudinary’s JavaScript SDK for URL generation. It provides a programmatic way to construct Cloudinary video transformation URLs.
After the installation is complete, you need to re-run npx react-native run-android
to rebuild the app.
Step 2 – Import Necessary Libraries and Configure Cloudinary
In App.tsx
, replace the content with the following to import the necessary modules and set up the configuration for Cloudinary.:
import React, { useState } from 'react'; import { View, Button, Text, StyleSheet, ActivityIndicator, Alert, ScrollView, Platform } from 'react-native'; import Video from 'react-native-video'; import { launchImageLibrary, Asset } from 'react-native-image-picker'; import { Cloudinary } from '@cloudinary/url-gen'; import { quality, format } from '@cloudinary/url-gen/actions/delivery'; import { auto as autoFormat } from '@cloudinary/url-gen/qualifiers/format'; // Cloudinary Configuration const cloudinary = new Cloudinary({ cloud: { cloudName: 'your_cloud_name' // Replace with your Cloudinary cloud name }, url: { secure: true } }); const CLOUDINARY_CONFIG = { cloudName: 'your_cloud_name', // Replace with your Cloudinary cloud name uploadPreset: 'your_custom_preset', // Replace with your upload preset };
Step 3 – Define App State and Types
Next, add the following to define the state variables and type definition for the object that will hold various statistics about the video compression:
export default function App() { type CompressionStats = { originalSize: number; duration: number; format: string; width: number; height: number; } | null; const [selectedVideoUri, setSelectedVideoUri] = useState<string | null>(null); const [selectedVideoInfo, setSelectedVideoInfo] = useState<Asset | null>(null); const [compressedVideoUri, setCompressedVideoUri] = useState<string | null>(null); const [cloudinaryPublicId, setCloudinaryPublicId] = useState(null); const [isUploading, setIsUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [compressionStats, setCompressionStats] = useState<CompressionStats>(null);
Step 4 – Implement Video Upload
Next we’ll use the launchImageLibrary
method from react-native-image-picker
to let users upload a video from their gallery. Add the following beneath the previous code:
const pickVideo = async () => { try { launchImageLibrary({ mediaType: 'video', }, (result) => { if (result.didCancel) { console.log('Video picker cancelled'); } else if (result.errorCode) { console.log('ImagePicker Error: ', result.errorCode); Alert.alert('Error picking video', result.errorMessage || 'Unknown error'); } else if (result.assets && result.assets.length > 0) { const asset = result.assets[0]; setSelectedVideoUri(asset.uri || null); setSelectedVideoInfo(asset); setCompressedVideoUri(null); setCloudinaryPublicId(null); setUploadProgress(0); setCompressionStats(null); console.log('Selected video:', asset); } }); } catch (error) { console.error('Error picking video:', error); Alert.alert('Error', 'Failed to pick video.'); } };
Step 5 – Upload Selected Video to Cloudinary
Now let’s create a function that takes the videoUri
as an argument and sends it in an HTTP POST request to Cloudinary’s upload API:
const uploadToCloudinary = async (videoUri: string) => { const formData = new FormData(); formData.append('file', { uri: videoUri, type: 'video/mp4', name: `video_${Date.now()}.mp4` }); formData.append('upload_preset', CLOUDINARY_CONFIG.uploadPreset); formData.append('resource_type', 'video'); try { const response = await fetch( `https://api.cloudinary.com/v1_1/${CLOUDINARY_CONFIG.cloudName}/video/upload`, { method: 'POST', body: formData, headers: { 'Content-Type': 'multipart/form-data', }, } ); const result = await response.json(); if (result.error) { throw new Error(result.error.message); } return result; } catch (error) { console.error('Upload error:', error); throw error; } };
Step 6 – Compress the Uploaded Video With Cloudinary
Next, let’s define a function to compress the uploaded video (using its public ID) and calculate the compression statistics:
const compressVideo = async () => { if (!selectedVideoUri) { Alert.alert('No video selected', 'Please select a video first.'); return; } setIsUploading(true); setUploadProgress(0); try { const uploadResult = await uploadToCloudinary(selectedVideoUri); setUploadProgress(100); setCloudinaryPublicId(uploadResult.public_id); // Get the public ID of the uploaded video const optimizedVideo = cloudinary .video(uploadResult.public_id) .delivery(quality(50)) // Pass an integer value between 1-100 for quality level .delivery(format('mp4') // Specify the format .toURL(); setCompressedVideoUri(optimizedVideo); const originalSize = selectedVideoInfo?.fileSize || 0; setCompressionStats({ originalSize, duration: uploadResult.duration, format: uploadResult.format, width: uploadResult.width, height: uploadResult.height }); Alert.alert( 'Success! Video uploaded and compressed!' ); } catch (error) { console.error('Compression error:', error); const errorMessage = (error instanceof Error && error.message) ? error.message : 'Unknown error occurred'; Alert.alert('Upload Failed', `Error: ${errorMessage}`); } finally { setIsUploading(false); setTimeout(() => setUploadProgress(0), 2000); } }; const formatFileSize = (bytes: number | undefined) => { if (!bytes) return 'Unknown'; const mb = bytes / (1024 * 1024); return `${mb.toFixed(2)} MB`; };
Step 7 – Add UI and Styling
Finally, add the following code to define the layout of the app and styling:
return ( <ScrollView contentContainerStyle={styles.container}> <Text style={styles.title}>Compress video with Cloudinary</Text> <View style={styles.buttonContainer}> <Button title="Pick a Video" onPress={pickVideo} color="#007AFF" /> </View> {selectedVideoUri && ( <View style={styles.videoContainer}> <Text style={styles.label}>Original Video:</Text> <Video source={{ uri: selectedVideoUri }} style={styles.videoPlayer} controls={true} resizeMode="contain" paused={true} onError={(error) => console.error('Video playback error:', error)} /> <Text style={styles.infoText}> Size: {formatFileSize(selectedVideoInfo?.fileSize)} </Text> </View> )} {selectedVideoUri && ( <View style={styles.buttonContainer}> <Button title={isUploading ? `Uploading... ${Math.round(uploadProgress)}%` : "Upload & Compress with Cloudinary"} onPress={compressVideo} disabled={isUploading} color={isUploading ? "#999" : "#28a745"} /> </View> )} {isUploading && ( <View style={styles.progressContainer}> <ActivityIndicator size="large" color="#007AFF" /> <Text style={styles.progressText}> Processing: {Math.round(uploadProgress)}% </Text> </View> )} {compressedVideoUri && ( <View style={styles.videoContainer}> <Text style={styles.label}>Compressed Video:</Text> <Video source={{ uri: compressedVideoUri }} style={styles.videoPlayer} controls={true} resizeMode="contain" paused={true} onError={(error) => console.error('Compressed video playback error:', error)} /> {compressionStats && ( <View style={styles.statsContainer}> <Text style={styles.statsTitle}>Compression Stats:</Text> <Text style={styles.statsText}> Original: {formatFileSize(compressionStats.originalSize)} </Text> <Text style={styles.statsText}> Duration: {compressionStats.duration}s </Text> <Text style={styles.statsText}> Resolution: {compressionStats.width}x{compressionStats.height} </Text> <Text style={styles.statsText}> Format: {compressionStats.format} </Text> <Text style={styles.statsText}> Cloudinary URL: {compressedVideoUri} </Text> </View> )} </View> )} </ScrollView> ); } const styles = StyleSheet.create({ container: { flexGrow: 1, justifyContent: 'flex-start', alignItems: 'center', padding: 20, paddingTop: 60, backgroundColor: '#f8f9fa', }, title: { fontSize: 28, fontWeight: 'bold', marginBottom: 30, color: '#333', textAlign: 'center', }, buttonContainer: { marginVertical: 10, width: '90%', }, videoContainer: { marginTop: 20, alignItems: 'center', width: '100%', backgroundColor: 'white', borderRadius: 12, padding: 15, elevation: 3, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.15, shadowRadius: 4, }, label: { fontSize: 18, marginBottom: 15, fontWeight: 'bold', color: '#333', }, videoPlayer: { width: 300, height: 200, backgroundColor: '#000', borderRadius: 8, marginBottom: 15, }, infoText: { fontSize: 14, color: '#666', marginBottom: 5, fontWeight: '500', }, progressContainer: { flexDirection: 'row', alignItems: 'center', marginTop: 20, padding: 20, backgroundColor: 'white', borderRadius: 12, elevation: 2, shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 2, }, progressText: { marginLeft: 15, fontSize: 16, color: '#333', fontWeight: '500', }, statsContainer: { backgroundColor: '#f8f9fa', padding: 15, borderRadius: 8, marginTop: 10, width: '100%', }, statsTitle: { fontSize: 16, fontWeight: 'bold', color: '#333', marginBottom: 8, }, statsText: { fontSize: 14, color: '#666', marginBottom: 3, }, compressionRatio: { fontSize: 14, color: '#28a745', fontWeight: 'bold', marginBottom: 3, }, });
Now when you run the app, you should see the compressed video and its Cloudinary URL as shown below:
Best Practices for Video Compression in React Native
Video compression is a complex process, but with the right tools and techniques, you can effectively manage the process for efficiency, security, and scalability. Here are some best practices you should consider for optimal results:
- Use Cloud Services for Video Compression and Transformation: Offload resource-intensive video compression and transformation tasks to a cloud-based media service like Cloudinary. This ensures optimal performance, consistent output quality, and scalability while reducing server load.
- Implement Client-Side Validation: Validate and restrict video file sizes before uploading to minimize bandwidth usage and reduce upload times, especially for large files. However, rely on server-side processing for final compression to ensure quality and compatibility.
- Enhance Upload Security: Use client-side file type validation to block malicious or unsupported files. You can also combine this with server-side validations to ensure only safe, valid video files are processed.
Wrapping Up
Implementing video compression in a React Native app can be streamlined with the right tools and taking the right steps. For simple projects, libraries like react-native-compressor offer lightweight solutions for basic compression needs. However, for scalable, production-ready applications, cloud-based services like Cloudinary provide significant advantages, including secure uploads, cross-platform compatibility, automated optimization, and robust transformation capabilities.
Deliver responsive videos that look great on any device with Cloudinary’s flexible tools. Join Cloudinary today and ensure your videos always look their best.