diff --git a/modules/expo-bluesky-swiss-army/index.ts b/modules/expo-bluesky-swiss-army/index.ts index ebd67913..2cf4f36c 100644 --- a/modules/expo-bluesky-swiss-army/index.ts +++ b/modules/expo-bluesky-swiss-army/index.ts @@ -1,6 +1,7 @@ import * as PlatformInfo from './src/PlatformInfo' +import {AudioCategory} from './src/PlatformInfo/types' import * as Referrer from './src/Referrer' import * as SharedPrefs from './src/SharedPrefs' import VisibilityView from './src/VisibilityView' -export {PlatformInfo, Referrer, SharedPrefs, VisibilityView} +export {AudioCategory, PlatformInfo, Referrer, SharedPrefs, VisibilityView} diff --git a/modules/expo-bluesky-swiss-army/ios/PlatformInfo/ExpoPlatformInfoModule.swift b/modules/expo-bluesky-swiss-army/ios/PlatformInfo/ExpoPlatformInfoModule.swift index ff1148a0..471f1438 100644 --- a/modules/expo-bluesky-swiss-army/ios/PlatformInfo/ExpoPlatformInfoModule.swift +++ b/modules/expo-bluesky-swiss-army/ios/PlatformInfo/ExpoPlatformInfoModule.swift @@ -8,14 +8,26 @@ public class ExpoPlatformInfoModule: Module { return UIAccessibility.isReduceMotionEnabled } + Function("setAudioCategory") { (audioCategoryString: String) in + let audioCategory = AVAudioSession.Category(rawValue: audioCategoryString) + try? AVAudioSession.sharedInstance().setCategory(audioCategory) + } + Function("setAudioMixWithOthers") { (mixWithOthers: Bool) in var options: AVAudioSession.CategoryOptions + let currentCategory = AVAudioSession.sharedInstance().category if mixWithOthers { options = [.mixWithOthers] } else { - options = [] + options = [.duckOthers] } - try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: .default, options: options) + try? AVAudioSession + .sharedInstance() + .setCategory( + currentCategory, + mode: .default, + options: options + ) } } } diff --git a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.native.ts b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.native.ts index 949bd78a..ba9dddf8 100644 --- a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.native.ts +++ b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.native.ts @@ -1,6 +1,8 @@ import {Platform} from 'react-native' import {requireNativeModule} from 'expo-modules-core' +import {AudioCategory} from './types' + const NativeModule = requireNativeModule('ExpoPlatformInfo') export function getIsReducedMotionEnabled(): boolean { @@ -11,3 +13,8 @@ export function setAudioMixWithOthers(mixWithOthers: boolean): void { if (Platform.OS !== 'ios') return NativeModule.setAudioMixWithOthers(mixWithOthers) } + +export function setAudioCategory(audioCategory: AudioCategory): void { + if (Platform.OS !== 'ios') return + NativeModule.setAudioCategory(audioCategory) +} diff --git a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.ts b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.ts index 2665748b..5659339f 100644 --- a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.ts +++ b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.ts @@ -1,9 +1,23 @@ import {NotImplementedError} from '../NotImplemented' +import {AudioCategory} from './types' export function getIsReducedMotionEnabled(): boolean { throw new NotImplementedError() } +/** + * Set whether the app's audio should mix with other apps' audio. + * @param mixWithOthers + */ export function setAudioMixWithOthers(mixWithOthers: boolean): void { throw new NotImplementedError({mixWithOthers}) } + +/** + * Set the audio category for the app. + * @param audioCategory + * @platform ios + */ +export function setAudioCategory(audioCategory: AudioCategory): void { + throw new NotImplementedError({audioCategory}) +} diff --git a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.web.ts b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.web.ts index 917c9739..cb64d00c 100644 --- a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.web.ts +++ b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.web.ts @@ -1,4 +1,5 @@ import {NotImplementedError} from '../NotImplemented' +import {AudioCategory} from './types' export function getIsReducedMotionEnabled(): boolean { if (typeof window === 'undefined') { @@ -10,3 +11,7 @@ export function getIsReducedMotionEnabled(): boolean { export function setAudioMixWithOthers(mixWithOthers: boolean): void { throw new NotImplementedError({mixWithOthers}) } + +export function setAudioCategory(audioCategory: AudioCategory): void { + throw new NotImplementedError({audioCategory}) +} diff --git a/modules/expo-bluesky-swiss-army/src/PlatformInfo/types.ts b/modules/expo-bluesky-swiss-army/src/PlatformInfo/types.ts new file mode 100644 index 00000000..374f3431 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/src/PlatformInfo/types.ts @@ -0,0 +1,15 @@ +/** + * Sets the audio session category on iOS. In general, we should only need to use this for the `playback` and `ambient` + * categories. This enum however includes other categories that are available in the native API for clarity and + * potential future use. + * @see https://developer.apple.com/documentation/avfoundation/avaudiosession/category + * @platform ios + */ +export enum AudioCategory { + Ambient = 'AVAudioSessionCategoryAmbient', + Playback = 'AVAudioSessionCategoryPlayback', + _SoloAmbient = 'AVAudioSessionCategorySoloAmbient', + _Record = 'AVAudioSessionCategoryRecord', + _PlayAndRecord = 'AVAudioSessionCategoryPlayAndRecord', + _MultiRoute = 'AVAudioSessionCategoryMultiRoute', +} diff --git a/src/App.native.tsx b/src/App.native.tsx index 29a6a738..71b53e7a 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -61,7 +61,7 @@ import {Provider as PortalProvider} from '#/components/Portal' import {Splash} from '#/Splash' import {Provider as TourProvider} from '#/tours' import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' -import {PlatformInfo} from '../modules/expo-bluesky-swiss-army' +import {AudioCategory, PlatformInfo} from '../modules/expo-bluesky-swiss-army' SplashScreen.preventAutoHideAsync() @@ -158,6 +158,7 @@ function App() { const [isReady, setReady] = useState(false) React.useEffect(() => { + PlatformInfo.setAudioCategory(AudioCategory.Ambient) PlatformInfo.setAudioMixWithOthers(true) initPersistedState().then(() => setReady(true)) }, []) diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx index 57686626..83bb2f42 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -12,7 +12,10 @@ import {android, atoms as a, useTheme} from '#/alf' import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker' import {Text} from '#/components/Typography' -import {PlatformInfo} from '../../../../../../modules/expo-bluesky-swiss-army' +import { + AudioCategory, + PlatformInfo, +} from '../../../../../../modules/expo-bluesky-swiss-army' export function VideoEmbedInnerNative() { const player = useVideoPlayer() @@ -39,10 +42,12 @@ export function VideoEmbedInnerNative() { style={a.flex_1} nativeControls={true} onEnterFullscreen={() => { + PlatformInfo.setAudioCategory(AudioCategory.Playback) PlatformInfo.setAudioMixWithOthers(false) player.muted = false }} onExitFullscreen={() => { + PlatformInfo.setAudioCategory(AudioCategory.Ambient) PlatformInfo.setAudioMixWithOthers(true) player.muted = true }} @@ -96,12 +101,16 @@ function Controls({ } }, [player]) - const toggleSound = useCallback(() => { - const newValue = !player.muted + const toggleMuted = useCallback(() => { + const muted = !player.muted // We want to set this to the _inverse_ of the new value, because we actually want for the audio to be mixed when // the video is muted, and vice versa. - PlatformInfo.setAudioMixWithOthers(!newValue) - player.muted = newValue + const mix = !muted + const category = muted ? AudioCategory.Ambient : AudioCategory.Playback + + PlatformInfo.setAudioCategory(category) + PlatformInfo.setAudioMixWithOthers(mix) + player.muted = muted }, [player]) return ( @@ -140,7 +149,7 @@ function Controls({ accessibilityRole="button" />