expo-video 2.1.8 → 2.1.10-canary-20250611-f0afe80
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 +28 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +6 -2
- package/android/src/main/java/expo/modules/video/VideoModule.kt +21 -1
- package/android/src/main/java/expo/modules/video/VideoView.kt +42 -35
- package/android/src/main/java/expo/modules/video/player/FirstFrameEventGenerator.kt +3 -2
- package/android/src/main/java/expo/modules/video/player/PlayerEvent.kt +22 -3
- package/android/src/main/java/expo/modules/video/player/VideoPlayer.kt +16 -1
- package/android/src/main/java/expo/modules/video/player/VideoPlayerAudioTracks.kt +125 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerListener.kt +3 -0
- package/android/src/main/java/expo/modules/video/records/Tracks.kt +21 -0
- package/android/src/main/java/expo/modules/video/records/VideoEventPayloads.kt +12 -1
- package/android/src/main/java/expo/modules/video/utils/PictureInPictureUtils.kt +36 -3
- package/build/VideoPlayer.types.d.ts +30 -0
- package/build/VideoPlayer.types.d.ts.map +1 -1
- package/build/VideoPlayer.types.js.map +1 -1
- package/build/VideoPlayer.web.d.ts +3 -1
- package/build/VideoPlayer.web.d.ts.map +1 -1
- package/build/VideoPlayer.web.js +2 -0
- package/build/VideoPlayer.web.js.map +1 -1
- package/build/VideoPlayerEvents.types.d.ts +34 -1
- package/build/VideoPlayerEvents.types.d.ts.map +1 -1
- package/build/VideoPlayerEvents.types.js.map +1 -1
- package/build/VideoView.types.d.ts +9 -1
- package/build/VideoView.types.d.ts.map +1 -1
- package/build/VideoView.types.js.map +1 -1
- package/build/VideoView.web.js +1 -1
- 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 +1 -1
- package/ios/Records/Tracks.swift +13 -0
- package/ios/Records/VideoEventPayloads.swift +11 -0
- package/ios/VideoModule.swift +11 -0
- package/ios/VideoPlayer.swift +21 -2
- package/ios/VideoPlayerAudioTracks.swift +72 -0
- package/ios/VideoPlayerItem.swift +7 -1
- package/ios/VideoPlayerObserver.swift +89 -22
- package/ios/VideoSourceLoader.swift +44 -8
- package/ios/VideoSourceLoaderListener.swift +34 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80-sources.jar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80-sources.jar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80-sources.jar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80-sources.jar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80-sources.jar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.aar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.aar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.aar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.aar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.1.8/expo.modules.video-2.1.8.module → 2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.module} +24 -24
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.module.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.module.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.module.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.module.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/{2.1.8/expo.modules.video-2.1.8.pom → 2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.pom} +2 -2
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.pom.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.pom.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.pom.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.10-canary-20250611-f0afe80/expo.modules.video-2.1.10-canary-20250611-f0afe80.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/VideoPlayer.types.ts +35 -0
- package/src/VideoPlayer.web.tsx +3 -0
- package/src/VideoPlayerEvents.types.ts +40 -0
- package/src/VideoView.types.ts +10 -1
- package/src/VideoView.web.tsx +1 -1
- package/src/index.ts +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8-sources.jar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8-sources.jar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8-sources.jar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8-sources.jar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8-sources.jar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8.aar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8.aar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8.aar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8.aar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8.module.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8.module.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8.module.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8.module.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8.pom.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8.pom.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8.pom.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.video/2.1.8/expo.modules.video-2.1.8.pom.sha512 +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,8 +8,36 @@
|
|
|
8
8
|
|
|
9
9
|
### 🐛 Bug fixes
|
|
10
10
|
|
|
11
|
+
- [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))
|
|
12
|
+
|
|
11
13
|
### 💡 Others
|
|
12
14
|
|
|
15
|
+
## 2.2.1 - 2025-06-10
|
|
16
|
+
|
|
17
|
+
_This version does not introduce any user-facing changes._
|
|
18
|
+
|
|
19
|
+
## 2.2.0 - 2025-06-04
|
|
20
|
+
|
|
21
|
+
### 🎉 New features
|
|
22
|
+
|
|
23
|
+
- [Android][iOS] Added support for Audio Track feature. You can now set the audio track using `player.audioTrack` and list available audio tracks using `player.availableAudioTracks`. ([#36207](https://github.com/expo/expo/pull/36207) by [@HADeveloper](https://github.com/HADeveloper))
|
|
24
|
+
|
|
25
|
+
### 🐛 Bug fixes
|
|
26
|
+
|
|
27
|
+
- [Android] Fix `onFirstFrameRender` not being emitted for sources with `pixelWidthHeightRatio` different than 1. ([#37009](https://github.com/expo/expo/pull/37009) by [@behenate](https://github.com/behenate))
|
|
28
|
+
- [Android] Fix `useExoShutter` prop not being exposed to the JS side. ([#37012](https://github.com/expo/expo/pull/37012) by [@behenate](https://github.com/behenate))
|
|
29
|
+
- [Android] Add missing `onFirstFrameRender` event to the `VideoView` definition. ([#37014](https://github.com/expo/expo/pull/37014) by [@behenate](https://github.com/behenate))
|
|
30
|
+
- [iOS] Fix player not entering 'error' state when loading fails on iOS. ([#37177](https://github.com/expo/expo/pull/37177) by [@behenate](https://github.com/behenate))
|
|
31
|
+
- [iOS] Fix player reporting status `readyToPlay` while a source is being loaded asynchronously. ([#37180](https://github.com/expo/expo/pull/37180) by [@behenate](https://github.com/behenate))
|
|
32
|
+
- [iOS] Fix player going into `loading` status for a single frame when unpausing with a full buffer. ([#37181](https://github.com/expo/expo/pull/37181) by [@behenate](https://github.com/behenate))
|
|
33
|
+
- [iOS] Fix player getting stuck in `loading` state for null sources. ([#37183](https://github.com/expo/expo/pull/37183) by [@behenate](https://github.com/behenate))
|
|
34
|
+
|
|
35
|
+
## 2.1.9 — 2025-05-08
|
|
36
|
+
|
|
37
|
+
### 🛠 Breaking changes
|
|
38
|
+
|
|
39
|
+
- [web] Add crossOrigin prop, change default value to no CORS. ([#36713](https://github.com/expo/expo/pull/36713) by [@aleqsio](https://github.com/aleqsio))
|
|
40
|
+
|
|
13
41
|
## 2.1.8 — 2025-04-30
|
|
14
42
|
|
|
15
43
|
_This version does not introduce any user-facing changes._
|
package/android/build.gradle
CHANGED
|
@@ -4,13 +4,13 @@ plugins {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
group = 'host.exp.exponent'
|
|
7
|
-
version = '2.1.
|
|
7
|
+
version = '2.1.10-canary-20250611-f0afe80'
|
|
8
8
|
|
|
9
9
|
android {
|
|
10
10
|
namespace "expo.modules.video"
|
|
11
11
|
defaultConfig {
|
|
12
12
|
versionCode 1
|
|
13
|
-
versionName '2.1.
|
|
13
|
+
versionName '2.1.10-canary-20250611-f0afe80'
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -12,8 +12,9 @@ import android.widget.ImageButton
|
|
|
12
12
|
import androidx.media3.ui.PlayerView
|
|
13
13
|
import expo.modules.kotlin.exception.CodedException
|
|
14
14
|
import expo.modules.video.player.VideoPlayer
|
|
15
|
-
import expo.modules.video.utils.
|
|
15
|
+
import expo.modules.video.utils.applyPiPParams
|
|
16
16
|
import expo.modules.video.utils.applyRectHint
|
|
17
|
+
import expo.modules.video.utils.calculatePiPAspectRatio
|
|
17
18
|
import expo.modules.video.utils.calculateRectHint
|
|
18
19
|
|
|
19
20
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
|
@@ -44,7 +45,10 @@ class FullscreenPlayerActivity : Activity() {
|
|
|
44
45
|
videoPlayer = videoView.videoPlayer
|
|
45
46
|
videoPlayer?.changePlayerView(playerView)
|
|
46
47
|
VideoManager.registerFullscreenPlayerActivity(hashCode().toString(), this)
|
|
47
|
-
|
|
48
|
+
playerView.player?.let {
|
|
49
|
+
val aspectRatio = calculatePiPAspectRatio(it.videoSize, playerView.width, playerView.height, videoView.contentFit)
|
|
50
|
+
applyPiPParams(this, videoView.autoEnterPiP, aspectRatio)
|
|
51
|
+
}
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
override fun onPostCreate(savedInstanceState: Bundle?) {
|
|
@@ -21,6 +21,7 @@ import expo.modules.video.enums.ContentFit
|
|
|
21
21
|
import expo.modules.video.player.VideoPlayer
|
|
22
22
|
import expo.modules.video.records.BufferOptions
|
|
23
23
|
import expo.modules.video.records.SubtitleTrack
|
|
24
|
+
import expo.modules.video.records.AudioTrack
|
|
24
25
|
import expo.modules.video.records.VideoSource
|
|
25
26
|
import expo.modules.video.records.VideoThumbnailOptions
|
|
26
27
|
import expo.modules.video.utils.runWithPiPMisconfigurationSoftHandling
|
|
@@ -146,6 +147,21 @@ class VideoModule : Module() {
|
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
Property("availableAudioTracks")
|
|
151
|
+
.get { ref: VideoPlayer ->
|
|
152
|
+
ref.audioTracks.availableAudioTracks
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
Property("audioTrack")
|
|
156
|
+
.get { ref: VideoPlayer ->
|
|
157
|
+
ref.audioTracks.currentAudioTrack
|
|
158
|
+
}
|
|
159
|
+
.set { ref: VideoPlayer, audioTrack: AudioTrack? ->
|
|
160
|
+
appContext.mainQueue.launch {
|
|
161
|
+
ref.audioTracks.currentAudioTrack = audioTrack
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
149
165
|
Property("currentOffsetFromLive")
|
|
150
166
|
.get { ref: VideoPlayer ->
|
|
151
167
|
runBlocking(appContext.mainQueue.coroutineContext) {
|
|
@@ -347,7 +363,8 @@ private inline fun <reified T : VideoView> ViewDefinitionBuilder<T>.VideoViewCom
|
|
|
347
363
|
"onPictureInPictureStart",
|
|
348
364
|
"onPictureInPictureStop",
|
|
349
365
|
"onFullscreenEnter",
|
|
350
|
-
"onFullscreenExit"
|
|
366
|
+
"onFullscreenExit",
|
|
367
|
+
"onFirstFrameRender"
|
|
351
368
|
)
|
|
352
369
|
Prop("player") { view: T, player: VideoPlayer ->
|
|
353
370
|
view.videoPlayer = player
|
|
@@ -369,6 +386,9 @@ private inline fun <reified T : VideoView> ViewDefinitionBuilder<T>.VideoViewCom
|
|
|
369
386
|
view.playerView.applyRequiresLinearPlayback(linearPlayback)
|
|
370
387
|
view.videoPlayer?.requiresLinearPlayback = linearPlayback
|
|
371
388
|
}
|
|
389
|
+
Prop("useExoShutter") { view: T, useExoShutter: Boolean? ->
|
|
390
|
+
view.useExoShutter = useExoShutter
|
|
391
|
+
}
|
|
372
392
|
AsyncFunction("enterFullscreen") { view: T ->
|
|
373
393
|
view.enterFullscreen()
|
|
374
394
|
}.runOnQueue(Queues.MAIN)
|
|
@@ -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,13 @@ 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
|
|
31
36
|
import expo.modules.video.utils.applyRectHint
|
|
37
|
+
import expo.modules.video.utils.calculatePiPAspectRatio
|
|
32
38
|
import expo.modules.video.utils.calculateRectHint
|
|
33
39
|
import expo.modules.video.utils.dispatchMotionEvent
|
|
34
40
|
import java.util.UUID
|
|
@@ -55,6 +61,8 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
55
61
|
private set
|
|
56
62
|
var showsSubtitlesButton = false
|
|
57
63
|
private set
|
|
64
|
+
var showsAudioTracksButton = false
|
|
65
|
+
private set
|
|
58
66
|
|
|
59
67
|
private val currentActivity = appContext.throwingActivity
|
|
60
68
|
private val decorView = currentActivity.window.decorView
|
|
@@ -70,17 +78,17 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
70
78
|
|
|
71
79
|
var useExoShutter: Boolean? = null
|
|
72
80
|
set(value) {
|
|
73
|
-
if (value ==
|
|
74
|
-
playerView.setShutterBackgroundColor(Color.TRANSPARENT)
|
|
75
|
-
} else {
|
|
81
|
+
if (value == true) {
|
|
76
82
|
playerView.setShutterBackgroundColor(Color.BLACK)
|
|
83
|
+
} else {
|
|
84
|
+
playerView.setShutterBackgroundColor(Color.TRANSPARENT)
|
|
77
85
|
}
|
|
78
86
|
applySurfaceViewVisibility()
|
|
79
87
|
field = value
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
var autoEnterPiP: Boolean by IgnoreSameSet(false) { new, _ ->
|
|
83
|
-
|
|
91
|
+
applyPiPParams(currentActivity, new, calculateCurrentPipAspectRatio())
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
var contentFit: ContentFit = ContentFit.CONTAIN
|
|
@@ -154,7 +162,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
154
162
|
}
|
|
155
163
|
|
|
156
164
|
fun applySurfaceViewVisibility() {
|
|
157
|
-
if (useExoShutter
|
|
165
|
+
if (useExoShutter != true && shouldHideSurfaceView) {
|
|
158
166
|
playerView.videoSurfaceView?.alpha = 0f
|
|
159
167
|
} else {
|
|
160
168
|
playerView.videoSurfaceView?.alpha = 1f
|
|
@@ -176,7 +184,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
176
184
|
currentActivity.overridePendingTransition(0, 0)
|
|
177
185
|
}
|
|
178
186
|
onFullscreenEnter(Unit)
|
|
179
|
-
|
|
187
|
+
applyPiPParams(currentActivity, false, calculateCurrentPipAspectRatio())
|
|
180
188
|
}
|
|
181
189
|
|
|
182
190
|
fun attachPlayer() {
|
|
@@ -190,7 +198,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
190
198
|
attachPlayer()
|
|
191
199
|
onFullscreenExit(Unit)
|
|
192
200
|
isInFullscreen = false
|
|
193
|
-
|
|
201
|
+
applyPiPParams(currentActivity, autoEnterPiP, calculateCurrentPipAspectRatio())
|
|
194
202
|
}
|
|
195
203
|
|
|
196
204
|
fun enterPictureInPicture() {
|
|
@@ -201,32 +209,9 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
201
209
|
val player = playerView.player
|
|
202
210
|
?: throw PictureInPictureEnterException("No player attached to the VideoView")
|
|
203
211
|
playerView.useController = false
|
|
204
|
-
|
|
205
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
206
|
-
var aspectRatio = if (contentFit == ContentFit.CONTAIN) {
|
|
207
|
-
Rational(player.videoSize.width, player.videoSize.height)
|
|
208
|
-
} else {
|
|
209
|
-
Rational(width, height)
|
|
210
|
-
}
|
|
211
|
-
// AspectRatio for the activity in picture-in-picture, must be between 2.39:1 and 1:2.39 (inclusive).
|
|
212
|
-
// https://developer.android.com/reference/android/app/PictureInPictureParams.Builder#setAspectRatio(android.util.Rational)
|
|
213
|
-
val maximumRatio = Rational(239, 100)
|
|
214
|
-
val minimumRatio = Rational(100, 239)
|
|
215
|
-
if (aspectRatio.toFloat() > maximumRatio.toFloat()) {
|
|
216
|
-
aspectRatio = maximumRatio
|
|
217
|
-
} else if (aspectRatio.toFloat() < minimumRatio.toFloat()) {
|
|
218
|
-
aspectRatio = minimumRatio
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
currentActivity.setPictureInPictureParams(
|
|
222
|
-
PictureInPictureParams
|
|
223
|
-
.Builder()
|
|
224
|
-
.setAspectRatio(aspectRatio)
|
|
225
|
-
.build()
|
|
226
|
-
)
|
|
227
|
-
}
|
|
228
|
-
|
|
212
|
+
applyPiPParams(currentActivity, autoEnterPiP, calculateCurrentPipAspectRatio())
|
|
229
213
|
willEnterPiP = true
|
|
214
|
+
|
|
230
215
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
231
216
|
currentActivity.enterPictureInPictureMode(PictureInPictureParams.Builder().build())
|
|
232
217
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
@@ -235,6 +220,11 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
235
220
|
}
|
|
236
221
|
}
|
|
237
222
|
|
|
223
|
+
private fun calculateCurrentPipAspectRatio(): Rational? {
|
|
224
|
+
val player = videoPlayer?.player ?: return null
|
|
225
|
+
return calculatePiPAspectRatio(player.videoSize, this.width, this.height, contentFit)
|
|
226
|
+
}
|
|
227
|
+
|
|
238
228
|
/**
|
|
239
229
|
* For optimal picture in picture experience it's best to only have one view. This method
|
|
240
230
|
* hides all children of the root view and makes the player the only visible child of the rootView.
|
|
@@ -261,8 +251,25 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
261
251
|
this.addView(playerView)
|
|
262
252
|
}
|
|
263
253
|
|
|
254
|
+
override fun onVideoSourceLoaded(
|
|
255
|
+
player: VideoPlayer,
|
|
256
|
+
videoSource: VideoSource?,
|
|
257
|
+
duration: Double?,
|
|
258
|
+
availableVideoTracks: List<VideoTrack>,
|
|
259
|
+
availableSubtitleTracks: List<SubtitleTrack>,
|
|
260
|
+
availableAudioTracks: List<AudioTrack>
|
|
261
|
+
) {
|
|
262
|
+
availableVideoTracks.firstOrNull()?.let {
|
|
263
|
+
val videoSize = VideoSize(it.size.width, it.size.height)
|
|
264
|
+
val aspectRatio = calculatePiPAspectRatio(videoSize, this.width, this.height, contentFit)
|
|
265
|
+
applyPiPParams(currentActivity, autoEnterPiP, aspectRatio)
|
|
266
|
+
}
|
|
267
|
+
super.onVideoSourceLoaded(player, videoSource, duration, availableVideoTracks, availableSubtitleTracks, availableAudioTracks)
|
|
268
|
+
}
|
|
269
|
+
|
|
264
270
|
override fun onTracksChanged(player: VideoPlayer, tracks: Tracks) {
|
|
265
271
|
showsSubtitlesButton = player.subtitles.availableSubtitleTracks.isNotEmpty()
|
|
272
|
+
showsAudioTracksButton = player.audioTracks.availableAudioTracks.size > 1
|
|
266
273
|
playerView.setShowSubtitleButton(showsSubtitlesButton)
|
|
267
274
|
super.onTracksChanged(player, tracks)
|
|
268
275
|
}
|
|
@@ -299,7 +306,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
299
306
|
.add(fragment, fragment.id)
|
|
300
307
|
.commitAllowingStateLoss()
|
|
301
308
|
}
|
|
302
|
-
|
|
309
|
+
applyPiPParams(currentActivity, autoEnterPiP)
|
|
303
310
|
}
|
|
304
311
|
|
|
305
312
|
override fun onDetachedFromWindow() {
|
|
@@ -311,7 +318,7 @@ open class VideoView(context: Context, appContext: AppContext, useTextureView: B
|
|
|
311
318
|
.remove(fragment)
|
|
312
319
|
.commitAllowingStateLoss()
|
|
313
320
|
}
|
|
314
|
-
|
|
321
|
+
applyPiPParams(currentActivity, false)
|
|
315
322
|
}
|
|
316
323
|
|
|
317
324
|
// After adding the `PlayerView` to the hierarchy the touch events stop being emitted to the JS side.
|
|
@@ -64,7 +64,7 @@ internal class FirstFrameEventGenerator(
|
|
|
64
64
|
|
|
65
65
|
private fun isPlayerSurfaceLayoutValid(): Boolean {
|
|
66
66
|
// Sometimes the video size announced by the track will is 1px off the render size.
|
|
67
|
-
val epsilon = 0.
|
|
67
|
+
val epsilon = 0.05
|
|
68
68
|
val player = playerReference.get() ?: run {
|
|
69
69
|
return false
|
|
70
70
|
}
|
|
@@ -75,13 +75,14 @@ internal class FirstFrameEventGenerator(
|
|
|
75
75
|
val surfaceHeight = player.surfaceSize.height
|
|
76
76
|
val sourceWidth = player.videoSize.width
|
|
77
77
|
val sourceHeight = player.videoSize.height
|
|
78
|
+
val sourcePixelWidthHeightRatio = player.videoSize.pixelWidthHeightRatio
|
|
78
79
|
|
|
79
80
|
if (surfaceWidth == 0 || surfaceHeight == 0) {
|
|
80
81
|
return false
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
val surfaceAspectRatio = surfaceWidth.toFloat() / surfaceHeight
|
|
84
|
-
val trackAspectRatio = sourceWidth.toFloat() / sourceHeight
|
|
85
|
+
val trackAspectRatio = sourceWidth.toFloat() / sourceHeight * sourcePixelWidthHeightRatio
|
|
85
86
|
|
|
86
87
|
val videoSizeIsUnknown = sourceWidth == 0 || sourceHeight == 0
|
|
87
88
|
val hasFillContentFit = currentPlayerView.resizeMode == ContentFit.FILL.toResizeMode()
|
|
@@ -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.AvailableSubtitleTracksChangedEventPayload
|
|
11
|
+
import expo.modules.video.records.AvailableAudioTracksChangedEventPayload
|
|
10
12
|
import expo.modules.video.records.IsPlayingEventPayload
|
|
11
13
|
import expo.modules.video.records.MutedChangedEventPayload
|
|
12
14
|
import expo.modules.video.records.PlaybackError
|
|
@@ -15,6 +17,7 @@ import expo.modules.video.records.SourceChangedEventPayload
|
|
|
15
17
|
import expo.modules.video.records.StatusChangedEventPayload
|
|
16
18
|
import expo.modules.video.records.SubtitleTrack
|
|
17
19
|
import expo.modules.video.records.SubtitleTrackChangedEventPayload
|
|
20
|
+
import expo.modules.video.records.AudioTrackChangedEventPayload
|
|
18
21
|
import expo.modules.video.records.TimeUpdate
|
|
19
22
|
import expo.modules.video.records.VideoEventPayload
|
|
20
23
|
import expo.modules.video.records.VideoSource
|
|
@@ -74,6 +77,11 @@ sealed class PlayerEvent {
|
|
|
74
77
|
override val jsEventPayload = SubtitleTrackChangedEventPayload(subtitleTrack, oldSubtitleTrack)
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
data class AudioTrackChanged(val audioTrack: AudioTrack?, val oldAudioTrack: AudioTrack?) : PlayerEvent() {
|
|
81
|
+
override val name = "audioTrackChange"
|
|
82
|
+
override val jsEventPayload = AudioTrackChangedEventPayload(audioTrack, oldAudioTrack)
|
|
83
|
+
}
|
|
84
|
+
|
|
77
85
|
data class VideoTrackChanged(val videoTrack: VideoTrack?, val oldVideoTrack: VideoTrack?) : PlayerEvent() {
|
|
78
86
|
override val name = "videoTrackChange"
|
|
79
87
|
override val jsEventPayload = VideoTrackChangedEventPayload(videoTrack, oldVideoTrack)
|
|
@@ -94,18 +102,28 @@ sealed class PlayerEvent {
|
|
|
94
102
|
override val jsEventPayload = AvailableSubtitleTracksChangedEventPayload(availableSubtitleTracks, oldAvailableSubtitleTracks)
|
|
95
103
|
}
|
|
96
104
|
|
|
105
|
+
data class AvailableAudioTracksChanged(
|
|
106
|
+
val availableAudioTracks: List<AudioTrack>,
|
|
107
|
+
val oldAvailableAudioTracks: List<AudioTrack>
|
|
108
|
+
) : PlayerEvent() {
|
|
109
|
+
override val name = "availableAudioTracksChange"
|
|
110
|
+
override val jsEventPayload = AvailableAudioTracksChangedEventPayload(availableAudioTracks, oldAvailableAudioTracks)
|
|
111
|
+
}
|
|
112
|
+
|
|
97
113
|
data class VideoSourceLoaded(
|
|
98
114
|
val videoSource: VideoSource?,
|
|
99
115
|
val duration: Double,
|
|
100
116
|
val availableVideoTracks: List<VideoTrack>,
|
|
101
|
-
val availableSubtitleTracks: List<SubtitleTrack
|
|
117
|
+
val availableSubtitleTracks: List<SubtitleTrack>,
|
|
118
|
+
val availableAudioTracks: List<AudioTrack>
|
|
102
119
|
) : PlayerEvent() {
|
|
103
120
|
override val name = "sourceLoad"
|
|
104
121
|
override val jsEventPayload = VideoSourceLoadedEventPayload(
|
|
105
122
|
videoSource,
|
|
106
123
|
duration,
|
|
107
124
|
availableVideoTracks,
|
|
108
|
-
availableSubtitleTracks
|
|
125
|
+
availableSubtitleTracks,
|
|
126
|
+
availableAudioTracks
|
|
109
127
|
)
|
|
110
128
|
}
|
|
111
129
|
|
|
@@ -138,7 +156,8 @@ sealed class PlayerEvent {
|
|
|
138
156
|
is AudioMixingModeChanged -> listeners.forEach { it.onAudioMixingModeChanged(player, audioMixingMode, oldAudioMixingMode) }
|
|
139
157
|
is VideoTrackChanged -> listeners.forEach { it.onVideoTrackChanged(player, videoTrack, oldVideoTrack) }
|
|
140
158
|
is RenderedFirstFrame -> listeners.forEach { it.onRenderedFirstFrame(player) }
|
|
141
|
-
|
|
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
|
|
142
161
|
else -> Unit
|
|
143
162
|
}
|
|
144
163
|
}
|
|
@@ -54,6 +54,7 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
|
|
|
54
54
|
private var currentPlayerView = MutableWeakReference<PlayerView?>(null)
|
|
55
55
|
val loadControl: VideoPlayerLoadControl = VideoPlayerLoadControl.Builder().build()
|
|
56
56
|
val subtitles: VideoPlayerSubtitles = VideoPlayerSubtitles(this)
|
|
57
|
+
val audioTracks: VideoPlayerAudioTracks = VideoPlayerAudioTracks(this)
|
|
57
58
|
val trackSelector = DefaultTrackSelector(context)
|
|
58
59
|
|
|
59
60
|
val player = ExoPlayer
|
|
@@ -180,13 +181,17 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
|
|
|
180
181
|
|
|
181
182
|
override fun onTracksChanged(tracks: Tracks) {
|
|
182
183
|
val oldSubtitleTracks = ArrayList(subtitles.availableSubtitleTracks)
|
|
184
|
+
val oldAudioTracks = ArrayList(audioTracks.availableAudioTracks)
|
|
183
185
|
val oldCurrentTrack = subtitles.currentSubtitleTrack
|
|
186
|
+
val oldCurrentAudioTrack = audioTracks.currentAudioTrack
|
|
184
187
|
|
|
185
188
|
// Emit the tracks change event to update the subtitles
|
|
186
189
|
sendEvent(PlayerEvent.TracksChanged(tracks))
|
|
187
190
|
|
|
188
191
|
val newSubtitleTracks = subtitles.availableSubtitleTracks
|
|
192
|
+
val newAudioTracks = audioTracks.availableAudioTracks
|
|
189
193
|
val newCurrentSubtitleTrack = subtitles.currentSubtitleTrack
|
|
194
|
+
val newCurrentAudioTrack = audioTracks.currentAudioTrack
|
|
190
195
|
availableVideoTracks = tracks.toVideoTracks()
|
|
191
196
|
|
|
192
197
|
if (isLoadingNewSource) {
|
|
@@ -195,7 +200,8 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
|
|
|
195
200
|
commitedSource,
|
|
196
201
|
this@VideoPlayer.player.duration / 1000.0,
|
|
197
202
|
availableVideoTracks,
|
|
198
|
-
newSubtitleTracks
|
|
203
|
+
newSubtitleTracks,
|
|
204
|
+
newAudioTracks
|
|
199
205
|
)
|
|
200
206
|
)
|
|
201
207
|
isLoadingNewSource = false
|
|
@@ -204,18 +210,27 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou
|
|
|
204
210
|
if (!oldSubtitleTracks.toArray().contentEquals(newSubtitleTracks.toArray())) {
|
|
205
211
|
sendEvent(PlayerEvent.AvailableSubtitleTracksChanged(newSubtitleTracks, oldSubtitleTracks))
|
|
206
212
|
}
|
|
213
|
+
if (!oldAudioTracks.toArray().contentEquals(newAudioTracks.toArray())) {
|
|
214
|
+
sendEvent(PlayerEvent.AvailableAudioTracksChanged(newAudioTracks, oldAudioTracks))
|
|
215
|
+
}
|
|
207
216
|
if (oldCurrentTrack != newCurrentSubtitleTrack) {
|
|
208
217
|
sendEvent(PlayerEvent.SubtitleTrackChanged(newCurrentSubtitleTrack, oldCurrentTrack))
|
|
209
218
|
}
|
|
219
|
+
if (oldCurrentAudioTrack != newCurrentAudioTrack) {
|
|
220
|
+
sendEvent(PlayerEvent.AudioTrackChanged(newCurrentAudioTrack, oldCurrentAudioTrack))
|
|
221
|
+
}
|
|
210
222
|
super.onTracksChanged(tracks)
|
|
211
223
|
}
|
|
212
224
|
|
|
213
225
|
override fun onTrackSelectionParametersChanged(parameters: TrackSelectionParameters) {
|
|
214
226
|
val oldTrack = subtitles.currentSubtitleTrack
|
|
227
|
+
val oldAudioTrack = audioTracks.currentAudioTrack
|
|
215
228
|
sendEvent(PlayerEvent.TrackSelectionParametersChanged(parameters))
|
|
216
229
|
|
|
217
230
|
val newTrack = subtitles.currentSubtitleTrack
|
|
231
|
+
val newAudioTrack = audioTracks.currentAudioTrack
|
|
218
232
|
sendEvent(PlayerEvent.SubtitleTrackChanged(newTrack, oldTrack))
|
|
233
|
+
sendEvent(PlayerEvent.AudioTrackChanged(newAudioTrack, oldAudioTrack))
|
|
219
234
|
super.onTrackSelectionParametersChanged(parameters)
|
|
220
235
|
}
|
|
221
236
|
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
package expo.modules.video.player
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.OptIn
|
|
4
|
+
import androidx.media3.common.C
|
|
5
|
+
import androidx.media3.common.Format
|
|
6
|
+
import androidx.media3.common.MimeTypes
|
|
7
|
+
import androidx.media3.common.TrackGroup
|
|
8
|
+
import androidx.media3.common.TrackSelectionOverride
|
|
9
|
+
import androidx.media3.common.TrackSelectionParameters
|
|
10
|
+
import androidx.media3.common.Tracks
|
|
11
|
+
import androidx.media3.common.util.UnstableApi
|
|
12
|
+
import expo.modules.video.records.AudioTrack
|
|
13
|
+
import java.lang.ref.WeakReference
|
|
14
|
+
|
|
15
|
+
@OptIn(UnstableApi::class)
|
|
16
|
+
class VideoPlayerAudioTracks(owner: VideoPlayer) : VideoPlayerListener {
|
|
17
|
+
private val owner = WeakReference(owner)
|
|
18
|
+
private val videoPlayer: VideoPlayer?
|
|
19
|
+
get() {
|
|
20
|
+
return owner.get()
|
|
21
|
+
}
|
|
22
|
+
private val formatsToGroups = mutableMapOf<Format, Pair<TrackGroup, Int>>()
|
|
23
|
+
private var currentAudioTrackFormat: Format? = null
|
|
24
|
+
private var currentOverride: TrackSelectionOverride? = null
|
|
25
|
+
|
|
26
|
+
var currentAudioTrack: AudioTrack?
|
|
27
|
+
get() {
|
|
28
|
+
return AudioTrack.fromFormat(currentAudioTrackFormat)
|
|
29
|
+
}
|
|
30
|
+
set(value) {
|
|
31
|
+
applyAudioTrack(value)
|
|
32
|
+
}
|
|
33
|
+
val availableAudioTracks = arrayListOf<AudioTrack>()
|
|
34
|
+
|
|
35
|
+
init {
|
|
36
|
+
owner.addListener(this)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fun setAudioTracksEnabled(enabled: Boolean) {
|
|
40
|
+
val currentParams = videoPlayer?.player?.trackSelectionParameters ?: return
|
|
41
|
+
var params = currentParams.buildUpon().setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, !enabled).build()
|
|
42
|
+
if (!enabled) {
|
|
43
|
+
params = params.buildUpon().clearOverridesOfType(C.TRACK_TYPE_AUDIO).build()
|
|
44
|
+
}
|
|
45
|
+
videoPlayer?.player?.trackSelectionParameters = params
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// VideoPlayerListener
|
|
49
|
+
override fun onTrackSelectionParametersChanged(player: VideoPlayer, trackSelectionParameters: TrackSelectionParameters) {
|
|
50
|
+
currentAudioTrackFormat = findSelectedAudioFormat()
|
|
51
|
+
super.onTrackSelectionParametersChanged(player, trackSelectionParameters)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override fun onTracksChanged(player: VideoPlayer, tracks: Tracks) {
|
|
55
|
+
formatsToGroups.clear()
|
|
56
|
+
availableAudioTracks.clear()
|
|
57
|
+
for (group in tracks.groups) {
|
|
58
|
+
for (i in 0..<group.length) {
|
|
59
|
+
val format: Format = group.getTrackFormat(i)
|
|
60
|
+
|
|
61
|
+
if (MimeTypes.isAudio(format.sampleMimeType)) {
|
|
62
|
+
formatsToGroups[format] = group.mediaTrackGroup to i
|
|
63
|
+
val track = AudioTrack.fromFormat(format) ?: continue
|
|
64
|
+
availableAudioTracks.add(track)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
currentAudioTrackFormat = findSelectedAudioFormat()
|
|
69
|
+
super.onTracksChanged(player, tracks)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Private methods
|
|
73
|
+
private fun applyAudioTrack(audioTrack: AudioTrack?) {
|
|
74
|
+
val player = videoPlayer?.player ?: return
|
|
75
|
+
var newParameters: TrackSelectionParameters = player.trackSelectionParameters
|
|
76
|
+
currentOverride?.let { override ->
|
|
77
|
+
newParameters = newParameters.buildUpon().clearOverridesOfType(C.TRACK_TYPE_AUDIO).build()
|
|
78
|
+
}
|
|
79
|
+
if (audioTrack == null) {
|
|
80
|
+
player.trackSelectionParameters = newParameters
|
|
81
|
+
setAudioTracksEnabled(false)
|
|
82
|
+
currentOverride = null
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
val format = formatsToGroups.keys.firstOrNull {
|
|
86
|
+
it.id == audioTrack.id
|
|
87
|
+
}
|
|
88
|
+
format?.let {
|
|
89
|
+
formatsToGroups[it]?.let { subtitlePair ->
|
|
90
|
+
val trackSelectionOverride = TrackSelectionOverride(subtitlePair.first, subtitlePair.second)
|
|
91
|
+
newParameters = newParameters.buildUpon().addOverride(trackSelectionOverride).build()
|
|
92
|
+
player.trackSelectionParameters = newParameters
|
|
93
|
+
setAudioTracksEnabled(true)
|
|
94
|
+
currentOverride = trackSelectionOverride
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private fun findSelectedAudioFormat(): Format? {
|
|
100
|
+
val trackSelectionParameters = videoPlayer?.player?.trackSelectionParameters
|
|
101
|
+
val preferredAudioLanguages = trackSelectionParameters?.preferredAudioLanguages
|
|
102
|
+
val overriddenFormat: Format? = trackSelectionParameters?.overrides?.let {
|
|
103
|
+
for ((group, trackSelectionOverride) in it) {
|
|
104
|
+
if (group.type == C.TRACK_TYPE_AUDIO) {
|
|
105
|
+
// For audioTracks only one index will be replaced
|
|
106
|
+
return@let trackSelectionOverride.trackIndices.firstOrNull()?.let { index ->
|
|
107
|
+
group.getFormat(index)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return@let null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
val preferredFormat: Format? = preferredAudioLanguages?.let { preferredAudioLanguages ->
|
|
115
|
+
for (preferredLanguage in preferredAudioLanguages) {
|
|
116
|
+
return@let formatsToGroups.keys.firstOrNull {
|
|
117
|
+
it.language == preferredLanguage
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return@let null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return overriddenFormat ?: preferredFormat
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -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
|
}
|
|
@@ -29,6 +29,27 @@ class SubtitleTrack(
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
class AudioTrack(
|
|
33
|
+
@Field val id: String,
|
|
34
|
+
@Field val language: String?,
|
|
35
|
+
@Field val label: String?
|
|
36
|
+
) : Record, Serializable {
|
|
37
|
+
companion object {
|
|
38
|
+
fun fromFormat(format: Format?): AudioTrack? {
|
|
39
|
+
format ?: return null
|
|
40
|
+
val id = format.id ?: return null
|
|
41
|
+
val language = format.language
|
|
42
|
+
val label = language?.let { Locale(it).displayLanguage } ?: "Unknown"
|
|
43
|
+
|
|
44
|
+
return AudioTrack(
|
|
45
|
+
id = id,
|
|
46
|
+
language = language,
|
|
47
|
+
label = label
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
32
53
|
@OptIn(UnstableApi::class)
|
|
33
54
|
class VideoTrack(
|
|
34
55
|
@Field val id: String,
|
|
@@ -50,6 +50,11 @@ class SubtitleTrackChangedEventPayload(
|
|
|
50
50
|
@Field val oldSubtitleTrack: SubtitleTrack?
|
|
51
51
|
) : VideoEventPayload
|
|
52
52
|
|
|
53
|
+
class AudioTrackChangedEventPayload(
|
|
54
|
+
@Field val audioTrack: AudioTrack?,
|
|
55
|
+
@Field val oldAudioTrack: AudioTrack?
|
|
56
|
+
) : VideoEventPayload
|
|
57
|
+
|
|
53
58
|
class VideoTrackChangedEventPayload(
|
|
54
59
|
@Field val videoTrack: VideoTrack?,
|
|
55
60
|
@Field val oldVideoTrack: VideoTrack?
|
|
@@ -60,9 +65,15 @@ class AvailableSubtitleTracksChangedEventPayload(
|
|
|
60
65
|
@Field val oldAvailableSubtitleTracks: List<SubtitleTrack>
|
|
61
66
|
) : VideoEventPayload
|
|
62
67
|
|
|
68
|
+
class AvailableAudioTracksChangedEventPayload(
|
|
69
|
+
@Field val availableAudioTracks: List<AudioTrack>,
|
|
70
|
+
@Field val oldAvailableAudioTracks: List<AudioTrack>
|
|
71
|
+
) : VideoEventPayload
|
|
72
|
+
|
|
63
73
|
class VideoSourceLoadedEventPayload(
|
|
64
74
|
@Field val videoSource: VideoSource?,
|
|
65
75
|
@Field val duration: Double,
|
|
66
76
|
@Field val availableVideoTracks: List<VideoTrack>,
|
|
67
|
-
@Field val availableSubtitleTracks: List<SubtitleTrack
|
|
77
|
+
@Field val availableSubtitleTracks: List<SubtitleTrack>,
|
|
78
|
+
@Field val availableAudioTracks: List<AudioTrack>
|
|
68
79
|
) : VideoEventPayload
|