expo-video-subtitle 0.1.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 (255) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/FORK.md +149 -0
  3. package/LICENSE +21 -0
  4. package/README.md +108 -0
  5. package/android/build.gradle +32 -0
  6. package/android/src/main/AndroidManifest.xml +10 -0
  7. package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +258 -0
  8. package/android/src/main/java/expo/modules/video/IntervalUpdateClock.kt +54 -0
  9. package/android/src/main/java/expo/modules/video/MediaMetadataRetriever.kt +89 -0
  10. package/android/src/main/java/expo/modules/video/PlayerViewExtension.kt +87 -0
  11. package/android/src/main/java/expo/modules/video/VideoCache.kt +109 -0
  12. package/android/src/main/java/expo/modules/video/VideoExceptions.kt +45 -0
  13. package/android/src/main/java/expo/modules/video/VideoModule.kt +446 -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 +457 -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/FullscreenOrientation.kt +25 -0
  23. package/android/src/main/java/expo/modules/video/enums/PlayerStatus.kt +10 -0
  24. package/android/src/main/java/expo/modules/video/enums/VideoRange.kt +27 -0
  25. package/android/src/main/java/expo/modules/video/listeners/VideoManagerListener.kt +10 -0
  26. package/android/src/main/java/expo/modules/video/listeners/VideoPlayerListener.kt +35 -0
  27. package/android/src/main/java/expo/modules/video/listeners/VideoViewListener.kt +13 -0
  28. package/android/src/main/java/expo/modules/video/managers/AudioFocusManager.kt +242 -0
  29. package/android/src/main/java/expo/modules/video/managers/PictureInPictureManager.kt +329 -0
  30. package/android/src/main/java/expo/modules/video/managers/VideoManager.kt +194 -0
  31. package/android/src/main/java/expo/modules/video/playbackService/ExpoVideoPlaybackService.kt +267 -0
  32. package/android/src/main/java/expo/modules/video/playbackService/PlaybackServiceConnection.kt +62 -0
  33. package/android/src/main/java/expo/modules/video/playbackService/VideoMediaSessionCallback.kt +47 -0
  34. package/android/src/main/java/expo/modules/video/player/DefaultLoadControl.java +552 -0
  35. package/android/src/main/java/expo/modules/video/player/FirstFrameEventGenerator.kt +116 -0
  36. package/android/src/main/java/expo/modules/video/player/PlayerEvent.kt +172 -0
  37. package/android/src/main/java/expo/modules/video/player/VideoPlayer.kt +598 -0
  38. package/android/src/main/java/expo/modules/video/player/VideoPlayerAudioTracks.kt +126 -0
  39. package/android/src/main/java/expo/modules/video/player/VideoPlayerKeepAwake.kt +91 -0
  40. package/android/src/main/java/expo/modules/video/player/VideoPlayerLoadControl.kt +57 -0
  41. package/android/src/main/java/expo/modules/video/player/VideoPlayerSubtitles.kt +126 -0
  42. package/android/src/main/java/expo/modules/video/records/BufferOptions.kt +18 -0
  43. package/android/src/main/java/expo/modules/video/records/ButtonOptions.kt +17 -0
  44. package/android/src/main/java/expo/modules/video/records/DRMOptions.kt +27 -0
  45. package/android/src/main/java/expo/modules/video/records/FullscreenOptions.kt +14 -0
  46. package/android/src/main/java/expo/modules/video/records/PiPParams.kt +38 -0
  47. package/android/src/main/java/expo/modules/video/records/PlaybackError.kt +21 -0
  48. package/android/src/main/java/expo/modules/video/records/PlayerBuilderOptions.kt +15 -0
  49. package/android/src/main/java/expo/modules/video/records/ScrubbingModeOptions.kt +33 -0
  50. package/android/src/main/java/expo/modules/video/records/SeekTolerance.kt +30 -0
  51. package/android/src/main/java/expo/modules/video/records/SubtitleSource.kt +54 -0
  52. package/android/src/main/java/expo/modules/video/records/SubtitleStyle.kt +98 -0
  53. package/android/src/main/java/expo/modules/video/records/Tracks.kt +115 -0
  54. package/android/src/main/java/expo/modules/video/records/VideoEventPayloads.kt +79 -0
  55. package/android/src/main/java/expo/modules/video/records/VideoMetadata.kt +14 -0
  56. package/android/src/main/java/expo/modules/video/records/VideoSize.kt +16 -0
  57. package/android/src/main/java/expo/modules/video/records/VideoSource.kt +113 -0
  58. package/android/src/main/java/expo/modules/video/records/VideoThumbnailOptions.kt +26 -0
  59. package/android/src/main/java/expo/modules/video/utils/DataSourceUtils.kt +76 -0
  60. package/android/src/main/java/expo/modules/video/utils/EventDispatcherUtils.kt +43 -0
  61. package/android/src/main/java/expo/modules/video/utils/FullscreenActivityOrientationHelper.kt +120 -0
  62. package/android/src/main/java/expo/modules/video/utils/MediaSessionUtils.kt +14 -0
  63. package/android/src/main/java/expo/modules/video/utils/MutableWeakReference.kt +26 -0
  64. package/android/src/main/java/expo/modules/video/utils/PictureInPictureHelperFragment.kt +49 -0
  65. package/android/src/main/java/expo/modules/video/utils/PictureInPictureUtils.kt +96 -0
  66. package/android/src/main/java/expo/modules/video/utils/SubtitleUtils.kt +68 -0
  67. package/android/src/main/java/expo/modules/video/utils/ViewVisibilityUtils.kt +28 -0
  68. package/android/src/main/java/expo/modules/video/utils/WeakMutableSet.kt +120 -0
  69. package/android/src/main/java/expo/modules/video/utils/YogaUtils.kt +20 -0
  70. package/android/src/main/res/drawable/seek_backwards_10s.xml +25 -0
  71. package/android/src/main/res/drawable/seek_backwards_15s.xml +25 -0
  72. package/android/src/main/res/drawable/seek_backwards_5s.xml +25 -0
  73. package/android/src/main/res/drawable/seek_forwards_10s.xml +30 -0
  74. package/android/src/main/res/drawable/seek_forwards_15s.xml +31 -0
  75. package/android/src/main/res/drawable/seek_forwards_5s.xml +30 -0
  76. package/android/src/main/res/layout/fullscreen_player_activity.xml +20 -0
  77. package/android/src/main/res/layout/surface_player_view.xml +7 -0
  78. package/android/src/main/res/layout/texture_player_view.xml +7 -0
  79. package/android/src/main/res/values/styles.xml +9 -0
  80. package/app.plugin.js +1 -0
  81. package/build/NativeVideoAirPlayButtonView.d.ts +3 -0
  82. package/build/NativeVideoAirPlayButtonView.d.ts.map +1 -0
  83. package/build/NativeVideoAirPlayButtonView.js +3 -0
  84. package/build/NativeVideoAirPlayButtonView.js.map +1 -0
  85. package/build/NativeVideoModule.d.ts +13 -0
  86. package/build/NativeVideoModule.d.ts.map +1 -0
  87. package/build/NativeVideoModule.js +3 -0
  88. package/build/NativeVideoModule.js.map +1 -0
  89. package/build/NativeVideoModule.web.d.ts +5 -0
  90. package/build/NativeVideoModule.web.d.ts.map +1 -0
  91. package/build/NativeVideoModule.web.js +5 -0
  92. package/build/NativeVideoModule.web.js.map +1 -0
  93. package/build/NativeVideoView.d.ts +4 -0
  94. package/build/NativeVideoView.d.ts.map +1 -0
  95. package/build/NativeVideoView.js +6 -0
  96. package/build/NativeVideoView.js.map +1 -0
  97. package/build/VideoAirPlayButton.d.ts +10 -0
  98. package/build/VideoAirPlayButton.d.ts.map +1 -0
  99. package/build/VideoAirPlayButton.ios.d.ts +3 -0
  100. package/build/VideoAirPlayButton.ios.d.ts.map +1 -0
  101. package/build/VideoAirPlayButton.ios.js +6 -0
  102. package/build/VideoAirPlayButton.ios.js.map +1 -0
  103. package/build/VideoAirPlayButton.js +13 -0
  104. package/build/VideoAirPlayButton.js.map +1 -0
  105. package/build/VideoAirPlayButton.types.d.ts +35 -0
  106. package/build/VideoAirPlayButton.types.d.ts.map +1 -0
  107. package/build/VideoAirPlayButton.types.js +2 -0
  108. package/build/VideoAirPlayButton.types.js.map +1 -0
  109. package/build/VideoModule.d.ts +35 -0
  110. package/build/VideoModule.d.ts.map +1 -0
  111. package/build/VideoModule.js +44 -0
  112. package/build/VideoModule.js.map +1 -0
  113. package/build/VideoPlayer.d.ts +17 -0
  114. package/build/VideoPlayer.d.ts.map +1 -0
  115. package/build/VideoPlayer.js +55 -0
  116. package/build/VideoPlayer.js.map +1 -0
  117. package/build/VideoPlayer.types.d.ts +782 -0
  118. package/build/VideoPlayer.types.d.ts.map +1 -0
  119. package/build/VideoPlayer.types.js +2 -0
  120. package/build/VideoPlayer.types.js.map +1 -0
  121. package/build/VideoPlayer.web.d.ts +79 -0
  122. package/build/VideoPlayer.web.d.ts.map +1 -0
  123. package/build/VideoPlayer.web.js +388 -0
  124. package/build/VideoPlayer.web.js.map +1 -0
  125. package/build/VideoPlayerEvents.types.d.ts +278 -0
  126. package/build/VideoPlayerEvents.types.d.ts.map +1 -0
  127. package/build/VideoPlayerEvents.types.js +2 -0
  128. package/build/VideoPlayerEvents.types.js.map +1 -0
  129. package/build/VideoThumbnail.d.ts +29 -0
  130. package/build/VideoThumbnail.d.ts.map +1 -0
  131. package/build/VideoThumbnail.js +4 -0
  132. package/build/VideoThumbnail.js.map +1 -0
  133. package/build/VideoView.d.ts +69 -0
  134. package/build/VideoView.d.ts.map +1 -0
  135. package/build/VideoView.js +100 -0
  136. package/build/VideoView.js.map +1 -0
  137. package/build/VideoView.types.d.ts +362 -0
  138. package/build/VideoView.types.d.ts.map +1 -0
  139. package/build/VideoView.types.js +2 -0
  140. package/build/VideoView.types.js.map +1 -0
  141. package/build/VideoView.web.d.ts +9 -0
  142. package/build/VideoView.web.d.ts.map +1 -0
  143. package/build/VideoView.web.js +239 -0
  144. package/build/VideoView.web.js.map +1 -0
  145. package/build/index.d.ts +11 -0
  146. package/build/index.d.ts.map +1 -0
  147. package/build/index.js +6 -0
  148. package/build/index.js.map +1 -0
  149. package/build/resolveAssetSource.d.ts +3 -0
  150. package/build/resolveAssetSource.d.ts.map +1 -0
  151. package/build/resolveAssetSource.js +3 -0
  152. package/build/resolveAssetSource.js.map +1 -0
  153. package/build/resolveAssetSource.web.d.ts +4 -0
  154. package/build/resolveAssetSource.web.d.ts.map +1 -0
  155. package/build/resolveAssetSource.web.js +16 -0
  156. package/build/resolveAssetSource.web.js.map +1 -0
  157. package/expo-module.config.json +9 -0
  158. package/ios/Cache/CachableRequest.swift +46 -0
  159. package/ios/Cache/CacheVideoAssetTransportProvider.swift +64 -0
  160. package/ios/Cache/CachedResource.swift +164 -0
  161. package/ios/Cache/CachingHelpers.swift +107 -0
  162. package/ios/Cache/MediaFileHandle.swift +139 -0
  163. package/ios/Cache/MediaInfo.swift +185 -0
  164. package/ios/Cache/ResourceLoaderDelegate.swift +306 -0
  165. package/ios/Cache/SynchronizedHashTable.swift +23 -0
  166. package/ios/Cache/VideoCacheManager.swift +192 -0
  167. package/ios/ContentKeyDelegate.swift +214 -0
  168. package/ios/ContentKeyManager.swift +21 -0
  169. package/ios/Enums/AudioMixingMode.swift +37 -0
  170. package/ios/Enums/ContentType.swift +11 -0
  171. package/ios/Enums/DRMType.swift +20 -0
  172. package/ios/Enums/FullscreenOrientation.swift +34 -0
  173. package/ios/Enums/KeepFullscreenOnPiPStopBehavior.swift +20 -0
  174. package/ios/Enums/PlayerStatus.swift +10 -0
  175. package/ios/Enums/VideoContentFit.swift +39 -0
  176. package/ios/Enums/VideoRange.swift +21 -0
  177. package/ios/ExpoVideo.podspec +27 -0
  178. package/ios/NowPlayingManager.swift +304 -0
  179. package/ios/OrientationAVPlayerViewController.swift +263 -0
  180. package/ios/Records/BufferOptions.swift +12 -0
  181. package/ios/Records/DRMOptions.swift +24 -0
  182. package/ios/Records/FullscreenOptions.swift +14 -0
  183. package/ios/Records/PlaybackError.swift +10 -0
  184. package/ios/Records/ScrubbingModeOptions.swift +7 -0
  185. package/ios/Records/SeekTolerance.swift +20 -0
  186. package/ios/Records/SubtitleSource.swift +32 -0
  187. package/ios/Records/SubtitleStyle.swift +118 -0
  188. package/ios/Records/Tracks.swift +229 -0
  189. package/ios/Records/VideoEventPayloads.swift +81 -0
  190. package/ios/Records/VideoMetadata.swift +16 -0
  191. package/ios/Records/VideoSize.swift +15 -0
  192. package/ios/Records/VideoSource.swift +28 -0
  193. package/ios/Thumbnails/VideoThumbnail.swift +27 -0
  194. package/ios/Thumbnails/VideoThumbnailGenerator.swift +68 -0
  195. package/ios/Thumbnails/VideoThumbnailOptions.swift +15 -0
  196. package/ios/Utils/AVAssetVariant+VideoTracks.swift +20 -0
  197. package/ios/Utils/AvAssetTrack+VideoTracks.swift +79 -0
  198. package/ios/Utils/FourCharCode+toString.swift +25 -0
  199. package/ios/Utils/HlsUriUtils.swift +27 -0
  200. package/ios/Utils/URL+MediaLibraryAssets.swift +68 -0
  201. package/ios/VideoAirPlayButtonView.swift +70 -0
  202. package/ios/VideoAsset.swift +98 -0
  203. package/ios/VideoAssetTransport/VideoAssetLoadPlan.swift +69 -0
  204. package/ios/VideoAssetTransport/VideoAssetSourceDescriptor.swift +29 -0
  205. package/ios/VideoAssetTransport/VideoAssetTransportProvider.swift +24 -0
  206. package/ios/VideoAssetTransport/VideoAssetTransportRegistry.swift +103 -0
  207. package/ios/VideoExceptions.swift +53 -0
  208. package/ios/VideoItem.swift +11 -0
  209. package/ios/VideoManager.swift +182 -0
  210. package/ios/VideoModule.swift +413 -0
  211. package/ios/VideoPlayer/DangerousPropertiesStore.swift +19 -0
  212. package/ios/VideoPlayer.swift +485 -0
  213. package/ios/VideoPlayerAudioTracks.swift +109 -0
  214. package/ios/VideoPlayerItem.swift +169 -0
  215. package/ios/VideoPlayerObserver.swift +612 -0
  216. package/ios/VideoPlayerSeeker.swift +77 -0
  217. package/ios/VideoPlayerSubtitleSideload.swift +203 -0
  218. package/ios/VideoPlayerSubtitles.swift +88 -0
  219. package/ios/VideoSourceLoader.swift +90 -0
  220. package/ios/VideoSourceLoaderListener.swift +34 -0
  221. package/ios/VideoView.swift +201 -0
  222. package/package.json +51 -0
  223. package/plugin/build/index.d.ts +3 -0
  224. package/plugin/build/index.js +3 -0
  225. package/plugin/build/withExpoVideo.d.ts +9 -0
  226. package/plugin/build/withExpoVideo.js +73 -0
  227. package/plugin/index.d.ts +1 -0
  228. package/plugin/index.js +1 -0
  229. package/plugin/jest.config.js +1 -0
  230. package/plugin/src/index.ts +3 -0
  231. package/plugin/src/withExpoVideo.ts +103 -0
  232. package/plugin/tsconfig.json +10 -0
  233. package/src/NativeVideoAirPlayButtonView.ts +3 -0
  234. package/src/NativeVideoModule.ts +16 -0
  235. package/src/NativeVideoModule.web.ts +4 -0
  236. package/src/NativeVideoView.ts +8 -0
  237. package/src/VideoAirPlayButton.ios.tsx +8 -0
  238. package/src/VideoAirPlayButton.tsx +14 -0
  239. package/src/VideoAirPlayButton.types.ts +39 -0
  240. package/src/VideoModule.ts +47 -0
  241. package/src/VideoPlayer.tsx +74 -0
  242. package/src/VideoPlayer.types.ts +893 -0
  243. package/src/VideoPlayer.web.tsx +466 -0
  244. package/src/VideoPlayerEvents.types.ts +332 -0
  245. package/src/VideoThumbnail.ts +31 -0
  246. package/src/VideoView.tsx +111 -0
  247. package/src/VideoView.types.ts +405 -0
  248. package/src/VideoView.web.tsx +293 -0
  249. package/src/index.ts +26 -0
  250. package/src/resolveAssetSource.ts +2 -0
  251. package/src/resolveAssetSource.web.ts +17 -0
  252. package/src/ts-declarations/react-native-assets.d.ts +22 -0
  253. package/tsconfig.all.json +11 -0
  254. package/tsconfig.json +10 -0
  255. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,446 @@
1
+ package expo.modules.video
2
+
3
+ import android.net.Uri
4
+ import androidx.media3.common.PlaybackParameters
5
+ import androidx.media3.common.Player.REPEAT_MODE_OFF
6
+ import androidx.media3.common.Player.REPEAT_MODE_ONE
7
+ import androidx.media3.common.util.UnstableApi
8
+ import expo.modules.kotlin.Promise
9
+ import expo.modules.kotlin.functions.Coroutine
10
+ import expo.modules.kotlin.functions.Queues
11
+ import expo.modules.kotlin.modules.Module
12
+ import expo.modules.kotlin.modules.ModuleDefinition
13
+ import expo.modules.kotlin.types.Either
14
+ import expo.modules.kotlin.views.ViewDefinitionBuilder
15
+ import expo.modules.video.enums.AudioMixingMode
16
+ import expo.modules.video.enums.ContentFit
17
+ import expo.modules.video.player.VideoPlayer
18
+ import expo.modules.video.records.BufferOptions
19
+ import expo.modules.video.records.PlayerBuilderOptions
20
+ import expo.modules.video.records.ButtonOptions
21
+ import expo.modules.video.records.FullscreenOptions
22
+ import expo.modules.video.records.SubtitleStyle
23
+ import expo.modules.video.records.SubtitleTrack
24
+ import expo.modules.video.records.AudioTrack
25
+ import expo.modules.video.records.ScrubbingModeOptions
26
+ import expo.modules.video.records.SeekTolerance
27
+ import expo.modules.video.records.VideoSource
28
+ import expo.modules.video.records.VideoThumbnailOptions
29
+ import expo.modules.video.utils.runWithPiPMisconfigurationSoftHandling
30
+ import expo.modules.video.managers.VideoManager
31
+ import kotlinx.coroutines.async
32
+ import kotlinx.coroutines.awaitAll
33
+ import kotlinx.coroutines.launch
34
+ import kotlinx.coroutines.runBlocking
35
+ import kotlin.time.Duration
36
+
37
+ // https://developer.android.com/guide/topics/media/media3/getting-started/migration-guide#improvements_in_media3
38
+ @androidx.annotation.OptIn(UnstableApi::class)
39
+ class VideoModule : Module() {
40
+ override fun definition() = ModuleDefinition {
41
+ Name("ExpoVideo")
42
+
43
+ OnCreate {
44
+ VideoManager.onModuleCreated(appContext)
45
+ }
46
+
47
+ OnDestroy {
48
+ VideoManager.onModuleDestroyed(appContext)
49
+ }
50
+
51
+ Function("isPictureInPictureSupported") {
52
+ VideoView.isPictureInPictureSupported(appContext.throwingActivity)
53
+ }
54
+
55
+ Function("getCurrentVideoCacheSize") {
56
+ VideoManager.cache.getCurrentCacheSize()
57
+ }
58
+
59
+ AsyncFunction("setVideoCacheSizeAsync") { size: Long ->
60
+ VideoManager.cache.setMaxCacheSize(size)
61
+ }
62
+
63
+ AsyncFunction("clearVideoCacheAsync") {
64
+ VideoManager.cache.clear()
65
+ }
66
+
67
+ View(SurfaceVideoView::class) {
68
+ VideoViewComponent<SurfaceVideoView>()
69
+ }
70
+ View(TextureVideoView::class) {
71
+ VideoViewComponent<TextureVideoView>()
72
+ }
73
+
74
+ Class(VideoPlayer::class) {
75
+ Constructor { source: VideoSource?, /* useSynchronousReplace - iOS-only */ _: Boolean?, playerBuilderOptions: PlayerBuilderOptions? ->
76
+ val player = VideoPlayer(appContext.throwingActivity.applicationContext, appContext, source, playerBuilderOptions)
77
+ appContext.mainQueue.launch {
78
+ player.prepare()
79
+ }
80
+ return@Constructor player
81
+ }
82
+
83
+ Property("playing")
84
+ .get { ref: VideoPlayer ->
85
+ ref.playing
86
+ }
87
+
88
+ Property("muted")
89
+ .get { ref: VideoPlayer ->
90
+ ref.muted
91
+ }
92
+ .set { ref: VideoPlayer, muted: Boolean ->
93
+ ref.muted = muted
94
+ }
95
+
96
+ Property("volume")
97
+ .get { ref: VideoPlayer ->
98
+ ref.volume
99
+ }
100
+ .set { ref: VideoPlayer, volume: Float ->
101
+ ref.userVolume = volume
102
+ ref.volume = volume
103
+ }
104
+
105
+ Property("currentTime")
106
+ .get { ref: VideoPlayer ->
107
+ // TODO: we shouldn't block the thread, but there are no events for the player position change,
108
+ // so we can't update the currentTime in a non-blocking way like the other properties.
109
+ // Until we think of something better we can temporarily do it this way
110
+ runBlocking(appContext.mainQueue.coroutineContext) {
111
+ ref.player.currentPosition / 1000f
112
+ }
113
+ }
114
+ .set { ref: VideoPlayer, currentTime: Double ->
115
+ appContext.mainQueue.launch {
116
+ ref.player.seekTo((currentTime * 1000).toLong())
117
+ }
118
+ }
119
+
120
+ Property("currentLiveTimestamp")
121
+ .get { ref: VideoPlayer ->
122
+ runBlocking(appContext.mainQueue.coroutineContext) {
123
+ ref.currentLiveTimestamp
124
+ }
125
+ }
126
+
127
+ Property("availableVideoTracks")
128
+ .get { ref: VideoPlayer ->
129
+ ref.availableVideoTracks
130
+ }
131
+
132
+ Property("videoTrack")
133
+ .get { ref: VideoPlayer ->
134
+ ref.currentVideoTrack
135
+ }
136
+
137
+ Property("availableSubtitleTracks")
138
+ .get { ref: VideoPlayer ->
139
+ ref.subtitles.availableSubtitleTracks
140
+ }
141
+
142
+ Property("subtitleTrack")
143
+ .get { ref: VideoPlayer ->
144
+ ref.subtitles.currentSubtitleTrack
145
+ }
146
+ .set { ref: VideoPlayer, subtitleTrack: SubtitleTrack? ->
147
+ appContext.mainQueue.launch {
148
+ ref.subtitles.currentSubtitleTrack = subtitleTrack
149
+ }
150
+ }
151
+
152
+ Property("availableAudioTracks")
153
+ .get { ref: VideoPlayer ->
154
+ ref.audioTracks.availableAudioTracks
155
+ }
156
+
157
+ Property("audioTrack")
158
+ .get { ref: VideoPlayer ->
159
+ ref.audioTracks.currentAudioTrack
160
+ }
161
+ .set { ref: VideoPlayer, audioTrack: AudioTrack? ->
162
+ appContext.mainQueue.launch {
163
+ ref.audioTracks.currentAudioTrack = audioTrack
164
+ }
165
+ }
166
+
167
+ Property("currentOffsetFromLive")
168
+ .get { ref: VideoPlayer ->
169
+ runBlocking(appContext.mainQueue.coroutineContext) {
170
+ ref.currentOffsetFromLive
171
+ }
172
+ }
173
+
174
+ Property("duration")
175
+ .get { ref: VideoPlayer ->
176
+ ref.duration
177
+ }
178
+
179
+ Property("playbackRate")
180
+ .get { ref: VideoPlayer ->
181
+ ref.playbackParameters.speed
182
+ }
183
+ .set { ref: VideoPlayer, playbackRate: Float ->
184
+ val pitch = if (ref.preservesPitch) 1f else playbackRate
185
+ ref.playbackParameters = PlaybackParameters(playbackRate, pitch)
186
+ }
187
+
188
+ Property("isLive")
189
+ .get { ref: VideoPlayer ->
190
+ ref.isLive
191
+ }
192
+
193
+ Property("preservesPitch")
194
+ .get { ref: VideoPlayer ->
195
+ ref.preservesPitch
196
+ }
197
+ .set { ref: VideoPlayer, preservesPitch: Boolean ->
198
+ ref.preservesPitch = preservesPitch
199
+ }
200
+
201
+ Property("showNowPlayingNotification")
202
+ .get { ref: VideoPlayer ->
203
+ ref.showNowPlayingNotification
204
+ }
205
+ .set { ref: VideoPlayer, showNotification: Boolean ->
206
+ ref.showNowPlayingNotification = showNotification
207
+ }
208
+
209
+ Property("status")
210
+ .get { ref: VideoPlayer ->
211
+ ref.status
212
+ }
213
+
214
+ Property("staysActiveInBackground")
215
+ .get { ref: VideoPlayer ->
216
+ ref.staysActiveInBackground
217
+ }
218
+ .set { ref: VideoPlayer, staysActive: Boolean ->
219
+ ref.staysActiveInBackground = staysActive
220
+ }
221
+
222
+ Property("loop")
223
+ .get { ref: VideoPlayer ->
224
+ runBlocking(appContext.mainQueue.coroutineContext) {
225
+ ref.player.repeatMode == REPEAT_MODE_ONE
226
+ }
227
+ }
228
+ .set { ref: VideoPlayer, loop: Boolean ->
229
+ appContext.mainQueue.launch {
230
+ ref.player.repeatMode = if (loop) {
231
+ REPEAT_MODE_ONE
232
+ } else {
233
+ REPEAT_MODE_OFF
234
+ }
235
+ }
236
+ }
237
+
238
+ Property("bufferedPosition")
239
+ .get { ref: VideoPlayer ->
240
+ // Same as currentTime
241
+ runBlocking(appContext.mainQueue.coroutineContext) {
242
+ ref.bufferedPosition
243
+ }
244
+ }
245
+
246
+ Property("bufferOptions")
247
+ .get { ref: VideoPlayer ->
248
+ ref.bufferOptions
249
+ }
250
+ .set { ref: VideoPlayer, bufferOptions: BufferOptions ->
251
+ ref.bufferOptions = bufferOptions
252
+ }
253
+
254
+ Property("isExternalPlaybackActive")
255
+ .get { ref: VideoPlayer ->
256
+ // isExternalPlaybackActive is not supported on Android as of now. Return false.
257
+ false
258
+ }
259
+
260
+ Function("play") { ref: VideoPlayer ->
261
+ appContext.mainQueue.launch {
262
+ ref.player.play()
263
+ }
264
+ }
265
+
266
+ Function("pause") { ref: VideoPlayer ->
267
+ appContext.mainQueue.launch {
268
+ ref.player.pause()
269
+ }
270
+ }
271
+
272
+ Property("timeUpdateEventInterval")
273
+ .get { ref: VideoPlayer ->
274
+ ref.intervalUpdateClock.interval / 1000.0
275
+ }
276
+ .set { ref: VideoPlayer, intervalSeconds: Float ->
277
+ ref.intervalUpdateClock.interval = (intervalSeconds * 1000).toLong()
278
+ }
279
+
280
+ Property("audioMixingMode")
281
+ .get { ref: VideoPlayer ->
282
+ ref.audioMixingMode
283
+ }
284
+ .set { ref: VideoPlayer, audioMixingMode: AudioMixingMode ->
285
+ ref.audioMixingMode = audioMixingMode
286
+ }
287
+
288
+ Property("keepScreenOnWhilePlaying")
289
+ .get { ref: VideoPlayer ->
290
+ ref.keepScreenOnWhilePlaying
291
+ }
292
+ .set { ref: VideoPlayer, value: Boolean? ->
293
+ ref.keepScreenOnWhilePlaying = value ?: true
294
+ }
295
+
296
+ Property("seekTolerance")
297
+ .get { ref: VideoPlayer ->
298
+ ref.seekTolerance
299
+ }
300
+ .set { ref: VideoPlayer, tolerance: SeekTolerance? ->
301
+ ref.seekTolerance = tolerance ?: SeekTolerance()
302
+ }
303
+
304
+ Property("scrubbingModeOptions")
305
+ .get { ref: VideoPlayer ->
306
+ ref.scrubbingModeOptions
307
+ }
308
+ .set { ref: VideoPlayer, options: ScrubbingModeOptions? ->
309
+ ref.scrubbingModeOptions = options ?: ScrubbingModeOptions()
310
+ }
311
+
312
+ Function("replace") { ref: VideoPlayer, source: Either<Uri, VideoSource>? ->
313
+ replaceImpl(ref, source)
314
+ }
315
+
316
+ // ExoPlayer automatically offloads loading of the asset onto a different thread so we can keep the same
317
+ // implementation until `replace` is deprecated and removed.
318
+ // TODO: @behenate see if we can further reduce load on the main thread
319
+ AsyncFunction("replaceAsync") { ref: VideoPlayer, source: Either<Uri, VideoSource>?, promise: Promise ->
320
+ replaceImpl(ref, source, promise)
321
+ }
322
+
323
+ Function("seekBy") { ref: VideoPlayer, seekTime: Double ->
324
+ appContext.mainQueue.launch {
325
+ val seekPos = ref.player.currentPosition + (seekTime * 1000).toLong()
326
+ ref.player.seekTo(seekPos)
327
+ }
328
+ }
329
+
330
+ Function("replay") { ref: VideoPlayer ->
331
+ appContext.mainQueue.launch {
332
+ ref.player.seekTo(0)
333
+ ref.player.play()
334
+ }
335
+ }
336
+
337
+ AsyncFunction("generateThumbnailsAsync") Coroutine { ref: VideoPlayer, times: List<Duration>, options: VideoThumbnailOptions? ->
338
+ return@Coroutine ref.toMetadataRetriever().safeUse {
339
+ val bitmaps = times.map { time ->
340
+ appContext.backgroundCoroutineScope.async {
341
+ generateThumbnailAtTime(time, options)
342
+ }
343
+ }
344
+
345
+ bitmaps.awaitAll()
346
+ }
347
+ }
348
+
349
+ Class<VideoThumbnail> {
350
+ Property("width") { ref -> ref.width }
351
+ Property("height") { ref -> ref.height }
352
+ Property("requestedTime") { ref -> ref.requestedTime }
353
+ Property("actualTime") { ref -> ref.actualTime }
354
+ }
355
+ }
356
+
357
+ OnActivityEntersForeground {
358
+ VideoManager.onAppForegrounded()
359
+ }
360
+
361
+ OnActivityEntersBackground {
362
+ VideoManager.onAppBackgrounded()
363
+ }
364
+ }
365
+ private fun replaceImpl(
366
+ ref: VideoPlayer,
367
+ source: Either<Uri, VideoSource>?,
368
+ promise: Promise? = null
369
+ ) {
370
+ val videoSource = source?.let {
371
+ if (it.`is`(VideoSource::class)) {
372
+ it.get(VideoSource::class)
373
+ } else {
374
+ VideoSource(it.get(Uri::class))
375
+ }
376
+ }
377
+
378
+ appContext.mainQueue.launch {
379
+ ref.uncommittedSource = videoSource
380
+ ref.prepare()
381
+ promise?.resolve()
382
+ }
383
+ }
384
+ }
385
+
386
+ @androidx.annotation.OptIn(UnstableApi::class)
387
+ private inline fun <reified T : VideoView> ViewDefinitionBuilder<T>.VideoViewComponent() {
388
+ Events(
389
+ "onPictureInPictureStart",
390
+ "onPictureInPictureStop",
391
+ "onFullscreenEnter",
392
+ "onFullscreenExit",
393
+ "onFirstFrameRender"
394
+ )
395
+ Prop("player") { view: T, player: VideoPlayer? ->
396
+ view.videoPlayer = player
397
+ }
398
+ Prop("nativeControls") { view: T, useNativeControls: Boolean ->
399
+ view.useNativeControls = useNativeControls
400
+ }
401
+ Prop("contentFit") { view: T, contentFit: ContentFit ->
402
+ view.contentFit = contentFit
403
+ }
404
+ Prop("startsPictureInPictureAutomatically") { view: T, autoEnterPiP: Boolean? ->
405
+ view.autoEnterPiP = autoEnterPiP ?: false
406
+ }
407
+ Prop("fullscreenOptions") { view: T, fullscreenOptions: FullscreenOptions? ->
408
+ if (fullscreenOptions != null) {
409
+ view.fullscreenOptions = fullscreenOptions
410
+ }
411
+ }
412
+ Prop("requiresLinearPlayback") { view: T, requiresLinearPlayback: Boolean? ->
413
+ view.requiresLinearPlayback = requiresLinearPlayback ?: false
414
+ }
415
+ Prop("buttonOptions") { view: T, buttonOptions: ButtonOptions? ->
416
+ view.buttonOptions = buttonOptions ?: ButtonOptions()
417
+ }
418
+ Prop("useExoShutter") { view: T, useExoShutter: Boolean? ->
419
+ view.useExoShutter = useExoShutter
420
+ }
421
+ Prop("subtitleStyle") { view: T, subtitleStyle: SubtitleStyle? ->
422
+ view.subtitleStyle = subtitleStyle
423
+ }
424
+ AsyncFunction("enterFullscreen") { view: T ->
425
+ view.enterFullscreen()
426
+ }.runOnQueue(Queues.MAIN)
427
+ AsyncFunction("exitFullscreen") { view: T ->
428
+ VideoManager.finishFullscreenPlayer(view.videoViewId)
429
+ }.runOnQueue(Queues.MAIN)
430
+ AsyncFunction("startPictureInPicture") { view: T ->
431
+ runWithPiPMisconfigurationSoftHandling(true) {
432
+ view.enterPictureInPicture()
433
+ }
434
+ }
435
+ AsyncFunction("stopPictureInPicture") {
436
+ throw MethodUnsupportedException("stopPictureInPicture")
437
+ }
438
+ OnViewDestroys {
439
+ VideoManager.unregisterVideoView(it)
440
+ }
441
+ OnViewDidUpdateProps { view ->
442
+ if (view.playerView.useController != view.useNativeControls) {
443
+ view.playerView.useController = view.useNativeControls
444
+ }
445
+ }
446
+ }
@@ -0,0 +1,20 @@
1
+ package expo.modules.video
2
+
3
+ import android.graphics.Bitmap
4
+ import expo.modules.kotlin.sharedobjects.SharedRef
5
+ import kotlin.time.Duration
6
+
7
+ class VideoThumbnail(
8
+ ref: Bitmap,
9
+ val requestedTime: Duration,
10
+ val actualTime: Duration
11
+ ) : SharedRef<Bitmap>(ref) {
12
+ val width = ref.width
13
+ val height = ref.height
14
+
15
+ override val nativeRefType: String = "image"
16
+
17
+ override fun getAdditionalMemoryPressure(): Int {
18
+ return ref.byteCount
19
+ }
20
+ }