expo-video 3.1.0-canary-20251216-3f01dbf → 3.1.0-canary-20251223-b83b31e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +1 -0
  4. package/android/src/main/java/expo/modules/video/VideoCache.kt +1 -0
  5. package/android/src/main/java/expo/modules/video/VideoModule.kt +7 -2
  6. package/android/src/main/java/expo/modules/video/VideoView.kt +51 -74
  7. package/android/src/main/java/expo/modules/video/delegates/IgnoreSameSet.kt +1 -1
  8. package/android/src/main/java/expo/modules/video/listeners/VideoManagerListener.kt +10 -0
  9. package/android/src/main/java/expo/modules/video/{player → listeners}/VideoPlayerListener.kt +3 -2
  10. package/android/src/main/java/expo/modules/video/listeners/VideoViewListener.kt +13 -0
  11. package/android/src/main/java/expo/modules/video/{AudioFocusManager.kt → managers/AudioFocusManager.kt} +3 -2
  12. package/android/src/main/java/expo/modules/video/managers/PictureInPictureManager.kt +329 -0
  13. package/android/src/main/java/expo/modules/video/{VideoManager.kt → managers/VideoManager.kt} +40 -5
  14. package/android/src/main/java/expo/modules/video/player/FirstFrameEventGenerator.kt +1 -0
  15. package/android/src/main/java/expo/modules/video/player/PlayerEvent.kt +1 -0
  16. package/android/src/main/java/expo/modules/video/player/VideoPlayer.kt +2 -1
  17. package/android/src/main/java/expo/modules/video/player/VideoPlayerAudioTracks.kt +1 -0
  18. package/android/src/main/java/expo/modules/video/player/VideoPlayerKeepAwake.kt +1 -1
  19. package/android/src/main/java/expo/modules/video/player/VideoPlayerSubtitles.kt +1 -0
  20. package/android/src/main/java/expo/modules/video/records/PiPParams.kt +38 -0
  21. package/android/src/main/java/expo/modules/video/utils/DataSourceUtils.kt +1 -0
  22. package/android/src/main/java/expo/modules/video/utils/PictureInPictureHelperFragment.kt +29 -0
  23. package/android/src/main/java/expo/modules/video/utils/ViewVisibilityUtils.kt +28 -0
  24. package/expo-module.config.json +1 -1
  25. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e-sources.jar +0 -0
  26. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e-sources.jar.md5 +1 -0
  27. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e-sources.jar.sha1 +1 -0
  28. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e-sources.jar.sha256 +1 -0
  29. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e-sources.jar.sha512 +1 -0
  30. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.aar +0 -0
  31. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.aar.md5 +1 -0
  32. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.aar.sha1 +1 -0
  33. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.aar.sha256 +1 -0
  34. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.aar.sha512 +1 -0
  35. package/local-maven-repo/host/exp/exponent/expo.modules.video/{3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.module → 3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.module} +22 -22
  36. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.module.md5 +1 -0
  37. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.module.sha1 +1 -0
  38. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.module.sha256 +1 -0
  39. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.module.sha512 +1 -0
  40. package/local-maven-repo/host/exp/exponent/expo.modules.video/{3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.pom → 3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.pom} +1 -1
  41. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.pom.md5 +1 -0
  42. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.pom.sha1 +1 -0
  43. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.pom.sha256 +1 -0
  44. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251223-b83b31e/expo.modules.video-3.1.0-canary-20251223-b83b31e.pom.sha512 +1 -0
  45. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml +4 -4
  46. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.md5 +1 -1
  47. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha1 +1 -1
  48. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha256 +1 -1
  49. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha512 +1 -1
  50. package/package.json +3 -3
  51. package/android/src/main/java/expo/modules/video/PictureInPictureHelperFragment.kt +0 -26
  52. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf-sources.jar +0 -0
  53. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf-sources.jar.md5 +0 -1
  54. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf-sources.jar.sha1 +0 -1
  55. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf-sources.jar.sha256 +0 -1
  56. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf-sources.jar.sha512 +0 -1
  57. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.aar +0 -0
  58. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.aar.md5 +0 -1
  59. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.aar.sha1 +0 -1
  60. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.aar.sha256 +0 -1
  61. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.aar.sha512 +0 -1
  62. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.module.md5 +0 -1
  63. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.module.sha1 +0 -1
  64. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.module.sha256 +0 -1
  65. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.module.sha512 +0 -1
  66. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.pom.md5 +0 -1
  67. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.pom.sha1 +0 -1
  68. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.pom.sha256 +0 -1
  69. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.1.0-canary-20251216-3f01dbf/expo.modules.video-3.1.0-canary-20251216-3f01dbf.pom.sha512 +0 -1
package/CHANGELOG.md CHANGED
@@ -16,6 +16,7 @@
16
16
  - [Android] Fix media controls (e.g. bluetooth) not working when `ExpoVideoPlaybackService` is not registered. ([#40728](https://github.com/expo/expo/pull/40728) by [@behenate](https://github.com/behenate))
17
17
  - [Web] Fix crash on older versions of Safari. ([#41101](https://github.com/expo/expo/pull/41101) by [@CamWass](https://github.com/CamWass))
18
18
  - [Web] Fix video pausing when entering fullscreen in electron apps. ([#40989](https://github.com/expo/expo/pull/40989) by [@behenate](https://github.com/behenate))
19
+ - [Android] Fix crashes when exiting PiP with one than more `VideoView` present on the screen. ([#41090](https://github.com/expo/expo/pull/41090) by [@behenate](https://github.com/behenate))
19
20
 
20
21
  ### 💡 Others
21
22
 
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '3.1.0-canary-20251216-3f01dbf'
7
+ version = '3.1.0-canary-20251223-b83b31e'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.video"
11
11
  defaultConfig {
12
12
  versionCode 1
13
- versionName '3.1.0-canary-20251216-3f01dbf'
13
+ versionName '3.1.0-canary-20251223-b83b31e'
14
14
  }
15
15
  }
16
16
 
@@ -22,6 +22,7 @@ import expo.modules.video.utils.applyPiPParams
22
22
  import expo.modules.video.utils.applyRectHint
23
23
  import expo.modules.video.utils.calculatePiPAspectRatio
24
24
  import expo.modules.video.utils.calculateRectHint
25
+ import expo.modules.video.managers.VideoManager
25
26
 
26
27
  @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
27
28
  class FullscreenPlayerActivity : Activity() {
@@ -9,6 +9,7 @@ import androidx.media3.database.StandaloneDatabaseProvider
9
9
  import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor
10
10
  import androidx.media3.datasource.cache.SimpleCache
11
11
  import expo.modules.kotlin.exception.Exceptions
12
+ import expo.modules.video.managers.VideoManager
12
13
  import java.io.File
13
14
  import java.lang.ref.WeakReference
14
15
  import java.util.UUID
@@ -27,6 +27,7 @@ import expo.modules.video.records.SeekTolerance
27
27
  import expo.modules.video.records.VideoSource
28
28
  import expo.modules.video.records.VideoThumbnailOptions
29
29
  import expo.modules.video.utils.runWithPiPMisconfigurationSoftHandling
30
+ import expo.modules.video.managers.VideoManager
30
31
  import kotlinx.coroutines.async
31
32
  import kotlinx.coroutines.awaitAll
32
33
  import kotlinx.coroutines.launch
@@ -43,6 +44,10 @@ class VideoModule : Module() {
43
44
  VideoManager.onModuleCreated(appContext)
44
45
  }
45
46
 
47
+ OnDestroy {
48
+ VideoManager.onModuleDestroyed(appContext)
49
+ }
50
+
46
51
  Function("isPictureInPictureSupported") {
47
52
  VideoView.isPictureInPictureSupported(appContext.throwingActivity)
48
53
  }
@@ -396,8 +401,8 @@ private inline fun <reified T : VideoView> ViewDefinitionBuilder<T>.VideoViewCom
396
401
  Prop("contentFit") { view: T, contentFit: ContentFit ->
397
402
  view.contentFit = contentFit
398
403
  }
399
- Prop("startsPictureInPictureAutomatically") { view: T, autoEnterPiP: Boolean ->
400
- view.autoEnterPiP = autoEnterPiP
404
+ Prop("startsPictureInPictureAutomatically") { view: T, autoEnterPiP: Boolean? ->
405
+ view.autoEnterPiP = autoEnterPiP ?: false
401
406
  }
402
407
  Prop("allowsFullscreen") { view: T, allowsFullscreen: Boolean? ->
403
408
  view.allowsFullscreen = allowsFullscreen ?: true
@@ -1,22 +1,17 @@
1
1
  package expo.modules.video
2
2
 
3
3
  import android.app.Activity
4
- import android.app.PictureInPictureParams
5
4
  import android.content.Context
6
5
  import android.content.Intent
7
6
  import android.graphics.Color
8
7
  import android.os.Build
9
- import android.util.Rational
10
8
  import android.view.accessibility.CaptioningManager
11
9
  import android.view.LayoutInflater
12
10
  import android.view.MotionEvent
13
11
  import android.view.View
14
12
  import android.view.ViewGroup
15
- import android.widget.FrameLayout
16
13
  import android.widget.ImageButton
17
- import androidx.fragment.app.FragmentActivity
18
14
  import androidx.media3.common.Tracks
19
- import androidx.media3.common.VideoSize
20
15
  import androidx.media3.ui.PlayerView
21
16
  import com.facebook.react.bridge.ReactContext
22
17
  import com.facebook.react.uimanager.UIManagerHelper
@@ -28,18 +23,20 @@ import expo.modules.kotlin.views.ExpoView
28
23
  import expo.modules.video.delegates.IgnoreSameSet
29
24
  import expo.modules.video.enums.ContentFit
30
25
  import expo.modules.video.player.VideoPlayer
31
- import expo.modules.video.player.VideoPlayerListener
26
+ import expo.modules.video.listeners.VideoPlayerListener
27
+ import expo.modules.video.listeners.VideoViewListener
32
28
  import expo.modules.video.records.AudioTrack
33
29
  import expo.modules.video.records.SubtitleTrack
34
30
  import expo.modules.video.records.VideoSource
35
31
  import expo.modules.video.records.VideoTrack
36
- import expo.modules.video.utils.applyPiPParams
37
32
  import expo.modules.video.records.FullscreenOptions
38
33
  import expo.modules.video.utils.SubtitleUtils
39
- import expo.modules.video.utils.applyRectHint
40
- import expo.modules.video.utils.calculatePiPAspectRatio
41
- import expo.modules.video.utils.calculateRectHint
42
34
  import expo.modules.video.utils.dispatchMotionEvent
35
+ import expo.modules.video.managers.VideoManager
36
+ import expo.modules.video.managers.calculateCurrentPipAspectRatio
37
+ import expo.modules.video.records.PiPParams
38
+ import expo.modules.video.utils.calculateRectHint
39
+ import java.lang.ref.WeakReference
43
40
  import java.util.UUID
44
41
 
45
42
  class SurfaceVideoView(context: Context, appContext: AppContext) : VideoView(context, appContext)
@@ -55,8 +52,6 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
55
52
  val onFullscreenExit by EventDispatcher<Unit>()
56
53
  val onFirstFrameRender by EventDispatcher<Unit>()
57
54
 
58
- var willEnterPiP: Boolean = false
59
-
60
55
  // In some situations we can't detect if the view will enter PiP, in that case the playback will be paused
61
56
  // We can get an event after PiP has started, that's when we should resume playback
62
57
  var wasAutoPaused: Boolean = false
@@ -67,13 +62,10 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
67
62
  var showsAudioTracksButton = false
68
63
  private set
69
64
 
65
+ private var listeners = mutableListOf<WeakReference<VideoViewListener>>()
70
66
  private val currentActivity = appContext.throwingActivity
71
67
  private val decorView = currentActivity.window.decorView
72
- private val rootView = decorView.findViewById<ViewGroup>(android.R.id.content)
73
68
  private val touchEventCoalescingKeyHelper = TouchEventCoalescingKeyHelper()
74
-
75
- private val rootViewChildrenOriginalVisibility: ArrayList<Int> = arrayListOf()
76
- private var pictureInPictureHelperTag: String? = null
77
69
  private var reactNativeEventDispatcher: EventDispatcher? = null
78
70
  private var captioningChangeListener: CaptioningManager.CaptioningChangeListener? = null
79
71
 
@@ -84,6 +76,13 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
84
76
  }
85
77
  }
86
78
 
79
+ var pipParams by IgnoreSameSet(PiPParams()) { new, old ->
80
+ listeners.forEach {
81
+ it.get()?.onPiPParamsChanged(this, old, new)
82
+ }
83
+ }
84
+ private set
85
+
87
86
  // We need to keep track of the target surface view visibility, but only apply it when `useExoShutter` is false.
88
87
  var shouldHideSurfaceView: Boolean = true
89
88
 
@@ -99,7 +98,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
99
98
  }
100
99
 
101
100
  var autoEnterPiP: Boolean by IgnoreSameSet(false) { new, _ ->
102
- applyPiPParams(currentActivity, new, calculateCurrentPipAspectRatio())
101
+ pipParams = pipParams.copy(autoEnter = new)
103
102
  }
104
103
 
105
104
  var contentFit: ContentFit = ContentFit.CONTAIN
@@ -217,7 +216,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
217
216
  currentActivity.overridePendingTransition(0, 0)
218
217
  }
219
218
  onFullscreenEnter(Unit)
220
- applyPiPParams(currentActivity, false, calculateCurrentPipAspectRatio())
219
+ pipParams = pipParams.copy(blocksAppFromEntering = true)
221
220
  }
222
221
 
223
222
  fun attachPlayer() {
@@ -231,7 +230,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
231
230
  attachPlayer()
232
231
  onFullscreenExit(Unit)
233
232
  isInFullscreen = false
234
- applyPiPParams(currentActivity, autoEnterPiP, calculateCurrentPipAspectRatio())
233
+ pipParams = pipParams.copy(blocksAppFromEntering = false)
235
234
  }
236
235
 
237
236
  fun enterPictureInPicture() {
@@ -239,49 +238,38 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
239
238
  throw PictureInPictureUnsupportedException()
240
239
  }
241
240
 
242
- val player = playerView.player
243
- ?: throw PictureInPictureEnterException("No player attached to the VideoView")
244
- playerView.useController = false
245
- applyPiPParams(currentActivity, autoEnterPiP, calculateCurrentPipAspectRatio())
246
- willEnterPiP = true
247
-
248
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
249
- currentActivity.enterPictureInPictureMode(PictureInPictureParams.Builder().build())
250
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
251
- @Suppress("DEPRECATION")
252
- currentActivity.enterPictureInPictureMode()
241
+ if (playerView.player == null) {
242
+ throw PictureInPictureEnterException("No player attached to the VideoView")
253
243
  }
244
+
245
+ pipParams = pipParams.copy(willEnter = true)
246
+ VideoManager.pictureInPicture.enterPictureInPicture(this)
254
247
  }
255
248
 
256
- private fun calculateCurrentPipAspectRatio(): Rational? {
257
- val player = videoPlayer?.player ?: return null
258
- return calculatePiPAspectRatio(player.videoSize, this.width, this.height, contentFit)
249
+ /**
250
+ * @param pipCandidate - VideoView that was elected to be displayed in PiP
251
+ */
252
+ fun onStartPictureInPicture(pipCandidate: VideoView?) {
253
+ onPictureInPictureStart(Unit)
259
254
  }
260
255
 
261
256
  /**
262
- * For optimal picture in picture experience it's best to only have one view. This method
263
- * hides all children of the root view and makes the player the only visible child of the rootView.
257
+ * @param pipCandidate - VideoView that was being displayed in PiP
264
258
  */
265
- fun layoutForPiPEnter() {
266
- playerView.useController = false
267
- (playerView.parent as? ViewGroup)?.removeView(playerView)
268
- for (i in 0 until rootView.childCount) {
269
- if (rootView.getChildAt(i) != playerView) {
270
- rootViewChildrenOriginalVisibility.add(rootView.getChildAt(i).visibility)
271
- rootView.getChildAt(i).visibility = View.GONE
272
- }
273
- }
274
- rootView.addView(playerView, FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
259
+ fun onStopPictureInPicture(pipCandidate: VideoView?) {
260
+ pipParams = pipParams.copy(willEnter = false)
261
+ onPictureInPictureStop(Unit)
275
262
  }
276
263
 
277
- fun layoutForPiPExit() {
278
- playerView.useController = useNativeControls
279
- rootView.removeView(playerView)
280
- for (i in 0 until rootView.childCount) {
281
- rootView.getChildAt(i).visibility = rootViewChildrenOriginalVisibility[i]
264
+ fun addVideoViewListener(listener: VideoViewListener) {
265
+ if (listeners.any { it.get() == listener }) {
266
+ return
282
267
  }
283
- rootViewChildrenOriginalVisibility.clear()
284
- this.addView(playerView)
268
+ listeners.add(WeakReference(listener))
269
+ }
270
+
271
+ fun removeVideoViewListener(listener: VideoViewListener) {
272
+ listeners.retainAll { it.get() != listener }
285
273
  }
286
274
 
287
275
  override fun onVideoSourceLoaded(
@@ -292,14 +280,16 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
292
280
  availableSubtitleTracks: List<SubtitleTrack>,
293
281
  availableAudioTracks: List<AudioTrack>
294
282
  ) {
295
- availableVideoTracks.firstOrNull()?.let {
296
- val videoSize = VideoSize(it.size.width, it.size.height)
297
- val aspectRatio = calculatePiPAspectRatio(videoSize, this.width, this.height, contentFit)
298
- applyPiPParams(currentActivity, autoEnterPiP, aspectRatio)
299
- }
283
+ pipParams = pipParams.copy(aspectRatio = calculateCurrentPipAspectRatio())
300
284
  super.onVideoSourceLoaded(player, videoSource, duration, availableVideoTracks, availableSubtitleTracks, availableAudioTracks)
301
285
  }
302
286
 
287
+ override fun onIsPlayingChanged(player: VideoPlayer, isPlaying: Boolean, oldIsPlaying: Boolean?) {
288
+ if (player == videoPlayer && isPlaying) {
289
+ wasAutoPaused = false
290
+ }
291
+ }
292
+
303
293
  override fun onTracksChanged(player: VideoPlayer, tracks: Tracks) {
304
294
  showsSubtitlesButton = player.subtitles.availableSubtitleTracks.isNotEmpty()
305
295
  showsAudioTracksButton = player.audioTracks.availableAudioTracks.size > 1
@@ -328,19 +318,13 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
328
318
  super.onLayout(changed, l, t, r, b)
329
319
  // On every re-layout ExoPlayer resets the timeBar to be enabled.
330
320
  // We need to disable it to keep scrubbing impossible.
321
+
322
+ pipParams = pipParams.copy(rectHint = calculateRectHint(playerView))
331
323
  playerView.setTimeBarInteractive(videoPlayer?.requiresLinearPlayback ?: true)
332
- applyRectHint(currentActivity, calculateRectHint(playerView))
333
324
  }
334
325
 
335
326
  override fun onAttachedToWindow() {
336
327
  super.onAttachedToWindow()
337
- (currentActivity as? FragmentActivity)?.let {
338
- val fragment = PictureInPictureHelperFragment(this)
339
- pictureInPictureHelperTag = fragment.id
340
- it.supportFragmentManager.beginTransaction()
341
- .add(fragment, fragment.id)
342
- .commitAllowingStateLoss()
343
- }
344
328
 
345
329
  // Set up listener for accessibility caption changes when attached to window
346
330
  setupCaptioningChangeListener()
@@ -350,7 +334,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
350
334
  // Set up window focus change listener
351
335
  decorView.onFocusChangeListener = windowFocusChangeListener
352
336
 
353
- applyPiPParams(currentActivity, autoEnterPiP)
337
+ pipParams = pipParams.copy(canEnter = true)
354
338
  }
355
339
 
356
340
  override fun onVisibilityChanged(changedView: View, visibility: Int) {
@@ -363,13 +347,6 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
363
347
 
364
348
  override fun onDetachedFromWindow() {
365
349
  super.onDetachedFromWindow()
366
- (currentActivity as? FragmentActivity)?.let {
367
- val fragment = it.supportFragmentManager.findFragmentByTag(pictureInPictureHelperTag ?: "")
368
- ?: return
369
- it.supportFragmentManager.beginTransaction()
370
- .remove(fragment)
371
- .commitAllowingStateLoss()
372
- }
373
350
 
374
351
  // Clean up captioning change listener
375
352
  captioningChangeListener?.let {
@@ -381,7 +358,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
381
358
  // Clean up window focus listener
382
359
  decorView.onFocusChangeListener = null
383
360
 
384
- applyPiPParams(currentActivity, false)
361
+ pipParams = pipParams.copy(canEnter = false)
385
362
  }
386
363
 
387
364
  // After adding the `PlayerView` to the hierarchy the touch events stop being emitted to the JS side.
@@ -16,7 +16,7 @@ class IgnoreSameSet<T : Any?>(private var value: T, val propertyMapper: ((T) ->
16
16
  }
17
17
 
18
18
  operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
19
- if (this.value == propertyMapper(value)) return
19
+ if (this.value?.equals(propertyMapper(value)) ?: false) return
20
20
  val oldValue = this.value
21
21
  this.value = propertyMapper(value)
22
22
  didSet?.invoke(this.value, oldValue)
@@ -0,0 +1,10 @@
1
+ package expo.modules.video.listeners
2
+
3
+ import expo.modules.video.VideoView
4
+
5
+ interface VideoManagerListener {
6
+ fun onVideoViewRegistered(videoView: VideoView, allVideoViews: Collection<VideoView>) {}
7
+ fun onVideoViewUnregistered(videoView: VideoView, allVideoViews: Collection<VideoView>) {}
8
+ fun onAppBackgrounded() {}
9
+ fun onAppForegrounded() {}
10
+ }
@@ -1,4 +1,4 @@
1
- package expo.modules.video.player
1
+ package expo.modules.video.listeners
2
2
 
3
3
  import androidx.annotation.OptIn
4
4
  import androidx.media3.common.TrackSelectionParameters
@@ -7,11 +7,12 @@ import androidx.media3.common.util.UnstableApi
7
7
  import expo.modules.video.VideoView
8
8
  import expo.modules.video.enums.AudioMixingMode
9
9
  import expo.modules.video.enums.PlayerStatus
10
+ import expo.modules.video.player.VideoPlayer
10
11
  import expo.modules.video.records.AudioTrack
11
12
  import expo.modules.video.records.PlaybackError
12
13
  import expo.modules.video.records.SubtitleTrack
13
- import expo.modules.video.records.VideoSource
14
14
  import expo.modules.video.records.TimeUpdate
15
+ import expo.modules.video.records.VideoSource
15
16
  import expo.modules.video.records.VideoTrack
16
17
 
17
18
  @OptIn(UnstableApi::class)
@@ -0,0 +1,13 @@
1
+ package expo.modules.video.listeners
2
+
3
+ import expo.modules.video.VideoView
4
+ import expo.modules.video.records.PiPParams
5
+
6
+ interface VideoViewListener {
7
+ /**
8
+ * Called when the ability of the view to autoEnterPip has changed.
9
+ * Note: This can be called when `autoEnterPiP` doesn't change, but other factors
10
+ * influence the view's ability to enter PiP.
11
+ */
12
+ fun onPiPParamsChanged(videoView: VideoView, oldPiPParams: PiPParams, newPiPParams: PiPParams) = Unit
13
+ }
@@ -1,4 +1,4 @@
1
- package expo.modules.video
1
+ package expo.modules.video.managers
2
2
 
3
3
  import android.content.Context
4
4
  import android.media.AudioAttributes
@@ -7,9 +7,10 @@ import android.media.AudioManager
7
7
  import android.os.Build
8
8
  import androidx.media3.common.util.UnstableApi
9
9
  import expo.modules.kotlin.AppContext
10
+ import expo.modules.video.FailedToGetAudioFocusManagerException
10
11
  import expo.modules.video.enums.AudioMixingMode
11
12
  import expo.modules.video.player.VideoPlayer
12
- import expo.modules.video.player.VideoPlayerListener
13
+ import expo.modules.video.listeners.VideoPlayerListener
13
14
  import kotlinx.coroutines.launch
14
15
  import java.lang.ref.WeakReference
15
16