I'm using react native to develop a music app and I'm stuck trying to figure out the best way to search for and play audio files already saved on the clients phone. I'm just not sure what would be the best modules/libraries to solve this problem, or whether the iOS/Android OS allows this type of access to user files at all.
I'm using react-native-sound to play audio files and I've found the react-native-fs module and this looks close to what I feel I need to search for audio files. There's a readDir function, but you pass it directory path constants like MainBundlePath, CachesDirectoryPath, DocumentDirectoryPath etc:
// require the module
var RNFS = require('react-native-fs');
// get a list of files and directories in the main bundle
RNFS.readDir(RNFS.MainBundlePath)
With:
1. MainBundlePath - The absolute path to the main bundle directory
2. CachesDirectoryPath - The absolute path to the caches directory
3. DocumentDirectoryPath - The absolute path to the document directory
Would any of these directories be where I look to find audio files already stored on the clients phone?
This is a 3 step process that requires storage permissions (since you're getting data from the local storage)
Install react-native-permissions to get storage permission from the user, then go to your AndroidManifest.xml located at YourProject/android/app/src/main/ and add the following:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Create functions to request and check for storage permissions before requesting for the songs, for example:
componentDidMount() {
Permissions.request('storage').then(response => {
this.setState({ photoPermission: response })
})
}
Then create another function to get the data from the device (You can also pass some parameters or filters in your function--follow the react-native-get-music-files docs)
_getSongs =() =>{
Alert.alert('seen')
MusicFiles.getAll({
}).then(tracks => {
console.log(tracks)
}).catch(error => {
console.log(error)
})
}
So, Here's what the final code looks like
import React, { Component } from 'react';
import { StyleSheet, Text, View,Alert} from 'react-native';
import MusicFiles from 'react-native-get-music-files';
import Permissions from 'react-native-permissions';
export default class App extends Component {
state = {
storagePermission:''
}
componentDidMount() {
Permissions.request('storage').then(response => {
this.setState({ storagePermission: response })
})
}
_getSongs =() =>{
Alert.alert('seen')
MusicFiles.getAll({
}).then(tracks => {
console.log(tracks)
}).catch(error => {
console.log(error)
})
}
render() {
return (
<View style={{flex:1,justifyContent:'center', alignItems:'center'}}>
<Text onPress={this._getSongs}>get songs</Text>
</View>
);
}
}
Then you get a JSON array of the list of the users songs which looks like this:
[
{
id : 1,
title : "La danza del fuego",
author : "Mago de Oz",
album : "Finisterra",
genre : "Folk",
duration : 132132312321, // miliseconds
cover : "file:///sdcard/0/123.png",
blur : "file:///sdcard/0/123.png",
path : "/sdcard/0/la-danza-del-fuego.mp3"
}
]
Good luck!
react-native-get-music-files is an npm package that searches for all music files on the local storage and sd card.
import MusicFiles from 'react-native-get-music-files';
MusicFiles.get(
(success) => {
//this.saveSongData(success)
},
(error) => {
console.log(error)
}
);
The function returns an array of objects that you can loop through like so:
[
{
id : 1,
title : "La danza del fuego",
author : "Mago de Oz",
album : "Finisterra",
genre : "Folk",
duration : 132132312321, // miliseconds
cover : "file:///sdcard/0/123.png",
blur : "file:///sdcard/0/123.png",
path : "/sdcard/0/la-danza-del-fuego.mp3"
}
]
Unfortunately, it currently supports Android only.
Related
i have a bunch of audio files local to my app and i want to load them dynamically based on a component's state, the only way i found to load the audio with expo av is to use "require", but this method keeps returning "invalid call" whenever i try to use a variable of any sort or any template literals in the path string in it.
i tried even storing the paths in a json file and then referrirng to the path directly there and still got the invalid call error.
const { sound } = await Audio.Sound.createAsync(require(audioPaths['paths'][fileKey]), {}, playbackStatusUpdate);
how do you guys go about this issue? my files are local so i can't take advantage of streaming/loading them from network. does expo av offer any alternative to the require method? i need any tips or advice you might have
PS: if you need any more details about the situation please ask me and i will fill you in
Edit: this is how my paths json looks like
{
"paths": [
"../assets/Records/1.mp3",
"../assets/Records/2.mp3",
"../assets/Records/3.mp3",
"../assets/Records/4.mp3"
]
}
The issue is related to audio paths not being declared as System.registerDynamic.
you should define paths in JSON like this
"paths": [
require('./assets/one.mp3'),
require('./assets/two.mp3'),
require('./assets/three.mp3'),
]
}
and call this without require,
const { sound } = await Audio.Sound.createAsync(audioPaths['paths'][fileKey], {}, playbackStatusUpdate);
here is a snack I used
Theoretically when you want to upload files in a react native app, you will use either formData, or fileupload or react-native-fs or expo-file-system.
I recommend you the expo-file-system since you use expo.
See complete implementation here
But saying i have a bunch of audio files local to my app means that your audio files are already uploaded into a directory in your project folder and just you want those audio to be played dynamically using the expo-av Audio.Sound.createAsync() with require(). This is how I would do that:
import * as React from 'react';
import { Text, View, StyleSheet, Button } from 'react-native';
import { Audio } from 'expo-av';
export default function App() {
const [sound, setSound] = React.useState();
async function playSound() {
console.log('Loading Sound');
const { sound } = await Audio.Sound.createAsync( require('./assets/Hello.mp3')
);
setSound(sound);
console.log('Playing Sound');
await sound.playAsync();
}
React.useEffect(() => {
return sound
? () => {
console.log('Unloading Sound');
sound.unloadAsync();
}
: undefined;
}, [sound]);
return (
<View style={styles.container}>
<Button title="Play Sound" onPress={playSound} />
</View>
);
}
This sample is for playing one audio, but in your question you want the audio to be played dynamically. For that you can only use react-native useEffect hook to create a kind of repeatable actions. I would first create a method playSound like this:
playSound = async () => {
await Audio.Sound.createAsync( require('' + source);
};
Here source is the path to an audio sent as variable and you may want to use function goToNext() and resumePlayList() to change the path of source variable like:
const goToNext = () => {
for(let i = 0; i < noGuest; i++){
source = JsonPath[i];
}
Amplitude analytics gives me events.js like-
class ApplicationEvents {
clickOpenButton() {
const name = "Click Open Button";
return { name };
}
clickOnPolicy() {
const name = "Click On Policy";
return { name };
}
clickOnConditions() {
const name = "Click On Conditions";
return { name };
}
export { ApplicationEvents };
How do i call these functions inside react native and where should i keep this events.js file inside app folder?
I was logging events earlier like this-
appLogger.logEvent(CLICK_ON_ICON) and inside utils folder was maintaining appConstant.js file. But now integrating these functions i am having trouble any documentation link will also help . Thanks in Advance.
I am using reactjs to create the web app, on a file uploading component, I utilize filepond, it works well, except when I am trying to upload a file, before it's progress reaches beyond 30 or 40 percent, the entire page reloads and the whole file upload process cancels. Is there any way I can stop the page from reloading? my basic filepond component is like this:
import React from 'react';
import "./ImageUploader.css";
import { FilePond, registerPlugin } from 'react-filepond/dist/react-filepond';
// Import FilePond styles
import 'filepond/dist/filepond.min.css';
// Import the Image EXIF Orientation and Image Preview plugins
// Note: These need to be installed separately
import FilePondPluginImageExifOrientation from 'filepond-plugin-image-exif-orientation';
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
import FilePondPluginFilePoster from "filepond-plugin-file-poster";
import FilePondPluginFileEncode from 'filepond-plugin-file-encode';
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css';
// // Register the plugins
registerPlugin(FilePondPluginImageExifOrientation, FilePondPluginImagePreview,FilePondPluginFilePoster,FilePondPluginFileEncode);
// Our app
const api_url = require("../../../../constants/api");
export default class ImageUploader extends React.Component {
constructor(props) {
super(props);
this.state = {
// Set initial files, type 'local' means this is a file
// that has already been uploaded to the server (see docs)
files: [
{
// the server file reference
source: '12345',
// set type to local to indicate an already uploaded file
options: {
type: 'local',
// pass poster property
metadata: {
poster: '../images/file.png'
}
}
}]
};
}
handleInit() {
console.log('FilePond instance has initialised', this.pond);
}
onProcessFiles() {
let { onProcessFiles } = this.props;
let pondFiles = this.pond.getFiles();
if (onProcessFiles) onProcessFiles(pondFiles);
}
render() {
return (
<FilePond ref={ref => this.pond = ref}
onaddfilestart={(file) => this.onProcessFiles()}
onprocessfile={(error, file) => this.onProcessFiles()}
allowFilePoster = {true}
allowFileEncode = {true}
files={this.state.files}
allowMultiple={true}
maxFiles={3}
server={api_url+'/uploads'}
onprocessfileabort = {()=>{ console.log("error!!!!")}}
oninit={() => this.handleInit() }
onupdatefiles={(fileItems) => {
// Set current file objects to this.state
this.setState({
files: fileItems.map(fileItem => fileItem.file)
});
}}>
</FilePond>
);
}
}
I'm writing a sampler app in React-Native using Expo and have run into the follow error:
react-native - require() must have a sing string literal argument
Pre-recorded samples are played called like this:
const SAMPLES = [
{ name: 'air horn', uri: require('../samples/air_horn.mp3') },
{ name: 'rim shot', uri: require('../samples/rimshot.mp3') },
]
playSample = (uri) => {
const { soundObject, status } = Expo.Audio.Sound.create(
uri,
{ shouldPlay: true }
)
}
It's somewhat based off the Expo documentation and works fine. However, I also want the user to record their own samples, which means it's coming from a custom URL:
playCustomSample = () => {
const customSample = this.props.navigation.state.params
? require(this.props.navigation.state.params.custom.toString())
: require('./samples/blank.mp3')
try {
const { soundObject, status } = Expo.Audio.Sound.create(
customSample,
{ shouldPlay: true }
)
} catch(error) {
console.log('ERROR:', error)
}
When I log the custom param I'm being passed by navigation, it's there. So I get that I'm doing it conceptually wrong--that require() requires a string, but I'm not going to know the name/location of the file until it is recorded.
How would I get access to the audio file without knowing the link ahead of time so I can include it?
Also, I don't have access to a Mac so ejecting it from Expo, so using something like react-native-audio or react-native-sound isn't an option.
I have audio files hosted on a server that I'd like my app to access after authenticating. Users send a GET request which includes an authentication token, and the server returns the binary audio data.
As far as I can see there is no way to save this 'blob' as an audio file to the filesystem. The current implementation of fetch in react-native doesn't support blobs: link
... and the ideally-suited react-native-fetch-blob library isn't supported in expo either: link
Additionally I can see no way of streaming the audio file from the server. The included audio library with expo allows streaming of audio from a url (e.g. http://example.com/myaudio.mp3) however I can't see any way to attach an authorisation header to the request (e.g. "Authorization": "Bearer [my-token]").
Is there a way of achieving this, either by downloading and saving the audio blob, or streaming from a url with an authorisation header included in the request? I could detach my project from Expo but I'd like to leave that as a last-resort.
Yes, it is. You need to use the Audio module exposed by expo to do it. Below are the steps that you have to follow to load and play an audio file from a given URL. I've also copied over the code for my component that is doing the same for me.
Load Audio module exposed by expo
import { Audio } from 'expo'
Create a new sound Object from it
soundObject = new Audio.Sound()
Asynchronously load your file
await this.soundObject.loadAsync({ uri: this.props.source })
Once loaded play the loaded file using
this.soundObject.playAsync()
Below is a simple component that I wrote for doing it -
import React, { Component } from 'react';
import { View, TouchableNativeFeedback } from 'react-native';
import { Audio } from 'expo';
class AudioPlayer extends Component {
constructor(props) {
super(props);
this.state = { isPlaying: false };
this.loadAudio = this.loadAudio.bind(this);
this.toggleAudioPlayback = this.toggleAudioPlayback.bind(this);
}
componentDidMount() {
this.loadAudio();
}
componentWillUnmount() {
this.soundObject.stopAsync();
}
async loadAudio() {
this.soundObject = new Audio.Sound();
try {
await this.soundObject.loadAsync({ uri: this.props.source /* url for your audio file */ });
} catch (e) {
console.log('ERROR Loading Audio', e);
}
}
toggleAudioPlayback() {
this.setState({
isPlaying: !this.state.isPlaying,
}, () => (this.state.isPlaying
? this.soundObject.playAsync()
: this.soundObject.stopAsync()));
}
render() {
return (
<TouchableNativeFeedback onPress={this.toggleAudioPlayback}>
<View style={this.props.style}>
{this.props.children}
</View>
</TouchableNativeFeedback>
);
}
}
export default AudioPlayer;
i figured it out. I should've load the sound in componentdidmount using async. In case someone met this problem
componentDidMount() {
this.loadAudio();
}
//async function to load the audio
async loadAudio() {
const { navigation } = this.props;
const id = navigation.getParam("id");
this.sound = new Audio.Sound();
for (let i = 0; i < soundArray.length; i++) {
if (soundArray[i].id === id) {
this.currentSound = soundArray[i];
console.log(this.currentSound);
break;
}
}
try {
await this.sound.loadAsync({
uri: this.currentSound.sound /* url for your audio file */
});
await this.sound.setOnPlaybackStatusUpdate(
this._updateScreenForSoundStatus
);
} catch (e) {
console.log("ERROR Loading Audio", e);
}
}