@stepincto/expo-video 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/android/build.gradle +32 -0
- package/android/src/main/AndroidManifest.xml +20 -0
- package/android/src/main/java/expo/modules/video/AudioFocusManager.kt +241 -0
- package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +145 -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/PictureInPictureHelperFragment.kt +26 -0
- package/android/src/main/java/expo/modules/video/PlayerViewExtension.kt +36 -0
- package/android/src/main/java/expo/modules/video/VideoCache.kt +104 -0
- package/android/src/main/java/expo/modules/video/VideoExceptions.kt +34 -0
- package/android/src/main/java/expo/modules/video/VideoManager.kt +133 -0
- package/android/src/main/java/expo/modules/video/VideoModule.kt +414 -0
- package/android/src/main/java/expo/modules/video/VideoThumbnail.kt +20 -0
- package/android/src/main/java/expo/modules/video/VideoView.kt +367 -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/PlayerStatus.kt +10 -0
- package/android/src/main/java/expo/modules/video/playbackService/ExpoVideoPlaybackService.kt +184 -0
- package/android/src/main/java/expo/modules/video/playbackService/PlaybackServiceConnection.kt +39 -0
- package/android/src/main/java/expo/modules/video/playbackService/VideoMediaSessionCallback.kt +47 -0
- package/android/src/main/java/expo/modules/video/player/FirstFrameEventGenerator.kt +93 -0
- package/android/src/main/java/expo/modules/video/player/PlayerEvent.kt +164 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayer.kt +460 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerAudioTracks.kt +125 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerListener.kt +32 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerLoadControl.kt +525 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerSubtitles.kt +125 -0
- package/android/src/main/java/expo/modules/video/records/BufferOptions.kt +15 -0
- package/android/src/main/java/expo/modules/video/records/DRMOptions.kt +25 -0
- package/android/src/main/java/expo/modules/video/records/PlaybackError.kt +19 -0
- package/android/src/main/java/expo/modules/video/records/Tracks.kt +81 -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 +12 -0
- package/android/src/main/java/expo/modules/video/records/VideoSize.kt +14 -0
- package/android/src/main/java/expo/modules/video/records/VideoSource.kt +104 -0
- package/android/src/main/java/expo/modules/video/records/VideoThumbnailOptions.kt +24 -0
- package/android/src/main/java/expo/modules/video/utils/DataSourceUtils.kt +75 -0
- package/android/src/main/java/expo/modules/video/utils/EventDispatcherUtils.kt +43 -0
- package/android/src/main/java/expo/modules/video/utils/MutableWeakReference.kt +15 -0
- package/android/src/main/java/expo/modules/video/utils/PictureInPictureUtils.kt +96 -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 +16 -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/NativeVideoModule.d.ts +16 -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 +3 -0
- package/build/NativeVideoModule.web.d.ts.map +1 -0
- package/build/NativeVideoModule.web.js +2 -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/VideoModule.d.ts +38 -0
- package/build/VideoModule.d.ts.map +1 -0
- package/build/VideoModule.js +53 -0
- package/build/VideoModule.js.map +1 -0
- package/build/VideoPlayer.d.ts +15 -0
- package/build/VideoPlayer.d.ts.map +1 -0
- package/build/VideoPlayer.js +52 -0
- package/build/VideoPlayer.js.map +1 -0
- package/build/VideoPlayer.types.d.ts +532 -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 +75 -0
- package/build/VideoPlayer.web.d.ts.map +1 -0
- package/build/VideoPlayer.web.js +376 -0
- package/build/VideoPlayer.web.js.map +1 -0
- package/build/VideoPlayerEvents.types.d.ts +262 -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 +3 -0
- package/build/VideoThumbnail.js.map +1 -0
- package/build/VideoView.d.ts +44 -0
- package/build/VideoView.d.ts.map +1 -0
- package/build/VideoView.js +76 -0
- package/build/VideoView.js.map +1 -0
- package/build/VideoView.types.d.ts +147 -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 +180 -0
- package/build/VideoView.web.js.map +1 -0
- package/build/index.d.ts +9 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +7 -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 +44 -0
- package/ios/Cache/CachedResource.swift +97 -0
- package/ios/Cache/CachingHelpers.swift +92 -0
- package/ios/Cache/MediaFileHandle.swift +94 -0
- package/ios/Cache/MediaInfo.swift +147 -0
- package/ios/Cache/ResourceLoaderDelegate.swift +274 -0
- package/ios/Cache/SynchronizedHashTable.swift +23 -0
- package/ios/Cache/VideoCacheManager.swift +338 -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 +12 -0
- package/ios/Enums/DRMType.swift +20 -0
- package/ios/Enums/PlayerStatus.swift +10 -0
- package/ios/Enums/VideoContentFit.swift +39 -0
- package/ios/ExpoVideo.podspec +29 -0
- package/ios/NowPlayingManager.swift +296 -0
- package/ios/Records/BufferOptions.swift +12 -0
- package/ios/Records/DRMOptions.swift +24 -0
- package/ios/Records/PlaybackError.swift +10 -0
- package/ios/Records/Tracks.swift +176 -0
- package/ios/Records/VideoEventPayloads.swift +76 -0
- package/ios/Records/VideoMetadata.swift +16 -0
- package/ios/Records/VideoSize.swift +15 -0
- package/ios/Records/VideoSource.swift +25 -0
- package/ios/Thumbnails/VideoThumbnail.swift +27 -0
- package/ios/Thumbnails/VideoThumbnailGenerator.swift +68 -0
- package/ios/Thumbnails/VideoThumbnailOptions.swift +15 -0
- package/ios/VideoAsset.swift +123 -0
- package/ios/VideoExceptions.swift +53 -0
- package/ios/VideoItem.swift +11 -0
- package/ios/VideoManager.swift +140 -0
- package/ios/VideoModule.swift +383 -0
- package/ios/VideoPlayer/DangerousPropertiesStore.swift +19 -0
- package/ios/VideoPlayer.swift +435 -0
- package/ios/VideoPlayerAudioTracks.swift +72 -0
- package/ios/VideoPlayerItem.swift +97 -0
- package/ios/VideoPlayerObserver.swift +523 -0
- package/ios/VideoPlayerSubtitles.swift +71 -0
- package/ios/VideoSourceLoader.swift +89 -0
- package/ios/VideoSourceLoaderListener.swift +34 -0
- package/ios/VideoView.swift +224 -0
- package/package.json +59 -0
- package/plugin/build/tsconfig.tsbuildinfo +1 -0
- package/plugin/build/withExpoVideo.d.ts +7 -0
- package/plugin/build/withExpoVideo.js +38 -0
- package/src/NativeVideoModule.ts +20 -0
- package/src/NativeVideoModule.web.ts +1 -0
- package/src/NativeVideoView.ts +8 -0
- package/src/VideoModule.ts +59 -0
- package/src/VideoPlayer.tsx +67 -0
- package/src/VideoPlayer.types.ts +613 -0
- package/src/VideoPlayer.web.tsx +451 -0
- package/src/VideoPlayerEvents.types.ts +313 -0
- package/src/VideoThumbnail.ts +31 -0
- package/src/VideoView.tsx +86 -0
- package/src/VideoView.types.ts +165 -0
- package/src/VideoView.web.tsx +214 -0
- package/src/index.ts +46 -0
- package/src/resolveAssetSource.ts +2 -0
- package/src/resolveAssetSource.web.ts +17 -0
- package/src/ts-declarations/react-native-assets.d.ts +1 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
package expo.modules.video
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.PictureInPictureParams
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.content.Intent
|
|
7
|
+
import android.graphics.Color
|
|
8
|
+
import android.os.Build
|
|
9
|
+
import android.util.Rational
|
|
10
|
+
import android.view.LayoutInflater
|
|
11
|
+
import android.view.MotionEvent
|
|
12
|
+
import android.view.View
|
|
13
|
+
import android.view.ViewGroup
|
|
14
|
+
import android.widget.FrameLayout
|
|
15
|
+
import android.widget.ImageButton
|
|
16
|
+
import androidx.fragment.app.FragmentActivity
|
|
17
|
+
import androidx.media3.common.Tracks
|
|
18
|
+
import androidx.media3.common.VideoSize
|
|
19
|
+
import androidx.media3.ui.PlayerView
|
|
20
|
+
import com.facebook.react.bridge.ReactContext
|
|
21
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
22
|
+
import com.facebook.react.uimanager.events.EventDispatcher
|
|
23
|
+
import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper
|
|
24
|
+
import expo.modules.kotlin.AppContext
|
|
25
|
+
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
26
|
+
import expo.modules.kotlin.views.ExpoView
|
|
27
|
+
import expo.modules.video.delegates.IgnoreSameSet
|
|
28
|
+
import expo.modules.video.enums.ContentFit
|
|
29
|
+
import expo.modules.video.player.VideoPlayer
|
|
30
|
+
import expo.modules.video.player.VideoPlayerListener
|
|
31
|
+
import expo.modules.video.records.AudioTrack
|
|
32
|
+
import expo.modules.video.records.SubtitleTrack
|
|
33
|
+
import expo.modules.video.records.VideoSource
|
|
34
|
+
import expo.modules.video.records.VideoTrack
|
|
35
|
+
import expo.modules.video.utils.applyPiPParams
|
|
36
|
+
import expo.modules.video.utils.applyRectHint
|
|
37
|
+
import expo.modules.video.utils.calculatePiPAspectRatio
|
|
38
|
+
import expo.modules.video.utils.calculateRectHint
|
|
39
|
+
import expo.modules.video.utils.dispatchMotionEvent
|
|
40
|
+
import java.util.UUID
|
|
41
|
+
|
|
42
|
+
class SurfaceVideoView(context: Context, appContext: AppContext) : VideoView(context, appContext)
|
|
43
|
+
class TextureVideoView(context: Context, appContext: AppContext) : VideoView(context, appContext, true)
|
|
44
|
+
|
|
45
|
+
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
|
46
|
+
open class VideoView(context: Context, appContext: AppContext, useTextureView: Boolean = false) : ExpoView(context, appContext), VideoPlayerListener {
|
|
47
|
+
val videoViewId: String = UUID.randomUUID().toString()
|
|
48
|
+
val playerView: PlayerView = LayoutInflater.from(context.applicationContext).inflate(getPlayerViewLayoutId(useTextureView), null) as PlayerView
|
|
49
|
+
val onPictureInPictureStart by EventDispatcher<Unit>()
|
|
50
|
+
val onPictureInPictureStop by EventDispatcher<Unit>()
|
|
51
|
+
val onFullscreenEnter by EventDispatcher<Unit>()
|
|
52
|
+
val onFullscreenExit by EventDispatcher<Unit>()
|
|
53
|
+
val onFirstFrameRender by EventDispatcher<Unit>()
|
|
54
|
+
|
|
55
|
+
var willEnterPiP: Boolean = false
|
|
56
|
+
|
|
57
|
+
// In some situations we can't detect if the view will enter PiP, in that case the playback will be paused
|
|
58
|
+
// We can get an event after PiP has started, that's when we should resume playback
|
|
59
|
+
var wasAutoPaused: Boolean = false
|
|
60
|
+
var isInFullscreen: Boolean = false
|
|
61
|
+
private set
|
|
62
|
+
var showsSubtitlesButton = false
|
|
63
|
+
private set
|
|
64
|
+
var showsAudioTracksButton = false
|
|
65
|
+
private set
|
|
66
|
+
|
|
67
|
+
private val currentActivity = appContext.throwingActivity
|
|
68
|
+
private val decorView = currentActivity.window.decorView
|
|
69
|
+
private val rootView = decorView.findViewById<ViewGroup>(android.R.id.content)
|
|
70
|
+
private val touchEventCoalescingKeyHelper = TouchEventCoalescingKeyHelper()
|
|
71
|
+
|
|
72
|
+
private val rootViewChildrenOriginalVisibility: ArrayList<Int> = arrayListOf()
|
|
73
|
+
private var pictureInPictureHelperTag: String? = null
|
|
74
|
+
private var reactNativeEventDispatcher: EventDispatcher? = null
|
|
75
|
+
|
|
76
|
+
// We need to keep track of the target surface view visibility, but only apply it when `useExoShutter` is false.
|
|
77
|
+
var shouldHideSurfaceView: Boolean = true
|
|
78
|
+
|
|
79
|
+
var useExoShutter: Boolean? = null
|
|
80
|
+
set(value) {
|
|
81
|
+
if (value == true) {
|
|
82
|
+
playerView.setShutterBackgroundColor(Color.BLACK)
|
|
83
|
+
} else {
|
|
84
|
+
playerView.setShutterBackgroundColor(Color.TRANSPARENT)
|
|
85
|
+
}
|
|
86
|
+
applySurfaceViewVisibility()
|
|
87
|
+
field = value
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
var autoEnterPiP: Boolean by IgnoreSameSet(false) { new, _ ->
|
|
91
|
+
applyPiPParams(currentActivity, new, calculateCurrentPipAspectRatio())
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
var contentFit: ContentFit = ContentFit.CONTAIN
|
|
95
|
+
set(value) {
|
|
96
|
+
playerView.resizeMode = value.toResizeMode()
|
|
97
|
+
field = value
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
var videoPlayer: VideoPlayer? = null
|
|
101
|
+
set(newPlayer) {
|
|
102
|
+
field?.let {
|
|
103
|
+
VideoManager.onVideoPlayerDetachedFromView(it, this)
|
|
104
|
+
}
|
|
105
|
+
videoPlayer?.removeListener(this)
|
|
106
|
+
newPlayer?.addListener(this)
|
|
107
|
+
field = newPlayer
|
|
108
|
+
shouldHideSurfaceView = true
|
|
109
|
+
attachPlayer()
|
|
110
|
+
newPlayer?.let {
|
|
111
|
+
VideoManager.onVideoPlayerAttachedToView(it, this)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
var useNativeControls: Boolean = true
|
|
116
|
+
set(value) {
|
|
117
|
+
playerView.useController = value
|
|
118
|
+
playerView.setShowSubtitleButton(value)
|
|
119
|
+
field = value
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
var allowsFullscreen: Boolean = true
|
|
123
|
+
set(value) {
|
|
124
|
+
if (value) {
|
|
125
|
+
playerView.setFullscreenButtonClickListener { enterFullscreen() }
|
|
126
|
+
} else {
|
|
127
|
+
playerView.setFullscreenButtonClickListener(null)
|
|
128
|
+
// Setting listener to null should hide the button, but judging by ExoPlayer source code
|
|
129
|
+
// there is a bug and the button isn't hidden. We need to do it manually.
|
|
130
|
+
playerView.setFullscreenButtonVisibility(false)
|
|
131
|
+
}
|
|
132
|
+
field = value
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private val mLayoutRunnable = Runnable {
|
|
136
|
+
measure(
|
|
137
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
138
|
+
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
|
139
|
+
)
|
|
140
|
+
layout(left, top, right, bottom)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
init {
|
|
144
|
+
VideoManager.registerVideoView(this)
|
|
145
|
+
playerView.setFullscreenButtonClickListener { enterFullscreen() }
|
|
146
|
+
// The prop `useNativeControls` prop is sometimes applied after the view is created, and sometimes there is a visible
|
|
147
|
+
// flash of controls event when they are set to off. Initially we set it to `false` and apply it in `onAttachedToWindow` to avoid this.
|
|
148
|
+
this.playerView.useController = false
|
|
149
|
+
|
|
150
|
+
// Start with the SurfaceView being transparent to avoid any flickers when the prop value is delivered.
|
|
151
|
+
this.playerView.setShutterBackgroundColor(Color.TRANSPARENT)
|
|
152
|
+
this.playerView.videoSurfaceView?.alpha = 0f
|
|
153
|
+
addView(
|
|
154
|
+
playerView,
|
|
155
|
+
ViewGroup.LayoutParams(
|
|
156
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
157
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
reactNativeEventDispatcher = UIManagerHelper.getEventDispatcher(appContext.reactContext as ReactContext, id)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fun applySurfaceViewVisibility() {
|
|
165
|
+
if (useExoShutter != true && shouldHideSurfaceView) {
|
|
166
|
+
playerView.videoSurfaceView?.alpha = 0f
|
|
167
|
+
} else {
|
|
168
|
+
playerView.videoSurfaceView?.alpha = 1f
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fun enterFullscreen() {
|
|
173
|
+
val intent = Intent(context, FullscreenPlayerActivity::class.java)
|
|
174
|
+
intent.putExtra(VideoManager.INTENT_PLAYER_KEY, videoViewId)
|
|
175
|
+
// Set before starting the activity to avoid entering PiP unintentionally
|
|
176
|
+
isInFullscreen = true
|
|
177
|
+
currentActivity.startActivity(intent)
|
|
178
|
+
|
|
179
|
+
// Disable the enter transition
|
|
180
|
+
if (Build.VERSION.SDK_INT >= 34) {
|
|
181
|
+
currentActivity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN, 0, 0)
|
|
182
|
+
} else {
|
|
183
|
+
@Suppress("DEPRECATION")
|
|
184
|
+
currentActivity.overridePendingTransition(0, 0)
|
|
185
|
+
}
|
|
186
|
+
onFullscreenEnter(Unit)
|
|
187
|
+
applyPiPParams(currentActivity, false, calculateCurrentPipAspectRatio())
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fun attachPlayer() {
|
|
191
|
+
videoPlayer?.changePlayerView(playerView)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fun exitFullscreen() {
|
|
195
|
+
// Fullscreen uses a different PlayerView instance, because of that we need to manually update the non-fullscreen player icon after exiting
|
|
196
|
+
val fullScreenButton: ImageButton = playerView.findViewById(androidx.media3.ui.R.id.exo_fullscreen)
|
|
197
|
+
fullScreenButton.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_enter)
|
|
198
|
+
attachPlayer()
|
|
199
|
+
onFullscreenExit(Unit)
|
|
200
|
+
isInFullscreen = false
|
|
201
|
+
applyPiPParams(currentActivity, autoEnterPiP, calculateCurrentPipAspectRatio())
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
fun enterPictureInPicture() {
|
|
205
|
+
if (!isPictureInPictureSupported(currentActivity)) {
|
|
206
|
+
throw PictureInPictureUnsupportedException()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
val player = playerView.player
|
|
210
|
+
?: throw PictureInPictureEnterException("No player attached to the VideoView")
|
|
211
|
+
playerView.useController = false
|
|
212
|
+
applyPiPParams(currentActivity, autoEnterPiP, calculateCurrentPipAspectRatio())
|
|
213
|
+
willEnterPiP = true
|
|
214
|
+
|
|
215
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
216
|
+
currentActivity.enterPictureInPictureMode(PictureInPictureParams.Builder().build())
|
|
217
|
+
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
218
|
+
@Suppress("DEPRECATION")
|
|
219
|
+
currentActivity.enterPictureInPictureMode()
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private fun calculateCurrentPipAspectRatio(): Rational? {
|
|
224
|
+
val player = videoPlayer?.player ?: return null
|
|
225
|
+
return calculatePiPAspectRatio(player.videoSize, this.width, this.height, contentFit)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* For optimal picture in picture experience it's best to only have one view. This method
|
|
230
|
+
* hides all children of the root view and makes the player the only visible child of the rootView.
|
|
231
|
+
*/
|
|
232
|
+
fun layoutForPiPEnter() {
|
|
233
|
+
playerView.useController = false
|
|
234
|
+
(playerView.parent as? ViewGroup)?.removeView(playerView)
|
|
235
|
+
for (i in 0 until rootView.childCount) {
|
|
236
|
+
if (rootView.getChildAt(i) != playerView) {
|
|
237
|
+
rootViewChildrenOriginalVisibility.add(rootView.getChildAt(i).visibility)
|
|
238
|
+
rootView.getChildAt(i).visibility = View.GONE
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
rootView.addView(playerView, FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
fun layoutForPiPExit() {
|
|
245
|
+
playerView.useController = useNativeControls
|
|
246
|
+
rootView.removeView(playerView)
|
|
247
|
+
for (i in 0 until rootView.childCount) {
|
|
248
|
+
rootView.getChildAt(i).visibility = rootViewChildrenOriginalVisibility[i]
|
|
249
|
+
}
|
|
250
|
+
rootViewChildrenOriginalVisibility.clear()
|
|
251
|
+
this.addView(playerView)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
override fun onVideoSourceLoaded(
|
|
255
|
+
player: VideoPlayer,
|
|
256
|
+
videoSource: VideoSource?,
|
|
257
|
+
duration: Double?,
|
|
258
|
+
availableVideoTracks: List<VideoTrack>,
|
|
259
|
+
availableSubtitleTracks: List<SubtitleTrack>,
|
|
260
|
+
availableAudioTracks: List<AudioTrack>
|
|
261
|
+
) {
|
|
262
|
+
availableVideoTracks.firstOrNull()?.let {
|
|
263
|
+
val videoSize = VideoSize(it.size.width, it.size.height)
|
|
264
|
+
val aspectRatio = calculatePiPAspectRatio(videoSize, this.width, this.height, contentFit)
|
|
265
|
+
applyPiPParams(currentActivity, autoEnterPiP, aspectRatio)
|
|
266
|
+
}
|
|
267
|
+
super.onVideoSourceLoaded(player, videoSource, duration, availableVideoTracks, availableSubtitleTracks, availableAudioTracks)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
override fun onTracksChanged(player: VideoPlayer, tracks: Tracks) {
|
|
271
|
+
showsSubtitlesButton = player.subtitles.availableSubtitleTracks.isNotEmpty()
|
|
272
|
+
showsAudioTracksButton = player.audioTracks.availableAudioTracks.size > 1
|
|
273
|
+
playerView.setShowSubtitleButton(showsSubtitlesButton)
|
|
274
|
+
super.onTracksChanged(player, tracks)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
override fun onRenderedFirstFrame(player: VideoPlayer) {
|
|
278
|
+
shouldHideSurfaceView = false
|
|
279
|
+
applySurfaceViewVisibility()
|
|
280
|
+
onFirstFrameRender(Unit)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
override fun requestLayout() {
|
|
284
|
+
super.requestLayout()
|
|
285
|
+
|
|
286
|
+
// Code borrowed from:
|
|
287
|
+
// https://github.com/facebook/react-native/blob/d19afc73f5048f81656d0b4424232ce6d69a6368/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbar.java#L166
|
|
288
|
+
// This fixes some layout issues with the exoplayer which caused the resizeMode to not work properly
|
|
289
|
+
post(mLayoutRunnable)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
|
293
|
+
super.onLayout(changed, l, t, r, b)
|
|
294
|
+
// On every re-layout ExoPlayer resets the timeBar to be enabled.
|
|
295
|
+
// We need to disable it to keep scrubbing impossible.
|
|
296
|
+
playerView.setTimeBarInteractive(videoPlayer?.requiresLinearPlayback ?: true)
|
|
297
|
+
applyRectHint(currentActivity, calculateRectHint(playerView))
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
override fun onAttachedToWindow() {
|
|
301
|
+
super.onAttachedToWindow()
|
|
302
|
+
(currentActivity as? FragmentActivity)?.let {
|
|
303
|
+
val fragment = PictureInPictureHelperFragment(this)
|
|
304
|
+
pictureInPictureHelperTag = fragment.id
|
|
305
|
+
it.supportFragmentManager.beginTransaction()
|
|
306
|
+
.add(fragment, fragment.id)
|
|
307
|
+
.commitAllowingStateLoss()
|
|
308
|
+
}
|
|
309
|
+
applyPiPParams(currentActivity, autoEnterPiP)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
override fun onDetachedFromWindow() {
|
|
313
|
+
super.onDetachedFromWindow()
|
|
314
|
+
(currentActivity as? FragmentActivity)?.let {
|
|
315
|
+
val fragment = it.supportFragmentManager.findFragmentByTag(pictureInPictureHelperTag ?: "")
|
|
316
|
+
?: return
|
|
317
|
+
it.supportFragmentManager.beginTransaction()
|
|
318
|
+
.remove(fragment)
|
|
319
|
+
.commitAllowingStateLoss()
|
|
320
|
+
}
|
|
321
|
+
applyPiPParams(currentActivity, false)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// After adding the `PlayerView` to the hierarchy the touch events stop being emitted to the JS side.
|
|
325
|
+
// The only workaround I have found is to dispatch the touch events manually using the `EventDispatcher`.
|
|
326
|
+
// The behavior is different when the native controls are enabled and disabled.
|
|
327
|
+
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
|
328
|
+
if (!useNativeControls) {
|
|
329
|
+
event?.eventTime?.let {
|
|
330
|
+
touchEventCoalescingKeyHelper.addCoalescingKey(it)
|
|
331
|
+
reactNativeEventDispatcher?.dispatchMotionEvent(this@VideoView, event, touchEventCoalescingKeyHelper)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (event?.actionMasked == MotionEvent.ACTION_UP) {
|
|
335
|
+
performClick()
|
|
336
|
+
}
|
|
337
|
+
// Mark the event as handled
|
|
338
|
+
return true
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
|
|
342
|
+
if (useNativeControls) {
|
|
343
|
+
event?.eventTime?.let {
|
|
344
|
+
touchEventCoalescingKeyHelper.addCoalescingKey(it)
|
|
345
|
+
reactNativeEventDispatcher?.dispatchMotionEvent(this@VideoView, MotionEvent.obtainNoHistory(event), touchEventCoalescingKeyHelper)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// Return false to receive all other events before the target `onTouchEvent`
|
|
349
|
+
return false
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private fun getPlayerViewLayoutId(useTextureView: Boolean): Int {
|
|
353
|
+
return if (useTextureView) {
|
|
354
|
+
R.layout.texture_player_view
|
|
355
|
+
} else {
|
|
356
|
+
R.layout.surface_player_view
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
companion object {
|
|
361
|
+
fun isPictureInPictureSupported(currentActivity: Activity): Boolean {
|
|
362
|
+
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && currentActivity.packageManager.hasSystemFeature(
|
|
363
|
+
android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package expo.modules.video.delegates
|
|
2
|
+
|
|
3
|
+
import kotlin.reflect.KProperty
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Property delegate, where the set is ignored unless the value has changed.
|
|
7
|
+
* @param T The type of the property.
|
|
8
|
+
* @param value The initial value of the property.
|
|
9
|
+
* @param propertyMapper A function that maps the new value to the property value.
|
|
10
|
+
* @param didSet A function that is called when the property value has changed.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
class IgnoreSameSet<T : Any?>(private var value: T, val propertyMapper: ((T) -> T) = { v -> v }, val didSet: ((new: T, old: T) -> Unit)? = null) {
|
|
14
|
+
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
|
15
|
+
return value
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
|
19
|
+
if (this.value == propertyMapper(value)) return
|
|
20
|
+
val oldValue = this.value
|
|
21
|
+
this.value = propertyMapper(value)
|
|
22
|
+
didSet?.invoke(this.value, oldValue)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
package expo.modules.video.drawing
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Canvas
|
|
5
|
+
import android.graphics.Outline
|
|
6
|
+
import android.graphics.Path
|
|
7
|
+
import android.graphics.RectF
|
|
8
|
+
import android.os.Build
|
|
9
|
+
import android.view.View
|
|
10
|
+
import android.view.ViewOutlineProvider
|
|
11
|
+
import com.facebook.react.modules.i18nmanager.I18nUtil
|
|
12
|
+
import com.facebook.react.uimanager.FloatUtil
|
|
13
|
+
import com.facebook.react.uimanager.PixelUtil
|
|
14
|
+
import com.facebook.yoga.YogaConstants
|
|
15
|
+
import expo.modules.video.utils.ifYogaUndefinedUse
|
|
16
|
+
|
|
17
|
+
class OutlineProvider(private val mContext: Context) : ViewOutlineProvider() {
|
|
18
|
+
enum class BorderRadiusConfig {
|
|
19
|
+
ALL,
|
|
20
|
+
TOP_LEFT,
|
|
21
|
+
TOP_RIGHT,
|
|
22
|
+
BOTTOM_RIGHT,
|
|
23
|
+
BOTTOM_LEFT,
|
|
24
|
+
TOP_START,
|
|
25
|
+
TOP_END,
|
|
26
|
+
BOTTOM_START,
|
|
27
|
+
BOTTOM_END
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
enum class CornerRadius {
|
|
31
|
+
TOP_LEFT,
|
|
32
|
+
TOP_RIGHT,
|
|
33
|
+
BOTTOM_RIGHT,
|
|
34
|
+
BOTTOM_LEFT
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private var mLayoutDirection = View.LAYOUT_DIRECTION_LTR
|
|
38
|
+
private val mBounds = RectF()
|
|
39
|
+
val borderRadiiConfig = FloatArray(9) { YogaConstants.UNDEFINED }
|
|
40
|
+
private val mCornerRadii = FloatArray(4)
|
|
41
|
+
private var mCornerRadiiInvalidated = true
|
|
42
|
+
private val mConvexPath = Path()
|
|
43
|
+
private var mConvexPathInvalidated = true
|
|
44
|
+
|
|
45
|
+
init {
|
|
46
|
+
updateCornerRadiiIfNeeded()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private fun updateCornerRadiiIfNeeded() {
|
|
50
|
+
if (!mCornerRadiiInvalidated) {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
val isRTL = mLayoutDirection == View.LAYOUT_DIRECTION_RTL
|
|
55
|
+
val isRTLSwap = I18nUtil.instance.doLeftAndRightSwapInRTL(mContext)
|
|
56
|
+
updateCornerRadius(
|
|
57
|
+
CornerRadius.TOP_LEFT,
|
|
58
|
+
BorderRadiusConfig.TOP_LEFT,
|
|
59
|
+
BorderRadiusConfig.TOP_RIGHT,
|
|
60
|
+
BorderRadiusConfig.TOP_START,
|
|
61
|
+
BorderRadiusConfig.TOP_END,
|
|
62
|
+
isRTL,
|
|
63
|
+
isRTLSwap
|
|
64
|
+
)
|
|
65
|
+
updateCornerRadius(
|
|
66
|
+
CornerRadius.TOP_RIGHT,
|
|
67
|
+
BorderRadiusConfig.TOP_RIGHT,
|
|
68
|
+
BorderRadiusConfig.TOP_LEFT,
|
|
69
|
+
BorderRadiusConfig.TOP_END,
|
|
70
|
+
BorderRadiusConfig.TOP_START,
|
|
71
|
+
isRTL,
|
|
72
|
+
isRTLSwap
|
|
73
|
+
)
|
|
74
|
+
updateCornerRadius(
|
|
75
|
+
CornerRadius.BOTTOM_LEFT,
|
|
76
|
+
BorderRadiusConfig.BOTTOM_LEFT,
|
|
77
|
+
BorderRadiusConfig.BOTTOM_RIGHT,
|
|
78
|
+
BorderRadiusConfig.BOTTOM_START,
|
|
79
|
+
BorderRadiusConfig.BOTTOM_END,
|
|
80
|
+
isRTL,
|
|
81
|
+
isRTLSwap
|
|
82
|
+
)
|
|
83
|
+
updateCornerRadius(
|
|
84
|
+
CornerRadius.BOTTOM_RIGHT,
|
|
85
|
+
BorderRadiusConfig.BOTTOM_RIGHT,
|
|
86
|
+
BorderRadiusConfig.BOTTOM_LEFT,
|
|
87
|
+
BorderRadiusConfig.BOTTOM_END,
|
|
88
|
+
BorderRadiusConfig.BOTTOM_START,
|
|
89
|
+
isRTL,
|
|
90
|
+
isRTLSwap
|
|
91
|
+
)
|
|
92
|
+
mCornerRadiiInvalidated = false
|
|
93
|
+
mConvexPathInvalidated = true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private fun updateCornerRadius(
|
|
97
|
+
outputPosition: CornerRadius,
|
|
98
|
+
inputPosition: BorderRadiusConfig,
|
|
99
|
+
oppositePosition: BorderRadiusConfig,
|
|
100
|
+
startPosition: BorderRadiusConfig,
|
|
101
|
+
endPosition: BorderRadiusConfig,
|
|
102
|
+
isRTL: Boolean,
|
|
103
|
+
isRTLSwap: Boolean
|
|
104
|
+
) {
|
|
105
|
+
var radius = borderRadiiConfig[inputPosition.ordinal]
|
|
106
|
+
if (isRTL) {
|
|
107
|
+
if (isRTLSwap) {
|
|
108
|
+
radius = borderRadiiConfig[oppositePosition.ordinal]
|
|
109
|
+
}
|
|
110
|
+
if (YogaConstants.isUndefined(radius)) {
|
|
111
|
+
radius = borderRadiiConfig[endPosition.ordinal]
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
if (YogaConstants.isUndefined(radius)) {
|
|
115
|
+
radius = borderRadiiConfig[startPosition.ordinal]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
radius = radius
|
|
119
|
+
.ifYogaUndefinedUse(borderRadiiConfig[BorderRadiusConfig.ALL.ordinal])
|
|
120
|
+
.ifYogaUndefinedUse(0f)
|
|
121
|
+
mCornerRadii[outputPosition.ordinal] = PixelUtil.toPixelFromDIP(radius)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private fun updateConvexPathIfNeeded() {
|
|
125
|
+
if (!mConvexPathInvalidated) {
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
mConvexPath.reset()
|
|
129
|
+
mConvexPath.addRoundRect(
|
|
130
|
+
mBounds,
|
|
131
|
+
floatArrayOf(
|
|
132
|
+
mCornerRadii[CornerRadius.TOP_LEFT.ordinal],
|
|
133
|
+
mCornerRadii[CornerRadius.TOP_LEFT.ordinal],
|
|
134
|
+
mCornerRadii[CornerRadius.TOP_RIGHT.ordinal],
|
|
135
|
+
mCornerRadii[CornerRadius.TOP_RIGHT.ordinal],
|
|
136
|
+
mCornerRadii[CornerRadius.BOTTOM_RIGHT.ordinal],
|
|
137
|
+
mCornerRadii[CornerRadius.BOTTOM_RIGHT.ordinal],
|
|
138
|
+
mCornerRadii[CornerRadius.BOTTOM_LEFT.ordinal],
|
|
139
|
+
mCornerRadii[CornerRadius.BOTTOM_LEFT.ordinal]
|
|
140
|
+
),
|
|
141
|
+
Path.Direction.CW
|
|
142
|
+
)
|
|
143
|
+
mConvexPathInvalidated = false
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fun hasEqualCorners(): Boolean {
|
|
147
|
+
updateCornerRadiiIfNeeded()
|
|
148
|
+
val initialCornerRadius = mCornerRadii[0]
|
|
149
|
+
return mCornerRadii.all { initialCornerRadius == it }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fun setBorderRadius(radius: Float, position: Int): Boolean {
|
|
153
|
+
if (!FloatUtil.floatsEqual(borderRadiiConfig[position], radius)) {
|
|
154
|
+
borderRadiiConfig[position] = radius
|
|
155
|
+
mCornerRadiiInvalidated = true
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private fun updateBoundsAndLayoutDirection(view: View) {
|
|
162
|
+
// Update layout direction
|
|
163
|
+
val layoutDirection = view.layoutDirection
|
|
164
|
+
if (mLayoutDirection != layoutDirection) {
|
|
165
|
+
mLayoutDirection = layoutDirection
|
|
166
|
+
mCornerRadiiInvalidated = true
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Update size
|
|
170
|
+
val left = 0
|
|
171
|
+
val top = 0
|
|
172
|
+
val right = view.width
|
|
173
|
+
val bottom = view.height
|
|
174
|
+
if (mBounds.left != left.toFloat() ||
|
|
175
|
+
mBounds.top != top.toFloat() ||
|
|
176
|
+
mBounds.right != right.toFloat() ||
|
|
177
|
+
mBounds.bottom != bottom.toFloat()
|
|
178
|
+
) {
|
|
179
|
+
mBounds[left.toFloat(), top.toFloat(), right.toFloat()] = bottom.toFloat()
|
|
180
|
+
mCornerRadiiInvalidated = true
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
override fun getOutline(view: View, outline: Outline) {
|
|
185
|
+
updateBoundsAndLayoutDirection(view)
|
|
186
|
+
|
|
187
|
+
// Calculate outline
|
|
188
|
+
updateCornerRadiiIfNeeded()
|
|
189
|
+
if (hasEqualCorners()) {
|
|
190
|
+
val cornerRadius = mCornerRadii[0]
|
|
191
|
+
if (cornerRadius > 0) {
|
|
192
|
+
outline.setRoundRect(0, 0, mBounds.width().toInt(), mBounds.height().toInt(), cornerRadius)
|
|
193
|
+
} else {
|
|
194
|
+
outline.setRect(0, 0, mBounds.width().toInt(), mBounds.height().toInt())
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
// Clipping is not supported when using a convex path, but drawing the elevation
|
|
198
|
+
// shadow is. For the particular case, we fallback to canvas clipping in the view
|
|
199
|
+
// which is supposed to call `clipCanvasIfNeeded` in its `draw` method.
|
|
200
|
+
updateConvexPathIfNeeded()
|
|
201
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
202
|
+
outline.setPath(mConvexPath)
|
|
203
|
+
} else {
|
|
204
|
+
outline.setConvexPath(mConvexPath)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fun clipCanvasIfNeeded(canvas: Canvas, view: View) {
|
|
210
|
+
updateBoundsAndLayoutDirection(view)
|
|
211
|
+
updateCornerRadiiIfNeeded()
|
|
212
|
+
if (!hasEqualCorners()) {
|
|
213
|
+
updateConvexPathIfNeeded()
|
|
214
|
+
canvas.clipPath(mConvexPath)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -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
|
+
}
|