React Native Expo Audio | Play live stream from latest position - react-native

I'm writing an audio player, using Expo Audio, for an app I'm making for an online radio.
The audio comes from an online live stream and, I've successfully added the player and all the things related to it; however, the one issue I'm having is that if I pause the audio when I resume playing it the audio continues from when I paused it rather than from the current position and I need to pause it and play it again to get it to update to what's currently being played.
I play it with playAsync() and I've tried pausing with pauseAsync(), stopAsync(), setStatusAsync({ shouldPlay: false, positionMillis: 0 });
Any tips on how I can get it to work the way it should?
Here's the code I have for the audio player, it's a class from which then I create an instance of to be able to manage it from different places in the app:
class audioPlayer {
static instance = null;
static createInstance() {
var object = new audioPlayer();
return object;
}
_radioStream;
/**
* #returns {audioPlayer}
*/
static getInstance() {
if (audioPlayer.instance == null) {
audioPlayer.instance = audioPlayer.createInstance();
}
return audioPlayer.instance;
}
// Call this first to create a new audio element
createAudio() {
this._radioStream = new Audio.Sound();
};
async loadAudioAsync() {
try {
await this._radioStream.loadAsync(
{ uri: "radio straem"},
);
store.dispatch(setLiveState(true));
this.toggleAudio(); // Autoplay at start
return true;
} catch (error) {
if (error.code === "E_LOAD_ERROR") {
// In the case of an error we try to load again
setTimeout(this.loadAudioAsync, 10000);
throw new Error(error.code);
} else {
throw new Error(error);
};
};
};
async unloadAudioAsync() {
await this._radioStream.unloadAsync();
};
async getStatusAsync() {
return await this._radioStream.getStatusAsync();
};
async toggleAudio() {
// We're gonna play or pause depending on the status
let { isLoaded, isPlaying } = await this._radioStream.getStatusAsync();
// If the user presses the audio and the stream connection has been lost or something
// we try to load it again
if (!isLoaded) {
let res = await this.loadAudioAsync(); // Try to loadAudio again
if (res) this.toggleAudio(); // Retrigger the toggle to start playing
}
if (isLoaded && !isPlaying) {
store.dispatch(setPlayingStatus(true));
await this._radioStream.playAsync();
} else if (isLoaded && isPlaying) {
store.dispatch(setPlayingStatus(false));
await this._radioStream.setStatusAsync({ shouldPlay: false, positionMillis: 0 });
};
};
};

I just had the same exact problem (for my internet radio https://notylus.fr).
It's seems that I found a solution : instead of using
playbackInstance.pauseAsync()
I now use
playbackInstance.stopAsync()
AND for the play part, I add
await playbackInstance.playAsync() //play stream
playbackInstance.setPositionAsync(0) //ensure that you're at position 0
Last
Regards,

Related

React-Native-Image-Picker Auto video recording possible?

I'm a beginner at React Native.
I am trying to access a native(built-in) camera app on Android device.
I used React-Native-Image-Picker to open the camera app but I would like to record a video somehow automatically(?) I mean not using my finger.
I need codes that make it to record and stop the video.
(I don't mean to give me a code rather, please advise if it is even possible?)
Any help would be very appreciated.
Thank you!
It is possible.
Package: https://github.com/mrousavy/react-native-vision-camera
Review the API and Guide section to see how to start and stop recording programmatically.
They also show an example app that demonstrates different types of capture including video recording, ref: https://github.com/mrousavy/react-native-vision-camera/blob/28fc6a68a5744efc85b532a338e2ab1bc8fa45fe/example/src/views/CaptureButton.tsx
...
const onStoppedRecording = useCallback(() => {
isRecording.current = false;
cancelAnimation(recordingProgress);
console.log('stopped recording video!');
}, [recordingProgress]);
const stopRecording = useCallback(async () => {
try {
if (camera.current == null) throw new Error('Camera ref is null!');
console.log('calling stopRecording()...');
await camera.current.stopRecording();
console.log('called stopRecording()!');
} catch (e) {
console.error('failed to stop recording!', e);
}
}, [camera]);
const startRecording = useCallback(() => {
try {
if (camera.current == null) throw new Error('Camera ref is null!');
console.log('calling startRecording()...');
camera.current.startRecording({
flash: flash,
onRecordingError: (error) => {
console.error('Recording failed!', error);
onStoppedRecording();
},
onRecordingFinished: (video) => {
console.log(`Recording successfully finished! ${video.path}`);
onMediaCaptured(video, 'video');
onStoppedRecording();
},
});
// TODO: wait until startRecording returns to actually find out if the recording has successfully started
console.log('called startRecording()!');
isRecording.current = true;
} catch (e) {
console.error('failed to start recording!', e, 'camera');
}
}, [camera, flash, onMediaCaptured, onStoppedRecording]);
//#endregion
...

Closing WebRTC track will not close camera device or tab camera indicator

Banging my head to the wall with this one, I can't seem to understand what is holding on the camera video stream and not closing when MediaStreamTrack.stop() called.
I have a typescript class where I handle getting the WebRTC stream track and passing it using an observable event to a functional reactjs component, the below code is the component registering to the event and using state for the stream track.
const [videoStreamTrack, setVideoStreamTrack] = useState < MediaStreamTrack > (
null
)
useEffect(() => {
return () => {
videoStreamTrack?.stop()
videoElement.current.srcObject.getVideoTracks().forEach((track) => {
track.stop()
videoElement.current.srcObject.removeTrack(track)
})
videoElement.current.srcObject = null
}
}, [])
case RoomEvents.WebcamProducerAdded:
case RoomEvents.VideoStreamReplaced: {
if (result.data?.track) {
if (result.data.track.kind === 'video') {
previewVideoStreamTrack?.stop()
setPreviewVideoStreamTrack(null)
setVideoStreamTrack(result.data.track)
}
}
break
}
In the "Room" class I use the below code to grab the stream.
const videoDevice = this.webcam.device
if (!videoDevice) {
throw new Error('no webcam devices')
}
const userMedia = await navigator.mediaDevices.getUserMedia({
video: this.environmentPlatformService.isMobile ?
true : {
deviceId: {
exact: this.webcam.device.deviceId
},
...VIDEO_CONSTRAINS[this.webcam.resolution],
},
})
const videoTrack = userMedia.getVideoTracks()[0]
this.eventSubject.next({
eventName: RoomEvents.WebcamProducerAdded,
data: {
track: videoTrack,
},
})
I am holding to this.webcam.device details using the code below.
async updateInputOutputMediaDevices(): Promise < MediaDeviceInfo[] > {
await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
})
const devices = await navigator.mediaDevices.enumerateDevices()
await this.updateWebcams(devices)
await this.updateAudioInputs(devices)
await this.updateAudioOutputs(devices)
return devices
}
private async updateWebcams(devices: MediaDeviceInfo[]) {
this.webcams = new Map < string, MediaDeviceInfo > ()
for (const device of devices.filter((d) => d.kind === 'videoinput')) {
this.webcams.set(device.deviceId, device)
}
const array = Array.from(this.webcams.values())
this.eventSubject.next({
eventName: RoomEvents.CanChangeWebcam,
data: {
canChangeWebcam: array.length > 1,
mediaDevices: array,
},
})
}
Refreshing the page will close the camera and tab indicator.
useEffect(() => {
return () => {
videoStreamTrack?.stop()
videoElement.current.srcObject.getVideoTracks().forEach((track) => {
track.stop()
videoElement.current.srcObject.removeTrack(track)
})
videoElement.current.srcObject = null
}
}, [])
So here you are search and destroying video tracks. Seems right-ish; we'll see
async updateInputOutputMediaDevices(): Promise < MediaDeviceInfo[] > {
await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
})
const devices = await navigator.mediaDevices.enumerateDevices()
await this.updateWebcams(devices)
await this.updateAudioInputs(devices)
await this.updateAudioOutputs(devices)
return devices
}
Above I see there's a call for audio might be where the hiccups are? Can't overly examine but maybe you're opening both and closing just video? Try doing a loop through all tracks not just video and see what's there?
#blanknamefornow answer helped me nail the issue.
We are calling getUserMedia in multiple places not only in the
“room” class handling mediasoup actions but also fore
preview/device-selection/etc and didn’t really ever closed the
tracks retrieved.
Sometimes, those tracks are held into useState
variables and when component unmounted if you try to access the
variables they are already nulled by reactjs. The workaround is
since the HTML elements are still referenced stop the track when
needed. I believe this was the missing ingredient when trying to
figure it out.

How to get all event logs of the contract in tron network using tronweb in node js without any limit?

How to get all events logs of the contract in tron network using tronweb in node js without any limit? or is there need of any middle ware storage like redis, etc?
Need to get all data at once before loading dapp home page. The dApp is made in react js. And Trongrid api have this limit of 200 records in single request.
You can use fingerprint (it works like continue token)
async getContractTransferEventsByUser(eventName, userId) {
let result = [];
let tronGrid = new TronGrid(this.tronWeb);
try {
let continueToken = '';
while (true) {
let res = await tronGrid.contract.getEvents(YOUR_CONTRACT_ADDRESS, {
only_confirmed: true,
event_name: eventName,
limit: 200,
fingerprint: continueToken,
order_by: "timestamp,asc",
min_timestamp: minTime, //remove if you don't need it
filters: { id: userId.toString() } //if you need to filter events by one or more values, for example, by user id (if this information is presented in event log), remove if you don't need it.
});
if (!res.success) {
console.warn("Can't get events for the contract");
break;
}
result = result.concat(res.data);
if (typeof res.meta.fingerprint !== 'undefined') {
continueToken = res.meta.fingerprint;
} else {
break;
}
}
} catch (error) {
console.error(error);
} finally {
return result;
}
},

React Native Firebase push notification

I have a requirement to automatically send push notifications to my application when new data is inserted into firebase.
Is there any way to do so ?
Thanks !
You can use Firebase Functions as a middleware function for sending push notifications via FCM to the device If the database value is changed.
Adding an example from my FirebaseDBtoFCMFunction repo.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendPushNotification = functions.database
.ref('/users/{user_id}') // Put your path here with the params.
.onWrite(async (change, context) => {
try {
const { after } = change;
const { _data } = after;
const { deviceToken } = _data.receiver; // Always send the device token within the data entry.
if(!deviceToken) return;
const payload = {
notification: {
title: 'Notification',
body: `FCM notification triggered!`
},
data: context.params // Passing the path params along with the notification to the device. [optional]
};
return await admin.messaging().sendToDevice(deviceToken, payload);
} catch (ex) {
return console.error('Error:', ex.toString());
}
});
Inside your application add child_change (valueChanged) or child_add event for specific database location than when it changes, it will fired.
From doc.
FirebaseDatabase.DefaultInstance
.GetReference("Leaders").OrderByChild("score")
.ValueChanged += HandleValueChanged;
}
void HandleValueChanged(object sender, ValueChangedEventArgs args) {
if (args.DatabaseError != null) {
Debug.LogError(args.DatabaseError.Message);
return;
}
// Do something with the data in args.Snapshot
}
For nodejs value listener

react-native-background-task Expected to run on UI thread

I am trying to sync data capture offline with an online api, I periodically run an background task using react-native-background-task to retrieve offline data and sync the data with an online api.
react-native-background-task error
// This component below triggers the background task on load
import { sync, clean } from "../../services/market/forms/tasks";
import MediaWorker from "../../services/market/forms/MediaWorker";
let worker = new MediaWorker();
BackgroundTask.define(async () => {
console.log("Life's good");
// loads data from db and sync them with the online service
await sync(worker);
// delete synced data from the db and end task
await clean();
});
export default class Onboard extends Component {
constructor(props) {
super(props);
}
async checkStatus() {
const status = await BackgroundTask.statusAsync();
if (status.available) {
// schedule the background task
BackgroundTask.schedule();
return;
}
const reason = status.unavailableReason;
if (reason === BackgroundTask.UNAVAILABLE_DENIED) {
Alert.alert(
"Denied",
'Please enable background "Background App Refresh" for this app'
);
} else if (reason === BackgroundTask.UNAVAILABLE_RESTRICTED) {
Alert.alert(
"Restricted",
"Background tasks are restricted on your device"
);
}
}
componentDidMount() {
this.checkStatus();
}
render() {
// Not important for the question
}
}
// snippet for sync function
export const sync = async worker => {
const formInstances = await loadFormInstance();
if (formInstances.length) {
// Send Textual data
const formInstancesText = filterFormInstances(formInstances, "text");
postFormTextInstance(formInstancesText);
// Get form image data and post
const formInstancesImage = filterFormInstances(formInstances, "image");
formInstancesImage.forEach(worker.send);
// Get form audio data and post
const formInstancesAudio = filterFormInstances(formInstances, "audio");
formInstancesAudio.forEach(worker.send);
// Get form video data and post
const formInstancesVideo = filterFormInstances(formInstances, "video");
formInstancesVideo.forEach(worker.send);
} else {
console.log("Nothing to sync");
BackgroundTask.finish();
}
};
// snippet for clean function
export const clean = async () => {
const formInstances = await loadFormInstance();
if (formInstances.length) {
const toBeDeleted = new Set();
formInstances.forEach(formInstance => {
const fields = formInstance.fields;
let allSynced = true;
for (let index in fields) {
const field = fields[index];
if (field.synced === false) {
allSynced = false;
break;
}
}
if (allSynced) {
toBeDeleted.add(formInstance.instanceID);
}
});
toBeDeleted.forEach(deleteFormInstance);
} else {
console.log("All tasks finished");
BackgroundTask.finish();
}
};
Adb log(Used for monitoring background activity)
Note: Background task runs successfully a lot of time, but fails occasionally with the red screen shown when the app is build in debug mode.
In release mode, the app completely crashes.
Stack trace generated by Crashlytics in production
I fixed it, it turned out react-native-background-task version wasn't compatible with my react-native version, i upgraded from 0.48.1 to 0.51.0 which requires react 16.0.0