How to load local audio files dynamically with expo av - react-native

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];
}

Related

Deep linking - doesn't work if app is closed

I'm implementing deep linking with expo in my react native app. I've managed to do it using this code with this tutorial and this documentation for adjusting it to my nested stacks:
const linking = {
prefixes:[prefix],
config: {
screens: {
Drawer: {
screens: {
Tabs: {
screens: {
Profile:"profile"
}
}
}
},
}
}
}
return (
<NavigationContainer linking={linking}>
<RootStackScreen actions={actions} showLoader={showLoader} user={user} {...props} />
</NavigationContainer>
)
}
If I use myscheme://profile it works as expected, but only if the app is opened in the background. When the app is closed, then it just open it in my initial home screen, I tried googling and searching but couldn't find any explanation that fits what I did. I also tried adding the getInitialRoute function to linking, which triggers when the app was closed and was opened from a deep link, but couldn't figure how I can use it to activate the navigation.
async getInitialURL() {
const url = await Linking.getInitialURL(); // This returns the link that was used to open the app
if (url != null) {
//const { path, queryParams } = Linking.parse(url);
//console.log(path,queryParams)
//Linking.openURL(url)
return url;
}
},
I suppose that you confirmed that your function getInitialURL is getting called when your app is launched? Also, the commented code within the if (url != null) { aren't supposed to be commented right?
If the above is fine then the issue could be related to the debugger being enabled. As per React Native's documentation (https://reactnative.dev/docs/linking#getinitialurl):
getInitialURL may return null while debugging is enabled. Disable the debugger to ensure it gets passed.
I was experiencing this same issue and doing the following helped me
From the component at the root of your navigation stack, where you configure deep linking, add the following code:
const ApplicationNavigator = () => {
useEffect(() => {
// THIS IS THE MAIN POINT OF THIS ANSWER
const navigateToInitialUrl = async () => {
const initialUrl = await Linking.getInitialURL()
if (initialUrl) {
await Linking.openURL(initialUrl)
}
}
navigateToInitialUrl()
}, [])
const linking = {
prefixes: ['<your_custom_scheme>://'],
config: {
/* configuration for matching screens with paths */
screens: {},
},
}
return (
// Your components/navigation setup
)
}
So apparently, your app received the url but somehow "uses" it to wake the app up from background. When it is in the foreground, the useEffect runs and uses the URL to navigate to the intended screen.
PS: Make sure that your linking tree matches your app tree
There are a couple of things you can check.
Verify that the structure for linking.config matches your navigation structure. I've had a similar issue in the past, and resolved it by making sure my config structure was correct.
Ensure that the linking object is setup properly. Refer to the docs to verify. From the looks of it, the linking object you've showed doesn't have the getInitialURL property in it.
Confirm that you've setup the native side of things as documented.
Hopefully something works out! Let me know if it doesn't. 🙂
Based on https://documentation.onesignal.com/v7.0/docs/react-native-sdk#handlers
Deep linking in iOS from an app closed state
You must be Modify the application:didFinishLaunchingWithOptions in your AppDelegate.m file to use the following:
NSMutableDictionary *newLaunchOptions = [NSMutableDictionary dictionaryWithDictionary:launchOptions];
if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
NSDictionary *remoteNotif = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if (remoteNotif[#"custom"] && remoteNotif[#"custom"][#"u"]) {
NSString *initialURL = remoteNotif[#"custom"][#"u"];
if (!launchOptions[UIApplicationLaunchOptionsURLKey]) {
newLaunchOptions[UIApplicationLaunchOptionsURLKey] = [NSURL URLWithString:initialURL];
}
}
}
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:newLaunchOptions];
also in reactnavigation:
https://reactnavigation.org/docs/deep-linking/
const linking = {
prefixes: ["https://example.com", "example://"],
config,
async getInitialURL() {
const url = await Linking.getInitialURL();
if (url != null) {
return url;
}
},
};
<NavigationContainer linking={linking}>
...
</NavigationContainer>
I was having the same problem. In iOS(flutter build) I solved this by adding "Content Available." The article is here: Apple Content Available Document. I am using OneSignal so in the api I added that field. Now even if the app is forced closed it awakes and deep links work. For Onesignal I had to use "content_available" : true. The complete Onesignal postman code is:
{
"app_id": "1234",
"included_segments": ["Test"],
"content_available" : true,
"contents": {
"en": "Hi"
},
"data": {
"dynamic_link": "https://google.com"
},
"headings": {
"en": "Testing"
}
}

React Native how to play a sound after an "onPress"?

Here is some of my code:
<TouchableOpacity
style={styles.button}
onPress={this.onPress}
>
<Text> Play Sound </Text>
</TouchableOpacity>
I want to write a function "onPress" which will play an .mp3 sound.
I have already imported react-native-sound and have my .mp3 file ready to go, I just don't know how to play the sound once the onPress function is called.
In my opinion, if you want to listen the sound, you can try this.
Syntax = react.
Import Sound from "react-native-sound";
Sound.setCategory('Playback');
const whoosh = new Sound('whoosh.mp3', Sound.MAIN_BUNDLE, (error) => {
if (error) {
console.log('failed to load the sound', error);
return;
};
whoosh.play((success) => {
if (success) {
console.log('successfully finished playing');
} else {
console.log('playback failed due to audio decoding errors');
reset the player to its uninitialized state (android only)
whoosh.reset();
}
The easiest way is First to create a new instance like Following.
create this in a constructor to load it when the component mount
Note: please put your mp3 or wav file in android/app/src/main/res/raw
const whoosh = new Sound('swoosh.mp3', Sound.MAIN_BUNDLE);
just call it in your function
whoosh.play()
npm install react-native-sound and link with your project.
Import sound from react-native-sound
// or
import Sound from 'react-native-sound';
Put your mp3 file inside the folder and mention the path:
const requireAudio = require('./xyz.mp3');
Place this code inside your "onPress" function.
const s = new Sound(requireAudio, (e) => {
if (e) {
console.log('Error in SOUND', e);
return;
}
s.play(() => s.release());
});

Is it possible to download an audio file and play it using React Native Expo?

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);
}
}

Search for audio files on phone using React Native

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.

React Native save an image to iOS camera roll

How can I save an image in react native's tag to iOS camera roll? has a uri source.
CameraRoll class has a method named saveImageWithTag but it's not clear how to use it. I think documentation is not good enough.
I have the same issue, and i check the react native CameraRoll API, it provide a static function
static saveImageWithTag(tag, successCallback, errorCallback)
Help you to save the image to the camera roll / gallery.
1.First, make sure your /node_modules/react-native/Libraries/CameraRoll/RCTCameraRoll.xcodeproj has been correctly linked to your current project.If not, you should do it first by following the official document here. (https://facebook.github.io/react-native/docs/linking-libraries-ios.html#content)
2.Second, get your image source well-prepared.For me, I put it in the project assets-library.
3.Coding like this.In this case, I save my image when componentDidMount execute.
(Here is my reference link: https://thebhwgroup.com/blog/accessing-iphone-camera-roll-images-react-native)
'use strict';
var React = require('react-native');
var {
CameraRoll,
View,
} = React;
var CameraRollTest = React.createClass({
componentDidMount() {
CameraRoll.saveImageWithTag('YOUR_IMAGE_TAG/URI', function(data) {
console.log(data);
}, function(err) {
console.log(err);
});
},
render: function() {
return (
<View>
</View>
);
}
});
module.exports = CameraRollTest;