Skip to content

Instantly share code, notes, and snippets.

@abakers
Last active June 26, 2023 09:11
Show Gist options
  • Save abakers/6d24cfadc9038696badeb23739c0f6d2 to your computer and use it in GitHub Desktop.
Save abakers/6d24cfadc9038696badeb23739c0f6d2 to your computer and use it in GitHub Desktop.
Example fix for duck issue on iOS
#import <React/RCTBridgeModule.h>
@interface RCTAudioModule : NSObject <RCTBridgeModule>
@end
#import "RCTAudioModule.h"
#import <AVFoundation/AVFoundation.h>
@implementation RCTAudioModule
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(setAVAudioMixWithOthers:(RCTPromiseResolveBlock)resolve :(RCTPromiseRejectBlock)reject)
{
NSError *error = nil;
BOOL session = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
withOptions: AVAudioSessionCategoryOptionMixWithOthers
error:&error];
if (error) {
reject(@"error", @"Failed to set AVAudioSession category", error);
} else {
NSNumber *sessionNumber = [NSNumber numberWithBool:session];
resolve(sessionNumber);
}
}
RCT_EXPORT_METHOD(setAVAudioDuckOthers:(RCTPromiseResolveBlock)resolve :(RCTPromiseRejectBlock)reject)
{
NSError *error = nil;
BOOL session = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
withOptions:AVAudioSessionCategoryOptionDuckOthers
error:&error];
if (error) {
reject(@"error", @"Failed to set AVAudioSession category", error);
} else {
NSNumber *sessionNumber = [NSNumber numberWithBool:session];
resolve(sessionNumber);
}
}
@end
import { Audio } from 'expo-av';
import { SoundObject } from 'expo-av/build/Audio';
import { useCallback, useEffect, useState } from 'react';
import { Platform, NativeModules } from 'react-native';
import useIsMounted from './useIsMounted';
interface IAudioProps {
uri?: string;
paused?: boolean;
}
export default ({
uri,
paused = false,
}: IAudioProps): void => {
const [soundObject, setSoundObject] =
useState<SoundObject>();
const isMounted = useIsMounted();
const toggleInterruptionModeIOS = useCallback(
async (mix = false) => {
if (Platform.OS === 'ios') {
try {
mix
? await NativeModules.AudioModule.setAVAudioMixWithOthers()
: await NativeModules.AudioModule.setAVAudioDuckOthers();
} catch (error) {
console.log(
'Failed to set interruption mode iOS: ',
error,
);
}
}
},
[],
);
useEffect(() => {
const run = async () => {
if (!!uri) {
try {
const file = await Audio.Sound.createAsync({
uri,
});
if (
file.sound &&
file.status.isLoaded &&
isMounted()
) {
setSoundObject(file);
await file.sound.playAsync();
file.sound.setOnPlaybackStatusUpdate(
async (status) => {
if (status.isLoaded) {
try {
if (status.didJustFinish) {
await file.sound.unloadAsync();
await toggleInterruptionModeIOS(true);
} else if (status.isPlaying) {
await toggleInterruptionModeIOS();
}
} catch (error) {
console.log(
'setOnPlaybackStatusUpdate error: ',
error,
);
}
}
},
);
}
} catch (error) {
console.log('Failed to play audio: ', error);
}
}
};
run();
}, [uri, isMounted, toggleInterruptionModeIOS]);
useEffect(() => {
// Functionality to handle pause press by the user
const run = async () => {
try {
if (soundObject) {
if (paused) {
const status =
await soundObject.sound.getStatusAsync();
if (!status.isLoaded) return;
await soundObject.sound.pauseAsync();
await toggleInterruptionModeIOS(true);
} else {
const status =
await soundObject.sound.getStatusAsync();
if (!status.isLoaded) return;
await soundObject.sound.playAsync();
await toggleInterruptionModeIOS();
}
}
} catch (error) {
console.log('Pause/Play error: ', error);
}
};
run();
}, [soundObject, paused, toggleInterruptionModeIOS]);
useEffect(() => {
return () => {
// Unload the file and toggle iOS interruption mode back to normal
const run = async () => {
if (soundObject) {
try {
await soundObject.sound.unloadAsync();
await toggleInterruptionModeIOS(true);
} catch (error) {
console.log('Failed to unload audio: ', error);
}
}
};
run();
};
}, [soundObject, toggleInterruptionModeIOS]);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment