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.
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).
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",
},
},
},
},
};
A Cloudinary account is needed before proceeding further. Once the account is ready, following are the steps to enable unsigned upload for the account.
This generates an upload preset with a random name which will be required for the unsigned upload.
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);
};
// 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 };
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"
}
//...
]
}
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.
Here are the steps for adding an eager transformation to the upload preset.
Add Eager Transformation
to open the Edit Transaction window.Format
dropdown under the Format & shape
section and select
M3U8 Playlist (HLS)
.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.
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'}}
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.