Expo/React Native: Play back sound effects with low latency? - react-native

I'm working on a game using Expo/React Native. I need to play back sound effects with as low latency as possible. For button presses and such.
For now I've tried it using expo-av. It works, but the latency is too high. Below is my implementation (trying to only load audio once, and then replay it).
function DetailsScreen() {
const [sound, setSound] = useState<Audio.Sound>();
async function playSound() {
if (!sound) return;
await sound.replayAsync();
}
useEffect(() => {
const loadSound = async () => {
const { sound } = await Audio.Sound.createAsync(
require('assets/sounds/Tink.aiff')
);
setSound(sound);
};
loadSound();
return sound
? () => {
sound.unloadAsync();
}
: undefined;
}, [sound, setSound]);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Button title="Play sound" onPress={playSound} />
</View>
);
}
When I develop natively for iOS I've used the AudioServicesPlaySystemSound API to play sound effects with very low latency. Is there any wrapper for this API for React Native? Or other ways to reduce latency?

Related

ActivityIndicator while takePictureAsync with Expo Camera

I'm developing a React Native app and I want to show an ActivityIndicator component while the Expo Camera is processing the image.
<TouchableOpacity style={{alignSelf: 'center'}} onPress={takePicture}>
<FontAwesome name="camera" style={{ color: "#FEA428", fontSize: 50, marginBottom: 20}}/>
</TouchableOpacity>
So the function takePicture do this:
const takePicture = async () => {
setLoading(prevState => !prevState)
if(cameraRef){
let photo = await cameraRef.takePictureAsync({quality: 0.5, skipProcessing: true, fixOrientation: false});
setLoading(prevState => !prevState)
setPhotoAbove(photo)
}
}
setLoading change the "isLoading" prop which say if the ActivityIndicator is going to be visible or not. However, after the first setLoading(prevState => !prevState) then the following code is never executed. Am I missing something here?
EDIT: I'm posting an Expo Snack with the code for testing purposes. In Web the problem isn't visible but for example if you run it on Android it will.
https://snack.expo.io/#avradev/0a8a01
Tks.
You should move your second setLoading just after your await method call like this:
const takePicture = async () => {
setLoading(prevState => !prevState)
if(cameraRef){
let photo = await cameraRef.takePictureAsync({quality: 0.5, skipProcessing: true, fixOrientation: false});
setLoading(prevState => !prevState);
setPhotoAbove(photo);
}
}

Pause video when new video plays react-native-video play

I have several Video players on a page.. would like to play only one video at a time so when one is playing, any other that was playing before stops... The Idea I had is to identify the ID of the video that has been tapped on then let this be the only video that is allowed to play so whatever was playing before should be paused. I am not sure of how to do this...
const MediaItem =({result}) => {
const [currentVideoPlayingId, setCurrentVideoPlayingId] = useState(' ')
const oneVideoPlaying = ()=>{
setCurrentVideoPlayingId(result.id)
}
console.log('currentVideoPlaying Id:',currentVideoPlayingId)
return (
<View style={styles.Container}>
<Card style={styles.CardView}>
<View style={styles.VideoItem}>
<VideoPlayer
video= {{uri:result.video_url} }
videoWidth={1600}
videoHeight={900}
ignoreSilentSwitch={"ignore"}
style={styles.Vid}
onStart={oneVideoPlaying}
thumbnail={{uri:result.thumbnail_url}}
/>
</View>
<Card.Title title={result.title} subtitle={result.description} />
</Card>
</View>
);
}
const styles = StyleSheet.create ({
Container: {
flex: 1
},
CardView:{
marginLeft:10,
marginRight:10,
marginTop: 20,
borderRadius:25,
width:320,
height:300
},
VideoItem:{
},
Vid:{
borderTopLeftRadius:25,
borderTopRightRadius:25,
},
})
export default MediaItem;

Expo Video re-render on state change

I have a video where all I want it a play button over the top. I have achieved this with the following:
<View style={{ backgroundColor: 'yellow', justifyContent: 'center', alignItems: 'center'}}>
<Video
ref={setupVideo}
source={require('../../assets/AppSampleCoverVideo.mp4')} // TODO:
rate={1.0}
volume={1.0}
isMuted={false}
shouldPlay={isPlaying}
resizeMode='contain'
isLooping={false}
style={styles.setupVideo}
//onPlaybackStatusUpdate={onPlaybackStatusUpdate}
//useNativeControls={true}
/>
<TouchableOpacity style={styles.playButton} onPress={toggleVideoPlay}>
{
!isPlaying ? (
<Ionicons style={styles.playIcon} color='white' size={ Dimensions.get('window').width / 5 } name='ios-play' />
) : null
}
</TouchableOpacity>
</View>
I control the video with the following:
const [isPlaying, setIsPlaying] = useState(false)
const setupVideo = useRef(null);
const toggleVideoPlay = async () => {
try {
let videoStatus = await setupVideo.current.getStatusAsync();
if(videoStatus.isPlaying) {
return setupVideo.current.pauseAsync();
} else {
if(videoStatus.durationMillis === videoStatus.positionMillis) return setupVideo.current.replayAsync();
return setupVideo.current.playAsync();
}
} catch(err) {
}
}
However, I want to be able to track when the video is playing and when it is not in order to set the isPlaying and control whether the play button icon is rendered or not. Anything I have tried causes either the video to flicker when calling setIsPlaying or the video re-renders and doesn't play at all.
Any help would be appreciated.

React Native Expo Video av-expo -- Directly play as fullscreen

I'm trying to do this by react-native using the av-expo video.
What I'm trying to do is to launch the video directly in full screen without going through the "Video" stack (without the double loading of the MediaPlayerScreen stack + the native fullScreen stack).
If the user mutes the full screen by the native fullScreen output button, then we go back directly to a stack
The idea is to use only the native fullscreen stack to display the video. Is this possible?
I don't know if I'm clear, if it can help to understand, here is the code of my MediaPlayerScreen component
export class MediaPlayerScreen extends Component {
static navigationOptions = {
//header: null,
headerTitle: '',
headerTransparent: false,
headerTintColor: 'white',
}
constructor(props) {
super(props)
this.AG = AG.instance
this.filePath =
this.AG.getFilePath() + props.navigation.state.params.file
this.windowWidth = Dimensions.get('window').width
this.windowHeight = Dimensions.get('window').height
}
//
onPlaybackStatusUpdate = (playbackStatus) => {
if (playbackStatus.didJustFinish) {
this.props.navigation.goBack()
}
}
//
_handleVideoRef = async (component) => {
const playbackObject = component
if (playbackObject) {
await playbackObject.loadAsync({
uri: this.filePath,
shouldPlay: false,
posterSource: this.poster,
})
// todo: Trigger fullScreen without videoStack loading
//playbackObject.presentFullscreenPlayer();
playbackObject.playAsync()
//playbackObject.setOnPlaybackStatusUpdate(this.onPlaybackStatusUpdate);
}
}
componentDidMount() {
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE)
}
componentWillUnmount() {
//playbackObject.dismissFullscreenPlayer();
//this.props.navigation.goBack();
ScreenOrientation.lockAsync(
ScreenOrientation.OrientationLock.PORTRAIT_UP
)
}
onFullscreenUpdate = ({ fullscreenUpdate, status }) => {
console.log(fullscreenUpdate, status)
switch (fullscreenUpdate) {
case Video.FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT:
console.log(' the fullscreen player is about to present')
break
case Video.FULLSCREEN_UPDATE_PLAYER_DID_PRESENT:
console.log('the fullscreen player just finished presenting')
break
case Video.FULLSCREEN_UPDATE_PLAYER_WILL_DISMISS:
console.log('the fullscreen player is about to dismiss')
break
case Video.FULLSCREEN_UPDATE_PLAYER_DID_DISMISS:
console.log('the fullscreen player just finished dismissing')
}
}
render() {
return (
<SafeAreaView style={styles.container}>
<Video
ref={this._handleVideoRef}
useNativeControls
rate={1.0}
resizeMode="contain"
onPlaybackStatusUpdate={(playbackStatus) =>
this.onPlaybackStatusUpdate(playbackStatus)
}
onFullscreenUpdate={this.onFullscreenUpdate}
style={{
width: this.windowHeight,
height: this.windowWidth,
}}
/>
</SafeAreaView>
)
}
}
Thanks for your help,
Meums/
hey if I am not getting you wrong you want to load the player fullscreen by default.you can follow this approach:
const videoRef = useRef(null);
<Video
ref={videoRef}
useNativeControls={false}
style={styles.container}
isLooping
source={{
uri: videoUri,
}}
onLoad={()=>{
videoRef?.current?.presentFullscreenPlayer();
}
resizeMode="contain"
onPlaybackStatusUpdate={(status) => setStatus(() => status)}
/>

react-native: No need to Display Splash Video Every time

I am Developing Sample Application, in this Application, When i was start or run the application,Splash Video Comes every time when i was run the application Or reload the Application .But, I don't need to be Splash Video every time.
this is my code
class SplashPage extends Component {
componentWillMount () {
var navigator = this.props.navigator;
setTimeout (() => {
navigator.replace({
component: LoginScreen,
// <-- This is the View you go to
});
}, 8700); //<-- Time until it jumps to "MainView"
}
render () {
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
<Video source={require('./images/splashVideo.mp4')}
rate={1.0}
volume={1.0}
muted={false}
paused={false}
resizeMode="cover"
repeat={false}
onError={this.videoError}
style={styles.backgroundVideo} />
<View>{StatusBar.setBackgroundColor('black', true)}</View>
</View>
);
}
}
Thanks in Advance
Yes I got it, This either Splash Screen/ Video no need to Every time.
Just you will be add in this lines in MainActivity.java file Add this lines.
#Override
public void invokeDefaultOnBackPressed() {
moveTaskToBack(true);
}