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,89 @@
1
+ package expo.modules.video
2
+
3
+ import android.graphics.Bitmap
4
+ import android.media.MediaMetadataRetriever
5
+ import android.os.Build
6
+ import expo.modules.video.records.VideoThumbnailOptions
7
+ import kotlin.math.max
8
+ import kotlin.math.roundToLong
9
+ import kotlin.time.Duration
10
+ import kotlin.time.DurationUnit
11
+ import kotlin.time.toDuration
12
+
13
+ suspend fun <T> MediaMetadataRetriever.safeUse(block: suspend MediaMetadataRetriever.() -> T): T {
14
+ try {
15
+ return block()
16
+ } finally {
17
+ try {
18
+ this.close()
19
+ } finally {
20
+ // ignore
21
+ }
22
+ }
23
+ }
24
+
25
+ fun MediaMetadataRetriever.generateThumbnailAtTime(
26
+ requestedTime: Duration,
27
+ options: VideoThumbnailOptions? = null
28
+ ): VideoThumbnail {
29
+ val sizeLimit = options?.toNativeSizeLimit()
30
+
31
+ val bitmap = if (sizeLimit != null) {
32
+ val (maxWidth, maxHeight) = sizeLimit
33
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
34
+ getScaledFrameAtTime(
35
+ requestedTime.inWholeMicroseconds,
36
+ MediaMetadataRetriever.OPTION_CLOSEST,
37
+ maxWidth,
38
+ maxHeight
39
+ )
40
+ } else {
41
+ getFrameAtTime(requestedTime.inWholeMicroseconds, MediaMetadataRetriever.OPTION_CLOSEST)
42
+ ?.constrainToDimensions(maxWidth, maxHeight)
43
+ }
44
+ } else {
45
+ getFrameAtTime(requestedTime.inWholeMicroseconds, MediaMetadataRetriever.OPTION_CLOSEST)
46
+ } ?: throw IllegalStateException("Failed to generate thumbnail")
47
+
48
+ val actualTime = calculateActualFrameTime(this, requestedTime)
49
+ return VideoThumbnail(bitmap, requestedTime, actualTime)
50
+ }
51
+
52
+ private fun calculateActualFrameTime(mediaMetadataRetriever: MediaMetadataRetriever, time: Duration): Duration {
53
+ // if we can't get the frame rate, we are returning the requested time
54
+ val frameTime = mediaMetadataRetriever.frameTime() ?: return time
55
+
56
+ // calculate closest frame index
57
+ val frameIndex = (time.inWholeMicroseconds.toDouble() / frameTime).roundToLong()
58
+
59
+ return (frameIndex * frameTime).toDuration(DurationUnit.MICROSECONDS)
60
+ }
61
+
62
+ private fun MediaMetadataRetriever.frameTime(): Double? {
63
+ // frame count is not available on Android SDK < 28
64
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.P) {
65
+ return null
66
+ }
67
+
68
+ val duration = this.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toDouble()
69
+ ?: return null
70
+ val frameCount = this.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT)?.toDouble()
71
+ ?: return null
72
+
73
+ return (duration * 1000) / frameCount
74
+ }
75
+
76
+ private fun Bitmap.constrainToDimensions(maxWidth: Int, maxHeight: Int): Bitmap {
77
+ val width = this.width
78
+ val height = this.height
79
+
80
+ val ratio = max(width / maxWidth.toFloat(), height / maxHeight.toFloat())
81
+ if (ratio <= 1) {
82
+ return this
83
+ }
84
+
85
+ val newWidth = (width / ratio).toInt()
86
+ val newHeight = (height / ratio).toInt()
87
+
88
+ return Bitmap.createScaledBitmap(this, newWidth, newHeight, true)
89
+ }
@@ -0,0 +1,87 @@
1
+ package expo.modules.video
2
+
3
+ import android.graphics.Color
4
+ import androidx.media3.ui.DefaultTimeBar
5
+ import androidx.media3.ui.PlayerView
6
+
7
+ @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
8
+ internal fun PlayerView.applyRequiresLinearPlayback(requireLinearPlayback: Boolean) {
9
+ setShowFastForwardButton(!requireLinearPlayback)
10
+ setShowRewindButton(!requireLinearPlayback)
11
+ setShowPreviousButton(!requireLinearPlayback)
12
+ setShowNextButton(!requireLinearPlayback)
13
+ setTimeBarInteractive(requireLinearPlayback)
14
+ }
15
+
16
+ @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
17
+ internal fun PlayerView.setTimeBarInteractive(interactive: Boolean) {
18
+ val timeBar = findViewById<DefaultTimeBar>(androidx.media3.ui.R.id.exo_progress)
19
+ if (interactive) {
20
+ timeBar?.setScrubberColor(Color.TRANSPARENT)
21
+ timeBar?.isEnabled = false
22
+ } else {
23
+ timeBar?.setScrubberColor(Color.WHITE)
24
+ timeBar?.isEnabled = true
25
+ }
26
+ }
27
+
28
+ @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
29
+ internal fun PlayerView.setFullscreenButtonVisibility(visible: Boolean) {
30
+ val fullscreenButton = findViewById<android.widget.ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
31
+ fullscreenButton?.visibility = if (visible) {
32
+ android.view.View.VISIBLE
33
+ } else {
34
+ android.view.View.GONE
35
+ }
36
+ }
37
+
38
+ @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
39
+ internal fun PlayerView.setSettingsButtonVisibility(visible: Boolean) {
40
+ val settingsButton = findViewById<android.widget.ImageButton>(androidx.media3.ui.R.id.exo_settings)
41
+ settingsButton?.visibility = if (visible) {
42
+ android.view.View.VISIBLE
43
+ } else {
44
+ android.view.View.GONE
45
+ }
46
+ }
47
+
48
+ @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
49
+ internal fun PlayerView.setPlayPauseButtonVisibility(visible: Boolean) {
50
+ val playPauseButton = findViewById<android.widget.ImageButton>(androidx.media3.ui.R.id.exo_play_pause)
51
+ playPauseButton?.visibility = if (visible) {
52
+ android.view.View.VISIBLE
53
+ } else {
54
+ android.view.View.GONE
55
+ }
56
+ }
57
+
58
+ @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
59
+ internal fun PlayerView.setBottomBarVisibility(visible: Boolean) {
60
+ val bottomBar = findViewById<android.view.ViewGroup>(androidx.media3.ui.R.id.exo_bottom_bar)
61
+ bottomBar?.visibility = if (visible) {
62
+ android.view.View.VISIBLE
63
+ } else {
64
+ android.view.View.GONE
65
+ }
66
+
67
+ val progressBar = findViewById<DefaultTimeBar>(androidx.media3.ui.R.id.exo_progress)
68
+ progressBar?.visibility = if (visible) {
69
+ android.view.View.VISIBLE
70
+ } else {
71
+ android.view.View.GONE
72
+ }
73
+ }
74
+
75
+ @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
76
+ internal fun PlayerView.applyButtonOptions(
77
+ config: expo.modules.video.records.ButtonOptions,
78
+ requiresLinearPlayback: Boolean = false
79
+ ) {
80
+ setShowFastForwardButton(!requiresLinearPlayback && config.showSeekForward)
81
+ setShowRewindButton(!requiresLinearPlayback && config.showSeekBackward)
82
+ setShowPreviousButton(!requiresLinearPlayback && config.showPrevious)
83
+ setShowNextButton(!requiresLinearPlayback && config.showNext)
84
+ setSettingsButtonVisibility(config.showSettings)
85
+ setPlayPauseButtonVisibility(config.showPlayPause)
86
+ setBottomBarVisibility(config.showBottomBar)
87
+ }
@@ -0,0 +1,109 @@
1
+ package expo.modules.video
2
+
3
+ import android.content.Context
4
+ import android.os.Looper
5
+ import android.util.Log
6
+ import androidx.media3.common.util.UnstableApi
7
+ import androidx.media3.database.DatabaseProvider
8
+ import androidx.media3.database.StandaloneDatabaseProvider
9
+ import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor
10
+ import androidx.media3.datasource.cache.SimpleCache
11
+ import expo.modules.kotlin.exception.Exceptions
12
+ import expo.modules.video.managers.VideoManager
13
+ import java.io.File
14
+ import java.lang.ref.WeakReference
15
+ import java.util.UUID
16
+
17
+ private const val SHARED_PREFERENCES_NAME = "ExpoVideoCache"
18
+ private const val CACHE_SIZE_KEY = "cacheSize"
19
+ private const val VIDEO_CACHE_PARENT_DIR = "ExpoVideoCache"
20
+ private const val VIDEO_CACHE_DIR_KEY = "cacheDir"
21
+ private const val DEFAULT_CACHE_SIZE = 1024 * 1024 * 1024L // 1GB
22
+
23
+ @UnstableApi
24
+ class VideoCache(context: Context) {
25
+ // We don't want a strong reference to the context, as this class is used inside of a singleton (VideoManager)
26
+ private val weakContext = WeakReference(context)
27
+ private val context: Context
28
+ get() {
29
+ return weakContext.get() ?: throw Exceptions.ReactContextLost()
30
+ }
31
+ private val databaseProvider: DatabaseProvider = StandaloneDatabaseProvider(context)
32
+ private val sharedPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
33
+ private var cacheEvictor = LeastRecentlyUsedCacheEvictor(getMaxCacheSize())
34
+ var instance = SimpleCache(getCacheDir(), cacheEvictor, databaseProvider)
35
+
36
+ // Function that gets the cache size from shared preferences
37
+ private fun getMaxCacheSize(): Long {
38
+ return sharedPreferences.getLong(CACHE_SIZE_KEY, DEFAULT_CACHE_SIZE)
39
+ }
40
+
41
+ fun release() {
42
+ instance.release()
43
+ }
44
+
45
+ fun setMaxCacheSize(size: Long) {
46
+ assertModificationReleaseConditions()
47
+ instance.release()
48
+ sharedPreferences.edit().putLong(CACHE_SIZE_KEY, size).apply()
49
+ cacheEvictor = LeastRecentlyUsedCacheEvictor(size)
50
+ instance = SimpleCache(getCacheDir(), cacheEvictor, databaseProvider)
51
+ }
52
+
53
+ fun getCurrentCacheSize(): Long {
54
+ return getFileSize(getCacheDir())
55
+ }
56
+
57
+ // We have to keep the current directory name in the shared preferences, when the cache is released, a new name will be assigned to avoid conflicts.
58
+ private fun getCacheDir(): File {
59
+ // Weird structure, because kotlin marks the result of `getString` as nullable
60
+ val videoCacheDirName = sharedPreferences.getString(VIDEO_CACHE_DIR_KEY, null) ?: run {
61
+ val newCacheDirName = generateCacheDirName()
62
+ sharedPreferences.edit().putString(VIDEO_CACHE_DIR_KEY, newCacheDirName).commit()
63
+ newCacheDirName
64
+ }
65
+ val cacheParentDir = File(context.cacheDir, VIDEO_CACHE_PARENT_DIR)
66
+ val cacheDir = File(cacheParentDir, videoCacheDirName)
67
+
68
+ if (!cacheDir.exists()) {
69
+ cacheDir.mkdirs()
70
+ }
71
+ return cacheDir
72
+ }
73
+
74
+ private fun generateCacheDirName(): String {
75
+ return UUID.randomUUID().toString()
76
+ }
77
+
78
+ fun clear() {
79
+ assertModificationReleaseConditions()
80
+
81
+ // Creates a new cache directory to avoid conflicts while removing the old cache
82
+ val oldCacheDirectory = getCacheDir()
83
+ val oldCache = instance
84
+ val newCacheName = generateCacheDirName()
85
+
86
+ sharedPreferences.edit().putString(VIDEO_CACHE_DIR_KEY, newCacheName).apply()
87
+ instance = SimpleCache(getCacheDir(), cacheEvictor, databaseProvider)
88
+ oldCache.release()
89
+ oldCacheDirectory.deleteRecursively()
90
+ }
91
+
92
+ private fun getFileSize(file: File): Long {
93
+ return file
94
+ .walkTopDown()
95
+ .filter { it.isFile }
96
+ .map { it.length() }
97
+ .sum()
98
+ }
99
+
100
+ private fun assertModificationReleaseConditions() {
101
+ if (VideoManager.hasRegisteredPlayers()) {
102
+ throw VideoCacheException("Cannot clear cache while there are active players")
103
+ }
104
+
105
+ if (Looper.myLooper() == Looper.getMainLooper()) {
106
+ Log.w("ExpoVideo", "Clearing cache on the main thread, this might cause performance issues")
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,45 @@
1
+ package expo.modules.video
2
+
3
+ import expo.modules.kotlin.exception.CodedException
4
+ import expo.modules.video.enums.DRMType
5
+
6
+ private const val defaultServiceBindingTip = "Make sure that the expo-video config plugin is properly configured to avoid issues with displaying the now playing notification and sustaining background playback."
7
+
8
+ internal class FullScreenVideoViewNotFoundException :
9
+ CodedException("VideoView id wasn't passed to the activity")
10
+
11
+ internal class FullScreenOptionsNotFoundException :
12
+ CodedException("Fullscreen options were not passed to the activity")
13
+
14
+ internal class VideoViewNotFoundException(id: String) :
15
+ CodedException("VideoView with id: $id not found")
16
+
17
+ internal class MethodUnsupportedException(methodName: String) :
18
+ CodedException("Method `$methodName` is not supported on Android")
19
+
20
+ internal class PictureInPictureEnterException(message: String?) :
21
+ CodedException("Failed to enter Picture in Picture mode${message?.let { ". $message" } ?: ""}")
22
+
23
+ internal class PictureInPictureConfigurationException :
24
+ CodedException("Current activity does not support picture-in-picture. Make sure you have configured the `expo-video` config plugin correctly.")
25
+
26
+ internal class PictureInPictureUnsupportedException :
27
+ CodedException("Picture in Picture mode is not supported on this device")
28
+
29
+ internal class UnsupportedDRMTypeException(type: DRMType) :
30
+ CodedException("DRM type `$type` is not supported on Android")
31
+
32
+ internal class PlaybackException(reason: String?, cause: Throwable? = null) :
33
+ CodedException("A playback exception has occurred: ${reason ?: "reason unknown"}", cause)
34
+
35
+ internal class FailedToGetAudioFocusManagerException :
36
+ CodedException("Failed to get AudioFocusManager service")
37
+
38
+ internal class VideoCacheException(message: String?, cause: Throwable? = null) :
39
+ CodedException(message ?: "Unexpected expo-video cache error", cause)
40
+
41
+ internal class NowPlayingException(message: String?, cause: Throwable? = null) :
42
+ CodedException(message ?: "Unexpected expo-video now playing exception", cause)
43
+
44
+ internal fun getPlaybackServiceErrorMessage(message: String?, tip: String = defaultServiceBindingTip) =
45
+ (message ?: "Expo-video playback service binder error") + ". $tip"