expo-video 2.3.0-canary-20250722-599a28f → 3.0.0-canary-20250811-5c940c0

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 (56) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +30 -0
  4. package/android/src/main/java/expo/modules/video/VideoView.kt +51 -0
  5. package/android/src/main/java/expo/modules/video/utils/SubtitleUtils.kt +57 -0
  6. package/build/VideoView.types.d.ts +1 -1
  7. package/build/VideoView.types.js.map +1 -1
  8. package/build/VideoView.web.js +1 -1
  9. package/build/VideoView.web.js.map +1 -1
  10. package/expo-module.config.json +1 -1
  11. package/ios/VideoModule.swift +1 -0
  12. package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f-sources.jar → 3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0-sources.jar} +0 -0
  13. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0-sources.jar.md5 +1 -0
  14. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0-sources.jar.sha1 +1 -0
  15. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0-sources.jar.sha256 +1 -0
  16. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0-sources.jar.sha512 +1 -0
  17. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.aar +0 -0
  18. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.aar.md5 +1 -0
  19. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.aar.sha1 +1 -0
  20. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.aar.sha256 +1 -0
  21. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.aar.sha512 +1 -0
  22. package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.module → 3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.module} +22 -22
  23. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.module.md5 +1 -0
  24. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.module.sha1 +1 -0
  25. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.module.sha256 +1 -0
  26. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.module.sha512 +1 -0
  27. package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.pom → 3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.pom} +1 -1
  28. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.pom.md5 +1 -0
  29. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.pom.sha1 +1 -0
  30. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.pom.sha256 +1 -0
  31. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250811-5c940c0/expo.modules.video-3.0.0-canary-20250811-5c940c0.pom.sha512 +1 -0
  32. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml +4 -4
  33. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.md5 +1 -1
  34. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha1 +1 -1
  35. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha256 +1 -1
  36. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha512 +1 -1
  37. package/package.json +3 -3
  38. package/src/VideoView.types.ts +1 -1
  39. package/src/VideoView.web.tsx +1 -1
  40. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f-sources.jar.md5 +0 -1
  41. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f-sources.jar.sha1 +0 -1
  42. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f-sources.jar.sha256 +0 -1
  43. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f-sources.jar.sha512 +0 -1
  44. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.aar +0 -0
  45. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.aar.md5 +0 -1
  46. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.aar.sha1 +0 -1
  47. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.aar.sha256 +0 -1
  48. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.aar.sha512 +0 -1
  49. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.module.md5 +0 -1
  50. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.module.sha1 +0 -1
  51. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.module.sha256 +0 -1
  52. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.module.sha512 +0 -1
  53. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.pom.md5 +0 -1
  54. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.pom.sha1 +0 -1
  55. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.pom.sha256 +0 -1
  56. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250722-599a28f/expo.modules.video-2.3.0-canary-20250722-599a28f.pom.sha512 +0 -1
package/CHANGELOG.md CHANGED
@@ -4,10 +4,13 @@
4
4
 
5
5
  ### 🛠 Breaking changes
6
6
 
7
+ - [web] Update default crossOrigin value to "anonymous" to prevent common CORS issues ([#38341](https://github.com/expo/expo/pull/38341) by [@hirbod](https://github.com/hirbod))
8
+
7
9
  ### 🎉 New features
8
10
 
9
11
  - [iOS] Add complete support for AirPlay streaming. Add a device selection button and `VideoPlayer.isExternalPlaybackActive` property and appropriate listeners. ([#37207](https://github.com/expo/expo/pull/37207) by [@behenate](https://github.com/behenate))
10
12
  - Add fullscreen orientation and auto-exit functionality. ([#36910](https://github.com/expo/expo/pull/36910) by [@behenate](https://github.com/behenate))
13
+ - [Android] React to system wide subtitle changes in real time, does not require an app reload ([#38591](https://github.com/expo/expo/pull/38591) by [@hirbod](https://github.com/hirbod))
11
14
 
12
15
  ### 🐛 Bug fixes
13
16
 
@@ -15,6 +18,8 @@
15
18
  - [Android] Fix accessing player.loop causes app to crash. ([#37928](https://github.com/expo/expo/pull/37928) by [@Wenszel](https://github.com/Wenszel))
16
19
  - [iOS] Setting `player.currentTime` doesn't seek to the correct time on some videos. ([#37672](https://github.com/expo/expo/pull/37300) by [@petrkonecny2](https://github.com/petrkonecny2))
17
20
  - [iOS] Fix tvOS compilation errors. ([#38085](https://github.com/expo/expo/pull/38085) by [@douglowder](https://github.com/douglowder))
21
+ - [Android] Fix: Respect accessibility settings for HLS subtitle sizing and system settings, added listener ([#38591](https://github.com/expo/expo/pull/38591) by [@hirbod](https://github.com/hirbod))
22
+ - [iOS] Fix inconsistent behavior of "replay" on iOS by calling "play()" after seeking to position 0. ([#38590](https://github.com/expo/expo/pull/38590)) by [@saviocmc](https://github.com/saviocmc)
18
23
 
19
24
  ### 💡 Others
20
25
 
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '2.3.0-canary-20250722-599a28f'
7
+ version = '3.0.0-canary-20250811-5c940c0'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.video"
11
11
  defaultConfig {
12
12
  versionCode 1
13
- versionName '2.3.0-canary-20250722-599a28f'
13
+ versionName '3.0.0-canary-20250811-5c940c0'
14
14
  }
15
15
  }
16
16
 
@@ -5,8 +5,10 @@ import android.content.pm.ActivityInfo
5
5
  import android.content.res.Configuration
6
6
  import android.os.Build
7
7
  import android.os.Bundle
8
+ import android.content.Context
8
9
  import android.util.Log
9
10
  import android.view.View
11
+ import android.view.accessibility.CaptioningManager
10
12
  import android.view.WindowInsets
11
13
  import android.view.WindowInsetsController
12
14
  import android.widget.ImageButton
@@ -15,6 +17,7 @@ import expo.modules.kotlin.exception.CodedException
15
17
  import expo.modules.video.player.VideoPlayer
16
18
  import expo.modules.video.records.FullscreenOptions
17
19
  import expo.modules.video.utils.FullscreenActivityOrientationHelper
20
+ import expo.modules.video.utils.SubtitleUtils
18
21
  import expo.modules.video.utils.applyPiPParams
19
22
  import expo.modules.video.utils.applyRectHint
20
23
  import expo.modules.video.utils.calculatePiPAspectRatio
@@ -31,6 +34,7 @@ class FullscreenPlayerActivity : Activity() {
31
34
  private var wasAutoPaused = false
32
35
  private lateinit var options: FullscreenOptions
33
36
  private lateinit var orientationHelper: FullscreenActivityOrientationHelper
37
+ private var captioningChangeListener: CaptioningManager.CaptioningChangeListener? = null
34
38
 
35
39
  override fun onCreate(savedInstanceState: Bundle?) {
36
40
  super.onCreate(savedInstanceState)
@@ -93,6 +97,12 @@ class FullscreenPlayerActivity : Activity() {
93
97
  }
94
98
  playerView.setShowSubtitleButton(videoView.showsSubtitlesButton)
95
99
 
100
+ // Configure subtitle view to fix sizing issues with embedded styles (same as VideoView)
101
+ SubtitleUtils.configureSubtitleView(playerView, this)
102
+
103
+ // Set up listener for accessibility caption changes
104
+ setupCaptioningChangeListener()
105
+
96
106
  playerView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
97
107
  applyRectHint(this, calculateRectHint(playerView))
98
108
  }
@@ -115,6 +125,8 @@ class FullscreenPlayerActivity : Activity() {
115
125
  override fun onResume() {
116
126
  orientationHelper.startOrientationEventListener()
117
127
  playerView.useController = videoView.useNativeControls
128
+ // Reconfigure subtitles when resuming (handles returning from settings)
129
+ SubtitleUtils.configureSubtitleView(playerView, this)
118
130
  super.onResume()
119
131
  }
120
132
 
@@ -132,6 +144,14 @@ class FullscreenPlayerActivity : Activity() {
132
144
 
133
145
  override fun onDestroy() {
134
146
  super.onDestroy()
147
+
148
+ // Clean up captioning change listener
149
+ captioningChangeListener?.let {
150
+ val captioningManager = getSystemService(Context.CAPTIONING_SERVICE) as? CaptioningManager
151
+ captioningManager?.removeCaptioningChangeListener(it)
152
+ captioningChangeListener = null
153
+ }
154
+
135
155
  videoView.exitFullscreen()
136
156
  VideoManager.unregisterFullscreenPlayerActivity(hashCode().toString())
137
157
  orientationHelper.stopOrientationEventListener()
@@ -176,6 +196,16 @@ class FullscreenPlayerActivity : Activity() {
176
196
  }
177
197
  }
178
198
 
199
+ private fun setupCaptioningChangeListener() {
200
+ val captioningManager = getSystemService(Context.CAPTIONING_SERVICE) as? CaptioningManager
201
+
202
+ captioningChangeListener = SubtitleUtils.createCaptioningChangeListener(playerView, this)
203
+
204
+ captioningChangeListener?.let { listener ->
205
+ captioningManager?.addCaptioningChangeListener(listener)
206
+ }
207
+ }
208
+
179
209
  override fun onConfigurationChanged(newConfig: Configuration) {
180
210
  super.onConfigurationChanged(newConfig)
181
211
  orientationHelper.onConfigurationChanged(newConfig)
@@ -7,6 +7,7 @@ import android.content.Intent
7
7
  import android.graphics.Color
8
8
  import android.os.Build
9
9
  import android.util.Rational
10
+ import android.view.accessibility.CaptioningManager
10
11
  import android.view.LayoutInflater
11
12
  import android.view.MotionEvent
12
13
  import android.view.View
@@ -34,6 +35,7 @@ import expo.modules.video.records.VideoSource
34
35
  import expo.modules.video.records.VideoTrack
35
36
  import expo.modules.video.utils.applyPiPParams
36
37
  import expo.modules.video.records.FullscreenOptions
38
+ import expo.modules.video.utils.SubtitleUtils
37
39
  import expo.modules.video.utils.applyRectHint
38
40
  import expo.modules.video.utils.calculatePiPAspectRatio
39
41
  import expo.modules.video.utils.calculateRectHint
@@ -73,6 +75,14 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
73
75
  private val rootViewChildrenOriginalVisibility: ArrayList<Int> = arrayListOf()
74
76
  private var pictureInPictureHelperTag: String? = null
75
77
  private var reactNativeEventDispatcher: EventDispatcher? = null
78
+ private var captioningChangeListener: CaptioningManager.CaptioningChangeListener? = null
79
+
80
+ private val windowFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
81
+ if (hasFocus) {
82
+ // Reconfigure when window gains focus (returning from settings)
83
+ SubtitleUtils.configureSubtitleView(playerView, context)
84
+ }
85
+ }
76
86
 
77
87
  // We need to keep track of the target surface view visibility, but only apply it when `useExoShutter` is false.
78
88
  var shouldHideSurfaceView: Boolean = true
@@ -162,6 +172,9 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
162
172
  // Start with the SurfaceView being transparent to avoid any flickers when the prop value is delivered.
163
173
  this.playerView.setShutterBackgroundColor(Color.TRANSPARENT)
164
174
  this.playerView.videoSurfaceView?.alpha = 0f
175
+
176
+ // Configure subtitle view to fix sizing issues with embedded styles
177
+ SubtitleUtils.configureSubtitleView(playerView, context)
165
178
  addView(
166
179
  playerView,
167
180
  ViewGroup.LayoutParams(
@@ -319,9 +332,26 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
319
332
  .add(fragment, fragment.id)
320
333
  .commitAllowingStateLoss()
321
334
  }
335
+
336
+ // Set up listener for accessibility caption changes when attached to window
337
+ setupCaptioningChangeListener()
338
+ // Reconfigure when view is attached (handles returning from settings)
339
+ SubtitleUtils.configureSubtitleView(playerView, context)
340
+
341
+ // Set up window focus change listener
342
+ decorView.onFocusChangeListener = windowFocusChangeListener
343
+
322
344
  applyPiPParams(currentActivity, autoEnterPiP)
323
345
  }
324
346
 
347
+ override fun onVisibilityChanged(changedView: View, visibility: Int) {
348
+ super.onVisibilityChanged(changedView, visibility)
349
+ if (visibility == View.VISIBLE) {
350
+ // Reconfigure subtitles when view becomes visible (immediate response)
351
+ SubtitleUtils.configureSubtitleView(playerView, context)
352
+ }
353
+ }
354
+
325
355
  override fun onDetachedFromWindow() {
326
356
  super.onDetachedFromWindow()
327
357
  (currentActivity as? FragmentActivity)?.let {
@@ -331,6 +361,17 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
331
361
  .remove(fragment)
332
362
  .commitAllowingStateLoss()
333
363
  }
364
+
365
+ // Clean up captioning change listener
366
+ captioningChangeListener?.let {
367
+ val captioningManager = context.getSystemService(Context.CAPTIONING_SERVICE) as? CaptioningManager
368
+ captioningManager?.removeCaptioningChangeListener(it)
369
+ captioningChangeListener = null
370
+ }
371
+
372
+ // Clean up window focus listener
373
+ decorView.onFocusChangeListener = null
374
+
334
375
  applyPiPParams(currentActivity, false)
335
376
  }
336
377
 
@@ -370,6 +411,16 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
370
411
  }
371
412
  }
372
413
 
414
+ private fun setupCaptioningChangeListener() {
415
+ val captioningManager = context.getSystemService(Context.CAPTIONING_SERVICE) as? CaptioningManager
416
+
417
+ captioningChangeListener = SubtitleUtils.createCaptioningChangeListener(playerView, context)
418
+
419
+ captioningChangeListener?.let { listener ->
420
+ captioningManager?.addCaptioningChangeListener(listener)
421
+ }
422
+ }
423
+
373
424
  companion object {
374
425
  fun isPictureInPictureSupported(currentActivity: Activity): Boolean {
375
426
  return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && currentActivity.packageManager.hasSystemFeature(
@@ -0,0 +1,57 @@
1
+ package expo.modules.video.utils
2
+
3
+ import android.content.Context
4
+ import android.util.TypedValue
5
+ import android.view.accessibility.CaptioningManager
6
+ import androidx.media3.ui.CaptionStyleCompat
7
+ import androidx.media3.ui.PlayerView
8
+
9
+ object SubtitleUtils {
10
+ /**
11
+ * Configures the PlayerView's subtitle view to fix sizing issues with embedded styles.
12
+ * Disables embedded HLS subtitle styling and applies Android accessibility settings
13
+ * with a reasonable base font size.
14
+ */
15
+ fun configureSubtitleView(playerView: PlayerView, context: Context) {
16
+ playerView.subtitleView?.apply {
17
+ setApplyEmbeddedStyles(false)
18
+ setApplyEmbeddedFontSizes(false)
19
+
20
+ // Apply system accessibility caption style but with reasonable base font size
21
+ val captioningManager = context.getSystemService(Context.CAPTIONING_SERVICE) as? CaptioningManager
22
+ val userStyle = captioningManager?.userStyle
23
+
24
+ if (userStyle != null) {
25
+ val systemStyle = CaptionStyleCompat.createFromCaptionStyle(userStyle)
26
+ setStyle(systemStyle)
27
+
28
+ val fontScale = captioningManager.fontScale
29
+ val baseFontSize = 16f // Reasonable base size
30
+ setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, baseFontSize * fontScale)
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Creates a CaptioningChangeListener that reconfigures subtitles when accessibility
37
+ * settings change.
38
+ */
39
+ fun createCaptioningChangeListener(
40
+ playerView: PlayerView,
41
+ context: Context
42
+ ): CaptioningManager.CaptioningChangeListener {
43
+ return object : CaptioningManager.CaptioningChangeListener() {
44
+ override fun onEnabledChanged(enabled: Boolean) {
45
+ configureSubtitleView(playerView, context)
46
+ }
47
+
48
+ override fun onUserStyleChanged(userStyle: android.view.accessibility.CaptioningManager.CaptionStyle) {
49
+ configureSubtitleView(playerView, context)
50
+ }
51
+
52
+ override fun onFontScaleChanged(fontScale: Float) {
53
+ configureSubtitleView(playerView, context)
54
+ }
55
+ }
56
+ }
57
+ }
@@ -146,7 +146,7 @@ export interface VideoViewProps extends ViewProps {
146
146
  * If undefined, does not use CORS at all.
147
147
  *
148
148
  * @platform web
149
- * @default undefined
149
+ * @default 'anonymous'
150
150
  */
151
151
  crossOrigin?: 'anonymous' | 'use-credentials';
152
152
  }
@@ -1 +1 @@
1
- {"version":3,"file":"VideoView.types.js","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"","sourcesContent":["import { ViewProps } from 'react-native';\n\nimport type { VideoPlayer } from './VideoPlayer.types';\n\n/**\n * Describes how a video should be scaled to fit in a container.\n * - `contain`: The video maintains its aspect ratio and fits inside the container, with possible letterboxing/pillarboxing.\n * - `cover`: The video maintains its aspect ratio and covers the entire container, potentially cropping some portions.\n * - `fill`: The video stretches/squeezes to completely fill the container, potentially causing distortion.\n */\nexport type VideoContentFit = 'contain' | 'cover' | 'fill';\n\n/**\n * Describes the type of the surface used to render the video.\n * - `surfaceView`: Uses the `SurfaceView` to render the video. This value should be used in the majority of cases. Provides significantly lower power consumption, better performance, and more features.\n * - `textureView`: Uses the `TextureView` to render the video. Should be used in cases where the SurfaceView is not supported or causes issues (for example, overlapping video views).\n *\n * You can learn more about surface types in the official [ExoPlayer documentation](https://developer.android.com/media/media3/ui/playerview#surfacetype).\n * @platform android\n */\nexport type SurfaceType = 'textureView' | 'surfaceView';\n\nexport interface VideoViewProps extends ViewProps {\n /**\n * A video player instance. Use [`useVideoPlayer()`](#usevideoplayersource-setup) hook to create one.\n */\n player: VideoPlayer;\n\n /**\n * Determines whether native controls should be displayed or not.\n * @default true\n */\n nativeControls?: boolean;\n\n /**\n * Describes how the video should be scaled to fit in the container.\n * Options are `'contain'`, `'cover'`, and `'fill'`.\n * @default 'contain'\n */\n contentFit?: VideoContentFit;\n\n /**\n * Determines whether fullscreen mode is allowed or not.\n *\n * > Note: This option has been deprecated in favor of the `fullscreenOptions` prop and will be disabled in the future.\n * @default true\n */\n allowsFullscreen?: boolean;\n\n /**\n * Determines the fullscreen mode options.\n */\n fullscreenOptions?: FullscreenOptions;\n\n /**\n * Determines whether the timecodes should be displayed or not.\n * @default true\n * @platform ios\n */\n showsTimecodes?: boolean;\n\n /**\n * Determines whether the player allows the user to skip media content.\n * @default false\n * @platform android\n * @platform ios\n */\n requiresLinearPlayback?: boolean;\n\n /**\n * Determines the type of the surface used to render the video.\n * > This prop should not be changed at runtime.\n * @default 'surfaceView'\n * @platform android\n */\n surfaceType?: SurfaceType;\n\n /**\n * Determines the position offset of the video inside the container.\n * @default { dx: 0, dy: 0 }\n * @platform ios\n */\n contentPosition?: { dx?: number; dy?: number };\n\n /**\n * A callback to call after the video player enters Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios\n * @platform web\n */\n onPictureInPictureStart?: () => void;\n\n /**\n * A callback to call after the video player exits Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios\n * @platform web\n */\n onPictureInPictureStop?: () => void;\n\n /**\n * Determines whether the player allows Picture in Picture (PiP) mode.\n * > **Note:** The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config)\n * > has to be configured for the PiP to work.\n * @platform android\n * @platform ios\n * @platform web\n */\n allowsPictureInPicture?: boolean;\n\n /**\n * Determines whether a video should be played \"inline\", that is, within the element's playback area.\n * @platform web\n */\n playsInline?: boolean;\n\n /**\n * Determines whether the player should start Picture in Picture (PiP) automatically when the app is in the background.\n * > **Note:** Only one player can be in Picture in Picture (PiP) mode at a time.\n *\n * > **Note:** The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config)\n * > has to be configured for the PiP to work.\n *\n * @default false\n * @platform android 12+\n * @platform ios\n */\n startsPictureInPictureAutomatically?: boolean;\n\n /**\n * Specifies whether to perform video frame analysis (Live Text in videos).\n * Check official [Apple documentation](https://developer.apple.com/documentation/avkit/avplayerviewcontroller/allowsvideoframeanalysis) for more details.\n * @default true\n * @platform ios 16.0+\n */\n allowsVideoFrameAnalysis?: boolean;\n\n /**\n * A callback to call after the video player enters fullscreen mode.\n */\n onFullscreenEnter?: () => void;\n\n /**\n * A callback to call after the video player exits fullscreen mode.\n */\n onFullscreenExit?: () => void;\n\n /**\n * A callback to call after the mounted `VideoPlayer` has rendered the first frame into the `VideoView`.\n * This event can be used to hide any cover images that conceal the initial loading of the player.\n * > **Note:** This event may also be called during playback when the current video track changes (for example when the player switches video quality).\n */\n onFirstFrameRender?: () => void;\n\n /**\n * Determines whether the player should use the default ExoPlayer shutter that covers the `VideoView` before the first video frame is rendered.\n * Setting this property to `false` makes the Android behavior the same as iOS.\n *\n * @platform android\n * @default false\n */\n useExoShutter?: boolean;\n\n /**\n * Determines the [cross origin policy](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/crossorigin) used by the underlying native view on web.\n * If undefined, does not use CORS at all.\n *\n * @platform web\n * @default undefined\n */\n crossOrigin?: 'anonymous' | 'use-credentials';\n}\n\n/**\n * Describes the orientation of the video in fullscreen mode. Available values are:\n * - `default`: The video is displayed in any of the available device rotations.\n * - `portrait`: The video is displayed in one of two available portrait orientations and rotates between them.\n * - `portraitUp`: The video is displayed in the portrait orientation - the notch of the phone points upwards.\n * - `portraitDown`: The video is displayed in the portrait orientation - the notch of the phone points downwards.\n * - `landscape`: The video is displayed in one of two available landscape orientations and rotates between them.\n * - `landscapeLeft`: The video is displayed in the left landscape orientation - the notch of the phone is in the left palm of the user.\n * - `landscapeRight`: The video is displayed in the right landscape orientation - the notch of the phone is in the right palm of the user.\n */\nexport type FullscreenOrientation =\n | 'default'\n | 'portrait'\n | 'portraitUp'\n | 'portraitDown'\n | 'landscape'\n | 'landscapeLeft'\n | 'landscapeRight';\n\n/**\n * Describes the options for fullscreen video mode.\n */\nexport type FullscreenOptions = {\n /**\n * Specifies whether the fullscreen mode should be available to the user. When `false`, the fullscreen button will be hidden in the player.\n * Equivalent to the `allowsFullscreen` prop.\n * @default true\n */\n enable: boolean;\n /**\n * Specifies the orientation of the video in fullscreen mode.\n * @default 'default'\n * @platform android\n * @platform ios\n */\n orientation?: FullscreenOrientation;\n /**\n * Specifies whether the app should exit fullscreen mode when the device is rotated to a different orientation than the one specified in the `orientation` prop.\n * For example, if the `orientation` prop is set to `landscape` and the device is rotated to `portrait`, the app will exit fullscreen mode.\n *\n * > This prop will have no effect if the `orientation` prop is set to `default`.\n * > The `VideoView` will never auto-exit fullscreen when the device auto-rotate feature has been disabled in settings.\n *\n * @default false\n * @platform android\n * @platform ios\n */\n autoExitOnRotate?: boolean;\n};\n"]}
1
+ {"version":3,"file":"VideoView.types.js","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"","sourcesContent":["import { ViewProps } from 'react-native';\n\nimport type { VideoPlayer } from './VideoPlayer.types';\n\n/**\n * Describes how a video should be scaled to fit in a container.\n * - `contain`: The video maintains its aspect ratio and fits inside the container, with possible letterboxing/pillarboxing.\n * - `cover`: The video maintains its aspect ratio and covers the entire container, potentially cropping some portions.\n * - `fill`: The video stretches/squeezes to completely fill the container, potentially causing distortion.\n */\nexport type VideoContentFit = 'contain' | 'cover' | 'fill';\n\n/**\n * Describes the type of the surface used to render the video.\n * - `surfaceView`: Uses the `SurfaceView` to render the video. This value should be used in the majority of cases. Provides significantly lower power consumption, better performance, and more features.\n * - `textureView`: Uses the `TextureView` to render the video. Should be used in cases where the SurfaceView is not supported or causes issues (for example, overlapping video views).\n *\n * You can learn more about surface types in the official [ExoPlayer documentation](https://developer.android.com/media/media3/ui/playerview#surfacetype).\n * @platform android\n */\nexport type SurfaceType = 'textureView' | 'surfaceView';\n\nexport interface VideoViewProps extends ViewProps {\n /**\n * A video player instance. Use [`useVideoPlayer()`](#usevideoplayersource-setup) hook to create one.\n */\n player: VideoPlayer;\n\n /**\n * Determines whether native controls should be displayed or not.\n * @default true\n */\n nativeControls?: boolean;\n\n /**\n * Describes how the video should be scaled to fit in the container.\n * Options are `'contain'`, `'cover'`, and `'fill'`.\n * @default 'contain'\n */\n contentFit?: VideoContentFit;\n\n /**\n * Determines whether fullscreen mode is allowed or not.\n *\n * > Note: This option has been deprecated in favor of the `fullscreenOptions` prop and will be disabled in the future.\n * @default true\n */\n allowsFullscreen?: boolean;\n\n /**\n * Determines the fullscreen mode options.\n */\n fullscreenOptions?: FullscreenOptions;\n\n /**\n * Determines whether the timecodes should be displayed or not.\n * @default true\n * @platform ios\n */\n showsTimecodes?: boolean;\n\n /**\n * Determines whether the player allows the user to skip media content.\n * @default false\n * @platform android\n * @platform ios\n */\n requiresLinearPlayback?: boolean;\n\n /**\n * Determines the type of the surface used to render the video.\n * > This prop should not be changed at runtime.\n * @default 'surfaceView'\n * @platform android\n */\n surfaceType?: SurfaceType;\n\n /**\n * Determines the position offset of the video inside the container.\n * @default { dx: 0, dy: 0 }\n * @platform ios\n */\n contentPosition?: { dx?: number; dy?: number };\n\n /**\n * A callback to call after the video player enters Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios\n * @platform web\n */\n onPictureInPictureStart?: () => void;\n\n /**\n * A callback to call after the video player exits Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios\n * @platform web\n */\n onPictureInPictureStop?: () => void;\n\n /**\n * Determines whether the player allows Picture in Picture (PiP) mode.\n * > **Note:** The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config)\n * > has to be configured for the PiP to work.\n * @platform android\n * @platform ios\n * @platform web\n */\n allowsPictureInPicture?: boolean;\n\n /**\n * Determines whether a video should be played \"inline\", that is, within the element's playback area.\n * @platform web\n */\n playsInline?: boolean;\n\n /**\n * Determines whether the player should start Picture in Picture (PiP) automatically when the app is in the background.\n * > **Note:** Only one player can be in Picture in Picture (PiP) mode at a time.\n *\n * > **Note:** The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config)\n * > has to be configured for the PiP to work.\n *\n * @default false\n * @platform android 12+\n * @platform ios\n */\n startsPictureInPictureAutomatically?: boolean;\n\n /**\n * Specifies whether to perform video frame analysis (Live Text in videos).\n * Check official [Apple documentation](https://developer.apple.com/documentation/avkit/avplayerviewcontroller/allowsvideoframeanalysis) for more details.\n * @default true\n * @platform ios 16.0+\n */\n allowsVideoFrameAnalysis?: boolean;\n\n /**\n * A callback to call after the video player enters fullscreen mode.\n */\n onFullscreenEnter?: () => void;\n\n /**\n * A callback to call after the video player exits fullscreen mode.\n */\n onFullscreenExit?: () => void;\n\n /**\n * A callback to call after the mounted `VideoPlayer` has rendered the first frame into the `VideoView`.\n * This event can be used to hide any cover images that conceal the initial loading of the player.\n * > **Note:** This event may also be called during playback when the current video track changes (for example when the player switches video quality).\n */\n onFirstFrameRender?: () => void;\n\n /**\n * Determines whether the player should use the default ExoPlayer shutter that covers the `VideoView` before the first video frame is rendered.\n * Setting this property to `false` makes the Android behavior the same as iOS.\n *\n * @platform android\n * @default false\n */\n useExoShutter?: boolean;\n\n /**\n * Determines the [cross origin policy](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/crossorigin) used by the underlying native view on web.\n * If undefined, does not use CORS at all.\n *\n * @platform web\n * @default 'anonymous'\n */\n crossOrigin?: 'anonymous' | 'use-credentials';\n}\n\n/**\n * Describes the orientation of the video in fullscreen mode. Available values are:\n * - `default`: The video is displayed in any of the available device rotations.\n * - `portrait`: The video is displayed in one of two available portrait orientations and rotates between them.\n * - `portraitUp`: The video is displayed in the portrait orientation - the notch of the phone points upwards.\n * - `portraitDown`: The video is displayed in the portrait orientation - the notch of the phone points downwards.\n * - `landscape`: The video is displayed in one of two available landscape orientations and rotates between them.\n * - `landscapeLeft`: The video is displayed in the left landscape orientation - the notch of the phone is in the left palm of the user.\n * - `landscapeRight`: The video is displayed in the right landscape orientation - the notch of the phone is in the right palm of the user.\n */\nexport type FullscreenOrientation =\n | 'default'\n | 'portrait'\n | 'portraitUp'\n | 'portraitDown'\n | 'landscape'\n | 'landscapeLeft'\n | 'landscapeRight';\n\n/**\n * Describes the options for fullscreen video mode.\n */\nexport type FullscreenOptions = {\n /**\n * Specifies whether the fullscreen mode should be available to the user. When `false`, the fullscreen button will be hidden in the player.\n * Equivalent to the `allowsFullscreen` prop.\n * @default true\n */\n enable: boolean;\n /**\n * Specifies the orientation of the video in fullscreen mode.\n * @default 'default'\n * @platform android\n * @platform ios\n */\n orientation?: FullscreenOrientation;\n /**\n * Specifies whether the app should exit fullscreen mode when the device is rotated to a different orientation than the one specified in the `orientation` prop.\n * For example, if the `orientation` prop is set to `landscape` and the device is rotated to `portrait`, the app will exit fullscreen mode.\n *\n * > This prop will have no effect if the `orientation` prop is set to `default`.\n * > The `VideoView` will never auto-exit fullscreen when the device auto-rotate feature has been disabled in settings.\n *\n * @default false\n * @platform android\n * @platform ios\n */\n autoExitOnRotate?: boolean;\n};\n"]}
@@ -156,7 +156,7 @@ export const VideoView = forwardRef((props, ref) => {
156
156
  detachAudioNodes();
157
157
  };
158
158
  }, [props.player]);
159
- return (<video controls={props.nativeControls ?? true} controlsList={props.allowsFullscreen ? undefined : 'nofullscreen'} crossOrigin={props.crossOrigin} style={{
159
+ return (<video controls={props.nativeControls ?? true} controlsList={props.allowsFullscreen ? undefined : 'nofullscreen'} crossOrigin={props.crossOrigin ?? 'anonymous'} style={{
160
160
  ...mapStyles(props.style),
161
161
  objectFit: props.contentFit,
162
162
  }} onPlay={() => {
@@ -1 +1 @@
1
- {"version":3,"file":"VideoView.web.js","sourceRoot":"","sources":["../src/VideoView.web.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';\nimport { StyleSheet } from 'react-native';\n\nimport VideoPlayer, { getSourceUri } from './VideoPlayer.web';\nimport type { VideoViewProps } from './VideoView.types';\n\nfunction createAudioContext(): AudioContext | null {\n return typeof window !== 'undefined' ? new window.AudioContext() : null;\n}\n\nfunction createZeroGainNode(audioContext: AudioContext | null): GainNode | null {\n const zeroGainNode = audioContext?.createGain() ?? null;\n\n if (audioContext && zeroGainNode) {\n zeroGainNode.gain.value = 0;\n zeroGainNode.connect(audioContext.destination);\n }\n return zeroGainNode;\n}\n\nfunction mapStyles(style: VideoViewProps['style']): React.CSSProperties {\n const flattenedStyles = StyleSheet.flatten(style);\n // Looking through react-native-web source code they also just pass styles directly without further conversions, so it's just a cast.\n return flattenedStyles as React.CSSProperties;\n}\n\nexport function isPictureInPictureSupported(): boolean {\n return typeof document === 'object' && typeof document.exitPictureInPicture === 'function';\n}\n\nexport const VideoView = forwardRef((props: { player?: VideoPlayer } & VideoViewProps, ref) => {\n const videoRef = useRef<null | HTMLVideoElement>(null);\n const mediaNodeRef = useRef<null | MediaElementAudioSourceNode>(null);\n const hasToSetupAudioContext = useRef(false);\n const fullscreenChangeListener = useRef<null | (() => void)>(null);\n const isWaitingForFirstFrame = useRef(false);\n\n /**\n * Audio context is used to mute all but one video when multiple video views are playing from one player simultaneously.\n * Using audio context nodes allows muting videos without displaying the mute icon in the video player.\n * We have to keep the context that called createMediaElementSource(videoRef), as the method can't be called\n * for the second time with another context and there is no way to unbind the video and audio context afterward.\n */\n const audioContextRef = useRef<null | AudioContext>(null);\n const zeroGainNodeRef = useRef<null | GainNode>(null);\n\n useImperativeHandle(ref, () => ({\n enterFullscreen: async () => {\n if (!props.allowsFullscreen) {\n return;\n }\n await videoRef.current?.requestFullscreen();\n },\n exitFullscreen: async () => {\n await document.exitFullscreen();\n },\n startPictureInPicture: async () => {\n await videoRef.current?.requestPictureInPicture();\n },\n stopPictureInPicture: async () => {\n try {\n await document.exitPictureInPicture();\n } catch (e) {\n if (e instanceof DOMException && e.name === 'InvalidStateError') {\n console.warn('The VideoView is not in Picture-in-Picture mode.');\n } else {\n throw e;\n }\n }\n },\n }));\n\n useEffect(() => {\n const onEnter = () => {\n props.onPictureInPictureStart?.();\n };\n const onLeave = () => {\n props.onPictureInPictureStop?.();\n };\n const onLoadStart = () => {\n isWaitingForFirstFrame.current = true;\n };\n const onCanPlay = () => {\n if (isWaitingForFirstFrame.current) {\n props.onFirstFrameRender?.();\n }\n isWaitingForFirstFrame.current = false;\n };\n videoRef.current?.addEventListener('enterpictureinpicture', onEnter);\n videoRef.current?.addEventListener('leavepictureinpicture', onLeave);\n videoRef.current?.addEventListener('loadstart', onLoadStart);\n videoRef.current?.addEventListener('loadeddata', onCanPlay);\n\n return () => {\n videoRef.current?.removeEventListener('enterpictureinpicture', onEnter);\n videoRef.current?.removeEventListener('leavepictureinpicture', onLeave);\n videoRef.current?.removeEventListener('loadstart', onLoadStart);\n videoRef.current?.removeEventListener('loadeddata', onCanPlay);\n };\n }, [videoRef, props.onPictureInPictureStop, props.onPictureInPictureStart]);\n\n // Adds the video view as a candidate for being the audio source for the player (when multiple views play from one\n // player only one will emit audio).\n function attachAudioNodes() {\n const audioContext = audioContextRef.current;\n const zeroGainNode = zeroGainNodeRef.current;\n const mediaNode = mediaNodeRef.current;\n\n if (audioContext && zeroGainNode && mediaNode) {\n props.player.mountAudioNode(audioContext, zeroGainNode, mediaNode);\n } else {\n console.warn(\n \"Couldn't mount audio node, this might affect the audio playback when using multiple video views with the same player.\"\n );\n }\n }\n\n function detachAudioNodes() {\n const audioContext = audioContextRef.current;\n const mediaNode = mediaNodeRef.current;\n if (audioContext && mediaNode && videoRef.current) {\n props.player.unmountAudioNode(videoRef.current, audioContext, mediaNode);\n }\n }\n\n function maybeSetupAudioContext() {\n if (\n !hasToSetupAudioContext.current ||\n !navigator.userActivation.hasBeenActive ||\n !videoRef.current\n ) {\n return;\n }\n const audioContext = createAudioContext();\n\n detachAudioNodes();\n audioContextRef.current = audioContext;\n zeroGainNodeRef.current = createZeroGainNode(audioContextRef.current);\n mediaNodeRef.current = audioContext\n ? audioContext.createMediaElementSource(videoRef.current)\n : null;\n attachAudioNodes();\n hasToSetupAudioContext.current = false;\n }\n\n function fullscreenListener() {\n if (document.fullscreenElement === videoRef.current) {\n props.onFullscreenEnter?.();\n } else {\n props.onFullscreenExit?.();\n }\n }\n\n function setupFullscreenListener() {\n fullscreenChangeListener.current = fullscreenListener;\n videoRef.current?.addEventListener('fullscreenchange', fullscreenChangeListener.current);\n }\n\n function cleanupFullscreenListener() {\n if (fullscreenChangeListener.current) {\n videoRef.current?.removeEventListener('fullscreenchange', fullscreenChangeListener.current);\n fullscreenChangeListener.current = null;\n }\n }\n\n useEffect(() => {\n if (videoRef.current) {\n props.player?.mountVideoView(videoRef.current);\n }\n setupFullscreenListener();\n attachAudioNodes();\n\n return () => {\n if (videoRef.current) {\n props.player?.unmountVideoView(videoRef.current);\n }\n cleanupFullscreenListener();\n detachAudioNodes();\n };\n }, [props.player]);\n\n return (\n <video\n controls={props.nativeControls ?? true}\n controlsList={props.allowsFullscreen ? undefined : 'nofullscreen'}\n crossOrigin={props.crossOrigin}\n style={{\n ...mapStyles(props.style),\n objectFit: props.contentFit,\n }}\n onPlay={() => {\n maybeSetupAudioContext();\n }}\n // The player can autoplay when muted, unmuting by a user should create the audio context\n onVolumeChange={() => {\n maybeSetupAudioContext();\n }}\n ref={(newRef) => {\n // This is called with a null value before `player.unmountVideoView` is called,\n // we can't assign null to videoRef if we want to unmount it from the player.\n if (newRef && !newRef.isEqualNode(videoRef.current)) {\n videoRef.current = newRef;\n hasToSetupAudioContext.current = true;\n maybeSetupAudioContext();\n }\n }}\n disablePictureInPicture={!props.allowsPictureInPicture}\n playsInline={props.playsInline}\n src={getSourceUri(props.player?.src) ?? ''}\n />\n );\n});\n\nexport default VideoView;\n"]}
1
+ {"version":3,"file":"VideoView.web.js","sourceRoot":"","sources":["../src/VideoView.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAoB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAG9D,SAAS,kBAAkB;IACzB,OAAO,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1E,CAAC;AAED,SAAS,kBAAkB,CAAC,YAAiC;IAC3D,MAAM,YAAY,GAAG,YAAY,EAAE,UAAU,EAAE,IAAI,IAAI,CAAC;IAExD,IAAI,YAAY,IAAI,YAAY,EAAE,CAAC;QACjC,YAAY,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QAC5B,YAAY,CAAC,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,SAAS,CAAC,KAA8B;IAC/C,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAClD,qIAAqI;IACrI,OAAO,eAAsC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,2BAA2B;IACzC,OAAO,OAAO,QAAQ,KAAK,QAAQ,IAAI,OAAO,QAAQ,CAAC,oBAAoB,KAAK,UAAU,CAAC;AAC7F,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,KAAgD,EAAE,GAAG,EAAE,EAAE;IAC5F,MAAM,QAAQ,GAAG,MAAM,CAA0B,IAAI,CAAC,CAAC;IACvD,MAAM,YAAY,GAAG,MAAM,CAAqC,IAAI,CAAC,CAAC;IACtE,MAAM,sBAAsB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,wBAAwB,GAAG,MAAM,CAAsB,IAAI,CAAC,CAAC;IACnE,MAAM,sBAAsB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE7C;;;;;OAKG;IACH,MAAM,eAAe,GAAG,MAAM,CAAsB,IAAI,CAAC,CAAC;IAC1D,MAAM,eAAe,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAC;IAEtD,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9B,eAAe,EAAE,KAAK,IAAI,EAAE;YAC1B,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC;gBAC5B,OAAO;YACT,CAAC;YACD,MAAM,QAAQ,CAAC,OAAO,EAAE,iBAAiB,EAAE,CAAC;QAC9C,CAAC;QACD,cAAc,EAAE,KAAK,IAAI,EAAE;YACzB,MAAM,QAAQ,CAAC,cAAc,EAAE,CAAC;QAClC,CAAC;QACD,qBAAqB,EAAE,KAAK,IAAI,EAAE;YAChC,MAAM,QAAQ,CAAC,OAAO,EAAE,uBAAuB,EAAE,CAAC;QACpD,CAAC;QACD,oBAAoB,EAAE,KAAK,IAAI,EAAE;YAC/B,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,oBAAoB,EAAE,CAAC;YACxC,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,IAAI,CAAC,YAAY,YAAY,IAAI,CAAC,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;oBAChE,OAAO,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;gBACnE,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,CAAC;gBACV,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC,CAAC,CAAC;IAEJ,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,KAAK,CAAC,uBAAuB,EAAE,EAAE,CAAC;QACpC,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,KAAK,CAAC,sBAAsB,EAAE,EAAE,CAAC;QACnC,CAAC,CAAC;QACF,MAAM,WAAW,GAAG,GAAG,EAAE;YACvB,sBAAsB,CAAC,OAAO,GAAG,IAAI,CAAC;QACxC,CAAC,CAAC;QACF,MAAM,SAAS,GAAG,GAAG,EAAE;YACrB,IAAI,sBAAsB,CAAC,OAAO,EAAE,CAAC;gBACnC,KAAK,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC/B,CAAC;YACD,sBAAsB,CAAC,OAAO,GAAG,KAAK,CAAC;QACzC,CAAC,CAAC;QACF,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;QACrE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;QACrE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;QAC7D,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QAE5D,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;YACxE,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;YACxE,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;YAChE,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QACjE,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,sBAAsB,EAAE,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;IAE5E,kHAAkH;IAClH,oCAAoC;IACpC,SAAS,gBAAgB;QACvB,MAAM,YAAY,GAAG,eAAe,CAAC,OAAO,CAAC;QAC7C,MAAM,YAAY,GAAG,eAAe,CAAC,OAAO,CAAC;QAC7C,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC;QAEvC,IAAI,YAAY,IAAI,YAAY,IAAI,SAAS,EAAE,CAAC;YAC9C,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,YAAY,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;QACrE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CACV,uHAAuH,CACxH,CAAC;QACJ,CAAC;IACH,CAAC;IAED,SAAS,gBAAgB;QACvB,MAAM,YAAY,GAAG,eAAe,CAAC,OAAO,CAAC;QAC7C,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC;QACvC,IAAI,YAAY,IAAI,SAAS,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;YAClD,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;IAED,SAAS,sBAAsB;QAC7B,IACE,CAAC,sBAAsB,CAAC,OAAO;YAC/B,CAAC,SAAS,CAAC,cAAc,CAAC,aAAa;YACvC,CAAC,QAAQ,CAAC,OAAO,EACjB,CAAC;YACD,OAAO;QACT,CAAC;QACD,MAAM,YAAY,GAAG,kBAAkB,EAAE,CAAC;QAE1C,gBAAgB,EAAE,CAAC;QACnB,eAAe,CAAC,OAAO,GAAG,YAAY,CAAC;QACvC,eAAe,CAAC,OAAO,GAAG,kBAAkB,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QACtE,YAAY,CAAC,OAAO,GAAG,YAAY;YACjC,CAAC,CAAC,YAAY,CAAC,wBAAwB,CAAC,QAAQ,CAAC,OAAO,CAAC;YACzD,CAAC,CAAC,IAAI,CAAC;QACT,gBAAgB,EAAE,CAAC;QACnB,sBAAsB,CAAC,OAAO,GAAG,KAAK,CAAC;IACzC,CAAC;IAED,SAAS,kBAAkB;QACzB,IAAI,QAAQ,CAAC,iBAAiB,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC;YACpD,KAAK,CAAC,iBAAiB,EAAE,EAAE,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,SAAS,uBAAuB;QAC9B,wBAAwB,CAAC,OAAO,GAAG,kBAAkB,CAAC;QACtD,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,kBAAkB,EAAE,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAC3F,CAAC;IAED,SAAS,yBAAyB;QAChC,IAAI,wBAAwB,CAAC,OAAO,EAAE,CAAC;YACrC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,kBAAkB,EAAE,wBAAwB,CAAC,OAAO,CAAC,CAAC;YAC5F,wBAAwB,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;YACrB,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACjD,CAAC;QACD,uBAAuB,EAAE,CAAC;QAC1B,gBAAgB,EAAE,CAAC;QAEnB,OAAO,GAAG,EAAE;YACV,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACrB,KAAK,CAAC,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACnD,CAAC;YACD,yBAAyB,EAAE,CAAC;YAC5B,gBAAgB,EAAE,CAAC;QACrB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAEnB,OAAO,CACL,CAAC,KAAK,CACJ,QAAQ,CAAC,CAAC,KAAK,CAAC,cAAc,IAAI,IAAI,CAAC,CACvC,YAAY,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAClE,WAAW,CAAC,CAAC,KAAK,CAAC,WAAW,IAAI,WAAW,CAAC,CAC9C,KAAK,CAAC,CAAC;YACL,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC;YACzB,SAAS,EAAE,KAAK,CAAC,UAAU;SAC5B,CAAC,CACF,MAAM,CAAC,CAAC,GAAG,EAAE;YACX,sBAAsB,EAAE,CAAC;QAC3B,CAAC,CAAC;IACF,yFAAyF;IACzF,cAAc,CAAC,CAAC,GAAG,EAAE;YACnB,sBAAsB,EAAE,CAAC;QAC3B,CAAC,CAAC,CACF,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE;YACd,+EAA+E;YAC/E,6EAA6E;YAC7E,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBACpD,QAAQ,CAAC,OAAO,GAAG,MAAM,CAAC;gBAC1B,sBAAsB,CAAC,OAAO,GAAG,IAAI,CAAC;gBACtC,sBAAsB,EAAE,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC,CACF,uBAAuB,CAAC,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC,CACvD,WAAW,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAC/B,GAAG,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,EAC3C,CACH,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,eAAe,SAAS,CAAC","sourcesContent":["import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';\nimport { StyleSheet } from 'react-native';\n\nimport VideoPlayer, { getSourceUri } from './VideoPlayer.web';\nimport type { VideoViewProps } from './VideoView.types';\n\nfunction createAudioContext(): AudioContext | null {\n return typeof window !== 'undefined' ? new window.AudioContext() : null;\n}\n\nfunction createZeroGainNode(audioContext: AudioContext | null): GainNode | null {\n const zeroGainNode = audioContext?.createGain() ?? null;\n\n if (audioContext && zeroGainNode) {\n zeroGainNode.gain.value = 0;\n zeroGainNode.connect(audioContext.destination);\n }\n return zeroGainNode;\n}\n\nfunction mapStyles(style: VideoViewProps['style']): React.CSSProperties {\n const flattenedStyles = StyleSheet.flatten(style);\n // Looking through react-native-web source code they also just pass styles directly without further conversions, so it's just a cast.\n return flattenedStyles as React.CSSProperties;\n}\n\nexport function isPictureInPictureSupported(): boolean {\n return typeof document === 'object' && typeof document.exitPictureInPicture === 'function';\n}\n\nexport const VideoView = forwardRef((props: { player?: VideoPlayer } & VideoViewProps, ref) => {\n const videoRef = useRef<null | HTMLVideoElement>(null);\n const mediaNodeRef = useRef<null | MediaElementAudioSourceNode>(null);\n const hasToSetupAudioContext = useRef(false);\n const fullscreenChangeListener = useRef<null | (() => void)>(null);\n const isWaitingForFirstFrame = useRef(false);\n\n /**\n * Audio context is used to mute all but one video when multiple video views are playing from one player simultaneously.\n * Using audio context nodes allows muting videos without displaying the mute icon in the video player.\n * We have to keep the context that called createMediaElementSource(videoRef), as the method can't be called\n * for the second time with another context and there is no way to unbind the video and audio context afterward.\n */\n const audioContextRef = useRef<null | AudioContext>(null);\n const zeroGainNodeRef = useRef<null | GainNode>(null);\n\n useImperativeHandle(ref, () => ({\n enterFullscreen: async () => {\n if (!props.allowsFullscreen) {\n return;\n }\n await videoRef.current?.requestFullscreen();\n },\n exitFullscreen: async () => {\n await document.exitFullscreen();\n },\n startPictureInPicture: async () => {\n await videoRef.current?.requestPictureInPicture();\n },\n stopPictureInPicture: async () => {\n try {\n await document.exitPictureInPicture();\n } catch (e) {\n if (e instanceof DOMException && e.name === 'InvalidStateError') {\n console.warn('The VideoView is not in Picture-in-Picture mode.');\n } else {\n throw e;\n }\n }\n },\n }));\n\n useEffect(() => {\n const onEnter = () => {\n props.onPictureInPictureStart?.();\n };\n const onLeave = () => {\n props.onPictureInPictureStop?.();\n };\n const onLoadStart = () => {\n isWaitingForFirstFrame.current = true;\n };\n const onCanPlay = () => {\n if (isWaitingForFirstFrame.current) {\n props.onFirstFrameRender?.();\n }\n isWaitingForFirstFrame.current = false;\n };\n videoRef.current?.addEventListener('enterpictureinpicture', onEnter);\n videoRef.current?.addEventListener('leavepictureinpicture', onLeave);\n videoRef.current?.addEventListener('loadstart', onLoadStart);\n videoRef.current?.addEventListener('loadeddata', onCanPlay);\n\n return () => {\n videoRef.current?.removeEventListener('enterpictureinpicture', onEnter);\n videoRef.current?.removeEventListener('leavepictureinpicture', onLeave);\n videoRef.current?.removeEventListener('loadstart', onLoadStart);\n videoRef.current?.removeEventListener('loadeddata', onCanPlay);\n };\n }, [videoRef, props.onPictureInPictureStop, props.onPictureInPictureStart]);\n\n // Adds the video view as a candidate for being the audio source for the player (when multiple views play from one\n // player only one will emit audio).\n function attachAudioNodes() {\n const audioContext = audioContextRef.current;\n const zeroGainNode = zeroGainNodeRef.current;\n const mediaNode = mediaNodeRef.current;\n\n if (audioContext && zeroGainNode && mediaNode) {\n props.player.mountAudioNode(audioContext, zeroGainNode, mediaNode);\n } else {\n console.warn(\n \"Couldn't mount audio node, this might affect the audio playback when using multiple video views with the same player.\"\n );\n }\n }\n\n function detachAudioNodes() {\n const audioContext = audioContextRef.current;\n const mediaNode = mediaNodeRef.current;\n if (audioContext && mediaNode && videoRef.current) {\n props.player.unmountAudioNode(videoRef.current, audioContext, mediaNode);\n }\n }\n\n function maybeSetupAudioContext() {\n if (\n !hasToSetupAudioContext.current ||\n !navigator.userActivation.hasBeenActive ||\n !videoRef.current\n ) {\n return;\n }\n const audioContext = createAudioContext();\n\n detachAudioNodes();\n audioContextRef.current = audioContext;\n zeroGainNodeRef.current = createZeroGainNode(audioContextRef.current);\n mediaNodeRef.current = audioContext\n ? audioContext.createMediaElementSource(videoRef.current)\n : null;\n attachAudioNodes();\n hasToSetupAudioContext.current = false;\n }\n\n function fullscreenListener() {\n if (document.fullscreenElement === videoRef.current) {\n props.onFullscreenEnter?.();\n } else {\n props.onFullscreenExit?.();\n }\n }\n\n function setupFullscreenListener() {\n fullscreenChangeListener.current = fullscreenListener;\n videoRef.current?.addEventListener('fullscreenchange', fullscreenChangeListener.current);\n }\n\n function cleanupFullscreenListener() {\n if (fullscreenChangeListener.current) {\n videoRef.current?.removeEventListener('fullscreenchange', fullscreenChangeListener.current);\n fullscreenChangeListener.current = null;\n }\n }\n\n useEffect(() => {\n if (videoRef.current) {\n props.player?.mountVideoView(videoRef.current);\n }\n setupFullscreenListener();\n attachAudioNodes();\n\n return () => {\n if (videoRef.current) {\n props.player?.unmountVideoView(videoRef.current);\n }\n cleanupFullscreenListener();\n detachAudioNodes();\n };\n }, [props.player]);\n\n return (\n <video\n controls={props.nativeControls ?? true}\n controlsList={props.allowsFullscreen ? undefined : 'nofullscreen'}\n crossOrigin={props.crossOrigin ?? 'anonymous'}\n style={{\n ...mapStyles(props.style),\n objectFit: props.contentFit,\n }}\n onPlay={() => {\n maybeSetupAudioContext();\n }}\n // The player can autoplay when muted, unmuting by a user should create the audio context\n onVolumeChange={() => {\n maybeSetupAudioContext();\n }}\n ref={(newRef) => {\n // This is called with a null value before `player.unmountVideoView` is called,\n // we can't assign null to videoRef if we want to unmount it from the player.\n if (newRef && !newRef.isEqualNode(videoRef.current)) {\n videoRef.current = newRef;\n hasToSetupAudioContext.current = true;\n maybeSetupAudioContext();\n }\n }}\n disablePictureInPicture={!props.allowsPictureInPicture}\n playsInline={props.playsInline}\n src={getSourceUri(props.player?.src) ?? ''}\n />\n );\n});\n\nexport default VideoView;\n"]}
@@ -8,7 +8,7 @@
8
8
  "publication": {
9
9
  "groupId": "host.exp.exponent",
10
10
  "artifactId": "expo.modules.video",
11
- "version": "2.3.0-canary-20250722-599a28f",
11
+ "version": "3.0.0-canary-20250811-5c940c0",
12
12
  "repository": "local-maven-repo"
13
13
  }
14
14
  }
@@ -339,6 +339,7 @@ public final class VideoModule: Module {
339
339
 
340
340
  Function("replay") { player in
341
341
  player.ref.seek(to: CMTime.zero)
342
+ player.ref.play()
342
343
  }
343
344
 
344
345
  AsyncFunction("generateThumbnailsAsync") { (player: VideoPlayer, times: [CMTime]?, options: VideoThumbnailOptions?) -> [VideoThumbnail] in
@@ -0,0 +1 @@
1
+ 271eee95c9680d81b7502617652075d4e1a95d1aeb76700dcfeac060913351765dedf97466588d4d6c8a01488d51249bf10325f99e1d2ba2f500f2121f1446e1
@@ -0,0 +1 @@
1
+ 469758e3cc6a4df377df5313647f7bb5d1e2829288be57619ffebcaa73000f56e7b4db7446ce8fc22e71270cc4630247016221ccd180b231689b90c3e1447f0e
@@ -3,7 +3,7 @@
3
3
  "component": {
4
4
  "group": "host.exp.exponent",
5
5
  "module": "expo.modules.video",
6
- "version": "2.3.0-canary-20250722-599a28f",
6
+ "version": "3.0.0-canary-20250811-5c940c0",
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.3.0-canary-20250722-599a28f.aar",
28
- "url": "expo.modules.video-2.3.0-canary-20250722-599a28f.aar",
29
- "size": 466120,
30
- "sha512": "9bbdd7b14aa471ce50972069209ace5b7f84790e377f5f69923693427084cea426ba002c64506288bac8d27865fb853d8c598e51617e1c485a96155562c1eceb",
31
- "sha256": "356699282396f6089d797276fadfe255438c6d315b5b197f1f9069f919665c17",
32
- "sha1": "dd4ac4c73d71f37356240d7978c60e6668796a35",
33
- "md5": "efc5b8fa6220a70ce17f5c7b0a04b618"
27
+ "name": "expo.modules.video-3.0.0-canary-20250811-5c940c0.aar",
28
+ "url": "expo.modules.video-3.0.0-canary-20250811-5c940c0.aar",
29
+ "size": 470597,
30
+ "sha512": "469758e3cc6a4df377df5313647f7bb5d1e2829288be57619ffebcaa73000f56e7b4db7446ce8fc22e71270cc4630247016221ccd180b231689b90c3e1447f0e",
31
+ "sha256": "ae5e41be0c79fad5f2abfc91e5c65102d7a501074b4d2a0e768a26927a2a0004",
32
+ "sha1": "7a4337bf8aadde6d25a7999b421ea85f4b4cf751",
33
+ "md5": "d9b74eafb2f6080cbf12445a872c47e8"
34
34
  }
35
35
  ]
36
36
  },
@@ -113,13 +113,13 @@
113
113
  ],
114
114
  "files": [
115
115
  {
116
- "name": "expo.modules.video-2.3.0-canary-20250722-599a28f.aar",
117
- "url": "expo.modules.video-2.3.0-canary-20250722-599a28f.aar",
118
- "size": 466120,
119
- "sha512": "9bbdd7b14aa471ce50972069209ace5b7f84790e377f5f69923693427084cea426ba002c64506288bac8d27865fb853d8c598e51617e1c485a96155562c1eceb",
120
- "sha256": "356699282396f6089d797276fadfe255438c6d315b5b197f1f9069f919665c17",
121
- "sha1": "dd4ac4c73d71f37356240d7978c60e6668796a35",
122
- "md5": "efc5b8fa6220a70ce17f5c7b0a04b618"
116
+ "name": "expo.modules.video-3.0.0-canary-20250811-5c940c0.aar",
117
+ "url": "expo.modules.video-3.0.0-canary-20250811-5c940c0.aar",
118
+ "size": 470597,
119
+ "sha512": "469758e3cc6a4df377df5313647f7bb5d1e2829288be57619ffebcaa73000f56e7b4db7446ce8fc22e71270cc4630247016221ccd180b231689b90c3e1447f0e",
120
+ "sha256": "ae5e41be0c79fad5f2abfc91e5c65102d7a501074b4d2a0e768a26927a2a0004",
121
+ "sha1": "7a4337bf8aadde6d25a7999b421ea85f4b4cf751",
122
+ "md5": "d9b74eafb2f6080cbf12445a872c47e8"
123
123
  }
124
124
  ]
125
125
  },
@@ -133,13 +133,13 @@
133
133
  },
134
134
  "files": [
135
135
  {
136
- "name": "expo.modules.video-2.3.0-canary-20250722-599a28f-sources.jar",
137
- "url": "expo.modules.video-2.3.0-canary-20250722-599a28f-sources.jar",
138
- "size": 56330,
139
- "sha512": "cba7f5a7ccefc07e58c85e3c26c279e5c8624fc0c84510814b3fda2bdf6ef73496ffa446816cfe45cf31dbda071f6d146b73d11ffe755ef77c484ccd56211f93",
140
- "sha256": "2fb05fdb332f403aee82cd5059ff214ef90621deea612eaaad68eb1fa6b86f1c",
141
- "sha1": "66d3436151ca2150712aaca6132f42ae1c2b94c7",
142
- "md5": "8e27a8a108cbb6d261d95e1aad97e27a"
136
+ "name": "expo.modules.video-3.0.0-canary-20250811-5c940c0-sources.jar",
137
+ "url": "expo.modules.video-3.0.0-canary-20250811-5c940c0-sources.jar",
138
+ "size": 57958,
139
+ "sha512": "271eee95c9680d81b7502617652075d4e1a95d1aeb76700dcfeac060913351765dedf97466588d4d6c8a01488d51249bf10325f99e1d2ba2f500f2121f1446e1",
140
+ "sha256": "bceacd039dc8527a9012b8e651199decbf343cfbd3b6a8cb976f5f20bfdcec35",
141
+ "sha1": "70dc3863ba236f8e99d01702acf79b27e553b89d",
142
+ "md5": "08f3652ee7565d5a1179da304b324ad9"
143
143
  }
144
144
  ]
145
145
  }
@@ -0,0 +1 @@
1
+ 3ec90666c96f49ab6df65dcb1a0a53ed100ecdc547a6f7f8c5c5873558f1bdb584ff9f0f2ddfee15d0518e7c4845f8aa5c1734c9c816bef414390928507c8188
@@ -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.3.0-canary-20250722-599a28f</version>
12
+ <version>3.0.0-canary-20250811-5c940c0</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
+ 8f5d679aec2e0da530f320ce677ebebbc2d52bdf3ba22614c8239f7ac4b012e56bdddb0a1406a1e4c05a308a1891a3892e9c5129dc37f83bdaa1ccee4aa8e7bd
@@ -3,11 +3,11 @@
3
3
  <groupId>host.exp.exponent</groupId>
4
4
  <artifactId>expo.modules.video</artifactId>
5
5
  <versioning>
6
- <latest>2.3.0-canary-20250722-599a28f</latest>
7
- <release>2.3.0-canary-20250722-599a28f</release>
6
+ <latest>3.0.0-canary-20250811-5c940c0</latest>
7
+ <release>3.0.0-canary-20250811-5c940c0</release>
8
8
  <versions>
9
- <version>2.3.0-canary-20250722-599a28f</version>
9
+ <version>3.0.0-canary-20250811-5c940c0</version>
10
10
  </versions>
11
- <lastUpdated>20250722134446</lastUpdated>
11
+ <lastUpdated>20250811152856</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- 5a86b698577e5551eb395078c0dc07c3
1
+ 8fc79e351e629dd6310b36531e6bb4fa
@@ -1 +1 @@
1
- b14b690636914ee2e6015810c11591060fe2f826
1
+ a9bbcf9b7076d3cee48a67dfd9ba3c53d35a3303
@@ -1 +1 @@
1
- 490a039d3d947d9107cdd95d8233d5c8b22fef899920ad7e07242fd2417dd003
1
+ 330d6387f3a3c57e4f2685710c30db89d51a3ce15500900596a79b0c1b2f0dbd
@@ -1 +1 @@
1
- 09080f321d7b36e0f0013ec221fdf47b87d436745b8bff21139378c57749d771ba3ba77f08a463bd299503b8c807e253bc12af0da4b64511eba1e546bdb5f661
1
+ ae65244d0aa7d6343510f79e57dd8e05da292b7123e2a277cb78d74e9f82342070abc000564c6d11c1bf21f856cbb9e650db6962bb63232e831d6877b4c1f7f9
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expo-video",
3
3
  "title": "Expo Video",
4
- "version": "2.3.0-canary-20250722-599a28f",
4
+ "version": "3.0.0-canary-20250811-5c940c0",
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,10 +31,10 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {},
33
33
  "devDependencies": {
34
- "expo-module-scripts": "4.1.10-canary-20250722-599a28f"
34
+ "expo-module-scripts": "4.1.11-canary-20250811-5c940c0"
35
35
  },
36
36
  "peerDependencies": {
37
- "expo": "54.0.0-canary-20250722-599a28f",
37
+ "expo": "54.0.0-canary-20250811-5c940c0",
38
38
  "react": "*",
39
39
  "react-native": "*"
40
40
  }
@@ -166,7 +166,7 @@ export interface VideoViewProps extends ViewProps {
166
166
  * If undefined, does not use CORS at all.
167
167
  *
168
168
  * @platform web
169
- * @default undefined
169
+ * @default 'anonymous'
170
170
  */
171
171
  crossOrigin?: 'anonymous' | 'use-credentials';
172
172
  }
@@ -183,7 +183,7 @@ export const VideoView = forwardRef((props: { player?: VideoPlayer } & VideoView
183
183
  <video
184
184
  controls={props.nativeControls ?? true}
185
185
  controlsList={props.allowsFullscreen ? undefined : 'nofullscreen'}
186
- crossOrigin={props.crossOrigin}
186
+ crossOrigin={props.crossOrigin ?? 'anonymous'}
187
187
  style={{
188
188
  ...mapStyles(props.style),
189
189
  objectFit: props.contentFit,
@@ -1 +0,0 @@
1
- cba7f5a7ccefc07e58c85e3c26c279e5c8624fc0c84510814b3fda2bdf6ef73496ffa446816cfe45cf31dbda071f6d146b73d11ffe755ef77c484ccd56211f93
@@ -1 +0,0 @@
1
- 9bbdd7b14aa471ce50972069209ace5b7f84790e377f5f69923693427084cea426ba002c64506288bac8d27865fb853d8c598e51617e1c485a96155562c1eceb
@@ -1 +0,0 @@
1
- a2c25c895c75afb39b81decd875b15ff72ebb256f0e201eadfb6e218cc60e31e5982cb7539c2e8d0e1ed1b79475a7c130c40e4ab59656a54d7816292c4712df8
@@ -1 +0,0 @@
1
- 7d9ddc0c4200702b31a00298285c992da26d71513cb882b845ee69a0fd1b595d008a15d934fcac23b958de3e521b31eb2406f5d40b96d0ea6498332db4ffee66