From 4350dbc853766dc4ba7cc304b7a12505ef0c4781 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 9 Aug 2024 02:33:42 +0100 Subject: [PATCH 01/12] [Video] Bundle of minor tweaks (#4904) * fix bg color * unique video urls for debug * improve controls slightly * mute until fullscreen --------- Co-authored-by: Samuel Newman <10959775+mozzius@users.noreply.github.com> --- src/view/com/posts/FeedItem.tsx | 20 +++++-- .../VideoEmbedInner/VideoEmbedInnerNative.tsx | 55 ++++++++++--------- .../util/post-embeds/VideoPlayerContext.tsx | 1 + 3 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 8592f0bec2..0071e2401b 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -1,4 +1,4 @@ -import React, {memo, useMemo, useState} from 'react' +import React, {memo, useId, useMemo, useState} from 'react' import {StyleSheet, View} from 'react-native' import { AppBskyActorDefs, @@ -137,7 +137,6 @@ let FeedItemInner = ({ const {openComposer} = useComposerControls() const pal = usePalette('default') const {_} = useLingui() - const gate = useGate() const href = useMemo(() => { const urip = new AtUri(post.uri) @@ -356,9 +355,7 @@ let FeedItemInner = ({ postAuthor={post.author} onOpenEmbed={onOpenEmbed} /> - {gate('video_debug') && ( - - )} + + ) +} + const styles = StyleSheet.create({ outer: { paddingLeft: 10, diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx index cc356fb069..b95b9c809b 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -1,5 +1,6 @@ import React, {useEffect, useRef, useState} from 'react' import {Pressable, View} from 'react-native' +import Animated, {FadeIn} from 'react-native-reanimated' import {VideoPlayer, VideoView} from 'expo-video' import {useVideoPlayer} from 'view/com/util/post-embeds/VideoPlayerContext' @@ -20,7 +21,10 @@ export function VideoEmbedInnerNative() { /> ref.current?.enterFullscreen()} + enterFullscreen={() => { + player.muted = false + ref.current?.enterFullscreen() + }} /> ) @@ -56,34 +60,33 @@ function Controls({ } }, [player]) - if (isNaN(timeRemaining)) { - return null - } - return ( - - - {minutes}:{seconds} - - + { + backgroundColor: 'rgba(0, 0, 0, 0.75)', + borderRadius: 6, + paddingHorizontal: 6, + paddingVertical: 3, + position: 'absolute', + left: 5, + bottom: 5, + }, + ]} + pointerEvents="none"> + + {minutes}:{seconds} + + + )} { player.loop = true + player.muted = true player.play() }) From a4f0c9c753ec8844eda094080d81fbba566de7df Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 9 Aug 2024 03:24:38 +0100 Subject: [PATCH 02/12] [Video] more minor tweaks (#4906) * update expo-video * pause when on a different screen * rm collapsable * add mute/unmute button --------- Co-authored-by: Samuel Newman <10959775+mozzius@users.noreply.github.com> --- package.json | 2 +- .../VideoEmbedInner/VideoEmbedInnerNative.tsx | 76 ++++++++++++++++--- yarn.lock | 8 +- 3 files changed, 72 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index faeee448c9..7c6e13afb6 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "expo-system-ui": "~3.0.4", "expo-task-manager": "~11.8.1", "expo-updates": "~0.25.14", - "expo-video": "^1.1.10", + "expo-video": "^1.2.4", "expo-web-browser": "~13.0.3", "fast-text-encoding": "^1.0.6", "history": "^5.3.0", diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx index b95b9c809b..26f6111bc7 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -1,18 +1,37 @@ -import React, {useEffect, useRef, useState} from 'react' +import React, {useCallback, useEffect, useRef, useState} from 'react' import {Pressable, View} from 'react-native' import Animated, {FadeIn} from 'react-native-reanimated' import {VideoPlayer, VideoView} from 'expo-video' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useIsFocused} from '@react-navigation/native' -import {useVideoPlayer} from 'view/com/util/post-embeds/VideoPlayerContext' -import {android, atoms as a} from '#/alf' +import {HITSLOP_30} from '#/lib/constants' +import {useVideoPlayer} from '#/view/com/util/post-embeds/VideoPlayerContext' +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' export function VideoEmbedInnerNative() { const player = useVideoPlayer() const ref = useRef(null) + const isScreenFocused = useIsFocused() + + // pause the video when the screen is not focused + useEffect(() => { + if (!isScreenFocused) { + let wasPlaying = player.playing + player.pause() + + return () => { + if (wasPlaying) player.play() + } + } + }, [isScreenFocused, player]) return ( - + void }) { + const {_} = useLingui() + const t = useTheme() + const [isMuted, setIsMuted] = useState(player.muted) const [duration, setDuration] = useState(() => Math.floor(player.duration)) const [currentTime, setCurrentTime] = useState(() => Math.floor(player.currentTime), @@ -55,11 +77,21 @@ function Controls({ // 1000 gets out of sync with the video time }, 250) + // eslint-disable-next-line @typescript-eslint/no-shadow + const sub = player.addListener('volumeChange', ({isMuted}) => { + setIsMuted(isMuted) + }) + return () => { clearInterval(interval) + sub.remove() } }, [player]) + const toggleSound = useCallback(() => { + player.muted = !player.muted + }, [player]) + return ( {!isNaN(timeRemaining) && ( @@ -74,12 +106,13 @@ function Controls({ position: 'absolute', left: 5, bottom: 5, + minHeight: 20, + justifyContent: 'center', }, - ]} - pointerEvents="none"> + ]}> @@ -90,10 +123,35 @@ function Controls({ + + + {isMuted ? ( + + ) : ( + + )} + + ) } diff --git a/yarn.lock b/yarn.lock index 16547aa3ad..ba1227f30b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12302,10 +12302,10 @@ expo-updates@~0.25.14: ignore "^5.3.1" resolve-from "^5.0.0" -expo-video@^1.1.10: - version "1.1.10" - resolved "https://registry.yarnpkg.com/expo-video/-/expo-video-1.1.10.tgz#b47c0d40c21f401236639424bd25d70c09316b7b" - integrity sha512-k9ecpgtwAK8Ut8enm8Jv398XkB/uVOyLLqk80M/d8pH9EN5CVrBQ7iEzWlR3quvVUFM7Uf5wRukJ4hk3mZ8NCg== +expo-video@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/expo-video/-/expo-video-1.2.4.tgz#787342aded4295a1b6864f59227d178b93e1bb53" + integrity sha512-pBK9mt7vYAbuPQjCSQxHQ7xrNjbmRheJep7JIStEg57O183/JRfP2blKuXniiSt1HBdZYPdoQnGRa3jGMXB9pg== expo-web-browser@~13.0.3: version "13.0.3" From cb574b7be3757c25063957ff561bae6f1be7577f Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 9 Aug 2024 03:05:12 -0700 Subject: [PATCH 03/12] [Videos] Add iOS function to set `AVAudioSession.CategoryOptions` to `.mixWithOthers` (#4905) * audio mixing pref * lint --- .../ios/PlatformInfo/ExpoPlatformInfoModule.swift | 10 ++++++++++ .../src/PlatformInfo/index.native.ts | 6 ++++++ .../expo-bluesky-swiss-army/src/PlatformInfo/index.ts | 4 ++++ .../src/PlatformInfo/index.web.ts | 6 ++++++ 4 files changed, 26 insertions(+) diff --git a/modules/expo-bluesky-swiss-army/ios/PlatformInfo/ExpoPlatformInfoModule.swift b/modules/expo-bluesky-swiss-army/ios/PlatformInfo/ExpoPlatformInfoModule.swift index 4a1e6d7e7d..ff1148a0b5 100644 --- a/modules/expo-bluesky-swiss-army/ios/PlatformInfo/ExpoPlatformInfoModule.swift +++ b/modules/expo-bluesky-swiss-army/ios/PlatformInfo/ExpoPlatformInfoModule.swift @@ -7,5 +7,15 @@ public class ExpoPlatformInfoModule: Module { Function("getIsReducedMotionEnabled") { return UIAccessibility.isReduceMotionEnabled } + + Function("setAudioMixWithOthers") { (mixWithOthers: Bool) in + var options: AVAudioSession.CategoryOptions + if mixWithOthers { + options = [.mixWithOthers] + } else { + options = [] + } + try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, 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 e05f173d64..949bd78a28 100644 --- a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.native.ts +++ b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.native.ts @@ -1,3 +1,4 @@ +import {Platform} from 'react-native' import {requireNativeModule} from 'expo-modules-core' const NativeModule = requireNativeModule('ExpoPlatformInfo') @@ -5,3 +6,8 @@ const NativeModule = requireNativeModule('ExpoPlatformInfo') export function getIsReducedMotionEnabled(): boolean { return NativeModule.getIsReducedMotionEnabled() } + +export function setAudioMixWithOthers(mixWithOthers: boolean): void { + if (Platform.OS !== 'ios') return + NativeModule.setAudioMixWithOthers(mixWithOthers) +} diff --git a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.ts b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.ts index 9b9b7fc0c7..2665748b0a 100644 --- a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.ts +++ b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.ts @@ -3,3 +3,7 @@ import {NotImplementedError} from '../NotImplemented' export function getIsReducedMotionEnabled(): boolean { throw new NotImplementedError() } + +export function setAudioMixWithOthers(mixWithOthers: boolean): void { + throw new NotImplementedError({mixWithOthers}) +} 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 c7ae6b7cd4..917c97396f 100644 --- a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.web.ts +++ b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.web.ts @@ -1,6 +1,12 @@ +import {NotImplementedError} from '../NotImplemented' + export function getIsReducedMotionEnabled(): boolean { if (typeof window === 'undefined') { return false } return window.matchMedia('(prefers-reduced-motion: reduce)').matches } + +export function setAudioMixWithOthers(mixWithOthers: boolean): void { + throw new NotImplementedError({mixWithOthers}) +} From 0f993a09c2ac7147f9a555cb243c1aff8fc2e48c Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 9 Aug 2024 03:05:43 -0700 Subject: [PATCH 04/12] [Video] Add `onEnterFullscreen` and `onExitFullscreen` to `VideoView` (#4907) * patch expo video to add enter/exit fullscreen events * rm logs * Update expo-video+1.2.4.patch.md --- patches/expo-video+1.1.10.patch | 20 ----- patches/expo-video+1.2.4.patch | 135 ++++++++++++++++++++++++++++++ patches/expo-video+1.2.4.patch.md | 6 ++ 3 files changed, 141 insertions(+), 20 deletions(-) delete mode 100644 patches/expo-video+1.1.10.patch create mode 100644 patches/expo-video+1.2.4.patch create mode 100644 patches/expo-video+1.2.4.patch.md diff --git a/patches/expo-video+1.1.10.patch b/patches/expo-video+1.1.10.patch deleted file mode 100644 index b183be9d41..0000000000 --- a/patches/expo-video+1.1.10.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt -+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt -@@ -11,6 +11,7 @@ internal fun PlayerView.applyRequiresLinearPlayback(requireLinearPlayback: Boole - setShowPreviousButton(!requireLinearPlayback) - setShowNextButton(!requireLinearPlayback) - setTimeBarInteractive(requireLinearPlayback) -+ setShowSubtitleButton(true) - } - - @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) -@@ -27,7 +28,8 @@ internal fun PlayerView.setTimeBarInteractive(interactive: Boolean) { - - @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) - internal fun PlayerView.setFullscreenButtonVisibility(visible: Boolean) { -- val fullscreenButton = findViewById(androidx.media3.ui.R.id.exo_fullscreen) -+ val fullscreenButton = -+ findViewById(androidx.media3.ui.R.id.exo_fullscreen) - fullscreenButton?.visibility = if (visible) { - android.view.View.VISIBLE - } else { diff --git a/patches/expo-video+1.2.4.patch b/patches/expo-video+1.2.4.patch new file mode 100644 index 0000000000..918c8a8d25 --- /dev/null +++ b/patches/expo-video+1.2.4.patch @@ -0,0 +1,135 @@ +diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt +index 9905e13..47342ff 100644 +--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt ++++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt +@@ -11,6 +11,7 @@ internal fun PlayerView.applyRequiresLinearPlayback(requireLinearPlayback: Boole + setShowPreviousButton(!requireLinearPlayback) + setShowNextButton(!requireLinearPlayback) + setTimeBarInteractive(requireLinearPlayback) ++ setShowSubtitleButton(true) + } + + @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) +@@ -27,7 +28,8 @@ internal fun PlayerView.setTimeBarInteractive(interactive: Boolean) { + + @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) + internal fun PlayerView.setFullscreenButtonVisibility(visible: Boolean) { +- val fullscreenButton = findViewById(androidx.media3.ui.R.id.exo_fullscreen) ++ val fullscreenButton = ++ findViewById(androidx.media3.ui.R.id.exo_fullscreen) + fullscreenButton?.visibility = if (visible) { + android.view.View.VISIBLE + } else { +diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt +index ec3da2a..5a1397a 100644 +--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt ++++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt +@@ -43,7 +43,9 @@ class VideoModule : Module() { + View(VideoView::class) { + Events( + "onPictureInPictureStart", +- "onPictureInPictureStop" ++ "onPictureInPictureStop", ++ "onEnterFullscreen", ++ "onExitFullscreen" + ) + + Prop("player") { view: VideoView, player: VideoPlayer -> +diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoView.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoView.kt +index a951d80..3932535 100644 +--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoView.kt ++++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoView.kt +@@ -36,6 +36,8 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap + val playerView: PlayerView = PlayerView(context.applicationContext) + val onPictureInPictureStart by EventDispatcher() + val onPictureInPictureStop by EventDispatcher() ++ val onEnterFullscreen by EventDispatcher() ++ val onExitFullscreen by EventDispatcher() + + var willEnterPiP: Boolean = false + var isInFullscreen: Boolean = false +@@ -154,6 +156,7 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap + @Suppress("DEPRECATION") + currentActivity.overridePendingTransition(0, 0) + } ++ onEnterFullscreen(mapOf()) + isInFullscreen = true + } + +@@ -162,6 +165,7 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap + val fullScreenButton: ImageButton = playerView.findViewById(androidx.media3.ui.R.id.exo_fullscreen) + fullScreenButton.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_enter) + videoPlayer?.changePlayerView(playerView) ++ this.onExitFullscreen(mapOf()) + isInFullscreen = false + } + +diff --git a/node_modules/expo-video/build/VideoView.types.d.ts b/node_modules/expo-video/build/VideoView.types.d.ts +index cb9ca6d..60e9f4e 100644 +--- a/node_modules/expo-video/build/VideoView.types.d.ts ++++ b/node_modules/expo-video/build/VideoView.types.d.ts +@@ -89,5 +89,8 @@ export interface VideoViewProps extends ViewProps { + * @platform ios 16.0+ + */ + allowsVideoFrameAnalysis?: boolean; ++ ++ onEnterFullscreen?: () => void; ++ onExitFullscreen?: () => void; + } + //# sourceMappingURL=VideoView.types.d.ts.map +diff --git a/node_modules/expo-video/ios/VideoModule.swift b/node_modules/expo-video/ios/VideoModule.swift +index c537a12..e4a918f 100644 +--- a/node_modules/expo-video/ios/VideoModule.swift ++++ b/node_modules/expo-video/ios/VideoModule.swift +@@ -16,7 +16,9 @@ public final class VideoModule: Module { + View(VideoView.self) { + Events( + "onPictureInPictureStart", +- "onPictureInPictureStop" ++ "onPictureInPictureStop", ++ "onEnterFullscreen", ++ "onExitFullscreen" + ) + + Prop("player") { (view, player: VideoPlayer?) in +diff --git a/node_modules/expo-video/ios/VideoView.swift b/node_modules/expo-video/ios/VideoView.swift +index f4579e4..10c5908 100644 +--- a/node_modules/expo-video/ios/VideoView.swift ++++ b/node_modules/expo-video/ios/VideoView.swift +@@ -41,6 +41,8 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate { + + let onPictureInPictureStart = EventDispatcher() + let onPictureInPictureStop = EventDispatcher() ++ let onEnterFullscreen = EventDispatcher() ++ let onExitFullscreen = EventDispatcher() + + public override var bounds: CGRect { + didSet { +@@ -163,6 +165,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate { + _ playerViewController: AVPlayerViewController, + willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator + ) { ++ onEnterFullscreen() + isFullscreen = true + } + +@@ -179,6 +182,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate { + if wasPlaying { + self.player?.pointer.play() + } ++ self.onExitFullscreen() + self.isFullscreen = false + } + } +diff --git a/node_modules/expo-video/src/VideoView.types.ts b/node_modules/expo-video/src/VideoView.types.ts +index 29fe5db..e1fbf59 100644 +--- a/node_modules/expo-video/src/VideoView.types.ts ++++ b/node_modules/expo-video/src/VideoView.types.ts +@@ -100,4 +100,7 @@ export interface VideoViewProps extends ViewProps { + * @platform ios 16.0+ + */ + allowsVideoFrameAnalysis?: boolean; ++ ++ onEnterFullscreen?: () => void; ++ onExitFullscreen?: () => void; + } diff --git a/patches/expo-video+1.2.4.patch.md b/patches/expo-video+1.2.4.patch.md new file mode 100644 index 0000000000..689cf9a926 --- /dev/null +++ b/patches/expo-video+1.2.4.patch.md @@ -0,0 +1,6 @@ +## uwu woad beawing, do not wemove + +## `expo-video` Patch + +This patch adds two props to `VideoView`: `onEnterFullscreen` and `onExitFullscreen` which do exactly what they say on +the tin. From dd0d50a6f0f69d8b58f7dd26303b6b89528d2d04 Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 9 Aug 2024 03:25:54 -0700 Subject: [PATCH 05/12] [Video] Prevent pausing of background audio with `expo-video` on iOS (#4908) * audio mixing pref * lint * patch expo video to add enter/exit fullscreen events * rm logs * fix audio problems * toggle mute when enter/exiting fullscreen --------- Co-authored-by: Samuel Newman <10959775+mozzius@users.noreply.github.com> --- src/App.native.tsx | 2 ++ .../VideoEmbedInner/VideoEmbedInnerNative.tsx | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/App.native.tsx b/src/App.native.tsx index d2c20fc8e7..29a6a738c5 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -61,6 +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' SplashScreen.preventAutoHideAsync() @@ -157,6 +158,7 @@ function App() { const [isReady, setReady] = useState(false) React.useEffect(() => { + 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 26f6111bc7..5768662606 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -12,6 +12,7 @@ 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' export function VideoEmbedInnerNative() { const player = useVideoPlayer() @@ -37,11 +38,18 @@ export function VideoEmbedInnerNative() { player={player} style={a.flex_1} nativeControls={true} + onEnterFullscreen={() => { + PlatformInfo.setAudioMixWithOthers(false) + player.muted = false + }} + onExitFullscreen={() => { + PlatformInfo.setAudioMixWithOthers(true) + player.muted = true + }} /> { - player.muted = false ref.current?.enterFullscreen() }} /> @@ -89,7 +97,11 @@ function Controls({ }, [player]) const toggleSound = useCallback(() => { - player.muted = !player.muted + const newValue = !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 }, [player]) return ( From 5bfe5aa503d73e687ccc048efc5138edea97bea3 Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 9 Aug 2024 14:35:26 -0700 Subject: [PATCH 06/12] [Video] More tweaks to `AVAudioSession` options (#4910) --- modules/expo-bluesky-swiss-army/index.ts | 3 ++- .../PlatformInfo/ExpoPlatformInfoModule.swift | 16 ++++++++++++-- .../src/PlatformInfo/index.native.ts | 7 +++++++ .../src/PlatformInfo/index.ts | 14 +++++++++++++ .../src/PlatformInfo/index.web.ts | 5 +++++ .../src/PlatformInfo/types.ts | 15 +++++++++++++ src/App.native.tsx | 3 ++- .../VideoEmbedInner/VideoEmbedInnerNative.tsx | 21 +++++++++++++------ 8 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 modules/expo-bluesky-swiss-army/src/PlatformInfo/types.ts diff --git a/modules/expo-bluesky-swiss-army/index.ts b/modules/expo-bluesky-swiss-army/index.ts index ebd67913e0..2cf4f36c52 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 ff1148a0b5..471f1438b0 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 949bd78a28..ba9dddf82a 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 2665748b0a..5659339fba 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 917c97396f..cb64d00cee 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 0000000000..374f343183 --- /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 29a6a738c5..71b53e7a38 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 5768662606..83bb2f4238 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" /> Date: Fri, 9 Aug 2024 23:32:28 +0100 Subject: [PATCH 07/12] animate controls + fade out time after a while (#4913) Co-authored-by: Samuel Newman <10959775+mozzius@users.noreply.github.com> --- .../VideoEmbedInner/VideoEmbedInnerNative.tsx | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx index 83bb2f4238..ae3a4a2297 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -1,6 +1,6 @@ import React, {useCallback, useEffect, useRef, useState} from 'react' import {Pressable, View} from 'react-native' -import Animated, {FadeIn} from 'react-native-reanimated' +import Animated, {FadeInDown, FadeOutDown} from 'react-native-reanimated' import {VideoPlayer, VideoView} from 'expo-video' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -39,7 +39,7 @@ export function VideoEmbedInnerNative() { { PlatformInfo.setAudioCategory(AudioCategory.Playback) @@ -86,6 +86,7 @@ function Controls({ // duration gets reset to 0 on loop if (player.duration) setDuration(Math.floor(player.duration)) setCurrentTime(Math.floor(player.currentTime)) + // how often should we update the time? // 1000 gets out of sync with the video time }, 250) @@ -113,11 +114,18 @@ function Controls({ player.muted = muted }, [player]) + // show countdown when: + // 1. timeRemaining is a number - was seeing NaNs + // 2. duration is greater than 0 - means metadata has loaded + // 3. we're less than 5 second into the video + const showTime = !isNaN(timeRemaining) && duration > 0 && currentTime <= 5 + return ( - {!isNaN(timeRemaining) && ( + {showTime && ( - - - {isMuted ? ( - - ) : ( - - )} + {duration > 0 && ( + + + {isMuted ? ( + + ) : ( + + )} + - + )} ) } From ab0da7c892ce7532840417b89a42302ab9db7018 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 9 Aug 2024 23:35:21 +0100 Subject: [PATCH 08/12] [Videos] handle app backgrounding (#4912) * play when returning from background * play when unfullscreening * play when entering fullscreen, just to be sure * state -> ref --------- Co-authored-by: Samuel Newman <10959775+mozzius@users.noreply.github.com> --- .../VideoEmbedInner/VideoEmbedInnerNative.tsx | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx index ae3a4a2297..33148da01a 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -7,6 +7,7 @@ import {useLingui} from '@lingui/react' import {useIsFocused} from '@react-navigation/native' import {HITSLOP_30} from '#/lib/constants' +import {useAppState} from '#/lib/hooks/useAppState' import {useVideoPlayer} from '#/view/com/util/post-embeds/VideoPlayerContext' import {android, atoms as a, useTheme} from '#/alf' import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' @@ -21,6 +22,18 @@ export function VideoEmbedInnerNative() { const player = useVideoPlayer() const ref = useRef(null) const isScreenFocused = useIsFocused() + const isAppFocused = useAppState() + const prevFocusedRef = useRef(isAppFocused) + + // resume video when coming back from background + useEffect(() => { + if (isAppFocused !== prevFocusedRef.current) { + prevFocusedRef.current = isAppFocused + if (isAppFocused === 'active') { + player.play() + } + } + }, [isAppFocused, player]) // pause the video when the screen is not focused useEffect(() => { @@ -34,6 +47,10 @@ export function VideoEmbedInnerNative() { } }, [isScreenFocused, player]) + const enterFullscreen = useCallback(() => { + ref.current?.enterFullscreen() + }, []) + return ( - { - ref.current?.enterFullscreen() - }} - /> + ) } @@ -102,6 +115,22 @@ function Controls({ } }, [player]) + const onPressFullscreen = useCallback(() => { + switch (player.status) { + case 'idle': + case 'loading': + case 'readyToPlay': { + if (!player.playing) player.play() + enterFullscreen() + break + } + case 'error': { + player.replay() + break + } + } + }, [player, enterFullscreen]) + 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 @@ -150,7 +179,7 @@ function Controls({ )} Date: Sat, 10 Aug 2024 00:49:11 +0100 Subject: [PATCH 09/12] [Videos] Add error boundary to native (#4914) * move error fallback to own component * use error boundary on native --------- Co-authored-by: Samuel Newman <10959775+mozzius@users.noreply.github.com> --- src/view/com/util/post-embeds/VideoEmbed.tsx | 71 +++++++++++++------ .../com/util/post-embeds/VideoEmbed.web.tsx | 48 ++----------- .../VideoEmbedInner/VideoFallback.tsx | 61 ++++++++++++++++ 3 files changed, 116 insertions(+), 64 deletions(-) create mode 100644 src/view/com/util/post-embeds/VideoEmbedInner/VideoFallback.tsx diff --git a/src/view/com/util/post-embeds/VideoEmbed.tsx b/src/view/com/util/post-embeds/VideoEmbed.tsx index 887efac1ab..4e2909f40b 100644 --- a/src/view/com/util/post-embeds/VideoEmbed.tsx +++ b/src/view/com/util/post-embeds/VideoEmbed.tsx @@ -1,6 +1,6 @@ -import React from 'react' +import React, {useCallback, useState} from 'react' import {View} from 'react-native' -import {msg} from '@lingui/macro' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {VideoEmbedInnerNative} from 'view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative' @@ -8,13 +8,23 @@ import {atoms as a, useTheme} from '#/alf' import {Button, ButtonIcon} from '#/components/Button' import {Play_Filled_Corner2_Rounded as PlayIcon} from '#/components/icons/Play' import {VisibilityView} from '../../../../../modules/expo-bluesky-swiss-army' +import {ErrorBoundary} from '../ErrorBoundary' import {useActiveVideoView} from './ActiveVideoContext' +import * as VideoFallback from './VideoEmbedInner/VideoFallback' export function VideoEmbed({source}: {source: string}) { const t = useTheme() const {active, setActive} = useActiveVideoView({source}) const {_} = useLingui() + const [key, setKey] = useState(0) + const renderError = useCallback( + (error: unknown) => ( + setKey(key + 1)} /> + ), + [key], + ) + return ( - { - if (isActive) { - setActive() - } - }}> - {active ? ( - - ) : ( - - )} - + + { + if (isActive) { + setActive() + } + }}> + {active ? ( + + ) : ( + + )} + + ) } + +function VideoError({retry}: {error: unknown; retry: () => void}) { + return ( + + + + An error occurred while loading the video. Please try again later. + + + + + ) +} diff --git a/src/view/com/util/post-embeds/VideoEmbed.web.tsx b/src/view/com/util/post-embeds/VideoEmbed.web.tsx index 70d887283e..5803b836df 100644 --- a/src/view/com/util/post-embeds/VideoEmbed.web.tsx +++ b/src/view/com/util/post-embeds/VideoEmbed.web.tsx @@ -1,17 +1,15 @@ import React, {useCallback, useEffect, useRef, useState} from 'react' import {View} from 'react-native' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' +import {Trans} from '@lingui/macro' import { HLSUnsupportedError, VideoEmbedInnerWeb, } from 'view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb' import {atoms as a, useTheme} from '#/alf' -import {Button, ButtonText} from '#/components/Button' -import {Text} from '#/components/Typography' import {ErrorBoundary} from '../ErrorBoundary' import {useActiveVideoView} from './ActiveVideoContext' +import * as VideoFallback from './VideoEmbedInner/VideoFallback' export function VideoEmbed({source}: {source: string}) { const t = useTheme() @@ -138,32 +136,11 @@ function ViewportObserver({ } function VideoError({error, retry}: {error: unknown; retry: () => void}) { - const t = useTheme() - const {_} = useLingui() - const isHLS = error instanceof HLSUnsupportedError return ( - - + + {isHLS ? ( Your browser does not support the video format. Please try a @@ -174,19 +151,8 @@ function VideoError({error, retry}: {error: unknown; retry: () => void}) { An error occurred while loading the video. Please try again later. )} - - {!isHLS && ( - - )} - + + {!isHLS && } + ) } diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoFallback.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoFallback.tsx new file mode 100644 index 0000000000..1b46163cce --- /dev/null +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoFallback.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import {Text as TypoText} from '#/components/Typography' + +export function Container({children}: {children: React.ReactNode}) { + const t = useTheme() + return ( + + {children} + + ) +} + +export function Text({children}: {children: React.ReactNode}) { + const t = useTheme() + return ( + + {children} + + ) +} + +export function RetryButton({onPress}: {onPress: () => void}) { + const {_} = useLingui() + + return ( + + ) +} From 65d6e561d429d6759d1eef674a964d1109a1afeb Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 9 Aug 2024 16:52:23 -0700 Subject: [PATCH 10/12] [Video] Resume background audio whenever muting video audio (#4915) --- .../PlatformInfo/ExpoPlatformInfoModule.swift | 21 +++++++++++++------ .../src/PlatformInfo/index.native.ts | 4 ++-- .../src/PlatformInfo/index.ts | 8 ++++--- .../src/PlatformInfo/index.web.ts | 4 ++-- src/App.native.tsx | 2 +- .../VideoEmbedInner/VideoEmbedInnerNative.tsx | 6 +++--- 6 files changed, 28 insertions(+), 17 deletions(-) diff --git a/modules/expo-bluesky-swiss-army/ios/PlatformInfo/ExpoPlatformInfoModule.swift b/modules/expo-bluesky-swiss-army/ios/PlatformInfo/ExpoPlatformInfoModule.swift index 471f1438b0..7fd60e5fa2 100644 --- a/modules/expo-bluesky-swiss-army/ios/PlatformInfo/ExpoPlatformInfoModule.swift +++ b/modules/expo-bluesky-swiss-army/ios/PlatformInfo/ExpoPlatformInfoModule.swift @@ -13,20 +13,29 @@ public class ExpoPlatformInfoModule: Module { try? AVAudioSession.sharedInstance().setCategory(audioCategory) } - Function("setAudioMixWithOthers") { (mixWithOthers: Bool) in - var options: AVAudioSession.CategoryOptions + Function("setAudioActive") { (active: Bool) in + var categoryOptions: AVAudioSession.CategoryOptions let currentCategory = AVAudioSession.sharedInstance().category - if mixWithOthers { - options = [.mixWithOthers] + + if active { + categoryOptions = [.mixWithOthers] + try? AVAudioSession.sharedInstance().setActive(true) } else { - options = [.duckOthers] + categoryOptions = [.duckOthers] + try? AVAudioSession + .sharedInstance() + .setActive( + false, + options: [.notifyOthersOnDeactivation] + ) } + try? AVAudioSession .sharedInstance() .setCategory( currentCategory, mode: .default, - options: options + options: categoryOptions ) } } 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 ba9dddf82a..b515206d9f 100644 --- a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.native.ts +++ b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.native.ts @@ -9,9 +9,9 @@ export function getIsReducedMotionEnabled(): boolean { return NativeModule.getIsReducedMotionEnabled() } -export function setAudioMixWithOthers(mixWithOthers: boolean): void { +export function setAudioActive(active: boolean): void { if (Platform.OS !== 'ios') return - NativeModule.setAudioMixWithOthers(mixWithOthers) + NativeModule.setAudioActive(active) } export function setAudioCategory(audioCategory: AudioCategory): void { diff --git a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.ts b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.ts index 5659339fba..81f8c45f4d 100644 --- a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.ts +++ b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.ts @@ -6,11 +6,13 @@ export function getIsReducedMotionEnabled(): boolean { } /** - * Set whether the app's audio should mix with other apps' audio. + * Set whether the app's audio should mix with other apps' audio. Will also resume background music playback when `false` + * if it was previously playing. * @param mixWithOthers + * @see https://developer.apple.com/documentation/avfaudio/avaudiosession/setactiveoptions/1616603-notifyothersondeactivation */ -export function setAudioMixWithOthers(mixWithOthers: boolean): void { - throw new NotImplementedError({mixWithOthers}) +export function setAudioActive(active: boolean): void { + throw new NotImplementedError({active}) } /** 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 cb64d00cee..61412753c9 100644 --- a/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.web.ts +++ b/modules/expo-bluesky-swiss-army/src/PlatformInfo/index.web.ts @@ -8,8 +8,8 @@ export function getIsReducedMotionEnabled(): boolean { return window.matchMedia('(prefers-reduced-motion: reduce)').matches } -export function setAudioMixWithOthers(mixWithOthers: boolean): void { - throw new NotImplementedError({mixWithOthers}) +export function setAudioActive(active: boolean): void { + throw new NotImplementedError({active}) } export function setAudioCategory(audioCategory: AudioCategory): void { diff --git a/src/App.native.tsx b/src/App.native.tsx index 71b53e7a38..8e7c53b93b 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -159,7 +159,7 @@ function App() { React.useEffect(() => { PlatformInfo.setAudioCategory(AudioCategory.Ambient) - PlatformInfo.setAudioMixWithOthers(true) + PlatformInfo.setAudioActive(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 33148da01a..0b48edf793 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -60,12 +60,12 @@ export function VideoEmbedInnerNative() { nativeControls={true} onEnterFullscreen={() => { PlatformInfo.setAudioCategory(AudioCategory.Playback) - PlatformInfo.setAudioMixWithOthers(false) + PlatformInfo.setAudioActive(false) player.muted = false }} onExitFullscreen={() => { PlatformInfo.setAudioCategory(AudioCategory.Ambient) - PlatformInfo.setAudioMixWithOthers(true) + PlatformInfo.setAudioActive(true) player.muted = true if (!player.playing) player.play() }} @@ -139,7 +139,7 @@ function Controls({ const category = muted ? AudioCategory.Ambient : AudioCategory.Playback PlatformInfo.setAudioCategory(category) - PlatformInfo.setAudioMixWithOthers(mix) + PlatformInfo.setAudioActive(mix) player.muted = muted }, [player]) From 836754213827ebdb3c4af2a115a70ab4364e1e94 Mon Sep 17 00:00:00 2001 From: Shubh Porwal <83606943+shubh73@users.noreply.github.com> Date: Mon, 12 Aug 2024 01:10:43 +0530 Subject: [PATCH 11/12] Fix `occurred` typo (#4919) Co-authored-by: Hailey --- src/components/dialogs/GifSelect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/dialogs/GifSelect.tsx b/src/components/dialogs/GifSelect.tsx index a64edcd6f0..51cfa10fb1 100644 --- a/src/components/dialogs/GifSelect.tsx +++ b/src/components/dialogs/GifSelect.tsx @@ -249,7 +249,7 @@ function DialogError({details}: {details?: string}) { const control = Dialog.useDialogContext() return ( - + Date: Sun, 11 Aug 2024 20:41:33 +0100 Subject: [PATCH 12/12] Mark string for localization (#4920) --- src/view/com/composer/videos/VideoTranscodeProgress.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/view/com/composer/videos/VideoTranscodeProgress.tsx b/src/view/com/composer/videos/VideoTranscodeProgress.tsx index db58448a30..a44b633cd5 100644 --- a/src/view/com/composer/videos/VideoTranscodeProgress.tsx +++ b/src/view/com/composer/videos/VideoTranscodeProgress.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native' // @ts-expect-error no type definition import ProgressPie from 'react-native-progress/Pie' import {ImagePickerAsset} from 'expo-image-picker' +import {Trans} from '@lingui/macro' import {atoms as a, useTheme} from '#/alf' import {Text} from '#/components/Typography' @@ -46,7 +47,9 @@ export function VideoTranscodeProgress({ color={t.atoms.text.color} progress={progress} /> - Compressing... + + Compressing... + )