MEDIA GUIDES / Video effects

Best Practices for Compressing Videos in React Native Apps

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

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:

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.

QUICK TIPS
Matthew Noyes
Cloudinary Logo Matthew Noyes

In my experience, here are tips that can help you better compress and manage videos in React Native apps:

  1. Pre-encode videos in adaptive bitrate (ABR) for CDN streaming
    Instead of uploading raw user videos, transcode them into multiple resolutions and bitrates, enabling smooth adaptive streaming via HLS/DASH when served from CDNs.
  2. Use proxy files for editing and preview
    When enabling in-app editing, generate lower-resolution proxy versions of videos. Users interact with lightweight proxies while the app processes or uploads the full-res files in the background.
  3. Cache compressed videos locally using hashing
    Implement a checksum or hash-based caching mechanism to avoid recompressing the same video. This can save significant time and processing for repeat uploads.
  4. Tune compression based on content type
    Adjust compression settings dynamically depending on whether the video contains high motion (e.g., sports), static scenes (e.g., interviews), or text overlays. Use preset profiles per category.
  5. Leverage background tasks for compression on iOS
    Use BGProcessingTaskRequest in iOS to perform compression during idle times or when the app is backgrounded—improves UX and saves battery.
  6. Analyze codec compatibility per device and OS version
    Not all devices handle codecs equally. Before compressing with modern codecs like H.265 or VP9, ensure compatibility using device-specific checks to avoid playback errors.
  7. Include motion vector suppression for smoother playback
    When compressing, configure motion estimation to avoid aggressive frame prediction. This improves performance on low-end GPUs during playback.
  8. Integrate upload progress smoothing
    Real-time compression may lead to jittery progress updates. Use an exponential moving average to present smoother, more reassuring progress bars to the user.
  9. Implement silent fallback for compression failure
    Set up auto-fallback to upload the original video if compression fails, with a silent retry in the background to preserve user flow and data integrity.
  10. Log and analyze compression metrics client-side
    Collect analytics on compression duration, output sizes, device types, and failure rates. Use this data to refine your presets and improve compression quality over time.
Last updated: Jun 13, 2025