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,20 @@
1
+ package expo.modules.video.enums
2
+
3
+ import expo.modules.kotlin.types.Enumerable
4
+
5
+ enum class AudioMixingMode(val value: String) : Enumerable {
6
+ MIX_WITH_OTHERS("mixWithOthers"),
7
+ DUCK_OTHERS("duckOthers"),
8
+ AUTO("auto"),
9
+ DO_NOT_MIX("doNotMix");
10
+
11
+ val priority: Int
12
+ get() {
13
+ return when (this) {
14
+ DO_NOT_MIX -> 3
15
+ AUTO -> 2
16
+ DUCK_OTHERS -> 1
17
+ MIX_WITH_OTHERS -> 0
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,19 @@
1
+ package expo.modules.video.enums
2
+
3
+ import androidx.media3.ui.AspectRatioFrameLayout
4
+ import expo.modules.kotlin.types.Enumerable
5
+
6
+ @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
7
+ enum class ContentFit(val value: String) : Enumerable {
8
+ CONTAIN("contain"),
9
+ FILL("fill"),
10
+ COVER("cover");
11
+
12
+ fun toResizeMode(): Int {
13
+ return when (this) {
14
+ CONTAIN -> AspectRatioFrameLayout.RESIZE_MODE_FIT
15
+ FILL -> AspectRatioFrameLayout.RESIZE_MODE_FILL
16
+ COVER -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,22 @@
1
+ package expo.modules.video.enums
2
+
3
+ import androidx.media3.common.MimeTypes
4
+ import expo.modules.kotlin.types.Enumerable
5
+
6
+ enum class ContentType(val value: String) : Enumerable {
7
+ AUTO("auto"),
8
+ PROGRESSIVE("progressive"),
9
+ HLS("hls"),
10
+ DASH("dash"),
11
+ SMOOTH_STREAMING("smoothStreaming");
12
+
13
+ fun toMimeTypeString(): String? {
14
+ return when (this) {
15
+ AUTO -> null
16
+ PROGRESSIVE -> null
17
+ HLS -> MimeTypes.APPLICATION_M3U8
18
+ DASH -> MimeTypes.APPLICATION_MPD
19
+ SMOOTH_STREAMING -> MimeTypes.APPLICATION_SS
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,26 @@
1
+ package expo.modules.video.enums
2
+
3
+ import androidx.media3.common.C
4
+ import expo.modules.kotlin.types.Enumerable
5
+ import expo.modules.video.UnsupportedDRMTypeException
6
+ import java.util.UUID
7
+
8
+ enum class DRMType(val value: String) : Enumerable {
9
+ CLEARKEY("clearkey"),
10
+ FAIRPLAY("fairplay"),
11
+ PLAYREADY("playready"),
12
+ WIDEVINE("widevine");
13
+
14
+ fun isSupported(): Boolean {
15
+ return this != FAIRPLAY
16
+ }
17
+
18
+ fun toUUID(): UUID {
19
+ return when (this) {
20
+ CLEARKEY -> C.CLEARKEY_UUID
21
+ FAIRPLAY -> throw UnsupportedDRMTypeException(this)
22
+ PLAYREADY -> C.PLAYREADY_UUID
23
+ WIDEVINE -> C.WIDEVINE_UUID
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,25 @@
1
+ package expo.modules.video.enums
2
+ import expo.modules.kotlin.types.Enumerable
3
+ import android.content.pm.ActivityInfo
4
+
5
+ enum class FullscreenOrientation(val value: String) : Enumerable {
6
+ LANDSCAPE("landscape"),
7
+ PORTRAIT("portrait"),
8
+ LANDSCAPE_LEFT("landscapeLeft"),
9
+ LANDSCAPE_RIGHT("landscapeRight"),
10
+ PORTRAIT_UP("portraitUp"),
11
+ PORTRAIT_DOWN("portraitDown"),
12
+ DEFAULT("default");
13
+
14
+ fun toActivityOrientation(): Int {
15
+ return when (this) {
16
+ LANDSCAPE -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
17
+ PORTRAIT -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
18
+ LANDSCAPE_LEFT -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
19
+ LANDSCAPE_RIGHT -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
20
+ PORTRAIT_UP -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
21
+ PORTRAIT_DOWN -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
22
+ DEFAULT -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,10 @@
1
+ package expo.modules.video.enums
2
+
3
+ import expo.modules.kotlin.types.Enumerable
4
+
5
+ enum class PlayerStatus(val value: String) : Enumerable {
6
+ IDLE("idle"),
7
+ LOADING("loading"),
8
+ READY_TO_PLAY("readyToPlay"),
9
+ ERROR("error")
10
+ }
@@ -0,0 +1,27 @@
1
+ package expo.modules.video.records
2
+
3
+ import androidx.annotation.OptIn
4
+ import androidx.media3.common.C
5
+ import androidx.media3.common.C.COLOR_TRANSFER_HLG
6
+ import androidx.media3.common.C.COLOR_TRANSFER_ST2084
7
+ import androidx.media3.common.util.UnstableApi
8
+ import expo.modules.kotlin.types.Enumerable
9
+
10
+ enum class VideoRange(val value: String) : Enumerable {
11
+ SDR("sdr"),
12
+ HLG("hlg"),
13
+ PQ("PQ");
14
+
15
+ companion object {
16
+ @OptIn(UnstableApi::class)
17
+ fun fromCColorTransfer(colorTransfer: @C.ColorTransfer Int?): VideoRange {
18
+ colorTransfer ?: return SDR
19
+
20
+ return when (colorTransfer) {
21
+ COLOR_TRANSFER_ST2084 -> PQ
22
+ COLOR_TRANSFER_HLG -> HLG
23
+ else -> SDR
24
+ }
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,10 @@
1
+ package expo.modules.video.listeners
2
+
3
+ import expo.modules.video.VideoView
4
+
5
+ interface VideoManagerListener {
6
+ fun onVideoViewRegistered(videoView: VideoView, allVideoViews: Collection<VideoView>) {}
7
+ fun onVideoViewUnregistered(videoView: VideoView, allVideoViews: Collection<VideoView>) {}
8
+ fun onAppBackgrounded() {}
9
+ fun onAppForegrounded() {}
10
+ }
@@ -0,0 +1,35 @@
1
+ package expo.modules.video.listeners
2
+
3
+ import androidx.annotation.OptIn
4
+ import androidx.media3.common.TrackSelectionParameters
5
+ import androidx.media3.common.Tracks
6
+ import androidx.media3.common.util.UnstableApi
7
+ import expo.modules.video.VideoView
8
+ import expo.modules.video.enums.AudioMixingMode
9
+ import expo.modules.video.enums.PlayerStatus
10
+ import expo.modules.video.player.VideoPlayer
11
+ import expo.modules.video.records.AudioTrack
12
+ import expo.modules.video.records.PlaybackError
13
+ import expo.modules.video.records.SubtitleTrack
14
+ import expo.modules.video.records.TimeUpdate
15
+ import expo.modules.video.records.VideoSource
16
+ import expo.modules.video.records.VideoTrack
17
+
18
+ @OptIn(UnstableApi::class)
19
+ interface VideoPlayerListener {
20
+ fun onStatusChanged(player: VideoPlayer, status: PlayerStatus, oldStatus: PlayerStatus?, error: PlaybackError?) {}
21
+ fun onIsPlayingChanged(player: VideoPlayer, isPlaying: Boolean, oldIsPlaying: Boolean?) {}
22
+ fun onVolumeChanged(player: VideoPlayer, volume: Float, oldVolume: Float?) {}
23
+ fun onMutedChanged(player: VideoPlayer, muted: Boolean, oldMuted: Boolean?) {}
24
+ fun onSourceChanged(player: VideoPlayer, source: VideoSource?, oldSource: VideoSource?) {}
25
+ fun onPlaybackRateChanged(player: VideoPlayer, rate: Float, oldRate: Float?) {}
26
+ fun onTracksChanged(player: VideoPlayer, tracks: Tracks) {}
27
+ fun onTrackSelectionParametersChanged(player: VideoPlayer, trackSelectionParameters: TrackSelectionParameters) {}
28
+ fun onTimeUpdate(player: VideoPlayer, timeUpdate: TimeUpdate) {}
29
+ fun onPlayedToEnd(player: VideoPlayer) {}
30
+ fun onAudioMixingModeChanged(player: VideoPlayer, audioMixingMode: AudioMixingMode, oldAudioMixingMode: AudioMixingMode?) {}
31
+ fun onVideoTrackChanged(player: VideoPlayer, videoTrack: VideoTrack?, oldVideoTrack: VideoTrack?) {}
32
+ fun onVideoSourceLoaded(player: VideoPlayer, videoSource: VideoSource?, duration: Double?, availableVideoTracks: List<VideoTrack>, availableSubtitleTracks: List<SubtitleTrack>, availableAudioTracks: List<AudioTrack>) {}
33
+ fun onTargetViewChanged(player: VideoPlayer, newTargetView: VideoView?, oldTargetView: VideoView?) {}
34
+ fun onRenderedFirstFrame(player: VideoPlayer) {}
35
+ }
@@ -0,0 +1,13 @@
1
+ package expo.modules.video.listeners
2
+
3
+ import expo.modules.video.VideoView
4
+ import expo.modules.video.records.PiPParams
5
+
6
+ interface VideoViewListener {
7
+ /**
8
+ * Called when the ability of the view to autoEnterPip has changed.
9
+ * Note: This can be called when `autoEnterPiP` doesn't change, but other factors
10
+ * influence the view's ability to enter PiP.
11
+ */
12
+ fun onPiPParamsChanged(videoView: VideoView, oldPiPParams: PiPParams, newPiPParams: PiPParams) = Unit
13
+ }
@@ -0,0 +1,242 @@
1
+ package expo.modules.video.managers
2
+
3
+ import android.content.Context
4
+ import android.media.AudioAttributes
5
+ import android.media.AudioFocusRequest
6
+ import android.media.AudioManager
7
+ import android.os.Build
8
+ import androidx.media3.common.util.UnstableApi
9
+ import expo.modules.kotlin.AppContext
10
+ import expo.modules.video.FailedToGetAudioFocusManagerException
11
+ import expo.modules.video.enums.AudioMixingMode
12
+ import expo.modules.video.player.VideoPlayer
13
+ import expo.modules.video.listeners.VideoPlayerListener
14
+ import kotlinx.coroutines.launch
15
+ import java.lang.ref.WeakReference
16
+
17
+ @UnstableApi
18
+ class AudioFocusManager(private val appContext: AppContext) : AudioManager.OnAudioFocusChangeListener, VideoPlayerListener {
19
+ private val audioManager by lazy {
20
+ appContext.reactContext?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager ?: run {
21
+ throw FailedToGetAudioFocusManagerException()
22
+ }
23
+ }
24
+
25
+ private var players: MutableList<WeakReference<VideoPlayer>> = mutableListOf()
26
+ private var currentFocusRequest: AudioFocusRequest? = null
27
+ private var currentMixingMode: AudioMixingMode = AudioMixingMode.MIX_WITH_OTHERS
28
+ private val anyPlayerRequiresFocus: Boolean
29
+ get() = players.toList().any {
30
+ playerRequiresFocus(it)
31
+ }
32
+
33
+ private fun requestAudioFocus() {
34
+ val audioMixingMode = findAudioMixingMode()
35
+
36
+ // We don't request AudioFocus if we want to mix the audio with others
37
+ if (audioMixingMode == AudioMixingMode.MIX_WITH_OTHERS || !anyPlayerRequiresFocus) {
38
+ abandonAudioFocus()
39
+ currentMixingMode = audioMixingMode
40
+ return
41
+ }
42
+ val audioFocusType = when (audioMixingMode) {
43
+ AudioMixingMode.DUCK_OTHERS -> AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
44
+ AudioMixingMode.AUTO -> AudioManager.AUDIOFOCUS_GAIN
45
+ AudioMixingMode.DO_NOT_MIX -> AudioManager.AUDIOFOCUS_GAIN
46
+ else -> AudioManager.AUDIOFOCUS_GAIN
47
+ }
48
+
49
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
50
+ // We already have audio focus
51
+ currentFocusRequest?.let {
52
+ if (it.focusGain == audioFocusType) {
53
+ return
54
+ }
55
+ }
56
+
57
+ val newFocusRequest = AudioFocusRequest.Builder(audioFocusType).run {
58
+ setAudioAttributes(
59
+ AudioAttributes.Builder().run {
60
+ setUsage(AudioAttributes.USAGE_MEDIA)
61
+ setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
62
+ setOnAudioFocusChangeListener(this@AudioFocusManager)
63
+ build()
64
+ }
65
+ ).build()
66
+ }
67
+ currentFocusRequest = newFocusRequest
68
+ audioManager.requestAudioFocus(newFocusRequest)
69
+ } else {
70
+ @Suppress("DEPRECATION")
71
+ audioManager.requestAudioFocus(
72
+ this,
73
+ AudioManager.STREAM_MUSIC,
74
+ audioFocusType
75
+ )
76
+ }
77
+ currentMixingMode = audioMixingMode
78
+ }
79
+
80
+ private fun abandonAudioFocus() {
81
+ currentFocusRequest?.let {
82
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
83
+ audioManager.abandonAudioFocusRequest(it)
84
+ } else {
85
+ @Suppress("DEPRECATION")
86
+ audioManager.abandonAudioFocus(this)
87
+ }
88
+ }
89
+ currentFocusRequest = null
90
+ }
91
+
92
+ fun registerPlayer(player: VideoPlayer) {
93
+ players.find { it.get() == player } ?: run {
94
+ players.add(WeakReference(player))
95
+ }
96
+ player.addListener(this)
97
+ updateAudioFocus()
98
+ }
99
+
100
+ fun unregisterPlayer(player: VideoPlayer) {
101
+ player.removeListener(this)
102
+ players.removeAll { it.get() == player }
103
+ updateAudioFocus()
104
+ }
105
+
106
+ // VideoPlayerListener
107
+
108
+ override fun onAudioMixingModeChanged(player: VideoPlayer, audioMixingMode: AudioMixingMode, oldAudioMixingMode: AudioMixingMode?) {
109
+ requestAudioFocus()
110
+ super.onAudioMixingModeChanged(player, audioMixingMode, oldAudioMixingMode)
111
+ }
112
+
113
+ override fun onIsPlayingChanged(player: VideoPlayer, isPlaying: Boolean, oldIsPlaying: Boolean?) {
114
+ // we can't use `updateAudioFocus`, because when losing focus the videos are paused sequentially,
115
+ // which can lead to unexpected results.
116
+ if (!isPlaying && !anyPlayerRequiresFocus) {
117
+ abandonAudioFocus()
118
+ } else if (isPlaying && anyPlayerRequiresFocus) {
119
+ requestAudioFocus()
120
+ }
121
+ }
122
+
123
+ override fun onVolumeChanged(player: VideoPlayer, volume: Float, oldVolume: Float?) {
124
+ updateAudioFocus()
125
+ }
126
+
127
+ override fun onMutedChanged(player: VideoPlayer, muted: Boolean, oldMuted: Boolean?) {
128
+ updateAudioFocus()
129
+ }
130
+
131
+ // AudioManager.OnAudioFocusChangeListener
132
+
133
+ override fun onAudioFocusChange(focusChange: Int) {
134
+ when (focusChange) {
135
+ AudioManager.AUDIOFOCUS_LOSS -> {
136
+ appContext.mainQueue.launch {
137
+ players.forEach {
138
+ pausePlayerIfUnmuted(it)
139
+ }
140
+ currentFocusRequest = null
141
+ }
142
+ }
143
+
144
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
145
+ // W could pause/mix the players here individually, but we will keep the behaviour in line with iOS,
146
+ // find the dominant audioMixingMode and apply it to all players.
147
+ val audioMixingMode = findAudioMixingMode()
148
+ if (audioMixingMode == AudioMixingMode.MIX_WITH_OTHERS) {
149
+ return
150
+ }
151
+ appContext.mainQueue.launch {
152
+ players.forEach {
153
+ pausePlayerIfUnmuted(it)
154
+ }
155
+ currentFocusRequest = null
156
+ }
157
+ }
158
+
159
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
160
+ val audioMixingMode = findAudioMixingMode()
161
+
162
+ appContext.mainQueue.launch {
163
+ players.forEach {
164
+ if (audioMixingMode == AudioMixingMode.DO_NOT_MIX) {
165
+ pausePlayerIfUnmuted(it)
166
+ } else {
167
+ duckPlayer(it)
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ AudioManager.AUDIOFOCUS_GAIN -> {
174
+ // TODO: For now this behaves like iOS and doesn't resume playback automatically
175
+ // In future versions we can add a prop to control this behavior.
176
+ appContext.mainQueue.launch {
177
+ players.forEach {
178
+ unduckPlayer(it)
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }
184
+
185
+ // Utils
186
+
187
+ private fun playerRequiresFocus(weakPlayer: WeakReference<VideoPlayer>): Boolean {
188
+ val player = weakPlayer?.get() ?: return false // Return false if player is null
189
+ return (!player.muted && player.playing && player.volume > 0) || player.audioMixingMode == AudioMixingMode.DO_NOT_MIX
190
+ }
191
+
192
+ private fun pausePlayerIfUnmuted(weakPlayer: WeakReference<VideoPlayer>) {
193
+ weakPlayer.get()?.let { videoPlayer ->
194
+ if (!videoPlayer.muted) {
195
+ appContext.mainQueue.launch {
196
+ videoPlayer.player.pause()
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ private fun duckPlayer(weakPlayer: WeakReference<VideoPlayer>) {
203
+ weakPlayer.get()?.let { player ->
204
+ appContext.mainQueue.launch {
205
+ player.volume /= 2f
206
+ }
207
+ }
208
+ }
209
+
210
+ private fun unduckPlayer(weakPlayer: WeakReference<VideoPlayer>) {
211
+ weakPlayer.get()?.let { player ->
212
+ if (!player.muted) {
213
+ appContext.mainQueue.launch {
214
+ player.volume = player.userVolume
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ private fun updateAudioFocus() {
221
+ if (anyPlayerRequiresFocus || findAudioMixingMode() != currentMixingMode) {
222
+ requestAudioFocus()
223
+ } else {
224
+ abandonAudioFocus()
225
+ }
226
+ }
227
+
228
+ private fun findAudioMixingMode(): AudioMixingMode {
229
+ val playersSnapshot = players.toList()
230
+
231
+ val mixingModes = playersSnapshot.mapNotNull { player ->
232
+ player.get()?.takeIf { it.playing }?.audioMixingMode
233
+ }
234
+ if (mixingModes.isEmpty()) {
235
+ return AudioMixingMode.MIX_WITH_OTHERS
236
+ }
237
+
238
+ return mixingModes.reduce { currentAudioMixingMode, next ->
239
+ next.takeIf { it.priority > currentAudioMixingMode.priority } ?: currentAudioMixingMode
240
+ }
241
+ }
242
+ }