expo-video 2.2.1 → 2.3.0-canary-20250713-8f814f8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -2
- package/android/build.gradle +2 -2
- package/android/src/main/AndroidManifest.xml +2 -2
- package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +51 -5
- package/android/src/main/java/expo/modules/video/VideoExceptions.kt +3 -0
- package/android/src/main/java/expo/modules/video/VideoModule.kt +15 -1
- package/android/src/main/java/expo/modules/video/VideoView.kt +48 -31
- package/android/src/main/java/expo/modules/video/enums/FullscreenOrientation.kt +25 -0
- package/android/src/main/java/expo/modules/video/player/PlayerEvent.kt +2 -1
- package/android/src/main/java/expo/modules/video/player/VideoPlayerListener.kt +3 -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/android/src/main/java/expo/modules/video/utils/PictureInPictureUtils.kt +36 -3
- package/android/src/main/res/layout/fullscreen_player_activity.xml +2 -1
- package/build/NativeVideoAirPlayButtonView.d.ts +3 -0
- package/build/NativeVideoAirPlayButtonView.d.ts.map +1 -0
- package/build/NativeVideoAirPlayButtonView.js +3 -0
- package/build/NativeVideoAirPlayButtonView.js.map +1 -0
- package/build/NativeVideoModule.web.d.ts +3 -1
- package/build/NativeVideoModule.web.d.ts.map +1 -1
- package/build/NativeVideoModule.web.js +4 -1
- package/build/NativeVideoModule.web.js.map +1 -1
- package/build/VideoAirPlayButton.d.ts +10 -0
- package/build/VideoAirPlayButton.d.ts.map +1 -0
- package/build/VideoAirPlayButton.ios.d.ts +3 -0
- package/build/VideoAirPlayButton.ios.d.ts.map +1 -0
- package/build/VideoAirPlayButton.ios.js +5 -0
- package/build/VideoAirPlayButton.ios.js.map +1 -0
- package/build/VideoAirPlayButton.js +12 -0
- package/build/VideoAirPlayButton.js.map +1 -0
- package/build/VideoAirPlayButton.types.d.ts +35 -0
- package/build/VideoAirPlayButton.types.d.ts.map +1 -0
- package/build/VideoAirPlayButton.types.js +2 -0
- package/build/VideoAirPlayButton.types.js.map +1 -0
- package/build/VideoPlayer.types.d.ts +6 -0
- package/build/VideoPlayer.types.d.ts.map +1 -1
- package/build/VideoPlayer.types.js.map +1 -1
- package/build/VideoPlayer.web.d.ts +1 -0
- package/build/VideoPlayer.web.d.ts.map +1 -1
- package/build/VideoPlayer.web.js +1 -0
- package/build/VideoPlayer.web.js.map +1 -1
- package/build/VideoPlayerEvents.types.d.ts +16 -0
- package/build/VideoPlayerEvents.types.d.ts.map +1 -1
- package/build/VideoPlayerEvents.types.js.map +1 -1
- package/build/VideoView.d.ts.map +1 -1
- package/build/VideoView.js +3 -0
- 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/index.d.ts +6 -4
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -2
- package/build/index.js.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/Enums/FullscreenOrientation.swift +32 -0
- package/ios/ExpoVideo.podspec +1 -1
- package/ios/OrientationAVPlayerViewController.swift +215 -0
- package/ios/Records/FullscreenOptions.swift +12 -0
- package/ios/Records/VideoEventPayloads.swift +5 -0
- package/ios/VideoAirPlayButtonView.swift +70 -0
- package/ios/VideoManager.swift +2 -2
- package/ios/VideoModule.swift +31 -0
- package/ios/VideoPlayer.swift +9 -1
- package/ios/VideoPlayerItem.swift +5 -1
- package/ios/VideoPlayerObserver.swift +26 -0
- package/ios/VideoView.swift +13 -46
- package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.2.1/expo.modules.video-2.2.1-sources.jar → 2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8-sources.jar} +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8-sources.jar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8-sources.jar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8-sources.jar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8-sources.jar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.aar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.aar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.aar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.aar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.2.1/expo.modules.video-2.2.1.module → 2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.module} +24 -24
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.module.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.module.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.module.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.module.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.2.1/expo.modules.video-2.2.1.pom → 2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.pom} +2 -2
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.pom.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.pom.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.pom.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.3.0-canary-20250713-8f814f8/expo.modules.video-2.3.0-canary-20250713-8f814f8.pom.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml +4 -4
- package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/maven-metadata.xml.sha512 +1 -1
- package/package.json +4 -5
- package/src/NativeVideoAirPlayButtonView.ts +3 -0
- package/src/NativeVideoModule.web.ts +4 -1
- package/src/VideoAirPlayButton.ios.tsx +8 -0
- package/src/VideoAirPlayButton.tsx +14 -0
- package/src/VideoAirPlayButton.types.ts +39 -0
- package/src/VideoPlayer.types.ts +7 -0
- package/src/VideoPlayer.web.tsx +1 -0
- package/src/VideoPlayerEvents.types.ts +19 -0
- package/src/VideoView.tsx +6 -0
- package/src/VideoView.types.ts +57 -0
- package/src/index.ts +6 -14
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1-sources.jar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1-sources.jar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1-sources.jar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1-sources.jar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.aar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.aar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.aar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.aar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.module.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.module.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.module.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.module.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.pom.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.pom.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.pom.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.2.1/expo.modules.video-2.2.1.pom.sha512 +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,15 +6,29 @@
|
|
|
6
6
|
|
|
7
7
|
### 🎉 New features
|
|
8
8
|
|
|
9
|
+
- [iOS] Add complete support for AirPlay streaming. Add a device selection button and `VideoPlayer.isExternalPlaybackActive` property and appropriate listeners. ([#37207](https://github.com/expo/expo/pull/37207) by [@behenate](https://github.com/behenate))
|
|
10
|
+
- Add fullscreen orientation and auto-exit functionality. ([#36910](https://github.com/expo/expo/pull/36910) by [@behenate](https://github.com/behenate))
|
|
11
|
+
|
|
9
12
|
### 🐛 Bug fixes
|
|
10
13
|
|
|
14
|
+
- [Android] Fix accessing player.loop causes app to crash. ([#37928](https://github.com/expo/expo/pull/37928) by [@Wenszel](https://github.com/Wenszel))
|
|
15
|
+
- [iOS] Setting `player.currentTime` doesn't seek to the correct time on some videos. ([#37672](https://github.com/expo/expo/pull/37300) by [@petrkonecny2](https://github.com/petrkonecny2))
|
|
16
|
+
|
|
11
17
|
### 💡 Others
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
- Export types using the `export type` syntax. ([#37396](https://github.com/expo/expo/pull/37396) by [@behenate](https://github.com/behenate))
|
|
20
|
+
|
|
21
|
+
## 2.2.2 - 2025-06-18
|
|
22
|
+
|
|
23
|
+
### 🐛 Bug fixes
|
|
24
|
+
|
|
25
|
+
- [Android] Fix aspect ratio of the Picture in Picture window when auto-entering for sources with ratio different from 16:9. ([#37225](https://github.com/expo/expo/pull/37225) by [@behenate](https://github.com/behenate))
|
|
26
|
+
|
|
27
|
+
## 2.2.1 - 2025-06-10
|
|
14
28
|
|
|
15
29
|
_This version does not introduce any user-facing changes._
|
|
16
30
|
|
|
17
|
-
## 2.2.0
|
|
31
|
+
## 2.2.0 - 2025-06-04
|
|
18
32
|
|
|
19
33
|
### 🎉 New features
|
|
20
34
|
|
package/android/build.gradle
CHANGED
|
@@ -4,13 +4,13 @@ plugins {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
group = 'host.exp.exponent'
|
|
7
|
-
version = '2.
|
|
7
|
+
version = '2.3.0-canary-20250713-8f814f8'
|
|
8
8
|
|
|
9
9
|
android {
|
|
10
10
|
namespace "expo.modules.video"
|
|
11
11
|
defaultConfig {
|
|
12
12
|
versionCode 1
|
|
13
|
-
versionName '2.
|
|
13
|
+
versionName '2.3.0-canary-20250713-8f814f8'
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<application>
|
|
7
7
|
<activity android:name=".FullscreenPlayerActivity"
|
|
8
8
|
android:supportsPictureInPicture="true"
|
|
9
|
-
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
|
10
|
-
android:theme="@style/Fullscreen"/>
|
|
9
|
+
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|navigation"
|
|
10
|
+
android:theme="@style/Fullscreen" />
|
|
11
11
|
<service
|
|
12
12
|
android:name=".playbackService.ExpoVideoPlaybackService"
|
|
13
13
|
android:exported="false"
|
|
@@ -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,8 +13,11 @@ 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
|
|
15
|
-
import expo.modules.video.
|
|
16
|
+
import expo.modules.video.records.FullscreenOptions
|
|
17
|
+
import expo.modules.video.utils.FullscreenActivityOrientationHelper
|
|
18
|
+
import expo.modules.video.utils.applyPiPParams
|
|
16
19
|
import expo.modules.video.utils.applyRectHint
|
|
20
|
+
import expo.modules.video.utils.calculatePiPAspectRatio
|
|
17
21
|
import expo.modules.video.utils.calculateRectHint
|
|
18
22
|
|
|
19
23
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
|
@@ -25,26 +29,56 @@ class FullscreenPlayerActivity : Activity() {
|
|
|
25
29
|
private lateinit var videoView: VideoView
|
|
26
30
|
private var didFinish = false
|
|
27
31
|
private var wasAutoPaused = false
|
|
32
|
+
private lateinit var options: FullscreenOptions
|
|
33
|
+
private lateinit var orientationHelper: FullscreenActivityOrientationHelper
|
|
28
34
|
|
|
29
35
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
30
36
|
super.onCreate(savedInstanceState)
|
|
31
|
-
setContentView(R.layout.fullscreen_player_activity)
|
|
32
|
-
mContentView = findViewById(R.id.enclosing_layout)
|
|
33
|
-
playerView = findViewById(R.id.player_view)
|
|
34
37
|
|
|
35
38
|
try {
|
|
36
39
|
videoViewId = intent.getStringExtra(VideoManager.INTENT_PLAYER_KEY)
|
|
37
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
|
+
?: throw FullScreenOptionsNotFoundException()
|
|
45
|
+
} else {
|
|
46
|
+
@Suppress("DEPRECATION")
|
|
47
|
+
intent.getSerializableExtra(INTENT_FULLSCREEN_OPTIONS_KEY) as? FullscreenOptions
|
|
48
|
+
?: throw FullScreenOptionsNotFoundException()
|
|
49
|
+
}
|
|
50
|
+
|
|
38
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()
|
|
39
64
|
} catch (e: CodedException) {
|
|
40
65
|
Log.e("ExpoVideo", "${e.message}", e)
|
|
41
66
|
finish()
|
|
42
67
|
return
|
|
43
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
|
+
|
|
44
75
|
videoPlayer = videoView.videoPlayer
|
|
45
76
|
videoPlayer?.changePlayerView(playerView)
|
|
46
77
|
VideoManager.registerFullscreenPlayerActivity(hashCode().toString(), this)
|
|
47
|
-
|
|
78
|
+
playerView.player?.let {
|
|
79
|
+
val aspectRatio = calculatePiPAspectRatio(it.videoSize, playerView.width, playerView.height, videoView.contentFit)
|
|
80
|
+
applyPiPParams(this, videoView.autoEnterPiP, aspectRatio)
|
|
81
|
+
}
|
|
48
82
|
}
|
|
49
83
|
|
|
50
84
|
override fun onPostCreate(savedInstanceState: Bundle?) {
|
|
@@ -79,6 +113,7 @@ class FullscreenPlayerActivity : Activity() {
|
|
|
79
113
|
}
|
|
80
114
|
|
|
81
115
|
override fun onResume() {
|
|
116
|
+
orientationHelper.startOrientationEventListener()
|
|
82
117
|
playerView.useController = videoView.useNativeControls
|
|
83
118
|
super.onResume()
|
|
84
119
|
}
|
|
@@ -91,6 +126,7 @@ class FullscreenPlayerActivity : Activity() {
|
|
|
91
126
|
videoPlayer?.player?.pause()
|
|
92
127
|
}
|
|
93
128
|
}
|
|
129
|
+
orientationHelper.stopOrientationEventListener()
|
|
94
130
|
super.onPause()
|
|
95
131
|
}
|
|
96
132
|
|
|
@@ -98,6 +134,7 @@ class FullscreenPlayerActivity : Activity() {
|
|
|
98
134
|
super.onDestroy()
|
|
99
135
|
videoView.exitFullscreen()
|
|
100
136
|
VideoManager.unregisterFullscreenPlayerActivity(hashCode().toString())
|
|
137
|
+
orientationHelper.stopOrientationEventListener()
|
|
101
138
|
}
|
|
102
139
|
|
|
103
140
|
private fun setupFullscreenButton() {
|
|
@@ -138,4 +175,13 @@ class FullscreenPlayerActivity : Activity() {
|
|
|
138
175
|
)
|
|
139
176
|
}
|
|
140
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
|
+
}
|
|
141
187
|
}
|
|
@@ -6,6 +6,9 @@ import expo.modules.video.enums.DRMType
|
|
|
6
6
|
internal class FullScreenVideoViewNotFoundException :
|
|
7
7
|
CodedException("VideoView id wasn't passed to the activity")
|
|
8
8
|
|
|
9
|
+
internal class FullScreenOptionsNotFoundException :
|
|
10
|
+
CodedException("Fullscreen options were not passed to the activity")
|
|
11
|
+
|
|
9
12
|
internal class VideoViewNotFoundException(id: String) :
|
|
10
13
|
CodedException("VideoView with id: $id not found")
|
|
11
14
|
|
|
@@ -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
|
|
@@ -225,7 +226,9 @@ class VideoModule : Module() {
|
|
|
225
226
|
|
|
226
227
|
Property("loop")
|
|
227
228
|
.get { ref: VideoPlayer ->
|
|
228
|
-
|
|
229
|
+
runBlocking(appContext.mainQueue.coroutineContext) {
|
|
230
|
+
ref.player.repeatMode == REPEAT_MODE_ONE
|
|
231
|
+
}
|
|
229
232
|
}
|
|
230
233
|
.set { ref: VideoPlayer, loop: Boolean ->
|
|
231
234
|
appContext.mainQueue.launch {
|
|
@@ -253,6 +256,12 @@ class VideoModule : Module() {
|
|
|
253
256
|
ref.bufferOptions = bufferOptions
|
|
254
257
|
}
|
|
255
258
|
|
|
259
|
+
Property("isExternalPlaybackActive")
|
|
260
|
+
.get { ref: VideoPlayer ->
|
|
261
|
+
// isExternalPlaybackActive is not supported on Android as of now. Return false.
|
|
262
|
+
false
|
|
263
|
+
}
|
|
264
|
+
|
|
256
265
|
Function("play") { ref: VideoPlayer ->
|
|
257
266
|
appContext.mainQueue.launch {
|
|
258
267
|
ref.player.play()
|
|
@@ -381,6 +390,11 @@ private inline fun <reified T : VideoView> ViewDefinitionBuilder<T>.VideoViewCom
|
|
|
381
390
|
Prop("allowsFullscreen") { view: T, allowsFullscreen: Boolean? ->
|
|
382
391
|
view.allowsFullscreen = allowsFullscreen ?: true
|
|
383
392
|
}
|
|
393
|
+
Prop("fullscreenOptions") { view: T, fullscreenOptions: FullscreenOptions? ->
|
|
394
|
+
if (fullscreenOptions != null) {
|
|
395
|
+
view.fullscreenOptions = fullscreenOptions
|
|
396
|
+
}
|
|
397
|
+
}
|
|
384
398
|
Prop("requiresLinearPlayback") { view: T, requiresLinearPlayback: Boolean? ->
|
|
385
399
|
val linearPlayback = requiresLinearPlayback ?: false
|
|
386
400
|
view.playerView.applyRequiresLinearPlayback(linearPlayback)
|
|
@@ -15,6 +15,7 @@ import android.widget.FrameLayout
|
|
|
15
15
|
import android.widget.ImageButton
|
|
16
16
|
import androidx.fragment.app.FragmentActivity
|
|
17
17
|
import androidx.media3.common.Tracks
|
|
18
|
+
import androidx.media3.common.VideoSize
|
|
18
19
|
import androidx.media3.ui.PlayerView
|
|
19
20
|
import com.facebook.react.bridge.ReactContext
|
|
20
21
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
@@ -27,8 +28,14 @@ import expo.modules.video.delegates.IgnoreSameSet
|
|
|
27
28
|
import expo.modules.video.enums.ContentFit
|
|
28
29
|
import expo.modules.video.player.VideoPlayer
|
|
29
30
|
import expo.modules.video.player.VideoPlayerListener
|
|
30
|
-
import expo.modules.video.
|
|
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.records.FullscreenOptions
|
|
31
37
|
import expo.modules.video.utils.applyRectHint
|
|
38
|
+
import expo.modules.video.utils.calculatePiPAspectRatio
|
|
32
39
|
import expo.modules.video.utils.calculateRectHint
|
|
33
40
|
import expo.modules.video.utils.dispatchMotionEvent
|
|
34
41
|
import java.util.UUID
|
|
@@ -82,7 +89,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
var autoEnterPiP: Boolean by IgnoreSameSet(false) { new, _ ->
|
|
85
|
-
|
|
92
|
+
applyPiPParams(currentActivity, new, calculateCurrentPipAspectRatio())
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
var contentFit: ContentFit = ContentFit.CONTAIN
|
|
@@ -126,6 +133,17 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
126
133
|
field = value
|
|
127
134
|
}
|
|
128
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
|
+
|
|
129
147
|
private val mLayoutRunnable = Runnable {
|
|
130
148
|
measure(
|
|
131
149
|
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
@@ -166,6 +184,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
166
184
|
fun enterFullscreen() {
|
|
167
185
|
val intent = Intent(context, FullscreenPlayerActivity::class.java)
|
|
168
186
|
intent.putExtra(VideoManager.INTENT_PLAYER_KEY, videoViewId)
|
|
187
|
+
intent.putExtra(FullscreenPlayerActivity.INTENT_FULLSCREEN_OPTIONS_KEY, fullscreenOptions)
|
|
169
188
|
// Set before starting the activity to avoid entering PiP unintentionally
|
|
170
189
|
isInFullscreen = true
|
|
171
190
|
currentActivity.startActivity(intent)
|
|
@@ -178,7 +197,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
178
197
|
currentActivity.overridePendingTransition(0, 0)
|
|
179
198
|
}
|
|
180
199
|
onFullscreenEnter(Unit)
|
|
181
|
-
|
|
200
|
+
applyPiPParams(currentActivity, false, calculateCurrentPipAspectRatio())
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
fun attachPlayer() {
|
|
@@ -192,7 +211,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
192
211
|
attachPlayer()
|
|
193
212
|
onFullscreenExit(Unit)
|
|
194
213
|
isInFullscreen = false
|
|
195
|
-
|
|
214
|
+
applyPiPParams(currentActivity, autoEnterPiP, calculateCurrentPipAspectRatio())
|
|
196
215
|
}
|
|
197
216
|
|
|
198
217
|
fun enterPictureInPicture() {
|
|
@@ -203,32 +222,9 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
203
222
|
val player = playerView.player
|
|
204
223
|
?: throw PictureInPictureEnterException("No player attached to the VideoView")
|
|
205
224
|
playerView.useController = false
|
|
206
|
-
|
|
207
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
208
|
-
var aspectRatio = if (contentFit == ContentFit.CONTAIN) {
|
|
209
|
-
Rational(player.videoSize.width, player.videoSize.height)
|
|
210
|
-
} else {
|
|
211
|
-
Rational(width, height)
|
|
212
|
-
}
|
|
213
|
-
// AspectRatio for the activity in picture-in-picture, must be between 2.39:1 and 1:2.39 (inclusive).
|
|
214
|
-
// https://developer.android.com/reference/android/app/PictureInPictureParams.Builder#setAspectRatio(android.util.Rational)
|
|
215
|
-
val maximumRatio = Rational(239, 100)
|
|
216
|
-
val minimumRatio = Rational(100, 239)
|
|
217
|
-
if (aspectRatio.toFloat() > maximumRatio.toFloat()) {
|
|
218
|
-
aspectRatio = maximumRatio
|
|
219
|
-
} else if (aspectRatio.toFloat() < minimumRatio.toFloat()) {
|
|
220
|
-
aspectRatio = minimumRatio
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
currentActivity.setPictureInPictureParams(
|
|
224
|
-
PictureInPictureParams
|
|
225
|
-
.Builder()
|
|
226
|
-
.setAspectRatio(aspectRatio)
|
|
227
|
-
.build()
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
|
|
225
|
+
applyPiPParams(currentActivity, autoEnterPiP, calculateCurrentPipAspectRatio())
|
|
231
226
|
willEnterPiP = true
|
|
227
|
+
|
|
232
228
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
233
229
|
currentActivity.enterPictureInPictureMode(PictureInPictureParams.Builder().build())
|
|
234
230
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
@@ -237,6 +233,11 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
237
233
|
}
|
|
238
234
|
}
|
|
239
235
|
|
|
236
|
+
private fun calculateCurrentPipAspectRatio(): Rational? {
|
|
237
|
+
val player = videoPlayer?.player ?: return null
|
|
238
|
+
return calculatePiPAspectRatio(player.videoSize, this.width, this.height, contentFit)
|
|
239
|
+
}
|
|
240
|
+
|
|
240
241
|
/**
|
|
241
242
|
* For optimal picture in picture experience it's best to only have one view. This method
|
|
242
243
|
* hides all children of the root view and makes the player the only visible child of the rootView.
|
|
@@ -263,6 +264,22 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
263
264
|
this.addView(playerView)
|
|
264
265
|
}
|
|
265
266
|
|
|
267
|
+
override fun onVideoSourceLoaded(
|
|
268
|
+
player: VideoPlayer,
|
|
269
|
+
videoSource: VideoSource?,
|
|
270
|
+
duration: Double?,
|
|
271
|
+
availableVideoTracks: List<VideoTrack>,
|
|
272
|
+
availableSubtitleTracks: List<SubtitleTrack>,
|
|
273
|
+
availableAudioTracks: List<AudioTrack>
|
|
274
|
+
) {
|
|
275
|
+
availableVideoTracks.firstOrNull()?.let {
|
|
276
|
+
val videoSize = VideoSize(it.size.width, it.size.height)
|
|
277
|
+
val aspectRatio = calculatePiPAspectRatio(videoSize, this.width, this.height, contentFit)
|
|
278
|
+
applyPiPParams(currentActivity, autoEnterPiP, aspectRatio)
|
|
279
|
+
}
|
|
280
|
+
super.onVideoSourceLoaded(player, videoSource, duration, availableVideoTracks, availableSubtitleTracks, availableAudioTracks)
|
|
281
|
+
}
|
|
282
|
+
|
|
266
283
|
override fun onTracksChanged(player: VideoPlayer, tracks: Tracks) {
|
|
267
284
|
showsSubtitlesButton = player.subtitles.availableSubtitleTracks.isNotEmpty()
|
|
268
285
|
showsAudioTracksButton = player.audioTracks.availableAudioTracks.size > 1
|
|
@@ -302,7 +319,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
302
319
|
.add(fragment, fragment.id)
|
|
303
320
|
.commitAllowingStateLoss()
|
|
304
321
|
}
|
|
305
|
-
|
|
322
|
+
applyPiPParams(currentActivity, autoEnterPiP)
|
|
306
323
|
}
|
|
307
324
|
|
|
308
325
|
override fun onDetachedFromWindow() {
|
|
@@ -314,7 +331,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
314
331
|
.remove(fragment)
|
|
315
332
|
.commitAllowingStateLoss()
|
|
316
333
|
}
|
|
317
|
-
|
|
334
|
+
applyPiPParams(currentActivity, false)
|
|
318
335
|
}
|
|
319
336
|
|
|
320
337
|
// After adding the `PlayerView` to the hierarchy the touch events stop being emitted to the JS side.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package expo.modules.video.enums
|
|
2
|
+
import expo.modules.kotlin.types.Enumerable
|
|
3
|
+
import android.content.pm.ActivityInfo
|
|
4
|
+
|
|
5
|
+
enum class FullscreenOrientation(val value: String) : Enumerable {
|
|
6
|
+
LANDSCAPE("landscape"),
|
|
7
|
+
PORTRAIT("portrait"),
|
|
8
|
+
LANDSCAPE_LEFT("landscapeLeft"),
|
|
9
|
+
LANDSCAPE_RIGHT("landscapeRight"),
|
|
10
|
+
PORTRAIT_UP("portraitUp"),
|
|
11
|
+
PORTRAIT_DOWN("portraitDown"),
|
|
12
|
+
DEFAULT("default");
|
|
13
|
+
|
|
14
|
+
fun toActivityOrientation(): Int {
|
|
15
|
+
return when (this) {
|
|
16
|
+
LANDSCAPE -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
|
17
|
+
PORTRAIT -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
|
18
|
+
LANDSCAPE_LEFT -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
|
19
|
+
LANDSCAPE_RIGHT -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
|
20
|
+
PORTRAIT_UP -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
|
21
|
+
PORTRAIT_DOWN -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
|
|
22
|
+
DEFAULT -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -156,7 +156,8 @@ sealed class PlayerEvent {
|
|
|
156
156
|
is AudioMixingModeChanged -> listeners.forEach { it.onAudioMixingModeChanged(player, audioMixingMode, oldAudioMixingMode) }
|
|
157
157
|
is VideoTrackChanged -> listeners.forEach { it.onVideoTrackChanged(player, videoTrack, oldVideoTrack) }
|
|
158
158
|
is RenderedFirstFrame -> listeners.forEach { it.onRenderedFirstFrame(player) }
|
|
159
|
-
|
|
159
|
+
is VideoSourceLoaded -> listeners.forEach { it.onVideoSourceLoaded(player, videoSource, duration, availableVideoTracks, availableSubtitleTracks, availableAudioTracks) }
|
|
160
|
+
// JS-only events - SubtitleTrackChanged - In the native events the TracksChanged can be used instead
|
|
160
161
|
else -> Unit
|
|
161
162
|
}
|
|
162
163
|
}
|
|
@@ -6,7 +6,9 @@ import androidx.media3.common.Tracks
|
|
|
6
6
|
import androidx.media3.common.util.UnstableApi
|
|
7
7
|
import expo.modules.video.enums.AudioMixingMode
|
|
8
8
|
import expo.modules.video.enums.PlayerStatus
|
|
9
|
+
import expo.modules.video.records.AudioTrack
|
|
9
10
|
import expo.modules.video.records.PlaybackError
|
|
11
|
+
import expo.modules.video.records.SubtitleTrack
|
|
10
12
|
import expo.modules.video.records.VideoSource
|
|
11
13
|
import expo.modules.video.records.TimeUpdate
|
|
12
14
|
import expo.modules.video.records.VideoTrack
|
|
@@ -25,5 +27,6 @@ interface VideoPlayerListener {
|
|
|
25
27
|
fun onPlayedToEnd(player: VideoPlayer) {}
|
|
26
28
|
fun onAudioMixingModeChanged(player: VideoPlayer, audioMixingMode: AudioMixingMode, oldAudioMixingMode: AudioMixingMode?) {}
|
|
27
29
|
fun onVideoTrackChanged(player: VideoPlayer, videoTrack: VideoTrack?, oldVideoTrack: VideoTrack?) {}
|
|
30
|
+
fun onVideoSourceLoaded(player: VideoPlayer, videoSource: VideoSource?, duration: Double?, availableVideoTracks: List<VideoTrack>, availableSubtitleTracks: List<SubtitleTrack>, availableAudioTracks: List<AudioTrack>) {}
|
|
28
31
|
fun onRenderedFirstFrame(player: VideoPlayer) {}
|
|
29
32
|
}
|
|
@@ -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
|
+
}
|
|
@@ -5,11 +5,14 @@ import android.app.PictureInPictureParams
|
|
|
5
5
|
import android.graphics.Rect
|
|
6
6
|
import android.os.Build
|
|
7
7
|
import android.util.Log
|
|
8
|
+
import android.util.Rational
|
|
8
9
|
import androidx.annotation.OptIn
|
|
10
|
+
import androidx.media3.common.VideoSize
|
|
9
11
|
import androidx.media3.common.util.UnstableApi
|
|
10
12
|
import androidx.media3.ui.PlayerView
|
|
11
13
|
import expo.modules.video.PictureInPictureConfigurationException
|
|
12
14
|
import expo.modules.video.VideoView.Companion.isPictureInPictureSupported
|
|
15
|
+
import expo.modules.video.enums.ContentFit
|
|
13
16
|
|
|
14
17
|
@OptIn(UnstableApi::class)
|
|
15
18
|
internal fun calculateRectHint(playerView: PlayerView): Rect {
|
|
@@ -34,6 +37,25 @@ internal fun calculateRectHint(playerView: PlayerView): Rect {
|
|
|
34
37
|
return hint
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
internal fun calculatePiPAspectRatio(videoSize: VideoSize, viewWidth: Int, viewHeight: Int, contentFit: ContentFit): Rational {
|
|
41
|
+
var aspectRatio = if (contentFit == ContentFit.CONTAIN) {
|
|
42
|
+
Rational(videoSize.width, videoSize.height)
|
|
43
|
+
} else {
|
|
44
|
+
Rational(viewWidth, viewHeight)
|
|
45
|
+
}
|
|
46
|
+
// AspectRatio for the activity in picture-in-picture, must be between 2.39:1 and 1:2.39 (inclusive).
|
|
47
|
+
// https://developer.android.com/reference/android/app/PictureInPictureParams.Builder#setAspectRatio(android.util.Rational)
|
|
48
|
+
val maximumRatio = Rational(239, 100)
|
|
49
|
+
val minimumRatio = Rational(100, 239)
|
|
50
|
+
|
|
51
|
+
if (aspectRatio.toFloat() > maximumRatio.toFloat()) {
|
|
52
|
+
aspectRatio = maximumRatio
|
|
53
|
+
} else if (aspectRatio.toFloat() < minimumRatio.toFloat()) {
|
|
54
|
+
aspectRatio = minimumRatio
|
|
55
|
+
}
|
|
56
|
+
return aspectRatio
|
|
57
|
+
}
|
|
58
|
+
|
|
37
59
|
internal fun applyRectHint(activity: Activity, rectHint: Rect) {
|
|
38
60
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPictureSupported(activity)) {
|
|
39
61
|
runWithPiPMisconfigurationSoftHandling {
|
|
@@ -54,10 +76,21 @@ internal fun runWithPiPMisconfigurationSoftHandling(shouldThrow: Boolean = false
|
|
|
54
76
|
}
|
|
55
77
|
}
|
|
56
78
|
|
|
57
|
-
internal fun
|
|
58
|
-
|
|
79
|
+
internal fun applyPiPParams(activity: Activity, autoEnterPiP: Boolean, aspectRatio: Rational? = null) {
|
|
80
|
+
// If the aspect ratio exceeds the limits, the app will crash
|
|
81
|
+
val safeAspectRatio = aspectRatio?.takeIf { it.toFloat() in 0.41841..2.39 }
|
|
82
|
+
|
|
83
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPictureSupported(activity)) {
|
|
84
|
+
val paramsBuilder = PictureInPictureParams.Builder()
|
|
85
|
+
|
|
86
|
+
safeAspectRatio?.let {
|
|
87
|
+
paramsBuilder.setAspectRatio(it)
|
|
88
|
+
}
|
|
89
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
90
|
+
paramsBuilder.setAutoEnterEnabled(autoEnterPiP)
|
|
91
|
+
}
|
|
59
92
|
runWithPiPMisconfigurationSoftHandling {
|
|
60
|
-
activity.setPictureInPictureParams(
|
|
93
|
+
activity.setPictureInPictureParams(paramsBuilder.build())
|
|
61
94
|
}
|
|
62
95
|
}
|
|
63
96
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NativeVideoAirPlayButtonView.d.ts","sourceRoot":"","sources":["../src/NativeVideoAirPlayButtonView.ts"],"names":[],"mappings":";AAEA,wBAAwE"}
|