[Video] Add `timeRemainingChange` event to `player` in `expo-video` (#5013)

zio/stable
Hailey 2024-08-29 08:28:41 -07:00 committed by GitHub
parent d92731b1eb
commit d52d29621e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 296 additions and 59 deletions

View File

@ -1,3 +1,27 @@
diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerEvent.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerEvent.kt
index 473f964..f37aff9 100644
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerEvent.kt
+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerEvent.kt
@@ -41,6 +41,11 @@ sealed class PlayerEvent {
override val name = "playToEnd"
}
+ data class PlayerTimeRemainingChanged(val timeRemaining: Double): PlayerEvent() {
+ override val name = "timeRemainingChange"
+ override val arguments = arrayOf(timeRemaining)
+ }
+
fun emit(player: VideoPlayer, listeners: List<VideoPlayerListener>) {
when (this) {
is StatusChanged -> listeners.forEach { it.onStatusChanged(player, status, oldStatus, error) }
@@ -49,6 +54,7 @@ sealed class PlayerEvent {
is SourceChanged -> listeners.forEach { it.onSourceChanged(player, source, oldSource) }
is PlaybackRateChanged -> listeners.forEach { it.onPlaybackRateChanged(player, rate, oldRate) }
is PlayedToEnd -> listeners.forEach { it.onPlayedToEnd(player) }
+ is PlayerTimeRemainingChanged -> listeners.forEach { it.onPlayerTimeRemainingChanged(player, timeRemaining) }
}
}
}
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 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 index 9905e13..47342ff 100644
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt --- a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt
@ -8,10 +32,10 @@ index 9905e13..47342ff 100644
setTimeBarInteractive(requireLinearPlayback) setTimeBarInteractive(requireLinearPlayback)
+ setShowSubtitleButton(true) + setShowSubtitleButton(true)
} }
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
@@ -27,7 +28,8 @@ internal fun PlayerView.setTimeBarInteractive(interactive: Boolean) { @@ -27,7 +28,8 @@ internal fun PlayerView.setTimeBarInteractive(interactive: Boolean) {
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
internal fun PlayerView.setFullscreenButtonVisibility(visible: Boolean) { internal fun PlayerView.setFullscreenButtonVisibility(visible: Boolean) {
- val fullscreenButton = findViewById<android.widget.ImageButton>(androidx.media3.ui.R.id.exo_fullscreen) - val fullscreenButton = findViewById<android.widget.ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
@ -20,6 +44,42 @@ index 9905e13..47342ff 100644
fullscreenButton?.visibility = if (visible) { fullscreenButton?.visibility = if (visible) {
android.view.View.VISIBLE android.view.View.VISIBLE
} else { } else {
diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/ProgressTracker.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/ProgressTracker.kt
new file mode 100644
index 0000000..0249e23
--- /dev/null
+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/ProgressTracker.kt
@@ -0,0 +1,29 @@
+import android.os.Handler
+import android.os.Looper
+import androidx.annotation.OptIn
+import androidx.media3.common.util.UnstableApi
+import expo.modules.video.PlayerEvent
+import expo.modules.video.VideoPlayer
+import kotlin.math.floor
+
+@OptIn(UnstableApi::class)
+class ProgressTracker(private val videoPlayer: VideoPlayer) : Runnable {
+ private val handler: Handler = Handler(Looper.getMainLooper())
+ private val player = videoPlayer.player
+
+ init {
+ handler.post(this)
+ }
+
+ override fun run() {
+ val currentPosition = player.currentPosition
+ val duration = player.duration
+ val timeRemaining = floor(((duration - currentPosition) / 1000).toDouble())
+ videoPlayer.sendEvent(PlayerEvent.PlayerTimeRemainingChanged(timeRemaining))
+ handler.postDelayed(this, 1000 /* ms */)
+ }
+
+ fun remove() {
+ handler.removeCallbacks(this)
+ }
+}
\ No newline at end of file
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 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 index ec3da2a..5a1397a 100644
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt --- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt
@ -33,8 +93,76 @@ index ec3da2a..5a1397a 100644
+ "onEnterFullscreen", + "onEnterFullscreen",
+ "onExitFullscreen" + "onExitFullscreen"
) )
Prop("player") { view: VideoView, player: VideoPlayer -> Prop("player") { view: VideoView, player: VideoPlayer ->
diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt
index 58f00af..5ad8237 100644
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt
+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt
@@ -1,5 +1,6 @@
package expo.modules.video
+import ProgressTracker
import android.content.Context
import android.view.SurfaceView
import androidx.media3.common.MediaItem
@@ -35,11 +36,13 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
.Builder(context, renderersFactory)
.setLooper(context.mainLooper)
.build()
+ var progressTracker: ProgressTracker? = null
val serviceConnection = PlaybackServiceConnection(WeakReference(player))
var playing by IgnoreSameSet(false) { new, old ->
sendEvent(PlayerEvent.IsPlayingChanged(new, old))
+ addOrRemoveProgressTracker()
}
var uncommittedSource: VideoSource? = source
@@ -141,6 +144,9 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
}
override fun close() {
+ this.progressTracker?.remove()
+ this.progressTracker = null
+
appContext?.reactContext?.unbindService(serviceConnection)
serviceConnection.playbackServiceBinder?.service?.unregisterPlayer(player)
VideoManager.unregisterVideoPlayer(this@VideoPlayer)
@@ -228,7 +234,7 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
listeners.removeAll { it.get() == videoPlayerListener }
}
- private fun sendEvent(event: PlayerEvent) {
+ fun sendEvent(event: PlayerEvent) {
// Emits to the native listeners
event.emit(this, listeners.mapNotNull { it.get() })
// Emits to the JS side
@@ -240,4 +246,13 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
sendEvent(eventName, *args)
}
}
+
+ private fun addOrRemoveProgressTracker() {
+ this.progressTracker?.remove()
+ if (this.playing) {
+ this.progressTracker = ProgressTracker(this)
+ } else {
+ this.progressTracker = null
+ }
+ }
}
diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayerListener.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayerListener.kt
index f654254..dcfe3f0 100644
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayerListener.kt
+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayerListener.kt
@@ -15,4 +15,5 @@ interface VideoPlayerListener {
fun onSourceChanged(player: VideoPlayer, source: VideoSource?, oldSource: VideoSource?) {}
fun onPlaybackRateChanged(player: VideoPlayer, rate: Float, oldRate: Float?) {}
fun onPlayedToEnd(player: VideoPlayer) {}
+ fun onPlayerTimeRemainingChanged(player: VideoPlayer, timeRemaining: Double) {}
}
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 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 index a951d80..3932535 100644
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoView.kt --- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoView.kt
@ -45,7 +173,7 @@ index a951d80..3932535 100644
val onPictureInPictureStop by EventDispatcher<Unit>() val onPictureInPictureStop by EventDispatcher<Unit>()
+ val onEnterFullscreen by EventDispatcher() + val onEnterFullscreen by EventDispatcher()
+ val onExitFullscreen by EventDispatcher() + val onExitFullscreen by EventDispatcher()
var willEnterPiP: Boolean = false var willEnterPiP: Boolean = false
var isInFullscreen: Boolean = false var isInFullscreen: Boolean = false
@@ -154,6 +156,7 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap @@ -154,6 +156,7 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap
@ -55,7 +183,7 @@ index a951d80..3932535 100644
+ onEnterFullscreen(mapOf()) + onEnterFullscreen(mapOf())
isInFullscreen = true isInFullscreen = true
} }
@@ -162,6 +165,7 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap @@ -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) val fullScreenButton: ImageButton = playerView.findViewById(androidx.media3.ui.R.id.exo_fullscreen)
fullScreenButton.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_enter) fullScreenButton.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_enter)
@ -63,9 +191,22 @@ index a951d80..3932535 100644
+ this.onExitFullscreen(mapOf()) + this.onExitFullscreen(mapOf())
isInFullscreen = false isInFullscreen = false
} }
diff --git a/node_modules/expo-video/build/VideoPlayer.types.d.ts b/node_modules/expo-video/build/VideoPlayer.types.d.ts
index a09fcfe..65fe29a 100644
--- a/node_modules/expo-video/build/VideoPlayer.types.d.ts
+++ b/node_modules/expo-video/build/VideoPlayer.types.d.ts
@@ -128,6 +128,8 @@ export type VideoPlayerEvents = {
* Handler for an event emitted when the current media source of the player changes.
*/
sourceChange(newSource: VideoSource, previousSource: VideoSource): void;
+
+ timeRemainingChange(timeRemaining: number): void;
};
/**
* Describes the current status of the player.
diff --git a/node_modules/expo-video/build/VideoView.types.d.ts b/node_modules/expo-video/build/VideoView.types.d.ts 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 index cb9ca6d..ed8bb7e 100644
--- a/node_modules/expo-video/build/VideoView.types.d.ts --- a/node_modules/expo-video/build/VideoView.types.d.ts
+++ b/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 { @@ -89,5 +89,8 @@ export interface VideoViewProps extends ViewProps {
@ -77,6 +218,7 @@ index cb9ca6d..60e9f4e 100644
+ onExitFullscreen?: () => void; + onExitFullscreen?: () => void;
} }
//# sourceMappingURL=VideoView.types.d.ts.map //# sourceMappingURL=VideoView.types.d.ts.map
\ No newline at end of file
diff --git a/node_modules/expo-video/ios/VideoModule.swift b/node_modules/expo-video/ios/VideoModule.swift diff --git a/node_modules/expo-video/ios/VideoModule.swift b/node_modules/expo-video/ios/VideoModule.swift
index c537a12..e4a918f 100644 index c537a12..e4a918f 100644
--- a/node_modules/expo-video/ios/VideoModule.swift --- a/node_modules/expo-video/ios/VideoModule.swift
@ -90,19 +232,111 @@ index c537a12..e4a918f 100644
+ "onEnterFullscreen", + "onEnterFullscreen",
+ "onExitFullscreen" + "onExitFullscreen"
) )
Prop("player") { (view, player: VideoPlayer?) in Prop("player") { (view, player: VideoPlayer?) in
diff --git a/node_modules/expo-video/ios/VideoPlayer.swift b/node_modules/expo-video/ios/VideoPlayer.swift
index 3315b88..f482390 100644
--- a/node_modules/expo-video/ios/VideoPlayer.swift
+++ b/node_modules/expo-video/ios/VideoPlayer.swift
@@ -185,6 +185,10 @@ internal final class VideoPlayer: SharedRef<AVPlayer>, Hashable, VideoPlayerObse
safeEmit(event: "sourceChange", arguments: newVideoPlayerItem?.videoSource, oldVideoPlayerItem?.videoSource)
}
+ func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) {
+ safeEmit(event: "timeRemainingChange", arguments: timeRemaining)
+ }
+
func safeEmit<each A: AnyArgument>(event: String, arguments: repeat each A) {
if self.appContext != nil {
self.emit(event: event, arguments: repeat each arguments)
diff --git a/node_modules/expo-video/ios/VideoPlayerObserver.swift b/node_modules/expo-video/ios/VideoPlayerObserver.swift
index d289e26..d0fdd30 100644
--- a/node_modules/expo-video/ios/VideoPlayerObserver.swift
+++ b/node_modules/expo-video/ios/VideoPlayerObserver.swift
@@ -21,6 +21,7 @@ protocol VideoPlayerObserverDelegate: AnyObject {
func onItemChanged(player: AVPlayer, oldVideoPlayerItem: VideoPlayerItem?, newVideoPlayerItem: VideoPlayerItem?)
func onIsMutedChanged(player: AVPlayer, oldIsMuted: Bool?, newIsMuted: Bool)
func onPlayerItemStatusChanged(player: AVPlayer, oldStatus: AVPlayerItem.Status?, newStatus: AVPlayerItem.Status)
+ func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double)
}
// Default implementations for the delegate
@@ -33,6 +34,7 @@ extension VideoPlayerObserverDelegate {
func onItemChanged(player: AVPlayer, oldVideoPlayerItem: VideoPlayerItem?, newVideoPlayerItem: VideoPlayerItem?) {}
func onIsMutedChanged(player: AVPlayer, oldIsMuted: Bool?, newIsMuted: Bool) {}
func onPlayerItemStatusChanged(player: AVPlayer, oldStatus: AVPlayerItem.Status?, newStatus: AVPlayerItem.Status) {}
+ func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) {}
}
// Wrapper used to store WeakReferences to the observer delegate
@@ -91,6 +93,7 @@ class VideoPlayerObserver {
private var playerVolumeObserver: NSKeyValueObservation?
private var playerCurrentItemObserver: NSKeyValueObservation?
private var playerIsMutedObserver: NSKeyValueObservation?
+ private var playerPeriodicTimeObserver: Any?
// Current player item observers
private var playbackBufferEmptyObserver: NSKeyValueObservation?
@@ -152,6 +155,9 @@ class VideoPlayerObserver {
playerVolumeObserver?.invalidate()
playerIsMutedObserver?.invalidate()
playerCurrentItemObserver?.invalidate()
+ if let playerPeriodicTimeObserver = self.playerPeriodicTimeObserver {
+ player?.removeTimeObserver(playerPeriodicTimeObserver)
+ }
}
private func initializeCurrentPlayerItemObservers(player: AVPlayer, playerItem: AVPlayerItem) {
@@ -270,6 +276,7 @@ class VideoPlayerObserver {
if isPlaying != (player.timeControlStatus == .playing) {
isPlaying = player.timeControlStatus == .playing
+ addOrRemovePeriodicTimeObserver()
}
}
@@ -310,4 +317,30 @@ class VideoPlayerObserver {
}
}
}
+
+ private func onPlayerTimeRemainingChanged(_ player: AVPlayer, _ timeRemaining: Double) {
+ delegates.forEach { delegate in
+ delegate.value?.onPlayerTimeRemainingChanged(player: player, timeRemaining: timeRemaining)
+ }
+ }
+
+ private func addOrRemovePeriodicTimeObserver() {
+ guard let player = self.player else {
+ return
+ }
+
+ if isPlaying {
+ // Add the time update listener
+ playerPeriodicTimeObserver = player.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1.0, preferredTimescale: Int32(NSEC_PER_SEC)), queue: nil) { event in
+ guard let duration = player.currentItem?.duration else {
+ return
+ }
+
+ let timeRemaining = (duration.seconds - event.seconds).rounded()
+ self.onPlayerTimeRemainingChanged(player, timeRemaining)
+ }
+ } else if let playerPeriodicTimeObserver = self.playerPeriodicTimeObserver {
+ player.removeTimeObserver(playerPeriodicTimeObserver)
+ }
+ }
}
diff --git a/node_modules/expo-video/ios/VideoView.swift b/node_modules/expo-video/ios/VideoView.swift diff --git a/node_modules/expo-video/ios/VideoView.swift b/node_modules/expo-video/ios/VideoView.swift
index f4579e4..10c5908 100644 index f4579e4..10c5908 100644
--- a/node_modules/expo-video/ios/VideoView.swift --- a/node_modules/expo-video/ios/VideoView.swift
+++ b/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 { @@ -41,6 +41,8 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
let onPictureInPictureStart = EventDispatcher() let onPictureInPictureStart = EventDispatcher()
let onPictureInPictureStop = EventDispatcher() let onPictureInPictureStop = EventDispatcher()
+ let onEnterFullscreen = EventDispatcher() + let onEnterFullscreen = EventDispatcher()
+ let onExitFullscreen = EventDispatcher() + let onExitFullscreen = EventDispatcher()
public override var bounds: CGRect { public override var bounds: CGRect {
didSet { didSet {
@@ -163,6 +165,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate { @@ -163,6 +165,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
@ -112,7 +346,7 @@ index f4579e4..10c5908 100644
+ onEnterFullscreen() + onEnterFullscreen()
isFullscreen = true isFullscreen = true
} }
@@ -179,6 +182,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate { @@ -179,6 +182,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
if wasPlaying { if wasPlaying {
self.player?.pointer.play() self.player?.pointer.play()
@ -121,6 +355,19 @@ index f4579e4..10c5908 100644
self.isFullscreen = false self.isFullscreen = false
} }
} }
diff --git a/node_modules/expo-video/src/VideoPlayer.types.ts b/node_modules/expo-video/src/VideoPlayer.types.ts
index aaf4b63..f438196 100644
--- a/node_modules/expo-video/src/VideoPlayer.types.ts
+++ b/node_modules/expo-video/src/VideoPlayer.types.ts
@@ -151,6 +151,8 @@ export type VideoPlayerEvents = {
* Handler for an event emitted when the current media source of the player changes.
*/
sourceChange(newSource: VideoSource, previousSource: VideoSource): void;
+
+ timeRemainingChange(timeRemaining: number): void;
};
/**
diff --git a/node_modules/expo-video/src/VideoView.types.ts b/node_modules/expo-video/src/VideoView.types.ts diff --git a/node_modules/expo-video/src/VideoView.types.ts b/node_modules/expo-video/src/VideoView.types.ts
index 29fe5db..e1fbf59 100644 index 29fe5db..e1fbf59 100644
--- a/node_modules/expo-video/src/VideoView.types.ts --- a/node_modules/expo-video/src/VideoView.types.ts

View File

@ -102,29 +102,22 @@ function VideoControls({
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const [isMuted, setIsMuted] = useState(player.muted) const [isMuted, setIsMuted] = useState(player.muted)
const [duration, setDuration] = useState(() => Math.floor(player.duration)) const [timeRemaining, setTimeRemaining] = React.useState(0)
const [currentTime, setCurrentTime] = useState(() =>
Math.floor(player.currentTime),
)
useEffect(() => { useEffect(() => {
const interval = setInterval(() => {
// 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)
// eslint-disable-next-line @typescript-eslint/no-shadow // eslint-disable-next-line @typescript-eslint/no-shadow
const sub = player.addListener('volumeChange', ({isMuted}) => { const volumeSub = player.addListener('volumeChange', ({isMuted}) => {
setIsMuted(isMuted) setIsMuted(isMuted)
}) })
const timeSub = player.addListener(
'timeRemainingChange',
secondsRemaining => {
setTimeRemaining(secondsRemaining)
},
)
return () => { return () => {
clearInterval(interval) volumeSub.remove()
sub.remove() timeSub.remove()
} }
}, [player]) }, [player])
@ -160,8 +153,7 @@ function VideoControls({
// 1. timeRemaining is a number - was seeing NaNs // 1. timeRemaining is a number - was seeing NaNs
// 2. duration is greater than 0 - means metadata has loaded // 2. duration is greater than 0 - means metadata has loaded
// 3. we're less than 5 second into the video // 3. we're less than 5 second into the video
const timeRemaining = duration - currentTime const showTime = !isNaN(timeRemaining)
const showTime = !isNaN(timeRemaining) && duration > 0 && currentTime <= 5
return ( return (
<View style={[a.absolute, a.inset_0]}> <View style={[a.absolute, a.inset_0]}>
@ -173,35 +165,33 @@ function VideoControls({
accessibilityHint={_(msg`Tap to enter full screen`)} accessibilityHint={_(msg`Tap to enter full screen`)}
accessibilityRole="button" accessibilityRole="button"
/> />
{duration > 0 && ( <Animated.View
<Animated.View entering={FadeInDown.duration(300)}
entering={FadeInDown.duration(300)} style={{
style={{ backgroundColor: 'rgba(0, 0, 0, 0.5)',
backgroundColor: 'rgba(0, 0, 0, 0.5)', borderRadius: 6,
borderRadius: 6, paddingHorizontal: 6,
paddingHorizontal: 6, paddingVertical: 3,
paddingVertical: 3, position: 'absolute',
position: 'absolute', bottom: 5,
bottom: 5, right: 5,
right: 5, minHeight: 20,
minHeight: 20, justifyContent: 'center',
justifyContent: 'center', }}>
}}> <Pressable
<Pressable onPress={toggleMuted}
onPress={toggleMuted} style={a.flex_1}
style={a.flex_1} accessibilityLabel={isMuted ? _(msg`Muted`) : _(msg`Unmuted`)}
accessibilityLabel={isMuted ? _(msg`Muted`) : _(msg`Unmuted`)} accessibilityHint={_(msg`Tap to toggle sound`)}
accessibilityHint={_(msg`Tap to toggle sound`)} accessibilityRole="button"
accessibilityRole="button" hitSlop={HITSLOP_30}>
hitSlop={HITSLOP_30}> {isMuted ? (
{isMuted ? ( <MuteIcon width={14} fill={t.palette.white} />
<MuteIcon width={14} fill={t.palette.white} /> ) : (
) : ( <UnmuteIcon width={14} fill={t.palette.white} />
<UnmuteIcon width={14} fill={t.palette.white} /> )}
)} </Pressable>
</Pressable> </Animated.View>
</Animated.View>
)}
</View> </View>
) )
} }