I am starting to go nuts with this one. I am recording an audio, once I stop the recording as part of the this process I also load the audio so that it is ready to be played back when necessary and here I do setOnPlaybackStatusUpdate. I use the playback callback so that I can update a my currentSeconds state based on positionMillis.
The problem is the currentSeconds and recordedDuration state values that I am getting. How come their values change between playAudio method which triggers the audio to be played and onPlaybackStatusUpdate which is the callback method?
When I output in the console their values in both methods this is what I obtain when I expect them to be the same:
In playAudio - currentSeconds: 0
In playAudio - recordedDuration: 4.5
In onPlaybackStatusUpdate - currentSeconds: 115.5
In onPlaybackStatusUpdate - recordedDuration: 0
And here is the code:
const AudioRecorder = useRef(new Audio.Recording());
const AudioPlayer = useRef(new Audio.Sound());
const timerMaxDuration = 120
const [currentSeconds, setCurrentSeconds] = useState<number>(timerMaxDuration);
const [recordedDuration, setRecordedDuration] = useState<number>(0);
const stopRecording = async () => {
try {
await AudioRecorder.current.stopAndUnloadAsync();
// To hear sound through speaker and not earpiece on iOS
await Audio.setAudioModeAsync({ allowsRecordingIOS: false });
const recordedURI = AudioRecorder.current.getURI();
SetRecordingURI(recordedURI)
AudioRecorder.current = new Audio.Recording();
send('STOP')
setRecordedDuration(+(timerMaxDuration - currentSeconds).toFixed(1)) // there is subtraction because during the recording there is a countdown from timerMaxDuration
setCurrentSeconds(0)
// Load audio after recording so that it is ready to be played
loadAudio(recordedURI)
} catch (error) {
console.log(error);
}
};
const loadAudio = async (recordedUri) => {
try {
const playerStatus = await AudioPlayer.current.getStatusAsync();
if (playerStatus.isLoaded === false) {
AudioPlayer.current.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate)
await AudioPlayer.current.loadAsync({ uri: recordedUri }, { progressUpdateIntervalMillis: 20 }, true)
}
} catch (error) {
console.log(error)
}
}
const playAudio = async () => {
console.log(`In playAudio - currentSeconds: ${currentSeconds}`)
console.log(`In playAudio - recordedDuration: ${recordedDuration}`)
try {
const playerStatus = await AudioPlayer.current.getStatusAsync();
if (playerStatus.isLoaded) {
if (playerStatus.isPlaying === false) {
AudioPlayer.current.playAsync();
send('PLAY')
}
}
} catch (error) {
console.log(error);
}
};
const onPlaybackStatusUpdate = playbackStatus => {
if (playbackStatus.isPlaying) {
console.log(`In onPlaybackStatusUpdate - currentSeconds: ${currentSeconds}`)
console.log(`In onPlaybackStatusUpdate - recordedDuration: ${recordedDuration}`)
if(currentSeconds >= recordedDuration){
stopAudio()
}
else{
setCurrentSeconds(+(playbackStatus.positionMillis / 1000).toFixed(1))
}
}
}
Ok so there was nothing wrong with the playback callback. The thing is that both the playback callback is an arrow functions which means that the only property value that will change inside the callback is the one of the argument playbackStatus, the other properties' values will remain the same as the time the function was created.
A walkaround in React is to use useEffect in the following way, which allows to access the state values currentSeconds and recordedDuration:
useEffect(() => {
if(currentSeconds >= recordedDuration)
stopAudio()
}, [currentSeconds]);
const onPlaybackStatusUpdate = playbackStatus => {
if (playbackStatus.isPlaying)
setCurrentSeconds(+(playbackStatus.positionMillis / 1000).toFixed(1))
}
Related
Hi I was trying to use Expo-AV but keep getting the warning
[Unhandled promise rejection: Error: Cannot load an AV asset from a null playback source]
When the sound play function first called it show this warning and dosen't plays but after it when I again recall the function it plays without the warning.
const [sound, setSound] = useState();
const [isPlaying, setIsPlaying] = useState(false);
async function playSound() {
console.log("Loading Sound");
const { sound } = await Audio.Sound.createAsync(
{ uri },
{ shouldPlay: true }
);
setSound(sound);
console.log("Playing Sound");
setIsPlaying(true);
await sound.playAsync();
sound._onPlaybackStatusUpdate = (status) => {
if (status.didJustFinish) {
setIsPlaying(false);
console.log("Finished");
}
};
}
<TouchableOpacity onPress={playSound()}>
<Text>Play</Text>
</TouchableOpacity>
Is there anyway to play after loading properly.
This works for me. Try it out
Over here I'm playing an audio that was selected by the user with the DocumentPicker library from Expo.
const prepareToPlay = async (audioPath) => {
const sound = new Audio.Sound();
try {
await sound.loadAsync({
uri: audioPath.file,
shouldPlay: true,
});
await sound.playAsync();
// Your sound is playing!
// Dont forget to unload the sound from memory
// when you are done using the Sound object
// await sound.unloadAsync();
} catch (error) {
// An error occurred!
console.error('AUDIO PLAY: ', error);
}
};
const audioToBePlayed = {
"file": "file:///Users/crosbyroads/Library/Developer/CoreSimulator/Devices/54AE93CW-1667-491F-A9C7-B457N8BC1207/data/Containers/Data/Application/5C442DA1-4EEA-4F24-93C2-38131484EE9E/Library/Caches/ExponentExperienceData/%crosbyroads%252FPlaySounds/DocumentPicker/EE3ECE8E-DFDC-46C9-8B68-4A8E0A8BB808.wav",
"name": "assets_audio_OS_SB_160_Dm_Cream_Stack_1.wav",
"size": 3180496,
"type": "audio/wav",
}
...
<Button onPress={() => prepareToPlay(audioToBePlayed)} title="Play"></Button>
/** Subscribe to my channel # https://youtube.com/crosbyroads */
I am trying to record video while using BlazePose with React Native. Wherever I add in cameraRef.current.camera.recordAsync() into causes the video to freeze and app to crash.
From what I've found, it might need to go in the handleCameraStream but I am unsure. Here is that method:
const handleCameraStream = async (
images: IterableIterator<tf.Tensor3D>,
updatePreview: () => void,
gl: ExpoWebGLRenderingContext) => {
const loop = async () => {
// const video = cameraRef.current.camera.recordAsync()
// Get the tensor and run pose detection.
const imageTensor = images.next().value as tf.Tensor3D;
const startTs = Date.now();
const poses = await model!.estimatePoses(
imageTensor,
undefined,
Date.now()
);
const latency = Date.now() - startTs;
setFps(Math.floor(1000 / latency));
setPoses(poses);
tf.dispose([imageTensor]);
if (rafId.current === 0) {
return;
}
// Render camera preview manually when autorender=false.
if (!AUTO_RENDER) {
updatePreview();
gl.endFrameEXP();
}
rafId.current = requestAnimationFrame(loop);
};
loop();
};
And here is my useEffect:
useEffect(() => {
async function prepare() {
rafId.current = null;
// Set initial orientation.
const curOrientation = await ScreenOrientation.getOrientationAsync();
setOrientation(curOrientation);
// Listens to orientation change.
ScreenOrientation.addOrientationChangeListener((event) => {
setOrientation(event.orientationInfo.orientation);
});
// Camera permission.
await Camera.requestCameraPermissionsAsync();
// Wait for tfjs to initialize the backend.
await tf.ready();
// Load model
const model = await posedetection.createDetector(
posedetection.SupportedModels.BlazePose,
{
runtime: "tfjs",
enableSmoothing: true,
modelType: "full",
}
);
setModel(model);
// Ready!
setTfReady(true);
}
prepare();}, []);
And finally the TensorCamera
<TensorCamera
ref={cameraRef}
style={styles.camera}
autorender={AUTO_RENDER}
type={cameraType}
// tensor related props
resizeWidth={getOutputTensorWidth()}
resizeHeight={getOutputTensorHeight()}
resizeDepth={3}
rotation={getTextureRotationAngleInDegrees()}
onReady={handleCameraStream}
/>
I am building a Timer to run in the background
I am Using React Native and Expo to build the app
Now the timer I am using I am trying to let it "run" in the background
To do this I am using AppState and a event listner, taking the Start Time, and the elapsed Time (Elabsed while the app is minimized)
And then recalculating the Time elapsed and adding that to the timer
In the Timer There are start, pause, reset and done buttons
Want I am not getting to work is that the elapsed time must not get set into the setstate if the pause button is clicked, and no matter how many IFs I put in there, the Time always gets changed
const appState = useRef(AppState.currentState);
const [timerOn, setTimerOn] = useState(false);
const [time, setTime] = useState(0);
const [Paused,isPaused] = useState("");
const getElapsedTime = async () => {
try {
const startTime = await AsyncStorage.getItem("#start_time");
const now = new Date();
return differenceInSeconds(now, Date.parse(startTime));
} catch (err) {
console.warn(err);
}
};
const recordStartTime = async () => {
try {
const now = new Date()
await AsyncStorage.setItem("#start_time", now.toISOString());
} catch (err) {
console.warn(err);
}
};
useEffect(() => {
Timer()
}, []);
function Timer(){
if(Paused == "no"){
AppState.addEventListener("change", handleAppStateChange);
return () => AppState.removeEventListener("change", handleAppStateChange);
}
else{
console.log("eVENT lISTNER")
}
}```
const handleAppStateChange = async (nextAppState) => {
if (appState.current.match(/inactive|background/) &&
nextAppState == "active" && Paused == "no") {
// We just became active again: recalculate elapsed time based
// on what we stored in AsyncStorage when we started.
const elapsed = await getElapsedTime();
// Update the elapsed seconds state
//THE BELOW STATE IS UPDATED TO "ELAPSED" EVENTHOUGH IN THE IF STATEMENT ABOVE SAYS "PAUSED = NO"
setTime(elapsed);
}
else{
console.log("YES")
}
appState.current = nextAppState;
};
useEffect(() => {
let interval = null;
if (timerOn) {
interval = setInterval(() => {
setTime((prevTime) => prevTime + 1);
}, 1000);
} else if (!timerOn) {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [timerOn]);
let MyHour = ("0" + Math.floor((time / 3600))).slice(-2);
let MyMinutes =("0" + Math.floor((time / 60) % 60)).slice(-2);
let MySeconds = ("0" + ((time % 60))).slice(-2);
function TimerBtn(){
isPaused("no")
setTimerOn(true)
recordStartTime()
}
function PauseBtn(){
isPaused("yes")
setTimerOn(false)
}
function ResetBtn(){
setTimerOn(false)
setTime(0)
}
function DoneBtn(){
}
Your problem is probably that you cannot access the current state of Paused inside a listener. Read this answer here for a detailed explanation
To work around, use a reference instead (useRef).
const [Paused,isPaused] = useState("");
to
const isPaused = useRef(true); // use booleans instead of "yed"/"no"
keep in mind to read and write to references you have to append '.current' to the reference, e.g:
isPaused.current = true;
// or
if (isPaused.current) {...}
I want to clear the react native app data if my app is disconnected from internet for more than 5 min.
I am using react native NetInfo to check network connectivity status.
Saving the time when app disconnected and checking when it will reconnect to internet.
If interval is more than 5 min then I want to clear the app data.
My Code is:
class OfflineMessage extends PureComponent {
constructor(props) {
super(props);
this.state = {
isConnected: true
};
}
componentDidMount() {
NetInfo.addEventListener((state) => {
this.handleConnection(state.isConnected);
});
}
componentWillUnmount() {
NetInfo.removeEventListener((state) => {
this.handleConnection(state.isConnected);
});
}
handleConnection = (isConnected) => {
this.setState({ isConnected });
if(!isConnected){
this.startTimer();
} else {
this.checkElapsed();
}
};
startTimer = async () => {
try {
console.log('Internet disconnected at: ');
await AsyncStorage.setItem('time', JSON.stringify(Date.now()));
} catch (error) {
// console.log('Something went wrong', error);
}
}
checkElapsed = async () => {
try {
let startTime = await AsyncStorage.getItem('time');
if(startTime){
let endTime = Date.now();
const elapsedTime = Math.floor((endTime -JSON.parse(startTime))/1000);
if(elapsedTime > 5){
alert("5 min is completed.");
// Clear app data
}
console.log('Time elapsed'+ elapsedTime);
}
} catch (error) {
// console.log('Something went wrong', error);
}
}
Problem:
Both the methods startTimer and checkElapsed called whenever connectivity status changes.
What is wrong with this code.
if I modify given code as :
state = {
isConnected: true
};
componentDidMount() {
this.unsubscribeFromNetInfo = NetInfo.addEventListener((state) => {
this.handleConnection(state.isConnected);
});
}
componentWillUnmount() {
this.unsubscribeFromNetInfo();
}
handleConnection = (isConnected) => {
console.log(isConnected);
this.setState({ isConnected });
};
EventListener called multiple times and status changes frequently true false,true,false .
Now, you are handling the NetInfo subscription wrong, according to https://github.com/react-native-community/react-native-netinfo#usage
You would have to do:
componentDidMount() {
this.unsubscribeFromNetInfo = NetInfo.addEventListener(state => {
this.handleConnection(state.isConnected);
});
}
componentWillUnmount() {
this.unsubscribeFromNetInfo();
}
Also, if you want to check for 5 minutes use:
if (elapsedTime > 5 * 60)
as your conversion
Math.floor((endTime - JSON.parse(startTime)) / 1000)
converts it to seconds not minutes.
In the current state, your app will trigger almost everything as the code only checks for 5 seconds.
Otherwise, the logic that you implemented itself should be working :)
I am using react-native-firebase with messaging to deliver notifications to my app with cloud functions, with admin.messaging().send(message), very similar to here: https://medium.com/the-modern-development-stack/react-native-push-notifications-with-firebase-cloud-functions-74b832d45386 .
I receive notifications when the app is in the background. Right now I am sending a text in the body of the notification, like 'a new location has been added to the map'. I want to be able to add some sort of deep link, so that when I swipe View on the notification (on iOS for example), it will take me to a specific screen inside the app. How do I pass data from the notification to the app?
I am using react-native-navigation in the app. I can only find code about deep links from inside the app (https://wix.github.io/react-native-navigation/#/deep-links?id=deep-links).
My solution was to use add what information I need in the data object of the notification message object:
in functions/index.js:
let message = {
notification: {
body: `new notification `
},
token: pushToken,
data: {
type: 'NEW_TRAINING',
title: locationTitle
}
};
and process as follows in the app for navigation:
this.notificationOpenedListener =
firebase.notifications().onNotificationOpened((notificationOpen: NotificationOpen) => {
if (notification.data.type === 'NEW_TRAINING') {
this.props.navigator.push({
screen: 'newtrainingscreen',
title: notification.data.title,
animated: true
});
}
I think you are fine with the "how firebase notification work"... cause of this, here is only an description of the Logic how you can Deeplinking into your App.
If you send a notification, add a data-field. Let's say your app has a Tab-Navigator and the sections "News","Service" and "Review".
In your Push-Notification - Datafield (let's name it "jumpToScreen" you define your value:
jumpToScreen = Service
I assume you still have the Handling to recieve Notifications from Firebase implemented.
So create an /lib/MessageHandler.js Class and put your business-logic inside.
import firebase from 'react-native-firebase';
/*
* Get a string from Firebase-Messages and return the Screen to jump to
*/
const getJumpPoint = (pointer) => {
switch (pointer) {
case 'News':
return 'NAV_NewsList'; // <= this are the names of your Screens
case 'Service':
return 'NAV_ServiceList';
case 'Review':
return 'NAV_ReviewDetail';
default: return false;
}
};
const MessageHandler = {
/**
* initPushNotification initialize Firebase Messaging
* #return fcmToken String
*/
initPushNotification: async () => {
try {
const notificationPermission = await firebase.messaging().hasPermission();
MessageHandler.setNotificationChannels();
if (notificationPermission) {
try {
return await MessageHandler.getNotificationToken();
} catch (error) {
console.log(`Error: failed to get Notification-Token \n ${error}`);
}
}
} catch (error) {
console.log(`Error while checking Notification-Permission\n ${error}`);
}
return false;
},
clearBadges: () => {
firebase.notifications().setBadge(0);
},
getNotificationToken: () => firebase.messaging().getToken(),
setNotificationChannels() {
try {
/* Notification-Channels is a must-have for Android >= 8 */
const channel = new firebase.notifications.Android.Channel(
'app-infos',
'App Infos',
firebase.notifications.Android.Importance.Max,
).setDescription('General Information');
firebase.notifications().android.createChannel(channel);
} catch (error) {
console.log('Error while creating Push_Notification-Channel');
}
},
requestPermission: () => {
try {
firebase.messaging().requestPermission();
firebase.analytics().logEvent('pushNotification_permission', { decision: 'denied' });
} catch (error) {
// User has rejected permissions
firebase.analytics().logEvent('pushNotification_permission', { decision: 'allowed' });
}
},
foregroundNotificationListener: (navigation) => {
// In-App Messages if App in Foreground
firebase.notifications().onNotification((notification) => {
MessageHandler.setNotificationChannels();
navigation.navigate(getJumpPoint(notification.data.screen));
});
},
backgroundNotificationListener: (navigation) => {
// In-App Messages if App in Background
firebase.notifications().onNotificationOpened((notificationOpen) => {
const { notification } = notificationOpen;
notification.android.setChannelId('app-infos');
if (notification.data.screen !== undefined) {
navigation.navigate(getJumpPoint(notification.data.screen));
}
});
},
appInitNotificationListener: () => {
// In-App Messages if App in Background
firebase.notifications().onNotificationOpend((notification) => {
notification.android.setChannelId('app-infos');
console.log('App-Init: Da kommt ne Message rein', notification);
firebase.notifications().displayNotification(notification);
});
},
};
export default MessageHandler;
In your index.js you can connect it like this:
import MessageHandler from './lib/MessageHandler';
export default class App extends Component {
state = {
loading: null,
connection: null,
settings: null,
};
async componentDidMount() {
const { navigation } = this.props;
await MessageHandler.initPushNotification();
this.notificationForegroundListener = MessageHandler.foregroundNotificationListener(navigation);
this.notificationBackgroundListener = MessageHandler.backgroundNotificationListener(navigation);
this.setState({ loading: false, data });
}
componentWillUnmount() {
this.notificationForegroundListener();
this.notificationBackgroundListener();
}
async componentDidMount() {
MessageHandler.requestPermission();
AppState.addEventListener('change', this.handleAppStateChange);
MessageHandler.clearBadges();
}
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange);
}
handleAppStateChange = (nextAppState) => {
if (nextAppState.match(/inactive|background/)) {
MessageHandler.clearBadges();
}
....
I hope this give you an Idea how to implement it for your needs.
I think you don't need to use deep links nor dynamic links but just use Firebase/Notifications properly. If I were you I would add the following logic in the componentDidMount method of your parent container:
async componentDidMount() {
// 1. Check notification permission
const notificationsEnabled = await firebase.messaging().hasPermission();
if (!notificationsEnabled) {
try {
await firebase.messaging().requestPermission(); // Request notification permission
// At this point the user has authorized the notifications
} catch (error) {
// The user has NOT authorized the notifications
}
}
// 2. Get the registration token for firebase notifications
const fcmToken = await firebase.messaging().getToken();
// Save the token
// 3. Listen for notifications. To do that, react-native-firebase offer you some methods:
firebase.messaging().onMessage(message => { /* */ })
firebase.notifications().onNotificationDisplayed(notification => { /* */ })
firebase.messaging().onNotification(notification => { /* */ })
firebase.messaging().onNotificationOpened(notification => {
/* For instance, you could use it and do the NAVIGATION at this point
this.props.navigation.navigate('SomeScreen');
// Note that you can send whatever you want in the *notification* object, so you can add to the notification the route name of the screen you want to navigate to.
*/
})
}
You can find the documentation here: https://rnfirebase.io/docs/v4.3.x/notifications/receiving-notifications