How to Read Audio Files From Device in EXPO - React Native

How to Read Audio Files From Device in EXPO - React Native

Post by: Niraj Dhungana

Posted on: Nov 11, 2021

#React Native# Expo

Expo is a great tool when it comes to developing mobile apps with React Native. Few days back I published a post on how we can create an audio player app using Expo React Native. Now in this post I want to show you guys how we can read the entire audio files from our device using Expo.

To read any media files from our device, inside Expo there is an API called MediaLibrary. We can use this API to read any media files from our device but here we will only look at how we can read audio files.

Also I have a video on my YouTube channel on this topic but this video is the part of the complete series.

Watch the video

Initialized Project With Expo to Read Audio Files

First create a project using Expo or you can use your existing project.

1
$ expo init MyMediaApp // to create a new project

Now we need to install expo-media-library 

1
$ expo install expo-media-library

To create this project I am writing all of the code inside App.js but if you want to you can create a separate file as well.

Now because we want to read audio files from the user's device, first we have to get permission to read all of the media files from the device.

This expo-media-library gives us some useful methods, such as getPermissionsAsync() and requestPermissionsAsync(). Using these methods we can get user permission to read media files from the device.

Let's import everything from expo-media-library as I already told you this will help us to read all the audio files from device. Also at the same time import useEffect hook as well.

1
2
3
4
5
6
import React, { useEffect } from 'react';
import * as MediaLibrary from 'expo-media-library';

export default function App() {
...
}

Now we have everything in place so let’s add this getPermission() method inside the useEffect hook.

1
2
3
useEffect(() => {
  getPermission();
}, []);

Currently we don’t have the getPermission() method so let’s create this method outside of the useEffect hook.

1
2
3
4
const getPermission = async () => {
  const permission = await MediaLibrary.getPermissionsAsync();
  console.log(permission);
};

You can see inside this getPermission() method I am using await MediaLibrary.getPermissionsAsync() method to find out the status of permission. Also I am storing the result inside the permission variable. And most important don't forget to wrap this function inside async keyword.

If you log this returned value inside the terminal, you will see something like this.

1
2
3
4
5
6
Object {
  "canAskAgain": true,
  "expires": "never",
  "granted": false,
  "status": "undetermined",
}

From this Object, for now we just care about the granted and canAskAgain.

1
2
3
4
5
6
const getPermission = async () => {
...
if (!permission.granted && permission.canAskAgain) {
      const { status, canAskAgain } = await MediaLibrary.requestPermissionsAsync();
  }
}

Now we can check if permission is not granted and we can ask again  then we will make a new request to get the permission. Also you can see I am de-structuring this permission object to take out status and canAskAgain.

If you run this project like this then you will see something like this.

permission-before-reading-audio-files

Now if you tap on allow then the status will be granted or if you deny the request then status will be denied. So, let's write some if condition to handle all of those conditions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const getPermission = async () => {
...
   if (!permission.granted && permission.canAskAgain) {
      ...
      if (status === 'denied' && canAskAgain) {
        // display some allert or request again to read media files.
        getPermission();
      }

      if (status === 'granted') {
        // we want to get all the audio files
        getAudioFiles();
      }

      if (status === 'denied' && !canAskAgain) {
        // we want to display some error to the user
      }
    }
  };

Inside the first if condition we are checking if status is equal to 'denied' and we canAskAgain. Then it means the user denied the request but doesn't tick that little box “Don’t ask again”.

permission-denied-before-reading-audio-files

It means we can again ask for permission to read the media files. For that you can directly call that getPermission() method from inside this if condition or you can first display some alert to the user. That this app needs to have permission to read media files so please accept this permission then you can call that getPermission() method. It’s entirely up to you.

Inside the second if condition we are checking if the user accepted the permission then we want to call that getAudioFiles() method. Currently we don’t have this method but we will create it.

And at the last if condition we are checking if the user denied the permission and also tick that don’t ask again box then we want to display some error to the user.

Let's create that getAudioFiles method.

1
2
3
4
const getAudioFiles = async () => {
   let media = await MediaLibrary.getAssetsAsync({ mediaType: 'audio' });
   console.log(media);
};

After getting user permission we can use the getAssetsAsync() method from inside that MediaLibrary to get media files out from the device. Also you can see I am using the {  mediaType:  'audio'  } option to only take out audio files. Because here we can select different types of media files like audio, video, photo and unknown.

But if you run this code nothing will happen and that is because, we miss out one more if condition inside that getPermission method. I did that by intention.

1
2
3
4
5
6
7
8
9
10
11
12
13
const getPermission = async () => {
  const permission = await MediaLibrary.getPermissionsAsync();
  console.log(permission);

  if (permission.granted) {
     // we want to get all the audio files
     getAudioFiles();
  }
  if (!permission.canAskAgain && !permission.granted) {
     console.log("user denied and we can't ask again");
  }
  ...
};

Here right after getting the permission status we can check if that status is granted then we want to read the audio files. Also we can add another if condition to check if the user denied permission and ticked on that don't ask me again. Then you can display some kind of error to the user.

Now after this you will something like this inside your terminal.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Object {
  "assets": Array [
    Object {
      "albumId": "82896267",
      "creationTime": 0,
      "duration": 5.82,
      "filename": "audio 4.mp3",     
      "height": 0,
      "id": "5201",
      "mediaType": "audio",
      "modificationTime": 1615998129000,
      "uri": "file:///storage/emulated/0/Music/audio 4.mp3",
      "width": 0,
    },
    ...
  ],
  "endCursor": "4",
  "hasNextPage": false,
  "totalCount": 4,
}

Inside this object you will see this object, where this assets array holds the 20 audio files by default. endCursor is the last count of the audio files, here it’s a 4 because my device has only 4 audio files. Your’s might be 20 or something else.

hasNextPage will hold a boolean value to tell us if more audio files are available or not. If your device has more than 20 audio files then it will be true. And at the end you will see the totalCount of audio files.

Now we have only access to 20 audio files by default but we want to take out all the audio files. So, let’s add some more codes to our getAudioFiles method.

But before that let’s create an audioFiles state using useState hook. So that, we can store all of our audio files.

1
const [audioFiles, setAudioFiles] = useState([]);

Here, I made audioFiles state and assigned an empty array as the default value.

1
2
3
4
5
6
7
8
9
10
11
const getAudioFiles = async () => {
   let media = await MediaLibrary.getAssetsAsync({ mediaType: 'audio' });
   console.log(media);

   media = await MediaLibrary.getAssetsAsync({
      mediaType: 'audio',
      first: media.totalCount,
    });

   setAudioFiles(media.assets)
};

You can see here I am using that same getAssetsAsync method and assigning the returned value to the media variable. I can do this because I used the let keyword while creating that media variable.

But here one thing is different. If you notice I am passing two options inside the getAssetsAsync method. I am passing mediaType: 'audio' and first: media.totalCount.

Here mediaType is audio because we want to read only audio files. Second is first where we are passing media.totalCount or the total length of audio files.

This first is the option which allows us to define the audio files that we want to render on the first page. Like as I already mentioned the default value is 20 that's why we are getting only 20 audio files. Now we changed it to the total length of the audio files.

So, ladies and gentlemen this is how we can take out all the audio files from our device using expo-media-library. If you want to you can render all of the audio files using FlatList or ScrollView components.

1
2
3
4
5
6
7
8
9
10
11
12
13
export default function App() {

....
  return (
    <FlatList
      data={audioFiles}
      keyExtractor={item => item.id}
      renderItem={({ item }) => (
        <Text style={styles.audioTitle}>{item.filename}</Text>
      )}
    />
  );
}

rendered-audio-files

Complete source is available on GitHub.