diff --git a/app.config.js b/app.config.js index cd8a4b03..25014ee8 100644 --- a/app.config.js +++ b/app.config.js @@ -191,7 +191,7 @@ module.exports = function (config) { 'expo-build-properties', { ios: { - deploymentTarget: '14.0', + deploymentTarget: '15.1', newArchEnabled: false, }, android: { diff --git a/package.json b/package.json index eaa03829..42794883 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.2.4", + "expo-video": "https://github.com/bluesky-social/expo/raw/main/packages/expo-video/expo-video-v1.2.4.tgz", "expo-web-browser": "~13.0.3", "fast-text-encoding": "^1.0.6", "history": "^5.3.0", diff --git a/patches/expo-video+1.2.4.patch b/patches/expo-video+1.2.4.patch deleted file mode 100644 index bc20fa0e..00000000 --- a/patches/expo-video+1.2.4.patch +++ /dev/null @@ -1,608 +0,0 @@ -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) { - 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 -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/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/VideoManager.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt -index 4b6c6d8..e20f51a 100644 ---- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt -+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt -@@ -1,5 +1,6 @@ - package expo.modules.video - -+import android.provider.MediaStore.Video - import androidx.annotation.OptIn - import androidx.media3.common.util.UnstableApi - import expo.modules.kotlin.AppContext -@@ -15,6 +16,8 @@ object VideoManager { - // Keeps track of all existing VideoPlayers, and whether they are attached to a VideoView - private var videoPlayersToVideoViews = mutableMapOf>() - -+ private var previouslyPlayingViews: MutableList? = null -+ - private lateinit var audioFocusManager: AudioFocusManager - - fun onModuleCreated(appContext: AppContext) { -@@ -69,16 +72,24 @@ object VideoManager { - return videoPlayersToVideoViews[videoPlayer]?.isNotEmpty() ?: false - } - -- fun onAppForegrounded() = Unit -+ fun onAppForegrounded() { -+ val previouslyPlayingViews = this.previouslyPlayingViews ?: return -+ for (videoView in previouslyPlayingViews) { -+ val player = videoView.videoPlayer?.player ?: continue -+ player.play() -+ } -+ this.previouslyPlayingViews = null -+ } - - fun onAppBackgrounded() { -+ val previouslyPlayingViews = mutableListOf() - for (videoView in videoViews.values) { -- if (videoView.videoPlayer?.staysActiveInBackground == false && -- !videoView.willEnterPiP && -- !videoView.isInFullscreen -- ) { -- videoView.videoPlayer?.player?.pause() -+ val player = videoView.videoPlayer?.player ?: continue -+ if (player.isPlaying) { -+ player.pause() -+ previouslyPlayingViews.add(videoView) - } - } -+ this.previouslyPlayingViews = previouslyPlayingViews - } - } -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/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 -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/VideoPlayer.types.d.ts b/node_modules/expo-video/build/VideoPlayer.types.d.ts -index a09fcfe..46cbae7 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. -@@ -136,7 +138,7 @@ export type VideoPlayerEvents = { - * - `readyToPlay`: The player has loaded enough data to start playing or to continue playback. - * - `error`: The player has encountered an error while loading or playing the video. - */ --export type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error'; -+export type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error' | 'waitingToPlayAtSpecifiedRate'; - export type VideoSource = string | { - /** - * The URI of the video. -diff --git a/node_modules/expo-video/build/VideoView.types.d.ts b/node_modules/expo-video/build/VideoView.types.d.ts -index cb9ca6d..ed8bb7e 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 -\ No newline at end of file -diff --git a/node_modules/expo-video/ios/Enums/PlayerStatus.swift b/node_modules/expo-video/ios/Enums/PlayerStatus.swift -index 6af69ca..189fbbe 100644 ---- a/node_modules/expo-video/ios/Enums/PlayerStatus.swift -+++ b/node_modules/expo-video/ios/Enums/PlayerStatus.swift -@@ -6,5 +6,8 @@ internal enum PlayerStatus: String, Enumerable { - case idle - case loading - case readyToPlay -+ case waitingToPlayAtSpecifiedRate -+ case unlikeToKeepUp -+ case playbackBufferEmpty - case error - } -diff --git a/node_modules/expo-video/ios/VideoManager.swift b/node_modules/expo-video/ios/VideoManager.swift -index 094a8b0..16e7081 100644 ---- a/node_modules/expo-video/ios/VideoManager.swift -+++ b/node_modules/expo-video/ios/VideoManager.swift -@@ -12,6 +12,7 @@ class VideoManager { - - private var videoViews = NSHashTable.weakObjects() - private var videoPlayers = NSHashTable.weakObjects() -+ private var previouslyPlayingPlayers: [VideoPlayer]? - - func register(videoPlayer: VideoPlayer) { - videoPlayers.add(videoPlayer) -@@ -33,63 +34,70 @@ class VideoManager { - for videoPlayer in videoPlayers.allObjects { - videoPlayer.setTracksEnabled(true) - } -+ -+ if let previouslyPlayingPlayers = self.previouslyPlayingPlayers { -+ previouslyPlayingPlayers.forEach { player in -+ player.pointer.play() -+ } -+ } - } - - func onAppBackgrounded() { -+ var previouslyPlayingPlayers: [VideoPlayer] = [] - for videoView in videoViews.allObjects { - guard let player = videoView.player else { - continue - } -- if player.staysActiveInBackground == true { -- player.setTracksEnabled(videoView.isInPictureInPicture) -- } else if !videoView.isInPictureInPicture { -+ if player.isPlaying { - player.pointer.pause() -+ previouslyPlayingPlayers.append(player) - } - } -+ self.previouslyPlayingPlayers = previouslyPlayingPlayers - } - - // MARK: - Audio Session Management - - internal func setAppropriateAudioSessionOrWarn() { -- let audioSession = AVAudioSession.sharedInstance() -- var audioSessionCategoryOptions: AVAudioSession.CategoryOptions = [] -- -- let isAnyPlayerPlaying = videoPlayers.allObjects.contains { player in -- player.isPlaying -- } -- let areAllPlayersMuted = videoPlayers.allObjects.allSatisfy { player in -- player.isMuted -- } -- let needsPiPSupport = videoViews.allObjects.contains { view in -- view.allowPictureInPicture -- } -- let anyPlayerShowsNotification = videoPlayers.allObjects.contains { player in -- player.showNowPlayingNotification -- } -- // The notification won't be shown if we allow the audio to mix with others -- let shouldAllowMixing = (!isAnyPlayerPlaying || areAllPlayersMuted) && !anyPlayerShowsNotification -- let isOutputtingAudio = !areAllPlayersMuted && isAnyPlayerPlaying -- let shouldUpdateToAllowMixing = !audioSession.categoryOptions.contains(.mixWithOthers) && shouldAllowMixing -- -- if shouldAllowMixing { -- audioSessionCategoryOptions.insert(.mixWithOthers) -- } -- -- if isOutputtingAudio || needsPiPSupport || shouldUpdateToAllowMixing || anyPlayerShowsNotification { -- do { -- try audioSession.setCategory(.playback, mode: .moviePlayback) -- } catch { -- log.warn("Failed to set audio session category. This might cause issues with audio playback and Picture in Picture. \(error.localizedDescription)") -- } -- } -- -- // Make sure audio session is active if any video is playing -- if isAnyPlayerPlaying { -- do { -- try audioSession.setActive(true) -- } catch { -- log.warn("Failed to activate the audio session. This might cause issues with audio playback. \(error.localizedDescription)") -- } -- } -+// let audioSession = AVAudioSession.sharedInstance() -+// var audioSessionCategoryOptions: AVAudioSession.CategoryOptions = [] -+// -+// let isAnyPlayerPlaying = videoPlayers.allObjects.contains { player in -+// player.isPlaying -+// } -+// let areAllPlayersMuted = videoPlayers.allObjects.allSatisfy { player in -+// player.isMuted -+// } -+// let needsPiPSupport = videoViews.allObjects.contains { view in -+// view.allowPictureInPicture -+// } -+// let anyPlayerShowsNotification = videoPlayers.allObjects.contains { player in -+// player.showNowPlayingNotification -+// } -+// // The notification won't be shown if we allow the audio to mix with others -+// let shouldAllowMixing = (!isAnyPlayerPlaying || areAllPlayersMuted) && !anyPlayerShowsNotification -+// let isOutputtingAudio = !areAllPlayersMuted && isAnyPlayerPlaying -+// let shouldUpdateToAllowMixing = !audioSession.categoryOptions.contains(.mixWithOthers) && shouldAllowMixing -+// -+// if shouldAllowMixing { -+// audioSessionCategoryOptions.insert(.mixWithOthers) -+// } -+// -+// if isOutputtingAudio || needsPiPSupport || shouldUpdateToAllowMixing || anyPlayerShowsNotification { -+// do { -+// try audioSession.setCategory(.playback, mode: .moviePlayback) -+// } catch { -+// log.warn("Failed to set audio session category. This might cause issues with audio playback and Picture in Picture. \(error.localizedDescription)") -+// } -+// } -+// -+// // Make sure audio session is active if any video is playing -+// if isAnyPlayerPlaying { -+// do { -+// try audioSession.setActive(true) -+// } catch { -+// log.warn("Failed to activate the audio session. This might cause issues with audio playback. \(error.localizedDescription)") -+// } -+// } - } - } -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/VideoPlayer.swift b/node_modules/expo-video/ios/VideoPlayer.swift -index 3315b88..733ab1f 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, Hashable, VideoPlayerObse - safeEmit(event: "sourceChange", arguments: newVideoPlayerItem?.videoSource, oldVideoPlayerItem?.videoSource) - } - -+ func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) { -+ safeEmit(event: "timeRemainingChange", arguments: timeRemaining) -+ } -+ - func safeEmit(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..7de8cbf 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) { -@@ -265,23 +271,24 @@ class VideoPlayerObserver { - if player.timeControlStatus != .waitingToPlayAtSpecifiedRate && player.status == .readyToPlay && currentItem?.isPlaybackBufferEmpty != true { - status = .readyToPlay - } else if player.timeControlStatus == .waitingToPlayAtSpecifiedRate { -- status = .loading -+ status = .waitingToPlayAtSpecifiedRate - } - - if isPlaying != (player.timeControlStatus == .playing) { - isPlaying = player.timeControlStatus == .playing -+ addPeriodicTimeObserverIfNeeded() - } - } - - private func onIsBufferEmptyChanged(_ playerItem: AVPlayerItem, _ change: NSKeyValueObservedChange) { - if playerItem.isPlaybackBufferEmpty { -- status = .loading -+ status = .playbackBufferEmpty - } - } - - private func onPlayerLikelyToKeepUpChanged(_ playerItem: AVPlayerItem, _ change: NSKeyValueObservedChange) { - if !playerItem.isPlaybackLikelyToKeepUp && playerItem.isPlaybackBufferEmpty { -- status = .loading -+ status = .unlikeToKeepUp - } else if playerItem.isPlaybackLikelyToKeepUp { - status = .readyToPlay - } -@@ -310,4 +317,28 @@ class VideoPlayerObserver { - } - } - } -+ -+ private func onPlayerTimeRemainingChanged(_ player: AVPlayer, _ timeRemaining: Double) { -+ delegates.forEach { delegate in -+ delegate.value?.onPlayerTimeRemainingChanged(player: player, timeRemaining: timeRemaining) -+ } -+ } -+ -+ private func addPeriodicTimeObserverIfNeeded() { -+ guard self.playerPeriodicTimeObserver == nil, 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) -+ } -+ } -+ } - } -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/VideoPlayer.types.ts b/node_modules/expo-video/src/VideoPlayer.types.ts -index aaf4b63..5ff6b7a 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; - }; - - /** -@@ -160,7 +162,7 @@ export type VideoPlayerEvents = { - * - `readyToPlay`: The player has loaded enough data to start playing or to continue playback. - * - `error`: The player has encountered an error while loading or playing the video. - */ --export type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error'; -+export type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error' | 'waitingToPlayAtSpecifiedRate'; - - export type VideoSource = - | string -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 deleted file mode 100644 index 7cd4d363..00000000 --- a/patches/expo-video+1.2.4.patch.md +++ /dev/null @@ -1,31 +0,0 @@ -## uwu woad beawing, do not wemove - -## `expo-video` Patch - -### `onEnterFullScreen`/`onExitFullScreen` - -Adds two props to `VideoView`: `onEnterFullscreen` and `onExitFullscreen` which do exactly what they say on -the tin. - -### Removing audio session management - -This patch also removes the audio session management that Expo does on its own, as we handle audio session management -ourselves. - -### Pausing/playing on background/foreground - -Instead of handling the pausing/playing of videos in React, we'll handle them here. There's some logic that we do not -need (around PIP mode) that we can remove, and just pause any playing players on background and then resume them on -foreground. - -### Additional `statusChange` Events - -`expo-video` uses the `loading` status for a variety of cases where the video is not actually "loading". We're making -those status events more specific here, so that we can determine if a video is truly loading or not. These statuses are: - -- `waitingToPlayAtSpecifiedRate` -- `unlikelyToKeepUp` -- `playbackBufferEmpty` - -It's unlikely we will ever need to pay attention to these statuses, so they are not being include in the TypeScript -types. diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx index 3fa15926..b747223b 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -56,13 +56,13 @@ export function VideoEmbedInnerNative({ contentFit="cover" nativeControls={isFullscreen} accessibilityIgnoresInvertColors - onEnterFullscreen={() => { + onFullscreenEnter={() => { PlatformInfo.setAudioCategory(AudioCategory.Playback) PlatformInfo.setAudioActive(true) player.muted = false setIsFullscreen(true) }} - onExitFullscreen={() => { + onFullscreenExit={() => { PlatformInfo.setAudioCategory(AudioCategory.Ambient) PlatformInfo.setAudioActive(false) player.muted = true diff --git a/yarn.lock b/yarn.lock index b084ca26..515fde94 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9917,12 +9917,7 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001520: - version "1.0.30001655" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz" - integrity sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg== - -caniuse-lite@^1.0.30001587: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001520, caniuse-lite@^1.0.30001587: version "1.0.30001655" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz" integrity sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg== @@ -12419,10 +12414,9 @@ expo-updates@~0.25.14: ignore "^5.3.1" resolve-from "^5.0.0" -expo-video@^1.2.4: +"expo-video@https://github.com/bluesky-social/expo/raw/main/packages/expo-video/expo-video-v1.2.4.tgz": version "1.2.4" - resolved "https://registry.yarnpkg.com/expo-video/-/expo-video-1.2.4.tgz#787342aded4295a1b6864f59227d178b93e1bb53" - integrity sha512-pBK9mt7vYAbuPQjCSQxHQ7xrNjbmRheJep7JIStEg57O183/JRfP2blKuXniiSt1HBdZYPdoQnGRa3jGMXB9pg== + resolved "https://github.com/bluesky-social/expo/raw/main/packages/expo-video/expo-video-v1.2.4.tgz#ebb6a672a385f9a059ca614fd148f7803d4267fc" expo-web-browser@~13.0.3: version "13.0.3"