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.
- package/CHANGELOG.md +34 -0
- package/FORK.md +149 -0
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/android/build.gradle +32 -0
- package/android/src/main/AndroidManifest.xml +10 -0
- package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +258 -0
- package/android/src/main/java/expo/modules/video/IntervalUpdateClock.kt +54 -0
- package/android/src/main/java/expo/modules/video/MediaMetadataRetriever.kt +89 -0
- package/android/src/main/java/expo/modules/video/PlayerViewExtension.kt +87 -0
- package/android/src/main/java/expo/modules/video/VideoCache.kt +109 -0
- package/android/src/main/java/expo/modules/video/VideoExceptions.kt +45 -0
- package/android/src/main/java/expo/modules/video/VideoModule.kt +446 -0
- package/android/src/main/java/expo/modules/video/VideoThumbnail.kt +20 -0
- package/android/src/main/java/expo/modules/video/VideoView.kt +457 -0
- package/android/src/main/java/expo/modules/video/delegates/IgnoreSameSet.kt +24 -0
- package/android/src/main/java/expo/modules/video/drawing/OutlineProvider.kt +217 -0
- package/android/src/main/java/expo/modules/video/enums/AudioMixingMode.kt +20 -0
- package/android/src/main/java/expo/modules/video/enums/ContentFit.kt +19 -0
- package/android/src/main/java/expo/modules/video/enums/ContentType.kt +22 -0
- package/android/src/main/java/expo/modules/video/enums/DRMType.kt +26 -0
- package/android/src/main/java/expo/modules/video/enums/FullscreenOrientation.kt +25 -0
- package/android/src/main/java/expo/modules/video/enums/PlayerStatus.kt +10 -0
- package/android/src/main/java/expo/modules/video/enums/VideoRange.kt +27 -0
- package/android/src/main/java/expo/modules/video/listeners/VideoManagerListener.kt +10 -0
- package/android/src/main/java/expo/modules/video/listeners/VideoPlayerListener.kt +35 -0
- package/android/src/main/java/expo/modules/video/listeners/VideoViewListener.kt +13 -0
- package/android/src/main/java/expo/modules/video/managers/AudioFocusManager.kt +242 -0
- package/android/src/main/java/expo/modules/video/managers/PictureInPictureManager.kt +329 -0
- package/android/src/main/java/expo/modules/video/managers/VideoManager.kt +194 -0
- package/android/src/main/java/expo/modules/video/playbackService/ExpoVideoPlaybackService.kt +267 -0
- package/android/src/main/java/expo/modules/video/playbackService/PlaybackServiceConnection.kt +62 -0
- package/android/src/main/java/expo/modules/video/playbackService/VideoMediaSessionCallback.kt +47 -0
- package/android/src/main/java/expo/modules/video/player/DefaultLoadControl.java +552 -0
- package/android/src/main/java/expo/modules/video/player/FirstFrameEventGenerator.kt +116 -0
- package/android/src/main/java/expo/modules/video/player/PlayerEvent.kt +172 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayer.kt +598 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerAudioTracks.kt +126 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerKeepAwake.kt +91 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerLoadControl.kt +57 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerSubtitles.kt +126 -0
- package/android/src/main/java/expo/modules/video/records/BufferOptions.kt +18 -0
- package/android/src/main/java/expo/modules/video/records/ButtonOptions.kt +17 -0
- package/android/src/main/java/expo/modules/video/records/DRMOptions.kt +27 -0
- package/android/src/main/java/expo/modules/video/records/FullscreenOptions.kt +14 -0
- package/android/src/main/java/expo/modules/video/records/PiPParams.kt +38 -0
- package/android/src/main/java/expo/modules/video/records/PlaybackError.kt +21 -0
- package/android/src/main/java/expo/modules/video/records/PlayerBuilderOptions.kt +15 -0
- package/android/src/main/java/expo/modules/video/records/ScrubbingModeOptions.kt +33 -0
- package/android/src/main/java/expo/modules/video/records/SeekTolerance.kt +30 -0
- package/android/src/main/java/expo/modules/video/records/SubtitleSource.kt +54 -0
- package/android/src/main/java/expo/modules/video/records/SubtitleStyle.kt +98 -0
- package/android/src/main/java/expo/modules/video/records/Tracks.kt +115 -0
- package/android/src/main/java/expo/modules/video/records/VideoEventPayloads.kt +79 -0
- package/android/src/main/java/expo/modules/video/records/VideoMetadata.kt +14 -0
- package/android/src/main/java/expo/modules/video/records/VideoSize.kt +16 -0
- package/android/src/main/java/expo/modules/video/records/VideoSource.kt +113 -0
- package/android/src/main/java/expo/modules/video/records/VideoThumbnailOptions.kt +26 -0
- package/android/src/main/java/expo/modules/video/utils/DataSourceUtils.kt +76 -0
- package/android/src/main/java/expo/modules/video/utils/EventDispatcherUtils.kt +43 -0
- package/android/src/main/java/expo/modules/video/utils/FullscreenActivityOrientationHelper.kt +120 -0
- package/android/src/main/java/expo/modules/video/utils/MediaSessionUtils.kt +14 -0
- package/android/src/main/java/expo/modules/video/utils/MutableWeakReference.kt +26 -0
- package/android/src/main/java/expo/modules/video/utils/PictureInPictureHelperFragment.kt +49 -0
- package/android/src/main/java/expo/modules/video/utils/PictureInPictureUtils.kt +96 -0
- package/android/src/main/java/expo/modules/video/utils/SubtitleUtils.kt +68 -0
- package/android/src/main/java/expo/modules/video/utils/ViewVisibilityUtils.kt +28 -0
- package/android/src/main/java/expo/modules/video/utils/WeakMutableSet.kt +120 -0
- package/android/src/main/java/expo/modules/video/utils/YogaUtils.kt +20 -0
- package/android/src/main/res/drawable/seek_backwards_10s.xml +25 -0
- package/android/src/main/res/drawable/seek_backwards_15s.xml +25 -0
- package/android/src/main/res/drawable/seek_backwards_5s.xml +25 -0
- package/android/src/main/res/drawable/seek_forwards_10s.xml +30 -0
- package/android/src/main/res/drawable/seek_forwards_15s.xml +31 -0
- package/android/src/main/res/drawable/seek_forwards_5s.xml +30 -0
- package/android/src/main/res/layout/fullscreen_player_activity.xml +20 -0
- package/android/src/main/res/layout/surface_player_view.xml +7 -0
- package/android/src/main/res/layout/texture_player_view.xml +7 -0
- package/android/src/main/res/values/styles.xml +9 -0
- package/app.plugin.js +1 -0
- package/build/NativeVideoAirPlayButtonView.d.ts +3 -0
- package/build/NativeVideoAirPlayButtonView.d.ts.map +1 -0
- package/build/NativeVideoAirPlayButtonView.js +3 -0
- package/build/NativeVideoAirPlayButtonView.js.map +1 -0
- package/build/NativeVideoModule.d.ts +13 -0
- package/build/NativeVideoModule.d.ts.map +1 -0
- package/build/NativeVideoModule.js +3 -0
- package/build/NativeVideoModule.js.map +1 -0
- package/build/NativeVideoModule.web.d.ts +5 -0
- package/build/NativeVideoModule.web.d.ts.map +1 -0
- package/build/NativeVideoModule.web.js +5 -0
- package/build/NativeVideoModule.web.js.map +1 -0
- package/build/NativeVideoView.d.ts +4 -0
- package/build/NativeVideoView.d.ts.map +1 -0
- package/build/NativeVideoView.js +6 -0
- package/build/NativeVideoView.js.map +1 -0
- package/build/VideoAirPlayButton.d.ts +10 -0
- package/build/VideoAirPlayButton.d.ts.map +1 -0
- package/build/VideoAirPlayButton.ios.d.ts +3 -0
- package/build/VideoAirPlayButton.ios.d.ts.map +1 -0
- package/build/VideoAirPlayButton.ios.js +6 -0
- package/build/VideoAirPlayButton.ios.js.map +1 -0
- package/build/VideoAirPlayButton.js +13 -0
- package/build/VideoAirPlayButton.js.map +1 -0
- package/build/VideoAirPlayButton.types.d.ts +35 -0
- package/build/VideoAirPlayButton.types.d.ts.map +1 -0
- package/build/VideoAirPlayButton.types.js +2 -0
- package/build/VideoAirPlayButton.types.js.map +1 -0
- package/build/VideoModule.d.ts +35 -0
- package/build/VideoModule.d.ts.map +1 -0
- package/build/VideoModule.js +44 -0
- package/build/VideoModule.js.map +1 -0
- package/build/VideoPlayer.d.ts +17 -0
- package/build/VideoPlayer.d.ts.map +1 -0
- package/build/VideoPlayer.js +55 -0
- package/build/VideoPlayer.js.map +1 -0
- package/build/VideoPlayer.types.d.ts +782 -0
- package/build/VideoPlayer.types.d.ts.map +1 -0
- package/build/VideoPlayer.types.js +2 -0
- package/build/VideoPlayer.types.js.map +1 -0
- package/build/VideoPlayer.web.d.ts +79 -0
- package/build/VideoPlayer.web.d.ts.map +1 -0
- package/build/VideoPlayer.web.js +388 -0
- package/build/VideoPlayer.web.js.map +1 -0
- package/build/VideoPlayerEvents.types.d.ts +278 -0
- package/build/VideoPlayerEvents.types.d.ts.map +1 -0
- package/build/VideoPlayerEvents.types.js +2 -0
- package/build/VideoPlayerEvents.types.js.map +1 -0
- package/build/VideoThumbnail.d.ts +29 -0
- package/build/VideoThumbnail.d.ts.map +1 -0
- package/build/VideoThumbnail.js +4 -0
- package/build/VideoThumbnail.js.map +1 -0
- package/build/VideoView.d.ts +69 -0
- package/build/VideoView.d.ts.map +1 -0
- package/build/VideoView.js +100 -0
- package/build/VideoView.js.map +1 -0
- package/build/VideoView.types.d.ts +362 -0
- package/build/VideoView.types.d.ts.map +1 -0
- package/build/VideoView.types.js +2 -0
- package/build/VideoView.types.js.map +1 -0
- package/build/VideoView.web.d.ts +9 -0
- package/build/VideoView.web.d.ts.map +1 -0
- package/build/VideoView.web.js +239 -0
- package/build/VideoView.web.js.map +1 -0
- package/build/index.d.ts +11 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +6 -0
- package/build/index.js.map +1 -0
- package/build/resolveAssetSource.d.ts +3 -0
- package/build/resolveAssetSource.d.ts.map +1 -0
- package/build/resolveAssetSource.js +3 -0
- package/build/resolveAssetSource.js.map +1 -0
- package/build/resolveAssetSource.web.d.ts +4 -0
- package/build/resolveAssetSource.web.d.ts.map +1 -0
- package/build/resolveAssetSource.web.js +16 -0
- package/build/resolveAssetSource.web.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/Cache/CachableRequest.swift +46 -0
- package/ios/Cache/CacheVideoAssetTransportProvider.swift +64 -0
- package/ios/Cache/CachedResource.swift +164 -0
- package/ios/Cache/CachingHelpers.swift +107 -0
- package/ios/Cache/MediaFileHandle.swift +139 -0
- package/ios/Cache/MediaInfo.swift +185 -0
- package/ios/Cache/ResourceLoaderDelegate.swift +306 -0
- package/ios/Cache/SynchronizedHashTable.swift +23 -0
- package/ios/Cache/VideoCacheManager.swift +192 -0
- package/ios/ContentKeyDelegate.swift +214 -0
- package/ios/ContentKeyManager.swift +21 -0
- package/ios/Enums/AudioMixingMode.swift +37 -0
- package/ios/Enums/ContentType.swift +11 -0
- package/ios/Enums/DRMType.swift +20 -0
- package/ios/Enums/FullscreenOrientation.swift +34 -0
- package/ios/Enums/KeepFullscreenOnPiPStopBehavior.swift +20 -0
- package/ios/Enums/PlayerStatus.swift +10 -0
- package/ios/Enums/VideoContentFit.swift +39 -0
- package/ios/Enums/VideoRange.swift +21 -0
- package/ios/ExpoVideo.podspec +27 -0
- package/ios/NowPlayingManager.swift +304 -0
- package/ios/OrientationAVPlayerViewController.swift +263 -0
- package/ios/Records/BufferOptions.swift +12 -0
- package/ios/Records/DRMOptions.swift +24 -0
- package/ios/Records/FullscreenOptions.swift +14 -0
- package/ios/Records/PlaybackError.swift +10 -0
- package/ios/Records/ScrubbingModeOptions.swift +7 -0
- package/ios/Records/SeekTolerance.swift +20 -0
- package/ios/Records/SubtitleSource.swift +32 -0
- package/ios/Records/SubtitleStyle.swift +118 -0
- package/ios/Records/Tracks.swift +229 -0
- package/ios/Records/VideoEventPayloads.swift +81 -0
- package/ios/Records/VideoMetadata.swift +16 -0
- package/ios/Records/VideoSize.swift +15 -0
- package/ios/Records/VideoSource.swift +28 -0
- package/ios/Thumbnails/VideoThumbnail.swift +27 -0
- package/ios/Thumbnails/VideoThumbnailGenerator.swift +68 -0
- package/ios/Thumbnails/VideoThumbnailOptions.swift +15 -0
- package/ios/Utils/AVAssetVariant+VideoTracks.swift +20 -0
- package/ios/Utils/AvAssetTrack+VideoTracks.swift +79 -0
- package/ios/Utils/FourCharCode+toString.swift +25 -0
- package/ios/Utils/HlsUriUtils.swift +27 -0
- package/ios/Utils/URL+MediaLibraryAssets.swift +68 -0
- package/ios/VideoAirPlayButtonView.swift +70 -0
- package/ios/VideoAsset.swift +98 -0
- package/ios/VideoAssetTransport/VideoAssetLoadPlan.swift +69 -0
- package/ios/VideoAssetTransport/VideoAssetSourceDescriptor.swift +29 -0
- package/ios/VideoAssetTransport/VideoAssetTransportProvider.swift +24 -0
- package/ios/VideoAssetTransport/VideoAssetTransportRegistry.swift +103 -0
- package/ios/VideoExceptions.swift +53 -0
- package/ios/VideoItem.swift +11 -0
- package/ios/VideoManager.swift +182 -0
- package/ios/VideoModule.swift +413 -0
- package/ios/VideoPlayer/DangerousPropertiesStore.swift +19 -0
- package/ios/VideoPlayer.swift +485 -0
- package/ios/VideoPlayerAudioTracks.swift +109 -0
- package/ios/VideoPlayerItem.swift +169 -0
- package/ios/VideoPlayerObserver.swift +612 -0
- package/ios/VideoPlayerSeeker.swift +77 -0
- package/ios/VideoPlayerSubtitleSideload.swift +203 -0
- package/ios/VideoPlayerSubtitles.swift +88 -0
- package/ios/VideoSourceLoader.swift +90 -0
- package/ios/VideoSourceLoaderListener.swift +34 -0
- package/ios/VideoView.swift +201 -0
- package/package.json +51 -0
- package/plugin/build/index.d.ts +3 -0
- package/plugin/build/index.js +3 -0
- package/plugin/build/withExpoVideo.d.ts +9 -0
- package/plugin/build/withExpoVideo.js +73 -0
- package/plugin/index.d.ts +1 -0
- package/plugin/index.js +1 -0
- package/plugin/jest.config.js +1 -0
- package/plugin/src/index.ts +3 -0
- package/plugin/src/withExpoVideo.ts +103 -0
- package/plugin/tsconfig.json +10 -0
- package/src/NativeVideoAirPlayButtonView.ts +3 -0
- package/src/NativeVideoModule.ts +16 -0
- package/src/NativeVideoModule.web.ts +4 -0
- package/src/NativeVideoView.ts +8 -0
- package/src/VideoAirPlayButton.ios.tsx +8 -0
- package/src/VideoAirPlayButton.tsx +14 -0
- package/src/VideoAirPlayButton.types.ts +39 -0
- package/src/VideoModule.ts +47 -0
- package/src/VideoPlayer.tsx +74 -0
- package/src/VideoPlayer.types.ts +893 -0
- package/src/VideoPlayer.web.tsx +466 -0
- package/src/VideoPlayerEvents.types.ts +332 -0
- package/src/VideoThumbnail.ts +31 -0
- package/src/VideoView.tsx +111 -0
- package/src/VideoView.types.ts +405 -0
- package/src/VideoView.web.tsx +293 -0
- package/src/index.ts +26 -0
- package/src/resolveAssetSource.ts +2 -0
- package/src/resolveAssetSource.web.ts +17 -0
- package/src/ts-declarations/react-native-assets.d.ts +22 -0
- package/tsconfig.all.json +11 -0
- package/tsconfig.json +10 -0
- 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"
|