@stepincto/expo-video 1.0.0

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 (180) hide show
  1. package/README.md +45 -0
  2. package/android/build.gradle +32 -0
  3. package/android/src/main/AndroidManifest.xml +20 -0
  4. package/android/src/main/java/expo/modules/video/AudioFocusManager.kt +241 -0
  5. package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +145 -0
  6. package/android/src/main/java/expo/modules/video/IntervalUpdateClock.kt +54 -0
  7. package/android/src/main/java/expo/modules/video/MediaMetadataRetriever.kt +89 -0
  8. package/android/src/main/java/expo/modules/video/PictureInPictureHelperFragment.kt +26 -0
  9. package/android/src/main/java/expo/modules/video/PlayerViewExtension.kt +36 -0
  10. package/android/src/main/java/expo/modules/video/VideoCache.kt +104 -0
  11. package/android/src/main/java/expo/modules/video/VideoExceptions.kt +34 -0
  12. package/android/src/main/java/expo/modules/video/VideoManager.kt +133 -0
  13. package/android/src/main/java/expo/modules/video/VideoModule.kt +414 -0
  14. package/android/src/main/java/expo/modules/video/VideoThumbnail.kt +20 -0
  15. package/android/src/main/java/expo/modules/video/VideoView.kt +367 -0
  16. package/android/src/main/java/expo/modules/video/delegates/IgnoreSameSet.kt +24 -0
  17. package/android/src/main/java/expo/modules/video/drawing/OutlineProvider.kt +217 -0
  18. package/android/src/main/java/expo/modules/video/enums/AudioMixingMode.kt +20 -0
  19. package/android/src/main/java/expo/modules/video/enums/ContentFit.kt +19 -0
  20. package/android/src/main/java/expo/modules/video/enums/ContentType.kt +22 -0
  21. package/android/src/main/java/expo/modules/video/enums/DRMType.kt +26 -0
  22. package/android/src/main/java/expo/modules/video/enums/PlayerStatus.kt +10 -0
  23. package/android/src/main/java/expo/modules/video/playbackService/ExpoVideoPlaybackService.kt +184 -0
  24. package/android/src/main/java/expo/modules/video/playbackService/PlaybackServiceConnection.kt +39 -0
  25. package/android/src/main/java/expo/modules/video/playbackService/VideoMediaSessionCallback.kt +47 -0
  26. package/android/src/main/java/expo/modules/video/player/FirstFrameEventGenerator.kt +93 -0
  27. package/android/src/main/java/expo/modules/video/player/PlayerEvent.kt +164 -0
  28. package/android/src/main/java/expo/modules/video/player/VideoPlayer.kt +460 -0
  29. package/android/src/main/java/expo/modules/video/player/VideoPlayerAudioTracks.kt +125 -0
  30. package/android/src/main/java/expo/modules/video/player/VideoPlayerListener.kt +32 -0
  31. package/android/src/main/java/expo/modules/video/player/VideoPlayerLoadControl.kt +525 -0
  32. package/android/src/main/java/expo/modules/video/player/VideoPlayerSubtitles.kt +125 -0
  33. package/android/src/main/java/expo/modules/video/records/BufferOptions.kt +15 -0
  34. package/android/src/main/java/expo/modules/video/records/DRMOptions.kt +25 -0
  35. package/android/src/main/java/expo/modules/video/records/PlaybackError.kt +19 -0
  36. package/android/src/main/java/expo/modules/video/records/Tracks.kt +81 -0
  37. package/android/src/main/java/expo/modules/video/records/VideoEventPayloads.kt +79 -0
  38. package/android/src/main/java/expo/modules/video/records/VideoMetadata.kt +12 -0
  39. package/android/src/main/java/expo/modules/video/records/VideoSize.kt +14 -0
  40. package/android/src/main/java/expo/modules/video/records/VideoSource.kt +104 -0
  41. package/android/src/main/java/expo/modules/video/records/VideoThumbnailOptions.kt +24 -0
  42. package/android/src/main/java/expo/modules/video/utils/DataSourceUtils.kt +75 -0
  43. package/android/src/main/java/expo/modules/video/utils/EventDispatcherUtils.kt +43 -0
  44. package/android/src/main/java/expo/modules/video/utils/MutableWeakReference.kt +15 -0
  45. package/android/src/main/java/expo/modules/video/utils/PictureInPictureUtils.kt +96 -0
  46. package/android/src/main/java/expo/modules/video/utils/YogaUtils.kt +20 -0
  47. package/android/src/main/res/drawable/seek_backwards_10s.xml +25 -0
  48. package/android/src/main/res/drawable/seek_backwards_15s.xml +25 -0
  49. package/android/src/main/res/drawable/seek_backwards_5s.xml +25 -0
  50. package/android/src/main/res/drawable/seek_forwards_10s.xml +30 -0
  51. package/android/src/main/res/drawable/seek_forwards_15s.xml +31 -0
  52. package/android/src/main/res/drawable/seek_forwards_5s.xml +30 -0
  53. package/android/src/main/res/layout/fullscreen_player_activity.xml +16 -0
  54. package/android/src/main/res/layout/surface_player_view.xml +7 -0
  55. package/android/src/main/res/layout/texture_player_view.xml +7 -0
  56. package/android/src/main/res/values/styles.xml +9 -0
  57. package/app.plugin.js +1 -0
  58. package/build/NativeVideoModule.d.ts +16 -0
  59. package/build/NativeVideoModule.d.ts.map +1 -0
  60. package/build/NativeVideoModule.js +3 -0
  61. package/build/NativeVideoModule.js.map +1 -0
  62. package/build/NativeVideoModule.web.d.ts +3 -0
  63. package/build/NativeVideoModule.web.d.ts.map +1 -0
  64. package/build/NativeVideoModule.web.js +2 -0
  65. package/build/NativeVideoModule.web.js.map +1 -0
  66. package/build/NativeVideoView.d.ts +4 -0
  67. package/build/NativeVideoView.d.ts.map +1 -0
  68. package/build/NativeVideoView.js +6 -0
  69. package/build/NativeVideoView.js.map +1 -0
  70. package/build/VideoModule.d.ts +38 -0
  71. package/build/VideoModule.d.ts.map +1 -0
  72. package/build/VideoModule.js +53 -0
  73. package/build/VideoModule.js.map +1 -0
  74. package/build/VideoPlayer.d.ts +15 -0
  75. package/build/VideoPlayer.d.ts.map +1 -0
  76. package/build/VideoPlayer.js +52 -0
  77. package/build/VideoPlayer.js.map +1 -0
  78. package/build/VideoPlayer.types.d.ts +532 -0
  79. package/build/VideoPlayer.types.d.ts.map +1 -0
  80. package/build/VideoPlayer.types.js +2 -0
  81. package/build/VideoPlayer.types.js.map +1 -0
  82. package/build/VideoPlayer.web.d.ts +75 -0
  83. package/build/VideoPlayer.web.d.ts.map +1 -0
  84. package/build/VideoPlayer.web.js +376 -0
  85. package/build/VideoPlayer.web.js.map +1 -0
  86. package/build/VideoPlayerEvents.types.d.ts +262 -0
  87. package/build/VideoPlayerEvents.types.d.ts.map +1 -0
  88. package/build/VideoPlayerEvents.types.js +2 -0
  89. package/build/VideoPlayerEvents.types.js.map +1 -0
  90. package/build/VideoThumbnail.d.ts +29 -0
  91. package/build/VideoThumbnail.d.ts.map +1 -0
  92. package/build/VideoThumbnail.js +3 -0
  93. package/build/VideoThumbnail.js.map +1 -0
  94. package/build/VideoView.d.ts +44 -0
  95. package/build/VideoView.d.ts.map +1 -0
  96. package/build/VideoView.js +76 -0
  97. package/build/VideoView.js.map +1 -0
  98. package/build/VideoView.types.d.ts +147 -0
  99. package/build/VideoView.types.d.ts.map +1 -0
  100. package/build/VideoView.types.js +2 -0
  101. package/build/VideoView.types.js.map +1 -0
  102. package/build/VideoView.web.d.ts +9 -0
  103. package/build/VideoView.web.d.ts.map +1 -0
  104. package/build/VideoView.web.js +180 -0
  105. package/build/VideoView.web.js.map +1 -0
  106. package/build/index.d.ts +9 -0
  107. package/build/index.d.ts.map +1 -0
  108. package/build/index.js +7 -0
  109. package/build/index.js.map +1 -0
  110. package/build/resolveAssetSource.d.ts +3 -0
  111. package/build/resolveAssetSource.d.ts.map +1 -0
  112. package/build/resolveAssetSource.js +3 -0
  113. package/build/resolveAssetSource.js.map +1 -0
  114. package/build/resolveAssetSource.web.d.ts +4 -0
  115. package/build/resolveAssetSource.web.d.ts.map +1 -0
  116. package/build/resolveAssetSource.web.js +16 -0
  117. package/build/resolveAssetSource.web.js.map +1 -0
  118. package/expo-module.config.json +9 -0
  119. package/ios/Cache/CachableRequest.swift +44 -0
  120. package/ios/Cache/CachedResource.swift +97 -0
  121. package/ios/Cache/CachingHelpers.swift +92 -0
  122. package/ios/Cache/MediaFileHandle.swift +94 -0
  123. package/ios/Cache/MediaInfo.swift +147 -0
  124. package/ios/Cache/ResourceLoaderDelegate.swift +274 -0
  125. package/ios/Cache/SynchronizedHashTable.swift +23 -0
  126. package/ios/Cache/VideoCacheManager.swift +338 -0
  127. package/ios/ContentKeyDelegate.swift +214 -0
  128. package/ios/ContentKeyManager.swift +21 -0
  129. package/ios/Enums/AudioMixingMode.swift +37 -0
  130. package/ios/Enums/ContentType.swift +12 -0
  131. package/ios/Enums/DRMType.swift +20 -0
  132. package/ios/Enums/PlayerStatus.swift +10 -0
  133. package/ios/Enums/VideoContentFit.swift +39 -0
  134. package/ios/ExpoVideo.podspec +29 -0
  135. package/ios/NowPlayingManager.swift +296 -0
  136. package/ios/Records/BufferOptions.swift +12 -0
  137. package/ios/Records/DRMOptions.swift +24 -0
  138. package/ios/Records/PlaybackError.swift +10 -0
  139. package/ios/Records/Tracks.swift +176 -0
  140. package/ios/Records/VideoEventPayloads.swift +76 -0
  141. package/ios/Records/VideoMetadata.swift +16 -0
  142. package/ios/Records/VideoSize.swift +15 -0
  143. package/ios/Records/VideoSource.swift +25 -0
  144. package/ios/Thumbnails/VideoThumbnail.swift +27 -0
  145. package/ios/Thumbnails/VideoThumbnailGenerator.swift +68 -0
  146. package/ios/Thumbnails/VideoThumbnailOptions.swift +15 -0
  147. package/ios/VideoAsset.swift +123 -0
  148. package/ios/VideoExceptions.swift +53 -0
  149. package/ios/VideoItem.swift +11 -0
  150. package/ios/VideoManager.swift +140 -0
  151. package/ios/VideoModule.swift +383 -0
  152. package/ios/VideoPlayer/DangerousPropertiesStore.swift +19 -0
  153. package/ios/VideoPlayer.swift +435 -0
  154. package/ios/VideoPlayerAudioTracks.swift +72 -0
  155. package/ios/VideoPlayerItem.swift +97 -0
  156. package/ios/VideoPlayerObserver.swift +523 -0
  157. package/ios/VideoPlayerSubtitles.swift +71 -0
  158. package/ios/VideoSourceLoader.swift +89 -0
  159. package/ios/VideoSourceLoaderListener.swift +34 -0
  160. package/ios/VideoView.swift +224 -0
  161. package/package.json +59 -0
  162. package/plugin/build/tsconfig.tsbuildinfo +1 -0
  163. package/plugin/build/withExpoVideo.d.ts +7 -0
  164. package/plugin/build/withExpoVideo.js +38 -0
  165. package/src/NativeVideoModule.ts +20 -0
  166. package/src/NativeVideoModule.web.ts +1 -0
  167. package/src/NativeVideoView.ts +8 -0
  168. package/src/VideoModule.ts +59 -0
  169. package/src/VideoPlayer.tsx +67 -0
  170. package/src/VideoPlayer.types.ts +613 -0
  171. package/src/VideoPlayer.web.tsx +451 -0
  172. package/src/VideoPlayerEvents.types.ts +313 -0
  173. package/src/VideoThumbnail.ts +31 -0
  174. package/src/VideoView.tsx +86 -0
  175. package/src/VideoView.types.ts +165 -0
  176. package/src/VideoView.web.tsx +214 -0
  177. package/src/index.ts +46 -0
  178. package/src/resolveAssetSource.ts +2 -0
  179. package/src/resolveAssetSource.web.ts +17 -0
  180. package/src/ts-declarations/react-native-assets.d.ts +1 -0
@@ -0,0 +1,34 @@
1
+ import Foundation
2
+
3
+ protocol VideoSourceLoaderListener: AnyObject {
4
+ func onLoadingStarted(loader: VideoSourceLoader, videoSource: VideoSource?)
5
+ func onLoadingFinished(loader: VideoSourceLoader, videoSource: VideoSource?, result: VideoPlayerItem?)
6
+ func onLoadingCancelled(loader: VideoSourceLoader, videoSource: VideoSource?)
7
+ }
8
+
9
+ extension VideoSourceLoaderListener {
10
+ func onLoadingStarted(loader: VideoSourceLoader, videoSource: VideoSource?) {}
11
+ func onLoadingFinished(loader: VideoSourceLoader, videoSource: VideoSource?, result: VideoPlayerItem?) {}
12
+ func onLoadingCancelled(loader: VideoSourceLoader, videoSource: VideoSource?) {}
13
+ }
14
+
15
+ final class WeakVideoSourceLoaderListener: Hashable {
16
+ private(set) weak var value: VideoSourceLoaderListener?
17
+
18
+ init(value: VideoSourceLoaderListener? = nil) {
19
+ self.value = value
20
+ }
21
+
22
+ static func == (lhs: WeakVideoSourceLoaderListener, rhs: WeakVideoSourceLoaderListener) -> Bool {
23
+ guard let lhsValue = lhs.value, let rhsValue = rhs.value else {
24
+ return lhs.value == nil && rhs.value == nil
25
+ }
26
+ return ObjectIdentifier(lhsValue) == ObjectIdentifier(rhsValue)
27
+ }
28
+
29
+ func hash(into hasher: inout Hasher) {
30
+ if let value {
31
+ hasher.combine(ObjectIdentifier(value))
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,224 @@
1
+ // Copyright 2023-present 650 Industries. All rights reserved.
2
+
3
+ import AVKit
4
+ import ExpoModulesCore
5
+
6
+ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate {
7
+ lazy var playerViewController = AVPlayerViewController()
8
+
9
+ weak var player: VideoPlayer? {
10
+ didSet {
11
+ playerViewController.player = player?.ref
12
+ }
13
+ }
14
+
15
+ #if os(tvOS)
16
+ var wasPlaying: Bool = false
17
+ #endif
18
+ var isFullscreen: Bool = false
19
+ var isInPictureInPicture = false
20
+ #if os(tvOS)
21
+ let startPictureInPictureAutomatically = false
22
+ #else
23
+ var startPictureInPictureAutomatically = false {
24
+ didSet {
25
+ playerViewController.canStartPictureInPictureAutomaticallyFromInline = startPictureInPictureAutomatically
26
+ }
27
+ }
28
+ #endif
29
+
30
+ var allowPictureInPicture: Bool = false {
31
+ didSet {
32
+ // PiP requires `.playback` audio session category in `.moviePlayback` mode
33
+ VideoManager.shared.setAppropriateAudioSessionOrWarn()
34
+ playerViewController.allowsPictureInPicturePlayback = allowPictureInPicture
35
+ }
36
+ }
37
+
38
+ let onPictureInPictureStart = EventDispatcher()
39
+ let onPictureInPictureStop = EventDispatcher()
40
+ let onFullscreenEnter = EventDispatcher()
41
+ let onFullscreenExit = EventDispatcher()
42
+ let onFirstFrameRender = EventDispatcher()
43
+
44
+ var firstFrameObserver: NSKeyValueObservation?
45
+
46
+ public override var bounds: CGRect {
47
+ didSet {
48
+ playerViewController.view.frame = self.bounds
49
+ }
50
+ }
51
+
52
+ public required init(appContext: AppContext? = nil) {
53
+ super.init(appContext: appContext)
54
+
55
+ VideoManager.shared.register(videoView: self)
56
+
57
+ clipsToBounds = true
58
+ playerViewController.delegate = self
59
+ playerViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
60
+ playerViewController.view.backgroundColor = .clear
61
+ // Now playing is managed by the `NowPlayingManager`
62
+ #if !os(tvOS)
63
+ playerViewController.updatesNowPlayingInfoCenter = false
64
+ #endif
65
+
66
+ addFirstFrameObserver()
67
+ addSubview(playerViewController.view)
68
+ }
69
+
70
+ deinit {
71
+ VideoManager.shared.unregister(videoView: self)
72
+ removeFirstFrameObserver()
73
+ }
74
+
75
+ func enterFullscreen() {
76
+ if isFullscreen {
77
+ return
78
+ }
79
+ let selectorName = "enterFullScreenAnimated:completionHandler:"
80
+ let selectorToForceFullScreenMode = NSSelectorFromString(selectorName)
81
+
82
+ if playerViewController.responds(to: selectorToForceFullScreenMode) {
83
+ playerViewController.perform(selectorToForceFullScreenMode, with: true, with: nil)
84
+ } else {
85
+ #if os(tvOS)
86
+ // For TV, save the currently playing state,
87
+ // remove the view controller from its superview,
88
+ // and present the view controller normally
89
+ wasPlaying = player?.isPlaying == true
90
+ self.playerViewController.view.removeFromSuperview()
91
+ self.reactViewController().present(self.playerViewController, animated: true)
92
+ onFullscreenEnter()
93
+ isFullscreen = true
94
+ #endif
95
+ }
96
+ }
97
+
98
+ func exitFullscreen() {
99
+ if !isFullscreen {
100
+ return
101
+ }
102
+ let selectorName = "exitFullScreenAnimated:completionHandler:"
103
+ let selectorToExitFullScreenMode = NSSelectorFromString(selectorName)
104
+
105
+ if playerViewController.responds(to: selectorToExitFullScreenMode) {
106
+ playerViewController.perform(selectorToExitFullScreenMode, with: true, with: nil)
107
+ }
108
+ }
109
+
110
+ func startPictureInPicture() throws {
111
+ if !AVPictureInPictureController.isPictureInPictureSupported() {
112
+ throw PictureInPictureUnsupportedException()
113
+ }
114
+
115
+ let selectorName = "startPictureInPicture"
116
+ let selectorToStartPictureInPicture = NSSelectorFromString(selectorName)
117
+
118
+ if playerViewController.responds(to: selectorToStartPictureInPicture) {
119
+ playerViewController.perform(selectorToStartPictureInPicture)
120
+ }
121
+ }
122
+
123
+ func stopPictureInPicture() {
124
+ let selectorName = "stopPictureInPicture"
125
+ let selectorToStopPictureInPicture = NSSelectorFromString(selectorName)
126
+
127
+ if playerViewController.responds(to: selectorToStopPictureInPicture) {
128
+ playerViewController.perform(selectorToStopPictureInPicture)
129
+ }
130
+ }
131
+
132
+ // MARK: - AVPlayerViewControllerDelegate
133
+
134
+ #if os(tvOS)
135
+ // TV actually presents the playerViewController, so it implements the view controller
136
+ // dismissal delegate methods
137
+ public func playerViewControllerWillBeginDismissalTransition(_ playerViewController: AVPlayerViewController) {
138
+ // Start an appearance transition
139
+ self.playerViewController.beginAppearanceTransition(true, animated: true)
140
+ }
141
+
142
+ public func playerViewControllerDidEndDismissalTransition(_ playerViewController: AVPlayerViewController) {
143
+ self.onFullscreenExit()
144
+ self.isFullscreen = false
145
+ // Reset the bounds of the view controller and add it back to our view
146
+ self.playerViewController.view.frame = self.bounds
147
+ addSubview(self.playerViewController.view)
148
+ // End the appearance transition
149
+ self.playerViewController.endAppearanceTransition()
150
+ // Ensure playing state is preserved
151
+ if wasPlaying {
152
+ self.player?.ref.play()
153
+ } else {
154
+ self.player?.ref.pause()
155
+ }
156
+ }
157
+ #endif
158
+
159
+ #if !os(tvOS)
160
+ public func playerViewController(
161
+ _ playerViewController: AVPlayerViewController,
162
+ willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator
163
+ ) {
164
+ onFullscreenEnter()
165
+ isFullscreen = true
166
+ }
167
+
168
+ public func playerViewController(
169
+ _ playerViewController: AVPlayerViewController,
170
+ willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator
171
+ ) {
172
+ // Platform's behavior is to pause the player when exiting the fullscreen mode.
173
+ // It seems better to continue playing, so we resume the player once the dismissing animation finishes.
174
+ let wasPlaying = player?.ref.timeControlStatus == .playing
175
+
176
+ coordinator.animate(alongsideTransition: nil) { context in
177
+ if !context.isCancelled {
178
+ if wasPlaying {
179
+ self.player?.ref.play()
180
+ }
181
+ self.onFullscreenExit()
182
+ self.isFullscreen = false
183
+ }
184
+ }
185
+ }
186
+ #endif
187
+
188
+ public func playerViewControllerDidStartPictureInPicture(_ playerViewController: AVPlayerViewController) {
189
+ isInPictureInPicture = true
190
+ onPictureInPictureStart()
191
+ }
192
+
193
+ public func playerViewControllerDidStopPictureInPicture(_ playerViewController: AVPlayerViewController) {
194
+ isInPictureInPicture = false
195
+ onPictureInPictureStop()
196
+ }
197
+
198
+ public override func didMoveToWindow() {
199
+ // TV is doing a normal view controller present, so we should not execute
200
+ // this code
201
+ #if !os(tvOS)
202
+ playerViewController.beginAppearanceTransition(self.window != nil, animated: true)
203
+ #endif
204
+ }
205
+
206
+ public override func safeAreaInsetsDidChange() {
207
+ super.safeAreaInsetsDidChange()
208
+ // This is the only way that I (@behenate) found to force re-calculation of the safe-area insets for native controls
209
+ playerViewController.view.removeFromSuperview()
210
+ addSubview(playerViewController.view)
211
+ }
212
+
213
+ private func addFirstFrameObserver() {
214
+ firstFrameObserver = playerViewController.observe(\.isReadyForDisplay, changeHandler: { [weak self] playerViewController, _ in
215
+ if playerViewController.isReadyForDisplay {
216
+ self?.onFirstFrameRender()
217
+ }
218
+ })
219
+ }
220
+ private func removeFirstFrameObserver() {
221
+ firstFrameObserver?.invalidate()
222
+ firstFrameObserver = nil
223
+ }
224
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@stepincto/expo-video",
3
+ "title": "Expo Video",
4
+ "version": "1.0.0",
5
+ "originalUpstreamVersion": "2.2.2",
6
+ "description": "A cross-platform, performant video component for React Native and Expo with Web support",
7
+ "main": "build/index.js",
8
+ "types": "build/index.d.ts",
9
+ "sideEffects": false,
10
+ "scripts": {
11
+ "build": "expo-module build",
12
+ "clean": "expo-module clean",
13
+ "lint": "expo-module lint",
14
+ "test": "expo-module test",
15
+ "prepare": "expo-module prepare",
16
+ "prepublishOnly": "expo-module prepublishOnly",
17
+ "expo-module": "expo-module",
18
+ "build:lib": "tsc -p tsconfig.json",
19
+ "build:plugin": "tsc -p plugin/tsconfig.json",
20
+ "build:once": "npm run build:lib && npm run build:plugin",
21
+ "pack:local": "npm pack --ignore-scripts"
22
+ },
23
+ "homepage": "https://docs.expo.dev/versions/latest/sdk/video/",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/expo/expo.git"
27
+ },
28
+ "keywords": [
29
+ "react-native",
30
+ "expo",
31
+ "video",
32
+ "player"
33
+ ],
34
+ "author": "650 Industries, Inc.",
35
+ "license": "MIT",
36
+ "devDependencies": {
37
+ "@types/react": "^19.1.9",
38
+ "@types/react-dom": "^19.1.7",
39
+ "expo-module-scripts": "^4.1.10",
40
+ "typescript": "^5.9.2"
41
+ },
42
+ "peerDependencies": {
43
+ "expo": "^53.0.0",
44
+ "expo-modules-core": ">=2.4 <2.6",
45
+ "react": ">=18",
46
+ "react-native": ">=0.75"
47
+ },
48
+ "files": [
49
+ "build",
50
+ "ios",
51
+ "android",
52
+ "plugin/build",
53
+ "src",
54
+ "app.plugin.js",
55
+ "expo-module.config.json",
56
+ "README.md",
57
+ "LICENSE"
58
+ ]
59
+ }
@@ -0,0 +1 @@
1
+ {"root":["../src/withexpovideo.ts"],"version":"5.9.2"}
@@ -0,0 +1,7 @@
1
+ import { type ConfigPlugin } from 'expo/config-plugins';
2
+ type WithExpoVideoOptions = {
3
+ supportsBackgroundPlayback?: boolean;
4
+ supportsPictureInPicture?: boolean;
5
+ };
6
+ declare const withExpoVideo: ConfigPlugin<WithExpoVideoOptions>;
7
+ export default withExpoVideo;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_plugins_1 = require("expo/config-plugins");
4
+ const withExpoVideo = (config, { supportsBackgroundPlayback, supportsPictureInPicture } = {}) => {
5
+ (0, config_plugins_1.withInfoPlist)(config, (config) => {
6
+ var _a;
7
+ const currentBackgroundModes = (_a = config.modResults.UIBackgroundModes) !== null && _a !== void 0 ? _a : [];
8
+ const shouldEnableBackgroundAudio = supportsBackgroundPlayback || supportsPictureInPicture;
9
+ // No-op if the values are not defined
10
+ if (typeof supportsBackgroundPlayback === 'undefined' &&
11
+ typeof supportsPictureInPicture === 'undefined') {
12
+ return config;
13
+ }
14
+ if (shouldEnableBackgroundAudio && !currentBackgroundModes.includes('audio')) {
15
+ config.modResults.UIBackgroundModes = [...currentBackgroundModes, 'audio'];
16
+ }
17
+ else if (!shouldEnableBackgroundAudio) {
18
+ config.modResults.UIBackgroundModes = currentBackgroundModes.filter((mode) => mode !== 'audio');
19
+ }
20
+ return config;
21
+ });
22
+ (0, config_plugins_1.withAndroidManifest)(config, (config) => {
23
+ const activity = config_plugins_1.AndroidConfig.Manifest.getMainActivityOrThrow(config.modResults);
24
+ // No-op if the values are not defined
25
+ if (typeof supportsPictureInPicture === 'undefined') {
26
+ return config;
27
+ }
28
+ if (supportsPictureInPicture) {
29
+ activity.$['android:supportsPictureInPicture'] = 'true';
30
+ }
31
+ else {
32
+ delete activity.$['android:supportsPictureInPicture'];
33
+ }
34
+ return config;
35
+ });
36
+ return config;
37
+ };
38
+ exports.default = withExpoVideo;
@@ -0,0 +1,20 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+
3
+ import type { VideoPlayer } from './VideoPlayer.types';
4
+ import type { VideoThumbnail } from './VideoThumbnail';
5
+
6
+ type ExpoVideoModule = {
7
+ VideoPlayer: typeof VideoPlayer;
8
+ VideoThumbnail: typeof VideoThumbnail;
9
+
10
+ isPictureInPictureSupported(): boolean;
11
+ setVideoCacheSizeAsync(sizeBytes: number): Promise<void>;
12
+ clearVideoCacheAsync(): Promise<void>;
13
+ getCurrentVideoCacheSize(): number;
14
+
15
+ preCacheVideoPartialAsync(url: string, chunkSize?: number): Promise<boolean>;
16
+ preCacheVideoAsync(url: string): Promise<boolean>;
17
+ isVideoCachedAsync(url: string): Promise<boolean>;
18
+ };
19
+
20
+ export default requireNativeModule<ExpoVideoModule>('ExpoVideo');
@@ -0,0 +1 @@
1
+ export default () => {};
@@ -0,0 +1,8 @@
1
+ import { requireNativeViewManager } from 'expo-modules-core';
2
+ import { Platform } from 'react-native';
3
+
4
+ const defaultViewName = Platform.OS === 'android' ? 'SurfaceVideoView' : 'VideoView';
5
+
6
+ export default requireNativeViewManager('ExpoVideo', defaultViewName);
7
+ export const NativeTextureVideoView =
8
+ Platform.OS === 'android' ? requireNativeViewManager('ExpoVideo', 'TextureVideoView') : null;
@@ -0,0 +1,59 @@
1
+ import NativeVideoModule from './NativeVideoModule';
2
+
3
+ /**
4
+ * Returns whether the current device supports Picture in Picture (PiP) mode.
5
+ *
6
+ * @returns A `boolean` which is `true` if the device supports PiP mode, and `false` otherwise.
7
+ * @platform android
8
+ * @platform ios
9
+ */
10
+ export function isPictureInPictureSupported(): boolean {
11
+ return NativeVideoModule.isPictureInPictureSupported();
12
+ }
13
+
14
+ /**
15
+ * Clears all video cache.
16
+ * > This function can be called only if there are no existing `VideoPlayer` instances.
17
+ *
18
+ * @returns A promise that fulfills after the cache has been cleaned.
19
+ * @platform android
20
+ * @platform ios
21
+ */
22
+ export function clearVideoCacheAsync(): Promise<void> {
23
+ return NativeVideoModule.clearVideoCacheAsync();
24
+ }
25
+
26
+ /**
27
+ * Sets desired video cache size in bytes. The default video cache size is 1GB. Value set by this function is persistent.
28
+ * The cache size is not guaranteed to be exact and the actual cache size may be slightly larger. The cache is evicted on a least-recently-used basis.
29
+ * > This function can be called only if there are no existing `VideoPlayer` instances.
30
+ *
31
+ * @returns A promise that fulfills after the cache size has been set.
32
+ * @platform android
33
+ * @platform ios
34
+ */
35
+ export function setVideoCacheSizeAsync(sizeBytes: number): Promise<void> {
36
+ return NativeVideoModule.setVideoCacheSizeAsync(sizeBytes);
37
+ }
38
+
39
+ /**
40
+ * Returns the space currently occupied by the video cache in bytes.
41
+ *
42
+ * @platform android
43
+ * @platform ios
44
+ */
45
+ export function getCurrentVideoCacheSize(): number {
46
+ return NativeVideoModule.getCurrentVideoCacheSize();
47
+ }
48
+
49
+ export async function preCacheVideoAsync(url: string): Promise<boolean> {
50
+ return NativeVideoModule.preCacheVideoAsync(url);
51
+ }
52
+
53
+ export async function preCacheVideoPartialAsync(url: string, chunkSize?: number): Promise<boolean> {
54
+ return NativeVideoModule.preCacheVideoPartialAsync(url, chunkSize);
55
+ }
56
+
57
+ export function isVideoCachedAsync(url: string): Promise<boolean> {
58
+ return NativeVideoModule.isVideoCachedAsync(url);
59
+ }
@@ -0,0 +1,67 @@
1
+ import { useReleasingSharedObject } from 'expo-modules-core';
2
+
3
+ import NativeVideoModule from './NativeVideoModule';
4
+ import { VideoSource, VideoPlayer } from './VideoPlayer.types';
5
+ import resolveAssetSource from './resolveAssetSource';
6
+
7
+ // TODO: Temporary solution until we develop a way of overriding prototypes that won't break the lazy loading of the module.
8
+ const replace = NativeVideoModule.VideoPlayer.prototype.replace;
9
+ NativeVideoModule.VideoPlayer.prototype.replace = function (
10
+ source: VideoSource,
11
+ disableWarning: boolean = false
12
+ ) {
13
+ if (!disableWarning) {
14
+ console.warn(
15
+ 'On iOS `VideoPlayer.replace` loads the asset data synchronously on the main thread, which can lead to UI freezes and will be deprecated in a future release. Switch to `replaceAsync` for better user experience.'
16
+ );
17
+ }
18
+
19
+ return replace.call(this, parseSource(source));
20
+ };
21
+
22
+ const replaceAsync = NativeVideoModule.VideoPlayer.prototype.replaceAsync;
23
+ NativeVideoModule.VideoPlayer.prototype.replaceAsync = function (source: VideoSource) {
24
+ return replaceAsync.call(this, parseSource(source));
25
+ };
26
+ /**
27
+ * Creates a direct instance of `VideoPlayer` that doesn't release automatically.
28
+ *
29
+ * > **info** For most use cases you should use the [`useVideoPlayer`](#usevideoplayer) hook instead. See the [Using the VideoPlayer Directly](#using-the-videoplayer-directly) section for more details.
30
+ * @param source
31
+ */
32
+ export function createVideoPlayer(source: VideoSource): VideoPlayer {
33
+ return new NativeVideoModule.VideoPlayer(parseSource(source));
34
+ }
35
+
36
+ /**
37
+ * Creates a `VideoPlayer`, which will be automatically cleaned up when the component is unmounted.
38
+ * @param source - A video source that is used to initialize the player.
39
+ * @param setup - A function that allows setting up the player. It will run after the player is created.
40
+ */
41
+ export function useVideoPlayer(
42
+ source: VideoSource,
43
+ setup?: (player: VideoPlayer) => void
44
+ ): VideoPlayer {
45
+ const parsedSource = parseSource(source);
46
+
47
+ return useReleasingSharedObject(() => {
48
+ const player = new NativeVideoModule.VideoPlayer(parsedSource);
49
+ setup?.(player);
50
+ return player;
51
+ }, [JSON.stringify(parsedSource)]);
52
+ }
53
+
54
+ function parseSource(source: VideoSource): VideoSource {
55
+ if (typeof source === 'number') {
56
+ // TODO(@kitten): This seems to not handle the `null` case. Is this correct?
57
+ return { uri: resolveAssetSource(source)!.uri };
58
+ } else if (typeof source === 'string') {
59
+ return { uri: source };
60
+ }
61
+
62
+ if (typeof source?.assetId === 'number' && !source.uri) {
63
+ // TODO(@kitten): This seems to not handle the `null` case. Is this correct?
64
+ return { ...source, uri: resolveAssetSource(source.assetId)!.uri };
65
+ }
66
+ return source;
67
+ }