Playing videos in React Native

Chirag Bhaiji

Chirag Bhaiji

September 14, 2021

These days a large number of mobile apps allow users to upload videos and play those videos efficiently. While building these features in a React Native app we ran into some challenges. In this blog we will discuss those challenges and the solutions.

Where to host the videos

Since we are dealing with videos we need a service that will store, host, encode and be a CDN. After looking at various service providers we decided to go with Cloudinary. Cloudinary is a service provider with an end-to-end image/video-management solution for web and mobile applications, covering everything from uploads, storage, manipulations, optimizations to delivery with a fast content delivery network (CDN).

Setting up react-native-video player

We decided to use react-native-video v5.1.1 for playing videos in React Native application. Here is the guide to set up the video player.

import Video from "react-native-video";

const Player = ({ uri }) => {
  return (
    <SafeAreaView style={styles.container}>
      <Video
        style={styles.player}
        source={{ uri }}
        controls
        resizeMode="contain"
      />
    </SafeAreaView>
  );
};

The above code snippet works perfectly on iOS but not on Android. On Android, we faced a known issue where the video doesn't play but the audio plays with significant delay. This issue can be resolved by setting exoplayer as the default player for Android in react-native.config.js in the root directory of the project.

module.exports = {
  dependencies: {
    "react-native-video": {
      platforms: {
        android: {
          sourceDir: "../node_modules/react-native-video/android-exoplayer",
        },
      },
    },
  },
};

Setting up Cloudinary

A Cloudinary account is needed before proceeding further. Once the account is ready, following are the steps to enable unsigned upload for the account.

  • Go to the Settings of Cloudinary.
  • Select the Upload tab.
  • Search for Upload presets section.
  • Click on Enable unsigned uploading.

This generates an upload preset with a random name which will be required for the unsigned upload.

Setting up Client for Upload

Selecting Video from the gallery

We decided to use react-native-image-crop-picker v0.36.2 library to select the video from the gallery. Here is the guide for setting it up.

import ImagePicker from "react-native-image-crop-picker";

const selectVideo = ({ setVideoToUpload }) => {
  ImagePicker.openPicker({ mediaType: "video" })
    .then(setVideoToUpload)
    .catch(console.error);
};

Uploading Video

// Cloud Name: Found on the Dashboard of Cloudinary.
const URL = "https://api.cloudinary.com/v1_1/<CLOUD_NAME>/video/upload";
// Random Name: Generated After Enabling The Unsigned Uploading.
const UPLOAD_PRESET = "<UPLOAD_PRESET_FOR_UNSIGNED_UPLOAD>";

const uploadVideo = (fileInfo, onSuccess, onError) => {
  const { name, uri, type } = fileInfo;
  let formData = new FormData();

  if (uri) {
    formData.append("file", { name, uri, type });
    formData.append("upload_preset", UPLOAD_PRESET);
  }

  axios
    .post(URL, formData, {
      headers: { "Content-Type": "multipart/form-data" },
    })
    .then(res => onSuccess(res.data))
    .catch(error => onError(error));
};

export default { uploadVideo };

Fetching Videos on client

import base64 from "base-64";

// API Key and Secret: Found on the Dashboard of Cloudinary.
const API_KEY = "<API_KEY>";
const API_SECRET = "<API_SECRET>";

const URL = `https://api.cloudinary.com/v1_1/<CLOUD_NAME>/resources/video`;

const getVideos = (onRes, onError) => {
  axios
    .get(URL, {
      headers: {
        Authorization: base64.encode(`${API_KEY}:${API_SECRET}`),
      },
    })
    .then(res => onRes(res.data.resources))
    .catch(error => onError(error));
};

export default { getVideos };

Response:

{
  "resources": [
    {
      "asset_id": "475675ddd87cb3bb380415736ed1e3dc",
      "public_id": "samples/elephants",
      "format": "mp4",
      "version": 1628233788,
      "resource_type": "video",
      "type": "upload",
      "created_at": "2021-08-06T07:09:48Z",
      "bytes": 38855178,
      "width": 1920,
      "height": 1080,
      "access_mode": "public",
      "url": "http://res.cloudinary.com/do77lourv/video/upload/v1628233788/samples/elephants.mp4",
      "secure_url": "https://res.cloudinary.com/do77lourv/video/upload/v1628233788/samples/elephants.mp4"
    }
    //...
  ]
}

Transforming uploaded videos for faster delivery

One way to play a video is to first completely download the video on the client's device and then play it locally. The biggest drawback of this approach is that it could take a long time before the video is fully downloaded and till then the device is not playing anything at all. This strategy also requires a sophisticated algorithm to manage memory.

Another solution is to use Adaptive Bitrate Streaming(ABS) for playing videos. As the name suggests in this case the video is being streamed. It is one of the most efficient ways to deliver videos based on the client's internet bandwidth and device capabilities.

To generate ABS we add an eager transformation to the upload preset we created while setting up the Cloudinary account earlier.

An eager transformation runs automatically for all the videos uploaded using the upload preset which has the transformation added.

Setup Transformation

Here are the steps for adding an eager transformation to the upload preset.

  • Go to the upload preset which was generated when the unsigned upload was enabled while setting up Cloudinary in the earlier section.
  • Click on edit.
  • Go to the Upload Manipulations section.
  • Click on Add Eager Transformation to open the Edit Transaction window.
  • Open the Format dropdown under the Format & shape section and select M3U8 Playlist (HLS).
  • Select any streaming profile from the dropdown under the More options. Any profile matching your maximum required quality can be selected here.

Next upload video with the same preset trigger the transformation to generate the M3U8 format which is the one we need for playing the videos as streams.

Consuming video streams

To play streams, the type property must be provided to the react-native-video's source prop. In this case, type will be m3u8. Also, the URI needs to be updated with this same extension.

- source={{ uri }}
+ source={{ uri: uri.replace(`.${format}`, '.m3u8'), type: 'm3u8'}}

Conclusion

Once all the above mentioned steps were performed then we were able to upload and stream videos on both ios and android without any issues.

If this blog was helpful, check out our full blog archive.

Stay up to date with our blogs.

Subscribe to receive email notifications for new blog posts.