mirror of
https://github.com/zoriya/react-native-omni.git
synced 2026-08-16 02:44:51 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe51950bcf | ||
|
|
cd4ad50604 | ||
|
|
7578fe944e | ||
|
|
1789d00fdc | ||
|
|
49f08137f0 | ||
|
|
153b492e0a | ||
|
|
6ef72f22d2 |
+9
-10
@@ -5,7 +5,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:8.8.0"
|
||||
classpath "com.android.tools.build:gradle:9.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,16 +143,15 @@ dependencies {
|
||||
// Add a dependency on NitroModules
|
||||
implementation project(":react-native-nitro-modules")
|
||||
|
||||
implementation("org.videolan.android:libvlc-all:4.0.0-eap24")
|
||||
implementation("androidx.media3:media3-common:1.4.1")
|
||||
implementation("androidx.media3:media3-session:1.4.1")
|
||||
implementation("androidx.media3:media3-ui:1.4.1")
|
||||
implementation 'androidx.media3:media3-exoplayer:1.10.0'
|
||||
implementation("androidx.media3:media3-exoplayer-hls:1.10.0")
|
||||
|
||||
implementation("androidx.media3:media3-cast:1.10.0")
|
||||
implementation("org.videolan.android:libvlc-all:4.0.0-eap29")
|
||||
implementation("androidx.media3:media3-common:1.11.0")
|
||||
implementation("androidx.media3:media3-session:1.11.0")
|
||||
implementation("androidx.media3:media3-ui:1.11.0")
|
||||
implementation 'androidx.media3:media3-exoplayer:1.11.0'
|
||||
implementation("androidx.media3:media3-exoplayer-hls:1.11.0")
|
||||
implementation("androidx.media3:media3-cast:1.11.0")
|
||||
implementation("com.google.android.gms:play-services-cast-framework:22.3.1")
|
||||
implementation("androidx.mediarouter:mediarouter:1.7.0")
|
||||
implementation("androidx.mediarouter:mediarouter:1.8.1")
|
||||
}
|
||||
|
||||
if (isNewArchitectureEnabled()) {
|
||||
|
||||
@@ -116,6 +116,14 @@ class EventMap(private val tracks: TrackProvider) : HybridOmniEventMapSpec(), Pl
|
||||
sourceListeners.forEach { it(source) }
|
||||
}
|
||||
|
||||
fun emitPrev() {
|
||||
onPrevListeners.forEach { it() }
|
||||
}
|
||||
|
||||
fun emitNext() {
|
||||
onNextListeners.forEach { it() }
|
||||
}
|
||||
|
||||
private fun selectedTrack(trackType: Int): Track? {
|
||||
val list = when (trackType) {
|
||||
TRACK_TYPE_VIDEO -> tracks.videos
|
||||
|
||||
@@ -60,13 +60,18 @@ class OmniCastTrackSelector : CastTrackSelector() {
|
||||
val overrideMatch = indices.firstOrNull { i ->
|
||||
overrides.keys.any { it.id == groups[i].id }
|
||||
}
|
||||
val match = indices.firstOrNull { i ->
|
||||
val matchesLabel = { i: Int ->
|
||||
val name: String? = tracks[i].name
|
||||
val language: String? = tracks[i].language
|
||||
(labels.isNotEmpty() && name != null && name in labels) ||
|
||||
(languages.isNotEmpty() && language != null &&
|
||||
Util.normalizeLanguageCode(language) in languages)
|
||||
labels.isNotEmpty() && name != null && name in labels
|
||||
}
|
||||
val matchesLanguage = { i: Int ->
|
||||
val language: String? = tracks[i].language
|
||||
languages.isNotEmpty() && language != null &&
|
||||
Util.normalizeLanguageCode(language) in languages
|
||||
}
|
||||
val match = indices.firstOrNull { matchesLabel(it) && matchesLanguage(it) }
|
||||
?: indices.firstOrNull { matchesLabel(it) }
|
||||
?: indices.firstOrNull { matchesLanguage(it) }
|
||||
val chosen = when {
|
||||
overrideMatch != null -> overrideMatch
|
||||
match != null -> match
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package dev.zoriya.omni
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Looper
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
@@ -13,11 +15,16 @@ import androidx.media3.common.AudioAttributes
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.datasource.DefaultDataSource
|
||||
import androidx.media3.datasource.DefaultHttpDataSource
|
||||
import androidx.media3.exoplayer.DefaultRenderersFactory
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.Renderer
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import androidx.media3.exoplayer.text.TextOutput
|
||||
import androidx.media3.exoplayer.text.TextRenderer
|
||||
import com.margelo.nitro.NitroModules
|
||||
import com.margelo.nitro.omni.AndroidBackend
|
||||
import com.margelo.nitro.omni.CastStatus
|
||||
@@ -62,9 +69,26 @@ class OmniPlayer(
|
||||
AndroidBackend.EXOPLAYER -> {
|
||||
val http = DefaultHttpDataSource.Factory().setAllowCrossProtocolRedirects(true)
|
||||
httpDataSourceFactory = http
|
||||
ExoPlayer.Builder(ctx)
|
||||
// karaoke subs make exoplayer crash without this...
|
||||
val renderersFactory = object : DefaultRenderersFactory(ctx) {
|
||||
override fun buildTextRenderers(
|
||||
context: Context,
|
||||
output: TextOutput,
|
||||
outputLooper: Looper,
|
||||
extensionRendererMode: Int,
|
||||
out: ArrayList<Renderer>,
|
||||
) {
|
||||
out.add(
|
||||
TextRenderer(output, outputLooper).apply {
|
||||
experimentalSetLegacyDecodingEnabled(true)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
ExoPlayer.Builder(ctx, renderersFactory)
|
||||
.setMediaSourceFactory(
|
||||
DefaultMediaSourceFactory(DefaultDataSource.Factory(ctx, http))
|
||||
.experimentalParseSubtitlesDuringExtraction(false)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
@@ -187,6 +211,19 @@ class OmniPlayer(
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
private fun inferSubtitleMimeType(link: String): String? {
|
||||
val ext = link.toUri().lastPathSegment?.substringAfterLast('.', "")?.lowercase()
|
||||
return when (ext) {
|
||||
"vtt", "webvtt" -> MimeTypes.TEXT_VTT
|
||||
"srt" -> MimeTypes.APPLICATION_SUBRIP
|
||||
"ass", "ssa" -> MimeTypes.TEXT_SSA
|
||||
"ttml", "dfxp", "xml" -> MimeTypes.APPLICATION_TTML
|
||||
"sup" -> MimeTypes.APPLICATION_PGS
|
||||
"sub" -> MimeTypes.APPLICATION_VOBSUB
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildMediaItem(
|
||||
src: com.margelo.nitro.omni.VideoSrc,
|
||||
metadata: com.margelo.nitro.omni.Metadata?,
|
||||
@@ -212,7 +249,10 @@ class OmniPlayer(
|
||||
.setId(subtitle.id)
|
||||
.setLanguage(subtitle.language)
|
||||
.setLabel(subtitle.label)
|
||||
.setMimeType(subtitle.mimeType)
|
||||
// exoplayer requires a mime type to pick a subtitle parser.
|
||||
// fall back to inferring it from the link's extension so
|
||||
// consumers don't have to provide one.
|
||||
.setMimeType(subtitle.mimeType ?: inferSubtitleMimeType(subtitle.link))
|
||||
.build()
|
||||
})
|
||||
.setRequestMetadata(
|
||||
@@ -407,11 +447,11 @@ class OmniPlayer(
|
||||
}
|
||||
|
||||
override fun playPrev() {
|
||||
runOnMainThreadSync { player.seekToPreviousMediaItem() }
|
||||
runOnMainThreadSync { eventMap.emitPrev() }
|
||||
}
|
||||
|
||||
override fun playNext() {
|
||||
runOnMainThreadSync { player.seekToNextMediaItem() }
|
||||
runOnMainThreadSync { eventMap.emitNext() }
|
||||
}
|
||||
|
||||
override fun selectVideo(video: Track) {
|
||||
@@ -441,15 +481,15 @@ class OmniPlayer(
|
||||
}?.mediaTrackGroup
|
||||
if (group != null) {
|
||||
params.setOverrideForType(TrackSelectionOverride(group, 0))
|
||||
}
|
||||
// also set preferred flags for cast selection
|
||||
val labels = track.label?.let { arrayOf(it) } ?: emptyArray()
|
||||
if (type == C.TRACK_TYPE_AUDIO) {
|
||||
params.setPreferredAudioLanguage(track.language)
|
||||
.setPreferredAudioLabels(*labels)
|
||||
} else {
|
||||
val labels = track.label?.let { arrayOf(it) } ?: emptyArray()
|
||||
if (type == C.TRACK_TYPE_AUDIO) {
|
||||
params.setPreferredAudioLanguage(track.language)
|
||||
.setPreferredAudioLabels(*labels)
|
||||
} else {
|
||||
params.setPreferredTextLanguage(track.language)
|
||||
.setPreferredTextLabels(*labels)
|
||||
}
|
||||
params.setPreferredTextLanguage(track.language)
|
||||
.setPreferredTextLabels(*labels)
|
||||
}
|
||||
}
|
||||
player.trackSelectionParameters = params.build()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dev.zoriya.omni
|
||||
|
||||
import android.app.PictureInPictureParams
|
||||
import android.graphics.Color
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
import android.util.Rational
|
||||
@@ -117,6 +118,7 @@ class OmniView(val context: ThemedReactContext) :
|
||||
override var subtitleAssets: SubtitleAssets? = null
|
||||
|
||||
init {
|
||||
view.setBackgroundColor(Color.BLACK)
|
||||
context.addLifecycleEventListener(this)
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.videolan.libvlc.MediaPlayer
|
||||
import org.videolan.libvlc.interfaces.IMedia
|
||||
import org.videolan.libvlc.interfaces.IMedia.VideoTrack
|
||||
import org.videolan.libvlc.interfaces.IVLCVout
|
||||
import java.security.MessageDigest
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
class VlcPlayer(ctx: Context) :
|
||||
@@ -106,6 +107,7 @@ class VlcPlayer(ctx: Context) :
|
||||
@Volatile
|
||||
private var cachedBufferedPosition: Long = 0L
|
||||
private var boundSurfaceView: SurfaceView? = null
|
||||
private var lastVideoSize: VideoSize = VideoSize.UNKNOWN
|
||||
|
||||
private val availableCommands: Player.Commands = Player.Commands.Builder()
|
||||
.add(COMMAND_PLAY_PAUSE)
|
||||
@@ -138,6 +140,7 @@ class VlcPlayer(ctx: Context) :
|
||||
override fun onEvent(event: MediaPlayer.Event) {
|
||||
when (event.type) {
|
||||
MediaPlayer.Event.Opening -> {
|
||||
lastVideoSize = VideoSize.UNKNOWN
|
||||
notifyListeners(EVENT_PLAYBACK_STATE_CHANGED) {
|
||||
it.onPlaybackStateChanged(STATE_BUFFERING)
|
||||
}
|
||||
@@ -160,6 +163,7 @@ class VlcPlayer(ctx: Context) :
|
||||
it.onIsPlayingChanged(true)
|
||||
it.onTracksChanged(getCurrentTracks())
|
||||
}
|
||||
maybeNotifyVideoSizeChanged()
|
||||
}
|
||||
|
||||
MediaPlayer.Event.Paused -> {
|
||||
@@ -213,12 +217,19 @@ class VlcPlayer(ctx: Context) :
|
||||
// this improves perf & battery life (native -> js bridge is expensive)
|
||||
MediaPlayer.Event.TimeChanged -> Unit
|
||||
|
||||
// vlc has no video-size event, Vout event still has unknown size.
|
||||
// This is the first callback with known size.
|
||||
MediaPlayer.Event.PositionChanged -> {
|
||||
if (lastVideoSize == VideoSize.UNKNOWN) maybeNotifyVideoSizeChanged()
|
||||
}
|
||||
|
||||
MediaPlayer.Event.ESAdded,
|
||||
MediaPlayer.Event.ESDeleted,
|
||||
MediaPlayer.Event.ESSelected -> {
|
||||
notifyListeners(EVENT_TRACKS_CHANGED) {
|
||||
it.onTracksChanged(getCurrentTracks())
|
||||
}
|
||||
maybeNotifyVideoSizeChanged()
|
||||
}
|
||||
|
||||
MediaPlayer.Event.LengthChanged -> {
|
||||
@@ -377,6 +388,20 @@ class VlcPlayer(ctx: Context) :
|
||||
}
|
||||
}
|
||||
|
||||
// vlc keys each slave's tracks by the md5 of its uri ("<md5(uri)>/spu/<n>")
|
||||
private fun subtitleSlaveForTrackId(trackId: String): MediaItem.SubtitleConfiguration? {
|
||||
val hash = trackId.substringBefore("/spu/", "")
|
||||
if (hash.isEmpty()) return null
|
||||
return mediaItems.getOrNull(currentMediaItemIndex)
|
||||
?.localConfiguration?.subtitleConfigurations
|
||||
?.firstOrNull { md5(it.uri.toString()) == hash }
|
||||
}
|
||||
|
||||
private fun md5(value: String): String =
|
||||
MessageDigest.getInstance("MD5")
|
||||
.digest(value.toByteArray())
|
||||
.joinToString("") { "%02x".format(it.toInt() and 0xFF) }
|
||||
|
||||
// vlc doesn't allow arbitrary headers :c
|
||||
private fun applyRequestHeaders(media: Media, extras: Bundle?) {
|
||||
if (extras == null) return
|
||||
@@ -589,10 +614,12 @@ class VlcPlayer(ctx: Context) :
|
||||
}
|
||||
|
||||
player.getTracks(IMedia.Track.Type.Text)?.forEach { track ->
|
||||
// external subs don't have label/language, match them back to surface them
|
||||
val slave = subtitleSlaveForTrackId(track.id)
|
||||
val format = Format.Builder()
|
||||
.setId(track.id)
|
||||
.setLabel(track.name)
|
||||
.setLanguage(track.language)
|
||||
.setLabel(slave?.label ?: track.name)
|
||||
.setLanguage(slave?.language ?: track.language)
|
||||
.setSampleMimeType("text/x-unknown")
|
||||
.build()
|
||||
val group = TrackGroup("vlc-sub-${track.id}", format)
|
||||
@@ -909,10 +936,30 @@ class VlcPlayer(ctx: Context) :
|
||||
|
||||
override fun clearVideoTextureView(textureView: TextureView?) = Unit
|
||||
|
||||
/**
|
||||
* VLC never pushes a video-size event, so emit our own once the selected video
|
||||
* track (and its sample aspect ratio) is known. Listeners such as OmniView use
|
||||
* this to size the content frame -> the PIP aspect ratio matches the video
|
||||
* instead of the raw SurfaceView bounds (ExoPlayer does this on its own).
|
||||
*/
|
||||
private fun maybeNotifyVideoSizeChanged() {
|
||||
val size = getVideoSize()
|
||||
if (size == VideoSize.UNKNOWN || size == lastVideoSize) return
|
||||
lastVideoSize = size
|
||||
notifyListeners(EVENT_VIDEO_SIZE_CHANGED) {
|
||||
it.onVideoSizeChanged(size)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getVideoSize(): VideoSize {
|
||||
val videoTrack = player.getSelectedTrack(IMedia.Track.Type.Video) as? VideoTrack ?: return VideoSize.UNKNOWN
|
||||
if (videoTrack.width <= 0 || videoTrack.height <= 0) return VideoSize.UNKNOWN
|
||||
return VideoSize(videoTrack.width, videoTrack.height)
|
||||
val par = if (videoTrack.sarNum > 0 && videoTrack.sarDen > 0) {
|
||||
videoTrack.sarNum.toFloat() / videoTrack.sarDen.toFloat()
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
return VideoSize(videoTrack.width, videoTrack.height, par)
|
||||
}
|
||||
|
||||
override fun getSurfaceSize(): Size = Size.UNKNOWN
|
||||
|
||||
@@ -6,7 +6,12 @@
|
||||
"name": "react-native-omni",
|
||||
"dependencies": {
|
||||
"@expo/config-plugins": "^57.0.3",
|
||||
"@videojs/react": "10.0.0-beta.25",
|
||||
"@videojs/core": "github:zoriya/v10#build26-core",
|
||||
"@videojs/media": "github:zoriya/v10#build26-media",
|
||||
"@videojs/react": "github:zoriya/v10#build26-react",
|
||||
"@videojs/spf": "github:zoriya/v10#build26-spf",
|
||||
"@videojs/store": "github:zoriya/v10#build26-store",
|
||||
"@videojs/utils": "github:zoriya/v10#build26-utils",
|
||||
"jassub": "^2.5.7",
|
||||
"libpgs": "^0.8.1",
|
||||
},
|
||||
@@ -723,15 +728,17 @@
|
||||
|
||||
"@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="],
|
||||
|
||||
"@videojs/core": ["@videojs/core@10.0.0-beta.25", "", { "dependencies": { "@videojs/spf": "10.0.0-beta.25", "@videojs/store": "10.0.0-beta.25", "@videojs/utils": "10.0.0-beta.25", "@vimeo/player": "^2.30.3", "dashjs": "^5.2.0", "hls.js": "^1.6.7", "mux-embed": "^5.17.10" } }, "sha512-Yxd6c+u7LrYl6aKzZRyR5PjDqUt1lUbgFwIhg6pMzI8bJszIGjLS7TDzXdALpEFJpM1Z08dbqCEzCGaMj7Eu6A=="],
|
||||
"@videojs/core": ["@videojs/core@github:zoriya/v10#02a4769", { "peerDependencies": { "@videojs/media": "*", "@videojs/store": "*", "@videojs/utils": "*" } }, "zoriya-v10-02a4769", "sha512-eq79BQJ+e2Lx6k9tCW68vBolcJD3OynwgE7Y8pmI9gb+RWQAYJFLEY8B576ueKHhcgOOT/taerI/Wg3eqougSg=="],
|
||||
|
||||
"@videojs/react": ["@videojs/react@10.0.0-beta.25", "", { "dependencies": { "@videojs/core": "10.0.0-beta.25", "@videojs/spf": "10.0.0-beta.25", "@videojs/store": "10.0.0-beta.25", "@videojs/utils": "10.0.0-beta.25" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-MUG8NFntK3u4OTArwqG4utZ11HU1Ku5kzzTfTA2BMG5shNj7KWqPz0BMR2Nnv6hwEvcxpm9vwZxk+AwkmoB5jA=="],
|
||||
"@videojs/media": ["@videojs/media@github:zoriya/v10#46507f9", { "dependencies": { "@vimeo/player": "^2.30.3", "dashjs": "^5.2.0", "hls.js": "^1.6.7", "mux-embed": "^5.17.10" }, "peerDependencies": { "@videojs/spf": "*", "@videojs/utils": "*" } }, "zoriya-v10-46507f9", "sha512-1f7jIP5KPGKo4NeyG+Q22eS7KhvYZam8yvG985Y9edCp7wFcrVcraEQwKJrESaa/GDNgeKIeEg05MZ+l+ZHftQ=="],
|
||||
|
||||
"@videojs/spf": ["@videojs/spf@10.0.0-beta.25", "", { "dependencies": { "@videojs/utils": "10.0.0-beta.25", "signal-polyfill": "^0.2.2" } }, "sha512-SETOzCPbSxuzCoX+bF2u7/Z80AA/Xwpqpy5ki//yslIyPxWkMT0GFydXdoZ/bJpNsBqGmDbDxEBDo1uMjnM4mQ=="],
|
||||
"@videojs/react": ["@videojs/react@github:zoriya/v10#784a383", { "peerDependencies": { "@videojs/core": "*", "@videojs/media": "*", "@videojs/spf": "*", "@videojs/store": "*", "@videojs/utils": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "zoriya-v10-784a383", "sha512-wLuXUjLfhLNZAVSxCsTmpidN8DZPy6Us788vXDSRJKh/a6WDTCWV2op26EK3eCgeC1P8Hwc5s5cOEhqx2fGMRQ=="],
|
||||
|
||||
"@videojs/store": ["@videojs/store@10.0.0-beta.25", "", { "dependencies": { "@videojs/utils": "10.0.0-beta.25" }, "peerDependencies": { "@videojs/element": "10.0.0-beta.25", "react": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@videojs/element", "react"] }, "sha512-XvPC0+MQ6AZ/oKGpSzmjOM56aYESMpPsTg5zddBDXhcqeAOUxpEsXDqhU1rsm751tU4ZavGxHENx70lBOa5dUA=="],
|
||||
"@videojs/spf": ["@videojs/spf@github:zoriya/v10#8920135", { "dependencies": { "signal-polyfill": "^0.2.2" }, "peerDependencies": { "@videojs/utils": "*" } }, "zoriya-v10-8920135", "sha512-jGXo5tyPpIk96XWksTKbWpi7POHzdt4KtvqDZ9+SDa+MOqLV3TcgM8soo6KO2jMnkv8d8APqaz+E27qfDf16kg=="],
|
||||
|
||||
"@videojs/utils": ["@videojs/utils@10.0.0-beta.25", "", {}, "sha512-tq/848qmzgTncopeYE5iiYs1Pfzj4OEdSe7zE2H6xoOB0PUMlnPOThA3aybHJ8Lh9bmw+wTb6QJ0NZanIzt2Zg=="],
|
||||
"@videojs/store": ["@videojs/store@github:zoriya/v10#3f9bddd", { "peerDependencies": { "@videojs/element": "*", "@videojs/utils": "*", "react": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@videojs/element", "react"] }, "zoriya-v10-3f9bddd", "sha512-QjSGJ6EO6uoij41F7KgMKrNi5XjvXfkODOrn17wcFa6+Ig1+ftnckkNZsJ44bARdJ1XUkuj3wNyGidYMrmKRmQ=="],
|
||||
|
||||
"@videojs/utils": ["@videojs/utils@github:zoriya/v10#9fed1b6", {}, "zoriya-v10-9fed1b6", "sha512-ZgbMw3v41aEru0056VVwy0UtiZk8zq6Tm6ddRAJtm5VXz2nEf36CoejFFvmGICW5cQ2WWhvMQmtqCSr+HmYGIw=="],
|
||||
|
||||
"@vimeo/player": ["@vimeo/player@2.30.4", "", { "dependencies": { "native-promise-only": "0.8.1", "weakmap-polyfill": "2.0.4" } }, "sha512-M8m1UAhJSb+KCWuXDLWHViwj+3YY/0ogwFquRfMHs9e9LYjXT9iB7n+sOCKwUusbiXuU2HKmXx+FEGHtYZfUSg=="],
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+6
-1
@@ -63,7 +63,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/config-plugins": "^57.0.3",
|
||||
"@videojs/react": "10.0.0-beta.25",
|
||||
"@videojs/core": "github:zoriya/v10#build26-core",
|
||||
"@videojs/media": "github:zoriya/v10#build26-media",
|
||||
"@videojs/react": "github:zoriya/v10#build26-react",
|
||||
"@videojs/spf": "github:zoriya/v10#build26-spf",
|
||||
"@videojs/store": "github:zoriya/v10#build26-store",
|
||||
"@videojs/utils": "github:zoriya/v10#build26-utils",
|
||||
"jassub": "^2.5.7",
|
||||
"libpgs": "^0.8.1"
|
||||
},
|
||||
|
||||
@@ -59,7 +59,6 @@ export class WebOmniPlayer implements OmniPlayer {
|
||||
this._store.state.toggleRemotePlayback();
|
||||
}
|
||||
|
||||
castOptions: CastOptions | null = null;
|
||||
_source: Source | undefined = undefined;
|
||||
private _showNotification = false;
|
||||
|
||||
|
||||
+19
-5
@@ -1,4 +1,5 @@
|
||||
import { createPlayer } from "@videojs/react";
|
||||
import { GoogleCast } from "@videojs/react/media/google-cast";
|
||||
import { videoFeatures } from "@videojs/react/video";
|
||||
import {
|
||||
createContext,
|
||||
@@ -7,6 +8,7 @@ import {
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { usePlayerState } from "./events";
|
||||
import { WebOmniPlayer } from "./player.web";
|
||||
import type { OmniPlayer, PlayerBackend } from "./types/player";
|
||||
import type { CastOptions, Source } from "./types/source";
|
||||
@@ -67,15 +69,27 @@ const PlayerInitializer = ({
|
||||
}
|
||||
}, [source, store]);
|
||||
|
||||
useEffect(() => {
|
||||
player.castOptions = cast ?? null;
|
||||
}, [cast]);
|
||||
|
||||
useEffect(() => {
|
||||
player.showNotification = showNotification;
|
||||
}, [showNotification]);
|
||||
|
||||
return <PlayerCtx.Provider value={player}>{children}</PlayerCtx.Provider>;
|
||||
return (
|
||||
<PlayerCtx.Provider value={player}>
|
||||
<CastBridge cast={cast} />
|
||||
{children}
|
||||
</PlayerCtx.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const CastBridge = ({ cast }: { cast?: CastOptions | null }) => {
|
||||
const source = usePlayerState("source");
|
||||
return (
|
||||
<GoogleCast
|
||||
receiver={cast?.receiverApplicationId}
|
||||
src={source?.castId}
|
||||
customData={source?.castData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const usePlayer = () => useContext(PlayerCtx);
|
||||
|
||||
+2
-10
@@ -1,4 +1,4 @@
|
||||
import type { HlsMediaConfig } from "@videojs/core/dom/media/hls-js";
|
||||
import type { HlsMediaConfig } from "@videojs/media/dom/hls-js";
|
||||
import { HlsJsVideo } from "@videojs/react/media/hlsjs-video";
|
||||
import { Video } from "@videojs/react/video";
|
||||
import {
|
||||
@@ -106,7 +106,6 @@ export const OmniView = ({
|
||||
autoplay,
|
||||
subtitleAssets,
|
||||
}: OmniViewProps & { style: CSSProperties }) => {
|
||||
const player = usePlayer() as WebOmniPlayer;
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const ref = useRef<HTMLVideoElement>(undefined!);
|
||||
const source = usePlayerState("source");
|
||||
@@ -119,8 +118,6 @@ export const OmniView = ({
|
||||
|
||||
const headersRef = useRef(source?.src?.headers);
|
||||
headersRef.current = source?.src?.headers;
|
||||
const castId = source?.castId;
|
||||
const castData = source?.castData;
|
||||
|
||||
const config = useMemo<HlsMediaConfig>(
|
||||
() => ({
|
||||
@@ -133,13 +130,8 @@ export const OmniView = ({
|
||||
}
|
||||
},
|
||||
},
|
||||
googleCast: {
|
||||
receiver: player.castOptions?.receiverApplicationId,
|
||||
customData: castData ?? null,
|
||||
...(castId && { src: castId }),
|
||||
},
|
||||
}),
|
||||
[player.castOptions, castData, castId],
|
||||
[],
|
||||
);
|
||||
|
||||
// While casting, the receiver renders subtitles (the player forwards the
|
||||
|
||||
Reference in New Issue
Block a user