expo-video 2.3.0-canary-20250713-8f814f8 → 3.0.0-canary-20250729-d8899ae

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 (57) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/video/player/VideoPlayer.kt +17 -6
  4. package/build/VideoView.types.d.ts +1 -1
  5. package/build/VideoView.types.js.map +1 -1
  6. package/build/VideoView.web.js +1 -1
  7. package/build/VideoView.web.js.map +1 -1
  8. package/expo-module.config.json +1 -1
  9. package/ios/Enums/FullscreenOrientation.swift +2 -0
  10. package/ios/OrientationAVPlayerViewController.swift +20 -4
  11. package/ios/VideoAirPlayButtonView.swift +1 -1
  12. package/ios/VideoModule.swift +1 -1
  13. package/ios/VideoView.swift +6 -4
  14. package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8-sources.jar → 3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae-sources.jar} +0 -0
  15. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae-sources.jar.md5 +1 -0
  16. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae-sources.jar.sha1 +1 -0
  17. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae-sources.jar.sha256 +1 -0
  18. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae-sources.jar.sha512 +1 -0
  19. package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.aar → 3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.aar} +0 -0
  20. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.aar.md5 +1 -0
  21. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.aar.sha1 +1 -0
  22. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.aar.sha256 +1 -0
  23. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.aar.sha512 +1 -0
  24. package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.module → 3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.module} +22 -22
  25. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.module.md5 +1 -0
  26. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.module.sha1 +1 -0
  27. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.module.sha256 +1 -0
  28. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.module.sha512 +1 -0
  29. package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.pom → 3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.pom} +1 -1
  30. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.pom.md5 +1 -0
  31. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.pom.sha1 +1 -0
  32. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.pom.sha256 +1 -0
  33. package/local-maven-repo/host/exp/exponent/expo.modules.video/3.0.0-canary-20250729-d8899ae/expo.modules.video-3.0.0-canary-20250729-d8899ae.pom.sha512 +1 -0
  34. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml +4 -4
  35. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.md5 +1 -1
  36. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha1 +1 -1
  37. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha256 +1 -1
  38. package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha512 +1 -1
  39. package/package.json +3 -3
  40. package/src/VideoView.types.ts +1 -1
  41. package/src/VideoView.web.tsx +1 -1
  42. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8-sources.jar.md5 +0 -1
  43. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8-sources.jar.sha1 +0 -1
  44. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8-sources.jar.sha256 +0 -1
  45. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8-sources.jar.sha512 +0 -1
  46. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.aar.md5 +0 -1
  47. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.aar.sha1 +0 -1
  48. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.aar.sha256 +0 -1
  49. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.aar.sha512 +0 -1
  50. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.module.md5 +0 -1
  51. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.module.sha1 +0 -1
  52. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.module.sha256 +0 -1
  53. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.module.sha512 +0 -1
  54. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.pom.md5 +0 -1
  55. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.pom.sha1 +0 -1
  56. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.pom.sha256 +0 -1
  57. package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.pom.sha512 +0 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,8 @@
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))
@@ -11,8 +13,10 @@
11
13
 
12
14
  ### 🐛 Bug fixes
13
15
 
16
+ - [Android] Fix duration property resetting to 0 on video repeat. ([#37984](https://github.com/expo/expo/pull/37984) by [@Wenszel](https://github.com/Wenszel))
14
17
  - [Android] Fix accessing player.loop causes app to crash. ([#37928](https://github.com/expo/expo/pull/37928) by [@Wenszel](https://github.com/Wenszel))
15
18
  - [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))
19
+ - [iOS] Fix tvOS compilation errors. ([#38085](https://github.com/expo/expo/pull/38085) by [@douglowder](https://github.com/douglowder))
16
20
 
17
21
  ### 💡 Others
18
22
 
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '2.3.0-canary-20250713-8f814f8'
7
+ version = '3.0.0-canary-20250729-d8899ae'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.video"
11
11
  defaultConfig {
12
12
  versionCode 1
13
- versionName '2.3.0-canary-20250713-8f814f8'
13
+ versionName '3.0.0-canary-20250729-d8899ae'
14
14
  }
15
15
  }
16
16
 
@@ -235,10 +235,13 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
235
235
  }
236
236
 
237
237
  override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
238
- this@VideoPlayer.duration = 0f
239
- this@VideoPlayer.isLive = false
240
238
  if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT) {
241
239
  sendEvent(PlayerEvent.PlayedToEnd())
240
+ } else {
241
+ // New playback info is set in the onPlaybackStateChanged event, which occurs after mediaItemTransition.
242
+ // The onPlaybackStateChanged is not triggered if the video repeats (since the state remains STATE_READY)
243
+ // That is why the playback info is not reset when the transition reason is MEDIA_ITEM_TRANSITION_REASON_REPEAT.
244
+ resetPlaybackInfo()
242
245
  }
243
246
  subtitles.setSubtitlesEnabled(false)
244
247
  super.onMediaItemTransition(mediaItem, reason)
@@ -249,8 +252,7 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
249
252
  return
250
253
  }
251
254
  if (playbackState == Player.STATE_READY) {
252
- this@VideoPlayer.duration = this@VideoPlayer.player.duration / 1000f
253
- this@VideoPlayer.isLive = this@VideoPlayer.player.isCurrentMediaItemLive
255
+ refreshPlaybackInfo()
254
256
  }
255
257
  setStatus(playerStateToPlayerStatus(playbackState), null)
256
258
  super.onPlaybackStateChanged(playbackState)
@@ -269,8 +271,7 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
269
271
 
270
272
  override fun onPlayerErrorChanged(error: PlaybackException?) {
271
273
  error?.let {
272
- this@VideoPlayer.duration = 0f
273
- this@VideoPlayer.isLive = false
274
+ resetPlaybackInfo()
274
275
  setStatus(ERROR, error)
275
276
  } ?: run {
276
277
  setStatus(playerStateToPlayerStatus(player.playbackState), null)
@@ -383,6 +384,16 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
383
384
  }
384
385
  }
385
386
 
387
+ private fun refreshPlaybackInfo() {
388
+ duration = player.duration / 1000f
389
+ isLive = player.isCurrentMediaItemLive
390
+ }
391
+
392
+ private fun resetPlaybackInfo() {
393
+ duration = 0f
394
+ isLive = false
395
+ }
396
+
386
397
  fun addListener(videoPlayerListener: VideoPlayerListener) {
387
398
  if (listeners.all { it.get() != videoPlayerListener }) {
388
399
  listeners.add(WeakReference(videoPlayerListener))
@@ -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":"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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 ?? '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-20250713-8f814f8",
11
+ "version": "3.0.0-canary-20250729-d8899ae",
12
12
  "repository": "local-maven-repo"
13
13
  }
14
14
  }
@@ -11,6 +11,7 @@ internal enum FullscreenOrientation: String, Enumerable {
11
11
  case portraitDown
12
12
  case `default`
13
13
 
14
+ #if !os(tvOS)
14
15
  func toUIInterfaceOrientationMask() -> UIInterfaceOrientationMask {
15
16
  switch self {
16
17
  case .landscape:
@@ -29,4 +30,5 @@ internal enum FullscreenOrientation: String, Enumerable {
29
30
  return .all
30
31
  }
31
32
  }
33
+ #endif
32
34
  }
@@ -8,7 +8,9 @@ import ExpoModulesCore
8
8
  */
9
9
  internal class OrientationAVPlayerViewController: AVPlayerViewController, AVPlayerViewControllerDelegate {
10
10
  weak var forwardDelegate: AVPlayerViewControllerDelegate?
11
+ #if !os(tvOS)
11
12
  var fullscreenOrientation: UIInterfaceOrientationMask = UIDevice.current.userInterfaceIdiom == .phone ? .allButUpsideDown : .all
13
+ #endif
12
14
  var autoExitOnRotate: Bool = false
13
15
 
14
16
  // Used to determine whether the user has rotated the device to the target orientation. Useful for auto-exit for example:
@@ -28,14 +30,19 @@ internal class OrientationAVPlayerViewController: AVPlayerViewController, AVPlay
28
30
  if !isFullscreen {
29
31
  hasRotatedToTargetOrientation = false
30
32
  }
33
+ #if os(tvOS)
34
+ hasRotatedToTargetOrientation = true
35
+ #else
31
36
  // Check if the current device orientation lines up with target orientation right away after entering fullscreen
32
37
  guard let deviceOrientationMask = UIDevice.current.orientation.toInterfaceOrientationMask(), isFullscreen else {
33
38
  return
34
39
  }
35
40
  hasRotatedToTargetOrientation = fullscreenOrientation.contains(deviceOrientationMask)
41
+ #endif
36
42
  }
37
43
  }
38
44
 
45
+ #if !os(tvOS)
39
46
  override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
40
47
  // Always remove the observer to avoid adding it multiple times
41
48
  NotificationCenter.default.removeObserver(
@@ -56,6 +63,7 @@ internal class OrientationAVPlayerViewController: AVPlayerViewController, AVPlay
56
63
  }
57
64
  return super.supportedInterfaceOrientations
58
65
  }
66
+ #endif
59
67
 
60
68
  convenience init(delegate: AVPlayerViewControllerDelegate?) {
61
69
  self.init()
@@ -63,11 +71,13 @@ internal class OrientationAVPlayerViewController: AVPlayerViewController, AVPlay
63
71
  }
64
72
 
65
73
  deinit {
74
+ #if !os(tvOS)
66
75
  NotificationCenter.default.removeObserver(
67
76
  self,
68
77
  name: UIDevice.orientationDidChangeNotification,
69
78
  object: nil
70
79
  )
80
+ #endif
71
81
  }
72
82
 
73
83
  func enterFullscreen(selectorUnsupportedFallback: (() -> Void)?) {
@@ -127,6 +137,7 @@ internal class OrientationAVPlayerViewController: AVPlayerViewController, AVPlay
127
137
  self.delegate = self
128
138
  }
129
139
 
140
+ #if !os(tvOS)
130
141
  @objc private func deviceOrientationDidChange(_ notification: Notification) {
131
142
  guard let deviceOrientationMask = UIDevice.current.orientation.toInterfaceOrientationMask(), isFullscreen else {
132
143
  return
@@ -170,6 +181,7 @@ internal class OrientationAVPlayerViewController: AVPlayerViewController, AVPlay
170
181
  }
171
182
  }
172
183
  }
184
+ #endif
173
185
 
174
186
  func playerViewControllerDidStartPictureInPicture(_ playerViewController: AVPlayerViewController) {
175
187
  isInPictureInPicture = true
@@ -181,16 +193,17 @@ internal class OrientationAVPlayerViewController: AVPlayerViewController, AVPlay
181
193
  forwardDelegate?.playerViewControllerDidStopPictureInPicture?(playerViewController)
182
194
  }
183
195
 
184
- #if os(tvOS)
196
+ #if os(tvOS)
185
197
  func playerViewControllerWillBeginDismissalTransition(_ playerViewController: AVPlayerViewController) {
186
- forwardDelegate?.playerViewControllerWillBeginDismissalTransition(playerViewController)
198
+ forwardDelegate?.playerViewControllerWillBeginDismissalTransition?(playerViewController)
187
199
  }
188
200
 
189
201
  func playerViewControllerDidEndDismissalTransition(_ playerViewController: AVPlayerViewController) {
190
- forwardDelegate?.playerViewControllerDidEndDismissalTransition(playerViewController)
202
+ forwardDelegate?.playerViewControllerDidEndDismissalTransition?(playerViewController)
191
203
  }
192
- #endif
204
+ #endif
193
205
 
206
+ #if !os(tvOS)
194
207
  private func forceRotationUpdate() {
195
208
  if #available(iOS 16.0, *) {
196
209
  let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
@@ -199,8 +212,10 @@ internal class OrientationAVPlayerViewController: AVPlayerViewController, AVPlay
199
212
  UIViewController.attemptRotationToDeviceOrientation()
200
213
  }
201
214
  }
215
+ #endif
202
216
  }
203
217
 
218
+ #if !os(tvOS)
204
219
  fileprivate extension UIDeviceOrientation {
205
220
  func toInterfaceOrientationMask() -> UIInterfaceOrientationMask? {
206
221
  switch self {
@@ -213,3 +228,4 @@ fileprivate extension UIDeviceOrientation {
213
228
  }
214
229
  }
215
230
  }
231
+ #endif
@@ -1,6 +1,6 @@
1
1
  // Copyright 2025-present 650 Industries. All rights reserved.
2
2
 
3
- import ContactsUI
3
+ // import ContactsUI
4
4
  import SwiftUI
5
5
  import ExpoModulesCore
6
6
  import AVKit
@@ -66,9 +66,9 @@ public final class VideoModule: Module {
66
66
  }
67
67
 
68
68
  Prop("fullscreenOptions") {(view, options: FullscreenOptions?) in
69
+ #if !os(tvOS)
69
70
  view.playerViewController.fullscreenOrientation = options?.orientation.toUIInterfaceOrientationMask() ?? .all
70
71
  view.playerViewController.autoExitOnRotate = options?.autoExitOnRotate ?? false
71
- #if !os(tvOS)
72
72
  view.playerViewController.setValue(options?.enable ?? true, forKey: "allowsEnteringFullScreen")
73
73
  #endif
74
74
  }
@@ -14,9 +14,8 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
14
14
 
15
15
  #if os(tvOS)
16
16
  var wasPlaying: Bool = false
17
- #endif
18
- #if os(tvOS)
19
17
  let startPictureInPictureAutomatically = false
18
+ var isFullscreen: Bool = false
20
19
  #else
21
20
  var startPictureInPictureAutomatically = false {
22
21
  didSet {
@@ -78,8 +77,8 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
78
77
  self.wasPlaying = self.player?.isPlaying == true
79
78
  self.playerViewController.view.removeFromSuperview()
80
79
  self.reactViewController().present(self.playerViewController, animated: true)
81
- onFullscreenEnter()
82
- isFullscreen = true
80
+ self.onFullscreenEnter()
81
+ self.isFullscreen = true
83
82
  #endif
84
83
  }
85
84
  playerViewController.enterFullscreen(selectorUnsupportedFallback: tvOSFallback)
@@ -87,6 +86,9 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
87
86
 
88
87
  func exitFullscreen() {
89
88
  playerViewController.exitFullscreen()
89
+ #if os(tvOS)
90
+ self.isFullscreen = false
91
+ #endif
90
92
  }
91
93
 
92
94
  func startPictureInPicture() throws {
@@ -0,0 +1 @@
1
+ cba7f5a7ccefc07e58c85e3c26c279e5c8624fc0c84510814b3fda2bdf6ef73496ffa446816cfe45cf31dbda071f6d146b73d11ffe755ef77c484ccd56211f93
@@ -0,0 +1 @@
1
+ 9bbdd7b14aa471ce50972069209ace5b7f84790e377f5f69923693427084cea426ba002c64506288bac8d27865fb853d8c598e51617e1c485a96155562c1eceb
@@ -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-20250713-8f814f8",
6
+ "version": "3.0.0-canary-20250729-d8899ae",
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-20250713-8f814f8.aar",
28
- "url": "expo.modules.video-2.3.0-canary-20250713-8f814f8.aar",
29
- "size": 465973,
30
- "sha512": "f57545d9b42dce22febf3df2ce670289051c3505587db4f5c52eaa287938f0507566d7249c5ccb735c879c8446ddf146c8c9a8078bd13c5da39bd236e9cb77fd",
31
- "sha256": "f2b051d0ed6bb9dc676f3d916d053d58c817d6f0ab6205861b0d8798e233e39f",
32
- "sha1": "a71982663d82edc73db573973fb2e5590921bbf2",
33
- "md5": "435b90708be624f67494a0cf0da2ce1f"
27
+ "name": "expo.modules.video-3.0.0-canary-20250729-d8899ae.aar",
28
+ "url": "expo.modules.video-3.0.0-canary-20250729-d8899ae.aar",
29
+ "size": 466120,
30
+ "sha512": "9bbdd7b14aa471ce50972069209ace5b7f84790e377f5f69923693427084cea426ba002c64506288bac8d27865fb853d8c598e51617e1c485a96155562c1eceb",
31
+ "sha256": "356699282396f6089d797276fadfe255438c6d315b5b197f1f9069f919665c17",
32
+ "sha1": "dd4ac4c73d71f37356240d7978c60e6668796a35",
33
+ "md5": "efc5b8fa6220a70ce17f5c7b0a04b618"
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-20250713-8f814f8.aar",
117
- "url": "expo.modules.video-2.3.0-canary-20250713-8f814f8.aar",
118
- "size": 465973,
119
- "sha512": "f57545d9b42dce22febf3df2ce670289051c3505587db4f5c52eaa287938f0507566d7249c5ccb735c879c8446ddf146c8c9a8078bd13c5da39bd236e9cb77fd",
120
- "sha256": "f2b051d0ed6bb9dc676f3d916d053d58c817d6f0ab6205861b0d8798e233e39f",
121
- "sha1": "a71982663d82edc73db573973fb2e5590921bbf2",
122
- "md5": "435b90708be624f67494a0cf0da2ce1f"
116
+ "name": "expo.modules.video-3.0.0-canary-20250729-d8899ae.aar",
117
+ "url": "expo.modules.video-3.0.0-canary-20250729-d8899ae.aar",
118
+ "size": 466120,
119
+ "sha512": "9bbdd7b14aa471ce50972069209ace5b7f84790e377f5f69923693427084cea426ba002c64506288bac8d27865fb853d8c598e51617e1c485a96155562c1eceb",
120
+ "sha256": "356699282396f6089d797276fadfe255438c6d315b5b197f1f9069f919665c17",
121
+ "sha1": "dd4ac4c73d71f37356240d7978c60e6668796a35",
122
+ "md5": "efc5b8fa6220a70ce17f5c7b0a04b618"
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-20250713-8f814f8-sources.jar",
137
- "url": "expo.modules.video-2.3.0-canary-20250713-8f814f8-sources.jar",
138
- "size": 56192,
139
- "sha512": "905363a166bbd4501a68119e90a7a072f0034fe2fe921cefec23eb7e640e4e0fdb4b5fb9ac973991c6749cbcd39468fbdfe6e2d97d5693908368bde417c4c335",
140
- "sha256": "597df67ee5500fa5df31511112975c2de6522ee4d2a3e80b022879ea2aeb4e5a",
141
- "sha1": "ea2aad4450f75602f64a2664f77f9f0df03ecef1",
142
- "md5": "4a28a9ea066141f9d73f8ab961a31918"
136
+ "name": "expo.modules.video-3.0.0-canary-20250729-d8899ae-sources.jar",
137
+ "url": "expo.modules.video-3.0.0-canary-20250729-d8899ae-sources.jar",
138
+ "size": 56330,
139
+ "sha512": "cba7f5a7ccefc07e58c85e3c26c279e5c8624fc0c84510814b3fda2bdf6ef73496ffa446816cfe45cf31dbda071f6d146b73d11ffe755ef77c484ccd56211f93",
140
+ "sha256": "2fb05fdb332f403aee82cd5059ff214ef90621deea612eaaad68eb1fa6b86f1c",
141
+ "sha1": "66d3436151ca2150712aaca6132f42ae1c2b94c7",
142
+ "md5": "8e27a8a108cbb6d261d95e1aad97e27a"
143
143
  }
144
144
  ]
145
145
  }
@@ -0,0 +1 @@
1
+ bb7377573d2416dc3f9c2c6711eded7fce508edd1b267f45cf610850089d024d1cc0114d7445b563ca58bf862e103f0d3cf55db00e2e073a2000b750ace13695
@@ -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-20250713-8f814f8</version>
12
+ <version>3.0.0-canary-20250729-d8899ae</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
+ 46d3c5f3e4eaa2964e912eaca83de5e97b04803bf6b3803b9fac0ed802f565432388264fdf6a860856fe5f458250ed838744b0eefd4221c1aa8e55497d52555d
@@ -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-20250713-8f814f8</latest>
7
- <release>2.3.0-canary-20250713-8f814f8</release>
6
+ <latest>3.0.0-canary-20250729-d8899ae</latest>
7
+ <release>3.0.0-canary-20250729-d8899ae</release>
8
8
  <versions>
9
- <version>2.3.0-canary-20250713-8f814f8</version>
9
+ <version>3.0.0-canary-20250729-d8899ae</version>
10
10
  </versions>
11
- <lastUpdated>20250713133433</lastUpdated>
11
+ <lastUpdated>20250729125127</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- e55afa0a28b1249551458d2c60880f54
1
+ 2f6b16737bf5950e68921364ec28c18b
@@ -1 +1 @@
1
- f1a8791721d45b66d721ec48d2f3ad9b2d9feef0
1
+ 13333dc9d963d34a7478b992073ffd7461b3345c
@@ -1 +1 @@
1
- 6eb2cae210bc8b25dfd27d352a464ce11a142fd122224a3c373ecc08b7d3f986
1
+ 800635bad52cedc727719a51f6a63c5f5bfa4cf31a25085670d6cfb67be5a1c7
@@ -1 +1 @@
1
- 8b5aa5fd1cd5e5d7cd4d4d24a9bccdea25c76fa31c3264d620beee046450d0d3a82015fd534cd93b96621753fbf0b5256fa1489dcc4fcf990452758bbd612350
1
+ 799404dfac23caab225a2116d930ebec31cd2c646ab8caeae7f363e22da4dadaa60e37fc00d3fb896dbb9f45191c36edc06c5bcb85a1643c58209ebe4c877260
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-20250713-8f814f8",
4
+ "version": "3.0.0-canary-20250729-d8899ae",
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-20250713-8f814f8"
34
+ "expo-module-scripts": "4.1.10-canary-20250729-d8899ae"
35
35
  },
36
36
  "peerDependencies": {
37
- "expo": "54.0.0-canary-20250713-8f814f8",
37
+ "expo": "54.0.0-canary-20250729-d8899ae",
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
- 905363a166bbd4501a68119e90a7a072f0034fe2fe921cefec23eb7e640e4e0fdb4b5fb9ac973991c6749cbcd39468fbdfe6e2d97d5693908368bde417c4c335
@@ -1 +0,0 @@
1
- f57545d9b42dce22febf3df2ce670289051c3505587db4f5c52eaa287938f0507566d7249c5ccb735c879c8446ddf146c8c9a8078bd13c5da39bd236e9cb77fd
@@ -1 +0,0 @@
1
- 85dc196f93ff8084fec2b578e8b6024322a6d39d18b1b9a98e4bb959cd259e0ab977a43d4508412b4c57f37ef49b5fafa4061cd0f823654347f94daa1f5e922a
@@ -1 +0,0 @@
1
- cf31a914b228afd7e56befaac89ed11b63a77c69a8ef8ad2ff1222f3dca31474a7908e44206ce31fcd5d3fc0f94623ccac81cff072b1010b51daaad02480a363