@stepincto/expo-video 1.0.4 → 1.0.6
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/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +46 -4
- package/android/src/main/java/expo/modules/video/VideoModule.kt +6 -0
- package/android/src/main/java/expo/modules/video/VideoView.kt +13 -0
- package/android/src/main/java/expo/modules/video/enums/FullscreenOrientation.kt +26 -0
- package/android/src/main/java/expo/modules/video/records/FullscreenOptions.kt +12 -0
- package/android/src/main/java/expo/modules/video/utils/FullscreenActivityOrientationHelper.kt +120 -0
- package/build/VideoModule.d.ts.map +1 -1
- package/build/VideoModule.js +24 -1
- package/build/VideoModule.js.map +1 -1
- package/build/VideoView.d.ts.map +1 -1
- package/build/VideoView.js +10 -3
- package/build/VideoView.js.map +1 -1
- package/build/VideoView.types.d.ts +47 -0
- package/build/VideoView.types.d.ts.map +1 -1
- package/build/VideoView.types.js.map +1 -1
- package/build/VideoView.web.d.ts.map +1 -1
- package/build/VideoView.web.js +3 -2
- package/build/VideoView.web.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/expo-module.config.json +7 -1
- package/ios/Cache/CachableRequest.swift +2 -41
- package/ios/Cache/CachedResource.swift +1 -1
- package/ios/Cache/MediaFileHandle.swift +10 -8
- package/ios/Cache/MediaInfo.swift +41 -6
- package/ios/Cache/VideoCacheManager.swift +53 -6
- package/ios/Enums/FullscreenOrientation.swift +34 -0
- package/ios/OrientationAVPlayerViewController.swift +231 -0
- package/ios/Records/FullscreenOptions.swift +12 -0
- package/ios/VideoAsset.swift +4 -1
- package/ios/VideoModule.swift +8 -0
- package/ios/VideoView.swift +19 -50
- package/package.json +3 -4
- package/src/VideoModule.ts +28 -1
- package/src/VideoView.tsx +28 -3
- package/src/VideoView.types.ts +57 -0
- package/src/VideoView.web.tsx +4 -2
- package/src/index.ts +7 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package expo.modules.video
|
|
2
2
|
|
|
3
3
|
import android.app.Activity
|
|
4
|
+
import android.content.pm.ActivityInfo
|
|
4
5
|
import android.content.res.Configuration
|
|
5
6
|
import android.os.Build
|
|
6
7
|
import android.os.Bundle
|
|
@@ -12,6 +13,8 @@ import android.widget.ImageButton
|
|
|
12
13
|
import androidx.media3.ui.PlayerView
|
|
13
14
|
import expo.modules.kotlin.exception.CodedException
|
|
14
15
|
import expo.modules.video.player.VideoPlayer
|
|
16
|
+
import expo.modules.video.records.FullscreenOptions
|
|
17
|
+
import expo.modules.video.utils.FullscreenActivityOrientationHelper
|
|
15
18
|
import expo.modules.video.utils.applyPiPParams
|
|
16
19
|
import expo.modules.video.utils.applyRectHint
|
|
17
20
|
import expo.modules.video.utils.calculatePiPAspectRatio
|
|
@@ -26,22 +29,49 @@ class FullscreenPlayerActivity : Activity() {
|
|
|
26
29
|
private lateinit var videoView: VideoView
|
|
27
30
|
private var didFinish = false
|
|
28
31
|
private var wasAutoPaused = false
|
|
32
|
+
private lateinit var options: FullscreenOptions
|
|
33
|
+
private lateinit var orientationHelper: FullscreenActivityOrientationHelper
|
|
29
34
|
|
|
30
35
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
31
36
|
super.onCreate(savedInstanceState)
|
|
32
|
-
setContentView(R.layout.fullscreen_player_activity)
|
|
33
|
-
mContentView = findViewById(R.id.enclosing_layout)
|
|
34
|
-
playerView = findViewById(R.id.player_view)
|
|
35
37
|
|
|
36
38
|
try {
|
|
37
39
|
videoViewId = intent.getStringExtra(VideoManager.INTENT_PLAYER_KEY)
|
|
38
40
|
?: throw FullScreenVideoViewNotFoundException()
|
|
41
|
+
|
|
42
|
+
options = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
43
|
+
intent.getSerializableExtra(INTENT_FULLSCREEN_OPTIONS_KEY, FullscreenOptions::class.java)
|
|
44
|
+
?: FullscreenOptions()
|
|
45
|
+
} else {
|
|
46
|
+
@Suppress("DEPRECATION")
|
|
47
|
+
(intent.getSerializableExtra(INTENT_FULLSCREEN_OPTIONS_KEY) as? FullscreenOptions)
|
|
48
|
+
?: FullscreenOptions()
|
|
49
|
+
}
|
|
50
|
+
|
|
39
51
|
videoView = VideoManager.getVideoView(videoViewId)
|
|
52
|
+
|
|
53
|
+
orientationHelper = FullscreenActivityOrientationHelper(
|
|
54
|
+
this,
|
|
55
|
+
options,
|
|
56
|
+
onShouldAutoExit = {
|
|
57
|
+
finish()
|
|
58
|
+
},
|
|
59
|
+
onShouldReleaseOrientation = {
|
|
60
|
+
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
orientationHelper.startOrientationEventListener()
|
|
40
64
|
} catch (e: CodedException) {
|
|
41
65
|
Log.e("ExpoVideo", "${e.message}", e)
|
|
42
66
|
finish()
|
|
43
67
|
return
|
|
44
68
|
}
|
|
69
|
+
|
|
70
|
+
setContentView(R.layout.fullscreen_player_activity)
|
|
71
|
+
mContentView = findViewById(R.id.enclosing_layout)
|
|
72
|
+
playerView = findViewById(R.id.player_view)
|
|
73
|
+
requestedOrientation = options.orientation.toActivityOrientation()
|
|
74
|
+
|
|
45
75
|
videoPlayer = videoView.videoPlayer
|
|
46
76
|
videoPlayer?.changePlayerView(playerView)
|
|
47
77
|
VideoManager.registerFullscreenPlayerActivity(hashCode().toString(), this)
|
|
@@ -83,7 +113,8 @@ class FullscreenPlayerActivity : Activity() {
|
|
|
83
113
|
}
|
|
84
114
|
|
|
85
115
|
override fun onResume() {
|
|
86
|
-
|
|
116
|
+
orientationHelper.startOrientationEventListener()
|
|
117
|
+
playerView.useController = true
|
|
87
118
|
super.onResume()
|
|
88
119
|
}
|
|
89
120
|
|
|
@@ -95,6 +126,7 @@ class FullscreenPlayerActivity : Activity() {
|
|
|
95
126
|
videoPlayer?.player?.pause()
|
|
96
127
|
}
|
|
97
128
|
}
|
|
129
|
+
orientationHelper.stopOrientationEventListener()
|
|
98
130
|
super.onPause()
|
|
99
131
|
}
|
|
100
132
|
|
|
@@ -102,6 +134,7 @@ class FullscreenPlayerActivity : Activity() {
|
|
|
102
134
|
super.onDestroy()
|
|
103
135
|
videoView.exitFullscreen()
|
|
104
136
|
VideoManager.unregisterFullscreenPlayerActivity(hashCode().toString())
|
|
137
|
+
orientationHelper.stopOrientationEventListener()
|
|
105
138
|
}
|
|
106
139
|
|
|
107
140
|
private fun setupFullscreenButton() {
|
|
@@ -142,4 +175,13 @@ class FullscreenPlayerActivity : Activity() {
|
|
|
142
175
|
)
|
|
143
176
|
}
|
|
144
177
|
}
|
|
178
|
+
|
|
179
|
+
override fun onConfigurationChanged(newConfig: Configuration) {
|
|
180
|
+
super.onConfigurationChanged(newConfig)
|
|
181
|
+
orientationHelper.onConfigurationChanged(newConfig)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
companion object {
|
|
185
|
+
const val INTENT_FULLSCREEN_OPTIONS_KEY = "fullscreen_options"
|
|
186
|
+
}
|
|
145
187
|
}
|
|
@@ -20,6 +20,7 @@ import expo.modules.video.enums.AudioMixingMode
|
|
|
20
20
|
import expo.modules.video.enums.ContentFit
|
|
21
21
|
import expo.modules.video.player.VideoPlayer
|
|
22
22
|
import expo.modules.video.records.BufferOptions
|
|
23
|
+
import expo.modules.video.records.FullscreenOptions
|
|
23
24
|
import expo.modules.video.records.SubtitleTrack
|
|
24
25
|
import expo.modules.video.records.AudioTrack
|
|
25
26
|
import expo.modules.video.records.VideoSource
|
|
@@ -381,6 +382,11 @@ private inline fun <reified T : VideoView> ViewDefinitionBuilder<T>.VideoViewCom
|
|
|
381
382
|
Prop("allowsFullscreen") { view: T, allowsFullscreen: Boolean? ->
|
|
382
383
|
view.allowsFullscreen = allowsFullscreen ?: true
|
|
383
384
|
}
|
|
385
|
+
Prop("fullscreenOptions") { view: T, fullscreenOptions: FullscreenOptions? ->
|
|
386
|
+
if (fullscreenOptions != null) {
|
|
387
|
+
view.fullscreenOptions = fullscreenOptions
|
|
388
|
+
}
|
|
389
|
+
}
|
|
384
390
|
Prop("requiresLinearPlayback") { view: T, requiresLinearPlayback: Boolean? ->
|
|
385
391
|
val linearPlayback = requiresLinearPlayback ?: false
|
|
386
392
|
view.playerView.applyRequiresLinearPlayback(linearPlayback)
|
|
@@ -32,6 +32,7 @@ import expo.modules.video.records.AudioTrack
|
|
|
32
32
|
import expo.modules.video.records.SubtitleTrack
|
|
33
33
|
import expo.modules.video.records.VideoSource
|
|
34
34
|
import expo.modules.video.records.VideoTrack
|
|
35
|
+
import expo.modules.video.records.FullscreenOptions
|
|
35
36
|
import expo.modules.video.utils.applyPiPParams
|
|
36
37
|
import expo.modules.video.utils.applyRectHint
|
|
37
38
|
import expo.modules.video.utils.calculatePiPAspectRatio
|
|
@@ -132,6 +133,17 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
132
133
|
field = value
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
var fullscreenOptions: FullscreenOptions = FullscreenOptions()
|
|
137
|
+
set(value) {
|
|
138
|
+
field = value
|
|
139
|
+
if (value.enable) {
|
|
140
|
+
playerView.setFullscreenButtonClickListener { enterFullscreen() }
|
|
141
|
+
} else {
|
|
142
|
+
playerView.setFullscreenButtonClickListener(null)
|
|
143
|
+
playerView.setFullscreenButtonVisibility(false)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
135
147
|
private val mLayoutRunnable = Runnable {
|
|
136
148
|
measure(
|
|
137
149
|
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
@@ -172,6 +184,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
172
184
|
fun enterFullscreen() {
|
|
173
185
|
val intent = Intent(context, FullscreenPlayerActivity::class.java)
|
|
174
186
|
intent.putExtra(VideoManager.INTENT_PLAYER_KEY, videoViewId)
|
|
187
|
+
intent.putExtra(FullscreenPlayerActivity.INTENT_FULLSCREEN_OPTIONS_KEY, fullscreenOptions)
|
|
175
188
|
// Set before starting the activity to avoid entering PiP unintentionally
|
|
176
189
|
isInFullscreen = true
|
|
177
190
|
currentActivity.startActivity(intent)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
package expo.modules.video.enums
|
|
2
|
+
|
|
3
|
+
import android.content.pm.ActivityInfo
|
|
4
|
+
import expo.modules.kotlin.types.Enumerable
|
|
5
|
+
|
|
6
|
+
enum class FullscreenOrientation(val value: String) : Enumerable {
|
|
7
|
+
LANDSCAPE("landscape"),
|
|
8
|
+
PORTRAIT("portrait"),
|
|
9
|
+
LANDSCAPE_LEFT("landscapeLeft"),
|
|
10
|
+
LANDSCAPE_RIGHT("landscapeRight"),
|
|
11
|
+
PORTRAIT_UP("portraitUp"),
|
|
12
|
+
PORTRAIT_DOWN("portraitDown"),
|
|
13
|
+
DEFAULT("default");
|
|
14
|
+
|
|
15
|
+
fun toActivityOrientation(): Int {
|
|
16
|
+
return when (this) {
|
|
17
|
+
LANDSCAPE -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
|
18
|
+
PORTRAIT -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
|
19
|
+
LANDSCAPE_LEFT -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
|
20
|
+
LANDSCAPE_RIGHT -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
|
21
|
+
PORTRAIT_UP -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
|
22
|
+
PORTRAIT_DOWN -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
|
|
23
|
+
DEFAULT -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
package expo.modules.video.records
|
|
2
|
+
|
|
3
|
+
import expo.modules.kotlin.records.Field
|
|
4
|
+
import expo.modules.kotlin.records.Record
|
|
5
|
+
import expo.modules.video.enums.FullscreenOrientation
|
|
6
|
+
import java.io.Serializable
|
|
7
|
+
|
|
8
|
+
data class FullscreenOptions(
|
|
9
|
+
@Field val enable: Boolean = true,
|
|
10
|
+
@Field val orientation: FullscreenOrientation = FullscreenOrientation.DEFAULT,
|
|
11
|
+
@Field val autoExitOnRotate: Boolean = false
|
|
12
|
+
) : Record, Serializable
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
package expo.modules.video.utils
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.res.Configuration
|
|
5
|
+
import android.hardware.SensorManager
|
|
6
|
+
import android.provider.Settings
|
|
7
|
+
import android.view.OrientationEventListener
|
|
8
|
+
import expo.modules.video.enums.FullscreenOrientation
|
|
9
|
+
import expo.modules.video.records.FullscreenOptions
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Helper for the auto-exit fullscreen functionality. Once the user has rotated the phone to the desired orientation, the orientation lock should be released, so that once rotation to a perpendicular orientation is detected, the fullscreen can be exited.
|
|
13
|
+
*/
|
|
14
|
+
class FullscreenActivityOrientationHelper(val context: Context, val options: FullscreenOptions, val onShouldAutoExit: (() -> Unit), val onShouldReleaseOrientation: (() -> Unit)) {
|
|
15
|
+
private var userHasRotatedToVideoOrientation = false
|
|
16
|
+
private val isLockedToLandscape = options.orientation == FullscreenOrientation.LANDSCAPE ||
|
|
17
|
+
options.orientation == FullscreenOrientation.LANDSCAPE_LEFT ||
|
|
18
|
+
options.orientation == FullscreenOrientation.LANDSCAPE_RIGHT
|
|
19
|
+
|
|
20
|
+
private val isLockedToPortrait = options.orientation == FullscreenOrientation.PORTRAIT ||
|
|
21
|
+
options.orientation == FullscreenOrientation.PORTRAIT_UP ||
|
|
22
|
+
options.orientation == FullscreenOrientation.PORTRAIT_DOWN
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Checks if the system's auto-rotation setting is currently enabled.
|
|
26
|
+
* Returns true if auto-rotation is unlocked (enabled), false otherwise (locked or error).
|
|
27
|
+
*/
|
|
28
|
+
val isAutoRotationEnabled: Boolean
|
|
29
|
+
get() {
|
|
30
|
+
return try {
|
|
31
|
+
val rotationStatus = Settings.System.getInt(
|
|
32
|
+
context.contentResolver,
|
|
33
|
+
Settings.System.ACCELEROMETER_ROTATION,
|
|
34
|
+
0
|
|
35
|
+
)
|
|
36
|
+
rotationStatus == 1
|
|
37
|
+
} catch (e: Exception) {
|
|
38
|
+
false
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Orientation listener running while the activity orientation is locked. The goal of the listener is to detect if the user has rotated the phone to the desired orientation.
|
|
43
|
+
Once they have done that auto-exit can be activated. That's when we can disable the lock and wait for the device to be rotated to portrait.
|
|
44
|
+
When the screen starts rotating we receive a configuration change and can send a signal to exit fullscreen.
|
|
45
|
+
It's better to unlock and wait for config change instead of trying to detect orientation based on angles, because the angles update faster than the phone rotation.
|
|
46
|
+
*/
|
|
47
|
+
private val orientationEventListener by lazy {
|
|
48
|
+
object : OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) {
|
|
49
|
+
override fun onOrientationChanged(orientation: Int) {
|
|
50
|
+
// Use narrower ranges to determine the orientation. Using a 90 degree range is too sensitive to small tilts.
|
|
51
|
+
val newPhysicalOrientation = when {
|
|
52
|
+
(orientation >= 0 && orientation <= 10) || (orientation >= 350 && orientation < 360) -> {
|
|
53
|
+
Configuration.ORIENTATION_PORTRAIT
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
(orientation >= 80 && orientation <= 100) -> {
|
|
57
|
+
Configuration.ORIENTATION_LANDSCAPE
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
(orientation >= 170 && orientation <= 190) -> {
|
|
61
|
+
Configuration.ORIENTATION_PORTRAIT
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
(orientation >= 260 && orientation <= 280) -> {
|
|
65
|
+
Configuration.ORIENTATION_LANDSCAPE
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
else -> {
|
|
69
|
+
Configuration.ORIENTATION_UNDEFINED
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!options.autoExitOnRotate) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
val canReleaseFromLandscape = newPhysicalOrientation == Configuration.ORIENTATION_PORTRAIT && isLockedToLandscape && userHasRotatedToVideoOrientation
|
|
78
|
+
val canReleaseFromPortrait = newPhysicalOrientation == Configuration.ORIENTATION_LANDSCAPE && isLockedToPortrait && userHasRotatedToVideoOrientation
|
|
79
|
+
|
|
80
|
+
if (canReleaseFromPortrait || canReleaseFromLandscape) {
|
|
81
|
+
if (!isAutoRotationEnabled) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
onShouldReleaseOrientation()
|
|
85
|
+
this@FullscreenActivityOrientationHelper.stopOrientationEventListener()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
val hasRotatedToVideoOrientationPortrait = newPhysicalOrientation == Configuration.ORIENTATION_PORTRAIT && isLockedToPortrait && !userHasRotatedToVideoOrientation
|
|
89
|
+
val hasRotatedToVideoOrientationLandscape = newPhysicalOrientation == Configuration.ORIENTATION_LANDSCAPE && isLockedToLandscape && !userHasRotatedToVideoOrientation
|
|
90
|
+
|
|
91
|
+
if (hasRotatedToVideoOrientationPortrait || hasRotatedToVideoOrientationLandscape) {
|
|
92
|
+
userHasRotatedToVideoOrientation = true
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fun onConfigurationChanged(newConfig: Configuration) {
|
|
99
|
+
val orientation = newConfig.orientation
|
|
100
|
+
if (!options.autoExitOnRotate) {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (isLockedToPortrait && orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
|
105
|
+
onShouldAutoExit()
|
|
106
|
+
} else if (isLockedToLandscape && orientation == Configuration.ORIENTATION_PORTRAIT) {
|
|
107
|
+
onShouldAutoExit()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fun startOrientationEventListener() {
|
|
112
|
+
if (orientationEventListener.canDetectOrientation()) {
|
|
113
|
+
orientationEventListener.enable()
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fun stopOrientationEventListener() {
|
|
118
|
+
orientationEventListener.disable()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoModule.d.ts","sourceRoot":"","sources":["../src/VideoModule.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VideoModule.d.ts","sourceRoot":"","sources":["../src/VideoModule.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAOD,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAsBtE;AAED,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEjG;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEhE"}
|
package/build/VideoModule.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import NativeVideoModule from './NativeVideoModule';
|
|
2
|
+
const globalCacheOperations = new Map(); // stores the promises that are currently caching files
|
|
2
3
|
/**
|
|
3
4
|
* Returns whether the current device supports Picture in Picture (PiP) mode.
|
|
4
5
|
*
|
|
@@ -41,8 +42,30 @@ export function setVideoCacheSizeAsync(sizeBytes) {
|
|
|
41
42
|
export function getCurrentVideoCacheSize() {
|
|
42
43
|
return NativeVideoModule.getCurrentVideoCacheSize();
|
|
43
44
|
}
|
|
45
|
+
// export async function preCacheVideoAsync(url: string): Promise<boolean> {
|
|
46
|
+
// return NativeVideoModule.preCacheVideoAsync(url);
|
|
47
|
+
// }
|
|
48
|
+
// Modify the existing function
|
|
44
49
|
export async function preCacheVideoAsync(url) {
|
|
45
|
-
return
|
|
50
|
+
// If already caching, return the existing promise
|
|
51
|
+
if (globalCacheOperations.has(url)) {
|
|
52
|
+
return await globalCacheOperations.get(url);
|
|
53
|
+
}
|
|
54
|
+
// Start new caching operation with proper error handling
|
|
55
|
+
const cachingPromise = NativeVideoModule.preCacheVideoAsync(url).catch((error) => {
|
|
56
|
+
// Ensure cleanup happens even on native errors
|
|
57
|
+
globalCacheOperations.delete(url);
|
|
58
|
+
throw error;
|
|
59
|
+
});
|
|
60
|
+
globalCacheOperations.set(url, cachingPromise);
|
|
61
|
+
try {
|
|
62
|
+
const result = await cachingPromise;
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
// Always cleanup, even if the promise was already cleaned up in catch
|
|
67
|
+
globalCacheOperations.delete(url);
|
|
68
|
+
}
|
|
46
69
|
}
|
|
47
70
|
export async function preCacheVideoPartialAsync(url, chunkSize) {
|
|
48
71
|
return NativeVideoModule.preCacheVideoPartialAsync(url, chunkSize);
|
package/build/VideoModule.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoModule.js","sourceRoot":"","sources":["../src/VideoModule.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B;IACzC,OAAO,iBAAiB,CAAC,2BAA2B,EAAE,CAAC;AACzD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,iBAAiB,CAAC,oBAAoB,EAAE,CAAC;AAClD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAAiB;IACtD,OAAO,iBAAiB,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO,iBAAiB,CAAC,wBAAwB,EAAE,CAAC;AACtD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAW;IAClD,OAAO,iBAAiB,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"VideoModule.js","sourceRoot":"","sources":["../src/VideoModule.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AAEpD,MAAM,qBAAqB,GAAG,IAAI,GAAG,EAA4B,CAAC,CAAC,uDAAuD;AAE1H;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B;IACzC,OAAO,iBAAiB,CAAC,2BAA2B,EAAE,CAAC;AACzD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,iBAAiB,CAAC,oBAAoB,EAAE,CAAC;AAClD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAAiB;IACtD,OAAO,iBAAiB,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO,iBAAiB,CAAC,wBAAwB,EAAE,CAAC;AACtD,CAAC;AAED,4EAA4E;AAC5E,sDAAsD;AACtD,IAAI;AAEJ,+BAA+B;AAC/B,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAW;IAClD,kDAAkD;IAClD,IAAI,qBAAqB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,OAAO,MAAM,qBAAqB,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;IAC/C,CAAC;IAED,yDAAyD;IACzD,MAAM,cAAc,GAAG,iBAAiB,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QAC/E,+CAA+C;QAC/C,qBAAqB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,qBAAqB,CAAC,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IAE/C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC;QACpC,OAAO,MAAM,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,sEAAsE;QACtE,qBAAqB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,GAAW,EAAE,SAAkB;IAC7E,OAAO,iBAAiB,CAAC,yBAAyB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,OAAO,iBAAiB,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;AACnD,CAAC","sourcesContent":["import NativeVideoModule from './NativeVideoModule';\n\nconst globalCacheOperations = new Map<string, Promise<boolean>>(); // stores the promises that are currently caching files\n\n/**\n * Returns whether the current device supports Picture in Picture (PiP) mode.\n *\n * @returns A `boolean` which is `true` if the device supports PiP mode, and `false` otherwise.\n * @platform android\n * @platform ios\n */\nexport function isPictureInPictureSupported(): boolean {\n return NativeVideoModule.isPictureInPictureSupported();\n}\n\n/**\n * Clears all video cache.\n * > This function can be called only if there are no existing `VideoPlayer` instances.\n *\n * @returns A promise that fulfills after the cache has been cleaned.\n * @platform android\n * @platform ios\n */\nexport function clearVideoCacheAsync(): Promise<void> {\n return NativeVideoModule.clearVideoCacheAsync();\n}\n\n/**\n * Sets desired video cache size in bytes. The default video cache size is 1GB. Value set by this function is persistent.\n * The cache size is not guaranteed to be exact and the actual cache size may be slightly larger. The cache is evicted on a least-recently-used basis.\n * > This function can be called only if there are no existing `VideoPlayer` instances.\n *\n * @returns A promise that fulfills after the cache size has been set.\n * @platform android\n * @platform ios\n */\nexport function setVideoCacheSizeAsync(sizeBytes: number): Promise<void> {\n return NativeVideoModule.setVideoCacheSizeAsync(sizeBytes);\n}\n\n/**\n * Returns the space currently occupied by the video cache in bytes.\n *\n * @platform android\n * @platform ios\n */\nexport function getCurrentVideoCacheSize(): number {\n return NativeVideoModule.getCurrentVideoCacheSize();\n}\n\n// export async function preCacheVideoAsync(url: string): Promise<boolean> {\n// return NativeVideoModule.preCacheVideoAsync(url);\n// }\n\n// Modify the existing function\nexport async function preCacheVideoAsync(url: string): Promise<boolean> {\n // If already caching, return the existing promise\n if (globalCacheOperations.has(url)) {\n return await globalCacheOperations.get(url)!;\n }\n\n // Start new caching operation with proper error handling\n const cachingPromise = NativeVideoModule.preCacheVideoAsync(url).catch((error) => {\n // Ensure cleanup happens even on native errors\n globalCacheOperations.delete(url);\n throw error;\n });\n \n globalCacheOperations.set(url, cachingPromise);\n\n try {\n const result = await cachingPromise;\n return result;\n } finally {\n // Always cleanup, even if the promise was already cleaned up in catch\n globalCacheOperations.delete(url);\n }\n}\n\nexport async function preCacheVideoPartialAsync(url: string, chunkSize?: number): Promise<boolean> {\n return NativeVideoModule.preCacheVideoPartialAsync(url, chunkSize);\n}\n\nexport function isVideoCachedAsync(url: string): Promise<boolean> {\n return NativeVideoModule.isVideoCachedAsync(url);\n}\n"]}
|
package/build/VideoView.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoView.d.ts","sourceRoot":"","sources":["../src/VideoView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAa,MAAM,OAAO,CAAC;AAK5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAED,qBAAa,SAAU,SAAQ,aAAa,CAAC,cAAc,CAAC;IAC1D,SAAS,iCAAoB;IAE7B;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAItC;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrC;;;;;;;;;OASG;IACG,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5C;;;;;OAKG;IACG,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3C,MAAM,IAAI,SAAS;
|
|
1
|
+
{"version":3,"file":"VideoView.d.ts","sourceRoot":"","sources":["../src/VideoView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAa,MAAM,OAAO,CAAC;AAK5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAED,qBAAa,SAAU,SAAQ,aAAa,CAAC,cAAc,CAAC;IAC1D,SAAS,iCAAoB;IAE7B;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAItC;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrC;;;;;;;;;OASG;IACG,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5C;;;;;OAKG;IACG,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3C,MAAM,IAAI,SAAS;CAkCpB"}
|
package/build/VideoView.js
CHANGED
|
@@ -52,12 +52,19 @@ export class VideoView extends PureComponent {
|
|
|
52
52
|
return await this.nativeRef.current?.stopPictureInPicture();
|
|
53
53
|
}
|
|
54
54
|
render() {
|
|
55
|
-
const { player, ...props } = this.props;
|
|
55
|
+
const { player, allowsFullscreen, ...props } = this.props;
|
|
56
56
|
const playerId = getPlayerId(player);
|
|
57
|
+
if (allowsFullscreen !== undefined) {
|
|
58
|
+
console.warn('The `allowsFullscreen` prop is deprecated and will be removed in a future release. Use `fullscreenOptions` prop instead.');
|
|
59
|
+
}
|
|
60
|
+
const fullscreenOptions = {
|
|
61
|
+
enable: allowsFullscreen,
|
|
62
|
+
...props.fullscreenOptions,
|
|
63
|
+
};
|
|
57
64
|
if (NativeTextureVideoView && this.props.surfaceType === 'textureView') {
|
|
58
|
-
return _jsx(NativeTextureVideoView, { ...props, player: playerId, ref: this.nativeRef });
|
|
65
|
+
return (_jsx(NativeTextureVideoView, { ...props, fullscreenOptions: fullscreenOptions, player: playerId, ref: this.nativeRef }));
|
|
59
66
|
}
|
|
60
|
-
return _jsx(NativeVideoView, { ...props, player: playerId, ref: this.nativeRef });
|
|
67
|
+
return (_jsx(NativeVideoView, { ...props, fullscreenOptions: fullscreenOptions, player: playerId, ref: this.nativeRef }));
|
|
61
68
|
}
|
|
62
69
|
}
|
|
63
70
|
// Temporary solution to pass the shared object ID instead of the player object.
|
package/build/VideoView.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoView.js","sourceRoot":"","sources":["../src/VideoView.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAa,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,eAAe,EAAE,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAI5E;;;;;;;;;GASG;AACH,MAAM,UAAU,2BAA2B;IACzC,OAAO,iBAAiB,CAAC,2BAA2B,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,OAAO,SAAU,SAAQ,aAA6B;IAC1D,SAAS,GAAG,SAAS,EAAO,CAAC;IAE7B;;OAEG;IACH,KAAK,CAAC,eAAe;QACnB,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC;IACzD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc;QAClB,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;IACxD,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,qBAAqB;QACzB,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,qBAAqB,EAAE,CAAC;IAC/D,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,oBAAoB;QACxB,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAC9D,CAAC;IAED,MAAM;QACJ,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"VideoView.js","sourceRoot":"","sources":["../src/VideoView.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAa,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,eAAe,EAAE,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAI5E;;;;;;;;;GASG;AACH,MAAM,UAAU,2BAA2B;IACzC,OAAO,iBAAiB,CAAC,2BAA2B,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,OAAO,SAAU,SAAQ,aAA6B;IAC1D,SAAS,GAAG,SAAS,EAAO,CAAC;IAE7B;;OAEG;IACH,KAAK,CAAC,eAAe;QACnB,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC;IACzD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc;QAClB,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;IACxD,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,qBAAqB;QACzB,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,qBAAqB,EAAE,CAAC;IAC/D,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,oBAAoB;QACxB,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAC9D,CAAC;IAED,MAAM;QACJ,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QAC1D,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QAErC,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;YACnC,OAAO,CAAC,IAAI,CACV,0HAA0H,CAC3H,CAAC;QACJ,CAAC;QAED,MAAM,iBAAiB,GAAG;YACxB,MAAM,EAAE,gBAAgB;YACxB,GAAG,KAAK,CAAC,iBAAiB;SAC3B,CAAC;QAEF,IAAI,sBAAsB,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,KAAK,aAAa,EAAE,CAAC;YACvE,OAAO,CACL,KAAC,sBAAsB,OACjB,KAAK,EACT,iBAAiB,EAAE,iBAAiB,EACpC,MAAM,EAAE,QAAQ,EAChB,GAAG,EAAE,IAAI,CAAC,SAAS,GACnB,CACH,CAAC;QACJ,CAAC;QACD,OAAO,CACL,KAAC,eAAe,OACV,KAAK,EACT,iBAAiB,EAAE,iBAAiB,EACpC,MAAM,EAAE,QAAQ,EAChB,GAAG,EAAE,IAAI,CAAC,SAAS,GACnB,CACH,CAAC;IACJ,CAAC;CACF;AAED,gFAAgF;AAChF,gEAAgE;AAChE,yEAAyE;AACzE,SAAS,WAAW,CAAC,MAA4B;IAC/C,IAAI,MAAM,YAAY,iBAAiB,CAAC,WAAW,EAAE,CAAC;QACpD,mBAAmB;QACnB,OAAO,MAAM,CAAC,yBAAyB,CAAC;IAC1C,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import { ReactNode, PureComponent, createRef } from 'react';\n\nimport NativeVideoModule from './NativeVideoModule';\nimport NativeVideoView, { NativeTextureVideoView } from './NativeVideoView';\nimport type { VideoPlayer } from './VideoPlayer.types';\nimport type { VideoViewProps } from './VideoView.types';\n\n/**\n * Returns whether the current device supports Picture in Picture (PiP) mode.\n *\n * > **Note:** All major web browsers support Picture in Picture (PiP) mode except Firefox.\n * > For more information, see [MDN web docs](https://developer.mozilla.org/en-US/docs/Web/API/Picture-in-Picture_API#browser_compatibility).\n * @returns A `boolean` which is `true` if the device supports PiP mode, and `false` otherwise.\n * @platform android\n * @platform ios\n * @platform web\n */\nexport function isPictureInPictureSupported(): boolean {\n return NativeVideoModule.isPictureInPictureSupported();\n}\n\nexport class VideoView extends PureComponent<VideoViewProps> {\n nativeRef = createRef<any>();\n\n /**\n * Enters fullscreen mode.\n */\n async enterFullscreen(): Promise<void> {\n return await this.nativeRef.current?.enterFullscreen();\n }\n\n /**\n * Exits fullscreen mode.\n */\n async exitFullscreen(): Promise<void> {\n return await this.nativeRef.current?.exitFullscreen();\n }\n\n /**\n * Enters Picture in Picture (PiP) mode. Throws an exception if the device does not support PiP.\n * > **Note:** Only one player can be in Picture in Picture (PiP) mode at a time.\n *\n * > **Note:** The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config)\n * > has to be configured for the PiP to work.\n * @platform android\n * @platform ios\n * @platform web\n */\n async startPictureInPicture(): Promise<void> {\n return await this.nativeRef.current?.startPictureInPicture();\n }\n\n /**\n * Exits Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios\n * @platform web\n */\n async stopPictureInPicture(): Promise<void> {\n return await this.nativeRef.current?.stopPictureInPicture();\n }\n\n render(): ReactNode {\n const { player, allowsFullscreen, ...props } = this.props;\n const playerId = getPlayerId(player);\n\n if (allowsFullscreen !== undefined) {\n console.warn(\n 'The `allowsFullscreen` prop is deprecated and will be removed in a future release. Use `fullscreenOptions` prop instead.'\n );\n }\n\n const fullscreenOptions = {\n enable: allowsFullscreen,\n ...props.fullscreenOptions,\n };\n\n if (NativeTextureVideoView && this.props.surfaceType === 'textureView') {\n return (\n <NativeTextureVideoView\n {...props}\n fullscreenOptions={fullscreenOptions}\n player={playerId}\n ref={this.nativeRef}\n />\n );\n }\n return (\n <NativeVideoView\n {...props}\n fullscreenOptions={fullscreenOptions}\n player={playerId}\n ref={this.nativeRef}\n />\n );\n }\n}\n\n// Temporary solution to pass the shared object ID instead of the player object.\n// We can't really pass it as an object in the old architecture.\n// Technically we can in the new architecture, but it's not possible yet.\nfunction getPlayerId(player: number | VideoPlayer): number | null {\n if (player instanceof NativeVideoModule.VideoPlayer) {\n // @ts-expect-error\n return player.__expo_shared_object_id__;\n }\n if (typeof player === 'number') {\n return player;\n }\n return null;\n}\n"]}
|
|
@@ -16,6 +16,47 @@ export type VideoContentFit = 'contain' | 'cover' | 'fill';
|
|
|
16
16
|
* @platform android
|
|
17
17
|
*/
|
|
18
18
|
export type SurfaceType = 'textureView' | 'surfaceView';
|
|
19
|
+
/**
|
|
20
|
+
* Describes the orientation of the video in fullscreen mode. Available values are:
|
|
21
|
+
* - `default`: The video is displayed in any of the available device rotations.
|
|
22
|
+
* - `portrait`: The video is displayed in one of two available portrait orientations and rotates between them.
|
|
23
|
+
* - `portraitUp`: The video is displayed in the portrait orientation - the notch of the phone points upwards.
|
|
24
|
+
* - `portraitDown`: The video is displayed in the portrait orientation - the notch of the phone points downwards.
|
|
25
|
+
* - `landscape`: The video is displayed in one of two available landscape orientations and rotates between them.
|
|
26
|
+
* - `landscapeLeft`: The video is displayed in the left landscape orientation - the notch of the phone is in the left palm of the user.
|
|
27
|
+
* - `landscapeRight`: The video is displayed in the right landscape orientation - the notch of the phone is in the right palm of the user.
|
|
28
|
+
*/
|
|
29
|
+
export type FullscreenOrientation = 'default' | 'portrait' | 'portraitUp' | 'portraitDown' | 'landscape' | 'landscapeLeft' | 'landscapeRight';
|
|
30
|
+
/**
|
|
31
|
+
* Describes the options for fullscreen video mode.
|
|
32
|
+
*/
|
|
33
|
+
export type FullscreenOptions = {
|
|
34
|
+
/**
|
|
35
|
+
* Specifies whether the fullscreen mode should be available to the user. When `false`, the fullscreen button will be hidden in the player.
|
|
36
|
+
* Equivalent to the `allowsFullscreen` prop.
|
|
37
|
+
* @default true
|
|
38
|
+
*/
|
|
39
|
+
enable?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Specifies the orientation of the video in fullscreen mode.
|
|
42
|
+
* @default 'default'
|
|
43
|
+
* @platform android
|
|
44
|
+
* @platform ios
|
|
45
|
+
*/
|
|
46
|
+
orientation?: FullscreenOrientation;
|
|
47
|
+
/**
|
|
48
|
+
* Specifies whether the app should exit fullscreen mode when the device is rotated to a different orientation than the one specified in the `orientation` prop.
|
|
49
|
+
* For example, if the `orientation` prop is set to `landscape` and the device is rotated to `portrait`, the app will exit fullscreen mode.
|
|
50
|
+
*
|
|
51
|
+
* > This prop will have no effect if the `orientation` prop is set to `default`.
|
|
52
|
+
* > The `VideoView` will never auto-exit fullscreen when the device auto-rotate feature has been disabled in settings.
|
|
53
|
+
*
|
|
54
|
+
* @default false
|
|
55
|
+
* @platform android
|
|
56
|
+
* @platform ios
|
|
57
|
+
*/
|
|
58
|
+
autoExitOnRotate?: boolean;
|
|
59
|
+
};
|
|
19
60
|
export interface VideoViewProps extends ViewProps {
|
|
20
61
|
/**
|
|
21
62
|
* A video player instance. Use [`useVideoPlayer()`](#usevideoplayersource-setup) hook to create one.
|
|
@@ -34,9 +75,15 @@ export interface VideoViewProps extends ViewProps {
|
|
|
34
75
|
contentFit?: VideoContentFit;
|
|
35
76
|
/**
|
|
36
77
|
* Determines whether fullscreen mode is allowed or not.
|
|
78
|
+
*
|
|
79
|
+
* > Note: This option has been deprecated in favor of the `fullscreenOptions` prop and will be disabled in the future.
|
|
37
80
|
* @default true
|
|
38
81
|
*/
|
|
39
82
|
allowsFullscreen?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Determines the fullscreen mode options.
|
|
85
|
+
*/
|
|
86
|
+
fullscreenOptions?: FullscreenOptions;
|
|
40
87
|
/**
|
|
41
88
|
* Determines whether the timecodes should be displayed or not.
|
|
42
89
|
* @default true
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoView.types.d.ts","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAE3D;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,aAAa,CAAC;AAExD,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C;;OAEG;IACH,MAAM,EAAE,WAAW,CAAC;IAEpB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAE7B
|
|
1
|
+
{"version":3,"file":"VideoView.types.d.ts","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAE3D;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,aAAa,CAAC;AAExD;;;;;;;;;GASG;AACH,MAAM,MAAM,qBAAqB,GAC7B,SAAS,GACT,UAAU,GACV,YAAY,GACZ,cAAc,GACd,WAAW,GACX,eAAe,GACf,gBAAgB,CAAC;AAErB;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C;;OAEG;IACH,MAAM,EAAE,WAAW,CAAC;IAEpB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAE7B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;OAEG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAEtC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B;;;;OAIG;IACH,eAAe,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE/C;;;;;OAKG;IACH,uBAAuB,CAAC,EAAE,MAAM,IAAI,CAAC;IAErC;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IAEpC;;;;;;;OAOG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;;;;;;;OAUG;IACH,mCAAmC,CAAC,EAAE,OAAO,CAAC;IAE9C;;;;;OAKG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAEnC;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAE/B;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAE9B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAEhC;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,WAAW,GAAG,iBAAiB,CAAC;CAC/C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoView.types.js","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"","sourcesContent":["import { ViewProps } from 'react-native';\n\nimport type { VideoPlayer } from './VideoPlayer.types';\n\n/**\n * Describes how a video should be scaled to fit in a container.\n * - `contain`: The video maintains its aspect ratio and fits inside the container, with possible letterboxing/pillarboxing.\n * - `cover`: The video maintains its aspect ratio and covers the entire container, potentially cropping some portions.\n * - `fill`: The video stretches/squeezes to completely fill the container, potentially causing distortion.\n */\nexport type VideoContentFit = 'contain' | 'cover' | 'fill';\n\n/**\n * Describes the type of the surface used to render the video.\n * - `surfaceView`: Uses the `SurfaceView` to render the video. This value should be used in the majority of cases. Provides significantly lower power consumption, better performance, and more features.\n * - `textureView`: Uses the `TextureView` to render the video. Should be used in cases where the SurfaceView is not supported or causes issues (for example, overlapping video views).\n *\n * You can learn more about surface types in the official [ExoPlayer documentation](https://developer.android.com/media/media3/ui/playerview#surfacetype).\n * @platform android\n */\nexport type SurfaceType = 'textureView' | 'surfaceView';\n\nexport interface VideoViewProps extends ViewProps {\n /**\n * A video player instance. Use [`useVideoPlayer()`](#usevideoplayersource-setup) hook to create one.\n */\n player: VideoPlayer;\n\n /**\n * Determines whether native controls should be displayed or not.\n * @default true\n */\n nativeControls?: boolean;\n\n /**\n * Describes how the video should be scaled to fit in the container.\n * Options are `'contain'`, `'cover'`, and `'fill'`.\n * @default 'contain'\n */\n contentFit?: VideoContentFit;\n\n /**\n * Determines whether fullscreen mode is allowed or not.\n * @default true\n */\n allowsFullscreen?: boolean;\n\n /**\n * Determines whether the timecodes should be displayed or not.\n * @default true\n * @platform ios\n */\n showsTimecodes?: boolean;\n\n /**\n * Determines whether the player allows the user to skip media content.\n * @default false\n * @platform android\n * @platform ios\n */\n requiresLinearPlayback?: boolean;\n\n /**\n * Determines the type of the surface used to render the video.\n * > This prop should not be changed at runtime.\n * @default 'surfaceView'\n * @platform android\n */\n surfaceType?: SurfaceType;\n\n /**\n * Determines the position offset of the video inside the container.\n * @default { dx: 0, dy: 0 }\n * @platform ios\n */\n contentPosition?: { dx?: number; dy?: number };\n\n /**\n * A callback to call after the video player enters Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios\n * @platform web\n */\n onPictureInPictureStart?: () => void;\n\n /**\n * A callback to call after the video player exits Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios\n * @platform web\n */\n onPictureInPictureStop?: () => void;\n\n /**\n * Determines whether the player allows Picture in Picture (PiP) mode.\n * > **Note:** The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config)\n * > has to be configured for the PiP to work.\n * @platform android\n * @platform ios\n * @platform web\n */\n allowsPictureInPicture?: boolean;\n\n /**\n * Determines whether a video should be played \"inline\", that is, within the element's playback area.\n * @platform web\n */\n playsInline?: boolean;\n\n /**\n * Determines whether the player should start Picture in Picture (PiP) automatically when the app is in the background.\n * > **Note:** Only one player can be in Picture in Picture (PiP) mode at a time.\n *\n * > **Note:** The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config)\n * > has to be configured for the PiP to work.\n *\n * @default false\n * @platform android 12+\n * @platform ios\n */\n startsPictureInPictureAutomatically?: boolean;\n\n /**\n * Specifies whether to perform video frame analysis (Live Text in videos).\n * Check official [Apple documentation](https://developer.apple.com/documentation/avkit/avplayerviewcontroller/allowsvideoframeanalysis) for more details.\n * @default true\n * @platform ios 16.0+\n */\n allowsVideoFrameAnalysis?: boolean;\n\n /**\n * A callback to call after the video player enters fullscreen mode.\n */\n onFullscreenEnter?: () => void;\n\n /**\n * A callback to call after the video player exits fullscreen mode.\n */\n onFullscreenExit?: () => void;\n\n /**\n * A callback to call after the mounted `VideoPlayer` has rendered the first frame into the `VideoView`.\n * This event can be used to hide any cover images that conceal the initial loading of the player.\n * > **Note:** This event may also be called during playback when the current video track changes (for example when the player switches video quality).\n */\n onFirstFrameRender?: () => void;\n\n /**\n * Determines whether the player should use the default ExoPlayer shutter that covers the `VideoView` before the first video frame is rendered.\n * Setting this property to `false` makes the Android behavior the same as iOS.\n *\n * @platform android\n * @default false\n */\n useExoShutter?: boolean;\n\n /**\n * Determines the [cross origin policy](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/crossorigin) used by the underlying native view on web.\n * If undefined, does not use CORS at all.\n *\n * @platform web\n * @default undefined\n */\n crossOrigin?: 'anonymous' | 'use-credentials';\n}\n"]}
|
|
1
|
+
{"version":3,"file":"VideoView.types.js","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"","sourcesContent":["import { ViewProps } from 'react-native';\n\nimport type { VideoPlayer } from './VideoPlayer.types';\n\n/**\n * Describes how a video should be scaled to fit in a container.\n * - `contain`: The video maintains its aspect ratio and fits inside the container, with possible letterboxing/pillarboxing.\n * - `cover`: The video maintains its aspect ratio and covers the entire container, potentially cropping some portions.\n * - `fill`: The video stretches/squeezes to completely fill the container, potentially causing distortion.\n */\nexport type VideoContentFit = 'contain' | 'cover' | 'fill';\n\n/**\n * Describes the type of the surface used to render the video.\n * - `surfaceView`: Uses the `SurfaceView` to render the video. This value should be used in the majority of cases. Provides significantly lower power consumption, better performance, and more features.\n * - `textureView`: Uses the `TextureView` to render the video. Should be used in cases where the SurfaceView is not supported or causes issues (for example, overlapping video views).\n *\n * You can learn more about surface types in the official [ExoPlayer documentation](https://developer.android.com/media/media3/ui/playerview#surfacetype).\n * @platform android\n */\nexport type SurfaceType = 'textureView' | 'surfaceView';\n\n/**\n * Describes the orientation of the video in fullscreen mode. Available values are:\n * - `default`: The video is displayed in any of the available device rotations.\n * - `portrait`: The video is displayed in one of two available portrait orientations and rotates between them.\n * - `portraitUp`: The video is displayed in the portrait orientation - the notch of the phone points upwards.\n * - `portraitDown`: The video is displayed in the portrait orientation - the notch of the phone points downwards.\n * - `landscape`: The video is displayed in one of two available landscape orientations and rotates between them.\n * - `landscapeLeft`: The video is displayed in the left landscape orientation - the notch of the phone is in the left palm of the user.\n * - `landscapeRight`: The video is displayed in the right landscape orientation - the notch of the phone is in the right palm of the user.\n */\nexport type FullscreenOrientation =\n | 'default'\n | 'portrait'\n | 'portraitUp'\n | 'portraitDown'\n | 'landscape'\n | 'landscapeLeft'\n | 'landscapeRight';\n\n/**\n * Describes the options for fullscreen video mode.\n */\nexport type FullscreenOptions = {\n /**\n * Specifies whether the fullscreen mode should be available to the user. When `false`, the fullscreen button will be hidden in the player.\n * Equivalent to the `allowsFullscreen` prop.\n * @default true\n */\n enable?: boolean;\n /**\n * Specifies the orientation of the video in fullscreen mode.\n * @default 'default'\n * @platform android\n * @platform ios\n */\n orientation?: FullscreenOrientation;\n /**\n * Specifies whether the app should exit fullscreen mode when the device is rotated to a different orientation than the one specified in the `orientation` prop.\n * For example, if the `orientation` prop is set to `landscape` and the device is rotated to `portrait`, the app will exit fullscreen mode.\n *\n * > This prop will have no effect if the `orientation` prop is set to `default`.\n * > The `VideoView` will never auto-exit fullscreen when the device auto-rotate feature has been disabled in settings.\n *\n * @default false\n * @platform android\n * @platform ios\n */\n autoExitOnRotate?: boolean;\n};\n\nexport interface VideoViewProps extends ViewProps {\n /**\n * A video player instance. Use [`useVideoPlayer()`](#usevideoplayersource-setup) hook to create one.\n */\n player: VideoPlayer;\n\n /**\n * Determines whether native controls should be displayed or not.\n * @default true\n */\n nativeControls?: boolean;\n\n /**\n * Describes how the video should be scaled to fit in the container.\n * Options are `'contain'`, `'cover'`, and `'fill'`.\n * @default 'contain'\n */\n contentFit?: VideoContentFit;\n\n /**\n * Determines whether fullscreen mode is allowed or not.\n *\n * > Note: This option has been deprecated in favor of the `fullscreenOptions` prop and will be disabled in the future.\n * @default true\n */\n allowsFullscreen?: boolean;\n\n /**\n * Determines the fullscreen mode options.\n */\n fullscreenOptions?: FullscreenOptions;\n\n /**\n * Determines whether the timecodes should be displayed or not.\n * @default true\n * @platform ios\n */\n showsTimecodes?: boolean;\n\n /**\n * Determines whether the player allows the user to skip media content.\n * @default false\n * @platform android\n * @platform ios\n */\n requiresLinearPlayback?: boolean;\n\n /**\n * Determines the type of the surface used to render the video.\n * > This prop should not be changed at runtime.\n * @default 'surfaceView'\n * @platform android\n */\n surfaceType?: SurfaceType;\n\n /**\n * Determines the position offset of the video inside the container.\n * @default { dx: 0, dy: 0 }\n * @platform ios\n */\n contentPosition?: { dx?: number; dy?: number };\n\n /**\n * A callback to call after the video player enters Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios\n * @platform web\n */\n onPictureInPictureStart?: () => void;\n\n /**\n * A callback to call after the video player exits Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios\n * @platform web\n */\n onPictureInPictureStop?: () => void;\n\n /**\n * Determines whether the player allows Picture in Picture (PiP) mode.\n * > **Note:** The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config)\n * > has to be configured for the PiP to work.\n * @platform android\n * @platform ios\n * @platform web\n */\n allowsPictureInPicture?: boolean;\n\n /**\n * Determines whether a video should be played \"inline\", that is, within the element's playback area.\n * @platform web\n */\n playsInline?: boolean;\n\n /**\n * Determines whether the player should start Picture in Picture (PiP) automatically when the app is in the background.\n * > **Note:** Only one player can be in Picture in Picture (PiP) mode at a time.\n *\n * > **Note:** The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config)\n * > has to be configured for the PiP to work.\n *\n * @default false\n * @platform android 12+\n * @platform ios\n */\n startsPictureInPictureAutomatically?: boolean;\n\n /**\n * Specifies whether to perform video frame analysis (Live Text in videos).\n * Check official [Apple documentation](https://developer.apple.com/documentation/avkit/avplayerviewcontroller/allowsvideoframeanalysis) for more details.\n * @default true\n * @platform ios 16.0+\n */\n allowsVideoFrameAnalysis?: boolean;\n\n /**\n * A callback to call after the video player enters fullscreen mode.\n */\n onFullscreenEnter?: () => void;\n\n /**\n * A callback to call after the video player exits fullscreen mode.\n */\n onFullscreenExit?: () => void;\n\n /**\n * A callback to call after the mounted `VideoPlayer` has rendered the first frame into the `VideoView`.\n * This event can be used to hide any cover images that conceal the initial loading of the player.\n * > **Note:** This event may also be called during playback when the current video track changes (for example when the player switches video quality).\n */\n onFirstFrameRender?: () => void;\n\n /**\n * Determines whether the player should use the default ExoPlayer shutter that covers the `VideoView` before the first video frame is rendered.\n * Setting this property to `false` makes the Android behavior the same as iOS.\n *\n * @platform android\n * @default false\n */\n useExoShutter?: boolean;\n\n /**\n * Determines the [cross origin policy](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/crossorigin) used by the underlying native view on web.\n * If undefined, does not use CORS at all.\n *\n * @platform web\n * @default undefined\n */\n crossOrigin?: 'anonymous' | 'use-credentials';\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoView.web.d.ts","sourceRoot":"","sources":["../src/VideoView.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6D,MAAM,OAAO,CAAC;AAGlF,OAAO,WAA6B,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAsBxD,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAED,eAAO,MAAM,SAAS;aAAiC,WAAW;
|
|
1
|
+
{"version":3,"file":"VideoView.web.d.ts","sourceRoot":"","sources":["../src/VideoView.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6D,MAAM,OAAO,CAAC;AAGlF,OAAO,WAA6B,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAsBxD,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAED,eAAO,MAAM,SAAS;aAAiC,WAAW;kDAuLhE,CAAC;AAEH,eAAe,SAAS,CAAC"}
|
package/build/VideoView.web.js
CHANGED
|
@@ -23,6 +23,7 @@ export function isPictureInPictureSupported() {
|
|
|
23
23
|
}
|
|
24
24
|
export const VideoView = forwardRef((props, ref) => {
|
|
25
25
|
const videoRef = useRef(null);
|
|
26
|
+
const fullscreenEnabled = props.fullscreenOptions?.enable ?? props.allowsFullscreen ?? true;
|
|
26
27
|
const mediaNodeRef = useRef(null);
|
|
27
28
|
const hasToSetupAudioContext = useRef(false);
|
|
28
29
|
const fullscreenChangeListener = useRef(null);
|
|
@@ -37,7 +38,7 @@ export const VideoView = forwardRef((props, ref) => {
|
|
|
37
38
|
const zeroGainNodeRef = useRef(null);
|
|
38
39
|
useImperativeHandle(ref, () => ({
|
|
39
40
|
enterFullscreen: async () => {
|
|
40
|
-
if (!
|
|
41
|
+
if (!fullscreenEnabled) {
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
43
44
|
await videoRef.current?.requestFullscreen();
|
|
@@ -157,7 +158,7 @@ export const VideoView = forwardRef((props, ref) => {
|
|
|
157
158
|
detachAudioNodes();
|
|
158
159
|
};
|
|
159
160
|
}, [props.player]);
|
|
160
|
-
return (_jsx("video", { controls: props.nativeControls ?? true, controlsList:
|
|
161
|
+
return (_jsx("video", { controls: props.nativeControls ?? true, controlsList: fullscreenEnabled ? undefined : 'nofullscreen', crossOrigin: props.crossOrigin, style: {
|
|
161
162
|
...mapStyles(props.style),
|
|
162
163
|
objectFit: props.contentFit,
|
|
163
164
|
}, onPlay: () => {
|