expo-video 2.2.1 → 2.2.3

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 (55) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +6 -2
  4. package/android/src/main/java/expo/modules/video/VideoCache.kt +5 -1
  5. package/android/src/main/java/expo/modules/video/VideoManager.kt +6 -1
  6. package/android/src/main/java/expo/modules/video/VideoView.kt +35 -31
  7. package/android/src/main/java/expo/modules/video/player/PlayerEvent.kt +2 -1
  8. package/android/src/main/java/expo/modules/video/player/VideoPlayer.kt +9 -3
  9. package/android/src/main/java/expo/modules/video/player/VideoPlayerListener.kt +3 -0
  10. package/android/src/main/java/expo/modules/video/utils/PictureInPictureUtils.kt +36 -3
  11. package/expo-module.config.json +1 -1
  12. package/ios/VideoPlayerItem.swift +1 -1
  13. package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.2.1/expo.modules.video-2.2.1-sources.jar → 2.2.3/expo.modules.video-2.2.3-sources.jar} +0 -0
  14. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3-sources.jar.md5 +1 -0
  15. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3-sources.jar.sha1 +1 -0
  16. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3-sources.jar.sha256 +1 -0
  17. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3-sources.jar.sha512 +1 -0
  18. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3.aar +0 -0
  19. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3.aar.md5 +1 -0
  20. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3.aar.sha1 +1 -0
  21. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3.aar.sha256 +1 -0
  22. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3.aar.sha512 +1 -0
  23. package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.2.1/expo.modules.video-2.2.1.module → 2.2.3/expo.modules.video-2.2.3.module} +22 -22
  24. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3.module.md5 +1 -0
  25. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3.module.sha1 +1 -0
  26. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3.module.sha256 +1 -0
  27. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3.module.sha512 +1 -0
  28. package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.2.1/expo.modules.video-2.2.1.pom → 2.2.3/expo.modules.video-2.2.3.pom} +1 -1
  29. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3.pom.md5 +1 -0
  30. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3.pom.sha1 +1 -0
  31. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3.pom.sha256 +1 -0
  32. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.3/expo.modules.video-2.2.3.pom.sha512 +1 -0
  33. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml +4 -4
  34. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.md5 +1 -1
  35. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha1 +1 -1
  36. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha256 +1 -1
  37. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha512 +1 -1
  38. package/package.json +3 -3
  39. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1-sources.jar.md5 +0 -1
  40. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1-sources.jar.sha1 +0 -1
  41. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1-sources.jar.sha256 +0 -1
  42. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1-sources.jar.sha512 +0 -1
  43. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.aar +0 -0
  44. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.aar.md5 +0 -1
  45. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.aar.sha1 +0 -1
  46. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.aar.sha256 +0 -1
  47. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.aar.sha512 +0 -1
  48. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.module.md5 +0 -1
  49. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.module.sha1 +0 -1
  50. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.module.sha256 +0 -1
  51. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.module.sha512 +0 -1
  52. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.pom.md5 +0 -1
  53. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.pom.sha1 +0 -1
  54. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.pom.sha256 +0 -1
  55. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.pom.sha512 +0 -1
package/CHANGELOG.md CHANGED
@@ -10,6 +10,20 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 2.2.3 — 2026-02-12
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [iOS] Prevents blocking main thread when loading asset tracks for non-HSL tracks ([#42037](https://github.com/expo/expo/pull/42037) by [@santitopo](https://github.com/santitopo))
18
+ - [Android] Fix crash due to `SimpleCache` directory lock conflicts. ([#42723](https://github.com/expo/expo/pull/42723) by [@santitopo](https://github.com/santitopo))
19
+ - [Android] Fix only one player getting released when reloading with multiple players present. ([#42780](https://github.com/expo/expo/pull/42780) by [@behenate](https://github.com/behenate))
20
+
21
+ ## 2.2.2 — 2025-06-18
22
+
23
+ ### 🐛 Bug fixes
24
+
25
+ - [Android] Fix aspect ratio of the Picture in Picture window when auto-entering for sources with ratio different from 16:9. ([#37225](https://github.com/expo/expo/pull/37225) by [@behenate](https://github.com/behenate))
26
+
13
27
  ## 2.2.1 — 2025-06-10
14
28
 
15
29
  _This version does not introduce any user-facing changes._
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '2.2.1'
7
+ version = '2.2.3'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.video"
11
11
  defaultConfig {
12
12
  versionCode 1
13
- versionName '2.2.1'
13
+ versionName '2.2.3'
14
14
  }
15
15
  }
16
16
 
@@ -12,8 +12,9 @@ import android.widget.ImageButton
12
12
  import androidx.media3.ui.PlayerView
13
13
  import expo.modules.kotlin.exception.CodedException
14
14
  import expo.modules.video.player.VideoPlayer
15
- import expo.modules.video.utils.applyAutoEnterPiP
15
+ import expo.modules.video.utils.applyPiPParams
16
16
  import expo.modules.video.utils.applyRectHint
17
+ import expo.modules.video.utils.calculatePiPAspectRatio
17
18
  import expo.modules.video.utils.calculateRectHint
18
19
 
19
20
  @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
@@ -44,7 +45,10 @@ class FullscreenPlayerActivity : Activity() {
44
45
  videoPlayer = videoView.videoPlayer
45
46
  videoPlayer?.changePlayerView(playerView)
46
47
  VideoManager.registerFullscreenPlayerActivity(hashCode().toString(), this)
47
- applyAutoEnterPiP(this, videoView.autoEnterPiP)
48
+ playerView.player?.let {
49
+ val aspectRatio = calculatePiPAspectRatio(it.videoSize, playerView.width, playerView.height, videoView.contentFit)
50
+ applyPiPParams(this, videoView.autoEnterPiP, aspectRatio)
51
+ }
48
52
  }
49
53
 
50
54
  override fun onPostCreate(savedInstanceState: Bundle?) {
@@ -37,6 +37,10 @@ class VideoCache(context: Context) {
37
37
  return sharedPreferences.getLong(CACHE_SIZE_KEY, DEFAULT_CACHE_SIZE)
38
38
  }
39
39
 
40
+ fun release() {
41
+ instance.release()
42
+ }
43
+
40
44
  fun setMaxCacheSize(size: Long) {
41
45
  assertModificationReleaseConditions()
42
46
  instance.release()
@@ -54,7 +58,7 @@ class VideoCache(context: Context) {
54
58
  // Weird structure, because kotlin marks the result of `getString` as nullable
55
59
  val videoCacheDirName = sharedPreferences.getString(VIDEO_CACHE_DIR_KEY, null) ?: run {
56
60
  val newCacheDirName = generateCacheDirName()
57
- sharedPreferences.edit().putString(VIDEO_CACHE_DIR_KEY, newCacheDirName).apply()
61
+ sharedPreferences.edit().putString(VIDEO_CACHE_DIR_KEY, newCacheDirName).commit()
58
62
  newCacheDirName
59
63
  }
60
64
  val cacheParentDir = File(context.cacheDir, VIDEO_CACHE_PARENT_DIR)
@@ -11,6 +11,7 @@ import java.lang.ref.WeakReference
11
11
  @OptIn(UnstableApi::class)
12
12
  object VideoManager {
13
13
  const val INTENT_PLAYER_KEY = "player_uuid"
14
+ private var appContext: WeakReference<AppContext?> = WeakReference(null)
14
15
 
15
16
  // Used for sharing videoViews between VideoView and FullscreenPlayerActivity
16
17
  private var videoViews = mutableMapOf<String, VideoView>()
@@ -22,7 +23,7 @@ object VideoManager {
22
23
  private lateinit var audioFocusManager: AudioFocusManager
23
24
  lateinit var cache: VideoCache
24
25
 
25
- fun onModuleCreated(appContext: AppContext) {
26
+ fun onModuleCreated(appContext: AppContext) = synchronized(this) {
26
27
  val context = appContext.reactContext ?: throw Exceptions.ReactContextLost()
27
28
 
28
29
  if (!this::audioFocusManager.isInitialized) {
@@ -30,7 +31,11 @@ object VideoManager {
30
31
  }
31
32
  if (!this::cache.isInitialized) {
32
33
  cache = VideoCache(context)
34
+ } else if (this.appContext.get()?.reactContext != appContext.reactContext) {
35
+ cache.release()
36
+ cache = VideoCache(context)
33
37
  }
38
+ this.appContext = WeakReference(appContext)
34
39
  }
35
40
 
36
41
  fun registerVideoView(videoView: VideoView) {
@@ -15,6 +15,7 @@ import android.widget.FrameLayout
15
15
  import android.widget.ImageButton
16
16
  import androidx.fragment.app.FragmentActivity
17
17
  import androidx.media3.common.Tracks
18
+ import androidx.media3.common.VideoSize
18
19
  import androidx.media3.ui.PlayerView
19
20
  import com.facebook.react.bridge.ReactContext
20
21
  import com.facebook.react.uimanager.UIManagerHelper
@@ -27,8 +28,13 @@ import expo.modules.video.delegates.IgnoreSameSet
27
28
  import expo.modules.video.enums.ContentFit
28
29
  import expo.modules.video.player.VideoPlayer
29
30
  import expo.modules.video.player.VideoPlayerListener
30
- import expo.modules.video.utils.applyAutoEnterPiP
31
+ import expo.modules.video.records.AudioTrack
32
+ import expo.modules.video.records.SubtitleTrack
33
+ import expo.modules.video.records.VideoSource
34
+ import expo.modules.video.records.VideoTrack
35
+ import expo.modules.video.utils.applyPiPParams
31
36
  import expo.modules.video.utils.applyRectHint
37
+ import expo.modules.video.utils.calculatePiPAspectRatio
32
38
  import expo.modules.video.utils.calculateRectHint
33
39
  import expo.modules.video.utils.dispatchMotionEvent
34
40
  import java.util.UUID
@@ -82,7 +88,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
82
88
  }
83
89
 
84
90
  var autoEnterPiP: Boolean by IgnoreSameSet(false) { new, _ ->
85
- applyAutoEnterPiP(currentActivity, new)
91
+ applyPiPParams(currentActivity, new, calculateCurrentPipAspectRatio())
86
92
  }
87
93
 
88
94
  var contentFit: ContentFit = ContentFit.CONTAIN
@@ -178,7 +184,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
178
184
  currentActivity.overridePendingTransition(0, 0)
179
185
  }
180
186
  onFullscreenEnter(Unit)
181
- applyAutoEnterPiP(currentActivity, false)
187
+ applyPiPParams(currentActivity, false, calculateCurrentPipAspectRatio())
182
188
  }
183
189
 
184
190
  fun attachPlayer() {
@@ -192,7 +198,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
192
198
  attachPlayer()
193
199
  onFullscreenExit(Unit)
194
200
  isInFullscreen = false
195
- applyAutoEnterPiP(currentActivity, autoEnterPiP)
201
+ applyPiPParams(currentActivity, autoEnterPiP, calculateCurrentPipAspectRatio())
196
202
  }
197
203
 
198
204
  fun enterPictureInPicture() {
@@ -203,32 +209,9 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
203
209
  val player = playerView.player
204
210
  ?: throw PictureInPictureEnterException("No player attached to the VideoView")
205
211
  playerView.useController = false
206
-
207
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
208
- var aspectRatio = if (contentFit == ContentFit.CONTAIN) {
209
- Rational(player.videoSize.width, player.videoSize.height)
210
- } else {
211
- Rational(width, height)
212
- }
213
- // AspectRatio for the activity in picture-in-picture, must be between 2.39:1 and 1:2.39 (inclusive).
214
- // https://developer.android.com/reference/android/app/PictureInPictureParams.Builder#setAspectRatio(android.util.Rational)
215
- val maximumRatio = Rational(239, 100)
216
- val minimumRatio = Rational(100, 239)
217
- if (aspectRatio.toFloat() > maximumRatio.toFloat()) {
218
- aspectRatio = maximumRatio
219
- } else if (aspectRatio.toFloat() < minimumRatio.toFloat()) {
220
- aspectRatio = minimumRatio
221
- }
222
-
223
- currentActivity.setPictureInPictureParams(
224
- PictureInPictureParams
225
- .Builder()
226
- .setAspectRatio(aspectRatio)
227
- .build()
228
- )
229
- }
230
-
212
+ applyPiPParams(currentActivity, autoEnterPiP, calculateCurrentPipAspectRatio())
231
213
  willEnterPiP = true
214
+
232
215
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
233
216
  currentActivity.enterPictureInPictureMode(PictureInPictureParams.Builder().build())
234
217
  } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@@ -237,6 +220,11 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
237
220
  }
238
221
  }
239
222
 
223
+ private fun calculateCurrentPipAspectRatio(): Rational? {
224
+ val player = videoPlayer?.player ?: return null
225
+ return calculatePiPAspectRatio(player.videoSize, this.width, this.height, contentFit)
226
+ }
227
+
240
228
  /**
241
229
  * For optimal picture in picture experience it's best to only have one view. This method
242
230
  * hides all children of the root view and makes the player the only visible child of the rootView.
@@ -263,6 +251,22 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
263
251
  this.addView(playerView)
264
252
  }
265
253
 
254
+ override fun onVideoSourceLoaded(
255
+ player: VideoPlayer,
256
+ videoSource: VideoSource?,
257
+ duration: Double?,
258
+ availableVideoTracks: List<VideoTrack>,
259
+ availableSubtitleTracks: List<SubtitleTrack>,
260
+ availableAudioTracks: List<AudioTrack>
261
+ ) {
262
+ availableVideoTracks.firstOrNull()?.let {
263
+ val videoSize = VideoSize(it.size.width, it.size.height)
264
+ val aspectRatio = calculatePiPAspectRatio(videoSize, this.width, this.height, contentFit)
265
+ applyPiPParams(currentActivity, autoEnterPiP, aspectRatio)
266
+ }
267
+ super.onVideoSourceLoaded(player, videoSource, duration, availableVideoTracks, availableSubtitleTracks, availableAudioTracks)
268
+ }
269
+
266
270
  override fun onTracksChanged(player: VideoPlayer, tracks: Tracks) {
267
271
  showsSubtitlesButton = player.subtitles.availableSubtitleTracks.isNotEmpty()
268
272
  showsAudioTracksButton = player.audioTracks.availableAudioTracks.size > 1
@@ -302,7 +306,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
302
306
  .add(fragment, fragment.id)
303
307
  .commitAllowingStateLoss()
304
308
  }
305
- applyAutoEnterPiP(currentActivity, autoEnterPiP)
309
+ applyPiPParams(currentActivity, autoEnterPiP)
306
310
  }
307
311
 
308
312
  override fun onDetachedFromWindow() {
@@ -314,7 +318,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
314
318
  .remove(fragment)
315
319
  .commitAllowingStateLoss()
316
320
  }
317
- applyAutoEnterPiP(currentActivity, false)
321
+ applyPiPParams(currentActivity, false)
318
322
  }
319
323
 
320
324
  // After adding the `PlayerView` to the hierarchy the touch events stop being emitted to the JS side.
@@ -156,7 +156,8 @@ sealed class PlayerEvent {
156
156
  is AudioMixingModeChanged -> listeners.forEach { it.onAudioMixingModeChanged(player, audioMixingMode, oldAudioMixingMode) }
157
157
  is VideoTrackChanged -> listeners.forEach { it.onVideoTrackChanged(player, videoTrack, oldVideoTrack) }
158
158
  is RenderedFirstFrame -> listeners.forEach { it.onRenderedFirstFrame(player) }
159
- // JS-only events - VideoSourceLoaded, SubtitleTrackChanged - In the native events the TracksChanged can be used instead
159
+ is VideoSourceLoaded -> listeners.forEach { it.onVideoSourceLoaded(player, videoSource, duration, availableVideoTracks, availableSubtitleTracks, availableAudioTracks) }
160
+ // JS-only events - SubtitleTrackChanged - In the native events the TracksChanged can be used instead
160
161
  else -> Unit
161
162
  }
162
163
  }
@@ -39,6 +39,9 @@ import expo.modules.video.records.TimeUpdate
39
39
  import expo.modules.video.records.VideoSource
40
40
  import expo.modules.video.utils.MutableWeakReference
41
41
  import expo.modules.video.records.VideoTrack
42
+ import kotlinx.coroutines.DelicateCoroutinesApi
43
+ import kotlinx.coroutines.Dispatchers
44
+ import kotlinx.coroutines.GlobalScope
42
45
  import kotlinx.coroutines.launch
43
46
  import java.io.FileInputStream
44
47
  import java.lang.ref.WeakReference
@@ -299,12 +302,15 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
299
302
  }
300
303
  }
301
304
 
305
+ @kotlin.OptIn(DelicateCoroutinesApi::class)
302
306
  override fun close() {
303
307
  appContext?.reactContext?.unbindService(serviceConnection)
304
308
  serviceConnection.playbackServiceBinder?.service?.unregisterPlayer(player)
305
309
  VideoManager.unregisterVideoPlayer(this@VideoPlayer)
306
310
 
307
- appContext?.mainQueue?.launch {
311
+ // Run on global scope (not appContext.mainQueue) so that reloading doesn't cancel the release process
312
+ // https://github.com/expo/expo/blob/cdf592a7fea56fc01b0149e9b2e5dbd294bcdc4c/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/AppContext.kt#L277-L279
313
+ GlobalScope.launch(Dispatchers.Main) {
308
314
  player.removeListener(playerListener)
309
315
  player.release()
310
316
  }
@@ -312,8 +318,8 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
312
318
  commitedSource = null
313
319
  }
314
320
 
315
- override fun deallocate() {
316
- super.deallocate()
321
+ override fun sharedObjectDidRelease() {
322
+ super.sharedObjectDidRelease()
317
323
  close()
318
324
  }
319
325
 
@@ -6,7 +6,9 @@ import androidx.media3.common.Tracks
6
6
  import androidx.media3.common.util.UnstableApi
7
7
  import expo.modules.video.enums.AudioMixingMode
8
8
  import expo.modules.video.enums.PlayerStatus
9
+ import expo.modules.video.records.AudioTrack
9
10
  import expo.modules.video.records.PlaybackError
11
+ import expo.modules.video.records.SubtitleTrack
10
12
  import expo.modules.video.records.VideoSource
11
13
  import expo.modules.video.records.TimeUpdate
12
14
  import expo.modules.video.records.VideoTrack
@@ -25,5 +27,6 @@ interface VideoPlayerListener {
25
27
  fun onPlayedToEnd(player: VideoPlayer) {}
26
28
  fun onAudioMixingModeChanged(player: VideoPlayer, audioMixingMode: AudioMixingMode, oldAudioMixingMode: AudioMixingMode?) {}
27
29
  fun onVideoTrackChanged(player: VideoPlayer, videoTrack: VideoTrack?, oldVideoTrack: VideoTrack?) {}
30
+ fun onVideoSourceLoaded(player: VideoPlayer, videoSource: VideoSource?, duration: Double?, availableVideoTracks: List<VideoTrack>, availableSubtitleTracks: List<SubtitleTrack>, availableAudioTracks: List<AudioTrack>) {}
28
31
  fun onRenderedFirstFrame(player: VideoPlayer) {}
29
32
  }
@@ -5,11 +5,14 @@ import android.app.PictureInPictureParams
5
5
  import android.graphics.Rect
6
6
  import android.os.Build
7
7
  import android.util.Log
8
+ import android.util.Rational
8
9
  import androidx.annotation.OptIn
10
+ import androidx.media3.common.VideoSize
9
11
  import androidx.media3.common.util.UnstableApi
10
12
  import androidx.media3.ui.PlayerView
11
13
  import expo.modules.video.PictureInPictureConfigurationException
12
14
  import expo.modules.video.VideoView.Companion.isPictureInPictureSupported
15
+ import expo.modules.video.enums.ContentFit
13
16
 
14
17
  @OptIn(UnstableApi::class)
15
18
  internal fun calculateRectHint(playerView: PlayerView): Rect {
@@ -34,6 +37,25 @@ internal fun calculateRectHint(playerView: PlayerView): Rect {
34
37
  return hint
35
38
  }
36
39
 
40
+ internal fun calculatePiPAspectRatio(videoSize: VideoSize, viewWidth: Int, viewHeight: Int, contentFit: ContentFit): Rational {
41
+ var aspectRatio = if (contentFit == ContentFit.CONTAIN) {
42
+ Rational(videoSize.width, videoSize.height)
43
+ } else {
44
+ Rational(viewWidth, viewHeight)
45
+ }
46
+ // AspectRatio for the activity in picture-in-picture, must be between 2.39:1 and 1:2.39 (inclusive).
47
+ // https://developer.android.com/reference/android/app/PictureInPictureParams.Builder#setAspectRatio(android.util.Rational)
48
+ val maximumRatio = Rational(239, 100)
49
+ val minimumRatio = Rational(100, 239)
50
+
51
+ if (aspectRatio.toFloat() > maximumRatio.toFloat()) {
52
+ aspectRatio = maximumRatio
53
+ } else if (aspectRatio.toFloat() < minimumRatio.toFloat()) {
54
+ aspectRatio = minimumRatio
55
+ }
56
+ return aspectRatio
57
+ }
58
+
37
59
  internal fun applyRectHint(activity: Activity, rectHint: Rect) {
38
60
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPictureSupported(activity)) {
39
61
  runWithPiPMisconfigurationSoftHandling {
@@ -54,10 +76,21 @@ internal fun runWithPiPMisconfigurationSoftHandling(shouldThrow: Boolean = false
54
76
  }
55
77
  }
56
78
 
57
- internal fun applyAutoEnterPiP(activity: Activity, autoEnterPiP: Boolean) {
58
- if (Build.VERSION.SDK_INT >= 31 && isPictureInPictureSupported(activity)) {
79
+ internal fun applyPiPParams(activity: Activity, autoEnterPiP: Boolean, aspectRatio: Rational? = null) {
80
+ // If the aspect ratio exceeds the limits, the app will crash
81
+ val safeAspectRatio = aspectRatio?.takeIf { it.toFloat() in 0.41841..2.39 }
82
+
83
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPictureSupported(activity)) {
84
+ val paramsBuilder = PictureInPictureParams.Builder()
85
+
86
+ safeAspectRatio?.let {
87
+ paramsBuilder.setAspectRatio(it)
88
+ }
89
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
90
+ paramsBuilder.setAutoEnterEnabled(autoEnterPiP)
91
+ }
59
92
  runWithPiPMisconfigurationSoftHandling {
60
- activity.setPictureInPictureParams(PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterPiP).build())
93
+ activity.setPictureInPictureParams(paramsBuilder.build())
61
94
  }
62
95
  }
63
96
  }
@@ -8,7 +8,7 @@
8
8
  "publication": {
9
9
  "groupId": "host.exp.exponent",
10
10
  "artifactId": "expo.modules.video",
11
- "version": "2.2.1",
11
+ "version": "2.2.3",
12
12
  "repository": "local-maven-repo"
13
13
  }
14
14
  }
@@ -60,7 +60,7 @@ class VideoPlayerItem: AVPlayerItem {
60
60
  log.warn("Failed to fetch HLS video tracks, this is not required for playback, but `expo-video` will have no knowledge of the available tracks: \(error.localizedDescription)")
61
61
  }
62
62
  } else {
63
- let avAssetTracks = asset.tracks(withMediaType: .video)
63
+ let avAssetTracks = (try? await asset.loadTracks(withMediaType: .video)) ?? []
64
64
  for avAssetTrack in avAssetTracks {
65
65
  tracks.append(await VideoTrack.from(assetTrack: avAssetTrack))
66
66
  }
@@ -0,0 +1 @@
1
+ e6799bceee8518b323135e5678d46f6a522dc58f5a93afd261d2a135439c63ba
@@ -0,0 +1 @@
1
+ f7b6186b8c1051adce261e8f5d0928420f8d3abc7840f2428a493cffcec07effefd9c4cf055fa699d721f364f240768495c5d42899c57af45b27dd80e9e4e479
@@ -0,0 +1 @@
1
+ 1fe1d851329a99a2dd8c4c0c2ae8c50b61003dbb
@@ -0,0 +1 @@
1
+ fc347476083828aa32f5c1aae1d9a7f6a6a599f6f0033169f3e6e7cc883bded8
@@ -0,0 +1 @@
1
+ 3f6df1da65ba53e596a119f0674357b0564370f3818f2878ee9e3e89c27843f47dc1d1396f69bd315d3c5b73de996d3f847b8fd4415921935a3eddf7f81f9acb
@@ -3,7 +3,7 @@
3
3
  "component": {
4
4
  "group": "host.exp.exponent",
5
5
  "module": "expo.modules.video",
6
- "version": "2.2.1",
6
+ "version": "2.2.3",
7
7
  "attributes": {
8
8
  "org.gradle.status": "release"
9
9
  }
@@ -24,13 +24,13 @@
24
24
  },
25
25
  "files": [
26
26
  {
27
- "name": "expo.modules.video-2.2.1.aar",
28
- "url": "expo.modules.video-2.2.1.aar",
29
- "size": 444322,
30
- "sha512": "a1501f7e947680eba85a049cf0520518bca4feca64832fbdc41383cb2c483f6253ecf8d429ad936966fcce1b0494175035399894dbce9c50f4765bd099edebf1",
31
- "sha256": "0022fab08b59c4346e67a4bf573af546f4f0e7eb322aee1115335e035318fa2c",
32
- "sha1": "06203802abf1c39df7bef9bddb6cf7dda03e6300",
33
- "md5": "b87e04623a8bc68b39b0d628a278460d"
27
+ "name": "expo.modules.video-2.2.3.aar",
28
+ "url": "expo.modules.video-2.2.3.aar",
29
+ "size": 447632,
30
+ "sha512": "3f6df1da65ba53e596a119f0674357b0564370f3818f2878ee9e3e89c27843f47dc1d1396f69bd315d3c5b73de996d3f847b8fd4415921935a3eddf7f81f9acb",
31
+ "sha256": "fc347476083828aa32f5c1aae1d9a7f6a6a599f6f0033169f3e6e7cc883bded8",
32
+ "sha1": "1fe1d851329a99a2dd8c4c0c2ae8c50b61003dbb",
33
+ "md5": "2a042a4174c19e76c2f2efad7779f8a0"
34
34
  }
35
35
  ]
36
36
  },
@@ -113,13 +113,13 @@
113
113
  ],
114
114
  "files": [
115
115
  {
116
- "name": "expo.modules.video-2.2.1.aar",
117
- "url": "expo.modules.video-2.2.1.aar",
118
- "size": 444322,
119
- "sha512": "a1501f7e947680eba85a049cf0520518bca4feca64832fbdc41383cb2c483f6253ecf8d429ad936966fcce1b0494175035399894dbce9c50f4765bd099edebf1",
120
- "sha256": "0022fab08b59c4346e67a4bf573af546f4f0e7eb322aee1115335e035318fa2c",
121
- "sha1": "06203802abf1c39df7bef9bddb6cf7dda03e6300",
122
- "md5": "b87e04623a8bc68b39b0d628a278460d"
116
+ "name": "expo.modules.video-2.2.3.aar",
117
+ "url": "expo.modules.video-2.2.3.aar",
118
+ "size": 447632,
119
+ "sha512": "3f6df1da65ba53e596a119f0674357b0564370f3818f2878ee9e3e89c27843f47dc1d1396f69bd315d3c5b73de996d3f847b8fd4415921935a3eddf7f81f9acb",
120
+ "sha256": "fc347476083828aa32f5c1aae1d9a7f6a6a599f6f0033169f3e6e7cc883bded8",
121
+ "sha1": "1fe1d851329a99a2dd8c4c0c2ae8c50b61003dbb",
122
+ "md5": "2a042a4174c19e76c2f2efad7779f8a0"
123
123
  }
124
124
  ]
125
125
  },
@@ -133,13 +133,13 @@
133
133
  },
134
134
  "files": [
135
135
  {
136
- "name": "expo.modules.video-2.2.1-sources.jar",
137
- "url": "expo.modules.video-2.2.1-sources.jar",
138
- "size": 52499,
139
- "sha512": "c7c374ab88183e831ed5a80a5e7a6f9ef2d29d4157c7b9496e30a9f87b14c4952b1e867b29f8feedbab21badccdf1b751f3a20367d1970a5e814311d96618f0e",
140
- "sha256": "ff54e2221752092ebdece914b77e4504b2864fc94504c8a1887ea67a2afa5ea0",
141
- "sha1": "f26b20423aa775f88468456856f576c24bc8149b",
142
- "md5": "6e1f79fc8674bec45f35f95a4f8933f3"
136
+ "name": "expo.modules.video-2.2.3-sources.jar",
137
+ "url": "expo.modules.video-2.2.3-sources.jar",
138
+ "size": 53383,
139
+ "sha512": "f7b6186b8c1051adce261e8f5d0928420f8d3abc7840f2428a493cffcec07effefd9c4cf055fa699d721f364f240768495c5d42899c57af45b27dd80e9e4e479",
140
+ "sha256": "e6799bceee8518b323135e5678d46f6a522dc58f5a93afd261d2a135439c63ba",
141
+ "sha1": "6f35f0b7f986458c6f7d49c23d73dcdaed96c05b",
142
+ "md5": "b674ad513f1cf05d8eee03faadcbbd7e"
143
143
  }
144
144
  ]
145
145
  }
@@ -0,0 +1 @@
1
+ a6a8c307f88be9002086dc56a4d7c4e629f36bf9e3da8986806db327b154dcd8
@@ -0,0 +1 @@
1
+ b273ad9af2cba841173be1c4d316d5224d1e4b9c89af076790c49ef5b539ed0360fed8b37f1ba24d4a5002e9d51b2872d2bf71ee59b8a23a7debb278570d453c
@@ -9,7 +9,7 @@
9
9
  <modelVersion>4.0.0</modelVersion>
10
10
  <groupId>host.exp.exponent</groupId>
11
11
  <artifactId>expo.modules.video</artifactId>
12
- <version>2.2.1</version>
12
+ <version>2.2.3</version>
13
13
  <packaging>aar</packaging>
14
14
  <name>expo.modules.video</name>
15
15
  <url>https://github.com/expo/expo</url>
@@ -0,0 +1 @@
1
+ 041e874ce0967ae65dac5267ce8a2f3bb34e15e2
@@ -0,0 +1 @@
1
+ c3a0b8f5a8a78673d18d09922d7246aae4b3f1c300fad5cd633656e56fc804e0
@@ -0,0 +1 @@
1
+ ce41e9b736e294503494722f0d9c5aad913e7099663103708c1c64a7624bb1a58f409eb6c051efa023f45e6bbd8a0efa09d2e1c01527e11512098abe06fa232d
@@ -3,11 +3,11 @@
3
3
  <groupId>host.exp.exponent</groupId>
4
4
  <artifactId>expo.modules.video</artifactId>
5
5
  <versioning>
6
- <latest>2.2.1</latest>
7
- <release>2.2.1</release>
6
+ <latest>2.2.3</latest>
7
+ <release>2.2.3</release>
8
8
  <versions>
9
- <version>2.2.1</version>
9
+ <version>2.2.3</version>
10
10
  </versions>
11
- <lastUpdated>20250610113903</lastUpdated>
11
+ <lastUpdated>20260212185040</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- d175f56e996dbf7ca76d75bfe82c4d41
1
+ c8d5dd68cfa316d03335635ff08f5738
@@ -1 +1 @@
1
- 1776813ac995e4f770f3461f9f4f7d125c03b710
1
+ 3d1392478705a1779cd1994093c4add9c11b49f2
@@ -1 +1 @@
1
- 56169c737354e30bd868954cb30731441d9fceba388fcf1274f0ee5462378bb3
1
+ 10a603f649fd09bac5a0f2a58a2dcb7d88bba3135d6afce9175de4be22b4ff04
@@ -1 +1 @@
1
- abde90e836a99c9272468d5749616b4e8cc162fb1d56fc7d4e7cc62e6233feaffb9e3cbd00c2d7f1af598756ace51c63b55e7b388552a93c5aaf8bed81786bf3
1
+ 069b4d44f3ef79e389fa14e43ca394ecfb5008f0f52378844f9ce04cf8671160015250013fa9211b3ddf61b8361a731bd32cc70d9e71a9ed4db52dcd183147e8
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expo-video",
3
3
  "title": "Expo Video",
4
- "version": "2.2.1",
4
+ "version": "2.2.3",
5
5
  "description": "A cross-platform, performant video component for React Native and Expo with Web support",
6
6
  "main": "build/index.js",
7
7
  "types": "build/index.d.ts",
@@ -31,12 +31,12 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {},
33
33
  "devDependencies": {
34
- "expo-module-scripts": "^4.1.7"
34
+ "expo-module-scripts": "^4.1.10"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "expo": "*",
38
38
  "react": "*",
39
39
  "react-native": "*"
40
40
  },
41
- "gitHead": "d0b4f7c27a356e8c53bfe45db39fff9e96906a2e"
41
+ "gitHead": "47b1764000623d5fa34adb5f8593aee8e39ec8e9"
42
42
  }
@@ -1 +0,0 @@
1
- ff54e2221752092ebdece914b77e4504b2864fc94504c8a1887ea67a2afa5ea0
@@ -1 +0,0 @@
1
- c7c374ab88183e831ed5a80a5e7a6f9ef2d29d4157c7b9496e30a9f87b14c4952b1e867b29f8feedbab21badccdf1b751f3a20367d1970a5e814311d96618f0e
@@ -1 +0,0 @@
1
- 06203802abf1c39df7bef9bddb6cf7dda03e6300
@@ -1 +0,0 @@
1
- 0022fab08b59c4346e67a4bf573af546f4f0e7eb322aee1115335e035318fa2c
@@ -1 +0,0 @@
1
- a1501f7e947680eba85a049cf0520518bca4feca64832fbdc41383cb2c483f6253ecf8d429ad936966fcce1b0494175035399894dbce9c50f4765bd099edebf1
@@ -1 +0,0 @@
1
- be39ceedddcaaf953d676467b3927f50e7ed548b
@@ -1 +0,0 @@
1
- 53814f15e51286b6229b601265835761f6c64266002c3216fb933fda817efbb2
@@ -1 +0,0 @@
1
- 0954a801c43e9242b4db377ad2b306f55d0f85dea74e8cb11aa206ed7ff680d00ea98ed496496987c4ed87af271f01f16bfff9909ed713aec68321b9abb4704e
@@ -1 +0,0 @@
1
- 0e372e48b5871a6a0626cd97273f6c85e835f4a9
@@ -1 +0,0 @@
1
- b3296cfce3d47bb8a61f69e5faea5ce137f18fece4687b2e24310c974d0d97b5
@@ -1 +0,0 @@
1
- bbd813cc4afc824a0160cbc648f16b3261b5f223cc78cd29135188337d54c7928cd90c7ec13e87991b7de47c4a4fab6b96e8db1775bcc503ade8fbf3ac036570