mirror of
https://github.com/zoriya/react-native-omni.git
synced 2026-08-16 02:44:51 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2ca76b4a9 | ||
|
|
77a8b2337d | ||
|
|
cebe97f168 | ||
|
|
e5615cbc0c |
@@ -106,21 +106,34 @@ class OmniPlayer(
|
|||||||
|
|
||||||
override var showNotification: Boolean? = false
|
override var showNotification: Boolean? = false
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value == true) {
|
field = value
|
||||||
|
syncNotificationService()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var serviceRunning = false
|
||||||
|
|
||||||
|
private fun syncNotificationService() {
|
||||||
|
val shouldShow = showNotification == true && source != null
|
||||||
|
when {
|
||||||
|
shouldShow && !serviceRunning -> {
|
||||||
val otherIsPlaying = notificationPlayer?.let { other ->
|
val otherIsPlaying = notificationPlayer?.let { other ->
|
||||||
runOnMainThreadSync { other.isPlaying }
|
other !== localPlayer && runOnMainThreadSync { other.isPlaying }
|
||||||
} == true
|
} == true
|
||||||
if (otherIsPlaying) {
|
if (otherIsPlaying) {
|
||||||
throw Error("Two players can't display notifications at the same time.")
|
throw Error("Two players can't display notifications at the same time.")
|
||||||
}
|
}
|
||||||
notificationPlayer = localPlayer
|
notificationPlayer = localPlayer
|
||||||
ctx.startForegroundService(Intent(ctx, OmniPlayerService::class.java))
|
ctx.startForegroundService(Intent(ctx, OmniPlayerService::class.java))
|
||||||
} else if (field == true && notificationPlayer == localPlayer) {
|
serviceRunning = true
|
||||||
|
}
|
||||||
|
|
||||||
|
!shouldShow && serviceRunning -> {
|
||||||
ctx.stopService(Intent(ctx, OmniPlayerService::class.java))
|
ctx.stopService(Intent(ctx, OmniPlayerService::class.java))
|
||||||
notificationPlayer = null
|
if (notificationPlayer == localPlayer) notificationPlayer = null
|
||||||
|
serviceRunning = false
|
||||||
}
|
}
|
||||||
field = value
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val castStatus: CastStatus
|
override val castStatus: CastStatus
|
||||||
get() = runOnMainThreadSync { computeCastStatus() }
|
get() = runOnMainThreadSync { computeCastStatus() }
|
||||||
@@ -266,7 +279,7 @@ class OmniPlayer(
|
|||||||
|
|
||||||
override var playbackRate by mainThreadProperty(
|
override var playbackRate by mainThreadProperty(
|
||||||
get = { player.playbackParameters.speed.toDouble() },
|
get = { player.playbackParameters.speed.toDouble() },
|
||||||
set = { value -> player.setPlaybackSpeed(value.toFloat().coerceAtLeast(0f)) }
|
set = { value -> player.setPlaybackSpeed(value.toFloat().coerceAtLeast(0.01f)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
override var muted by mainThreadProperty(
|
override var muted by mainThreadProperty(
|
||||||
@@ -297,6 +310,7 @@ class OmniPlayer(
|
|||||||
runOnMainThreadSync {
|
runOnMainThreadSync {
|
||||||
player.clearMediaItems()
|
player.clearMediaItems()
|
||||||
}
|
}
|
||||||
|
syncNotificationService()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val handleAudioFocus =
|
val handleAudioFocus =
|
||||||
@@ -329,6 +343,7 @@ class OmniPlayer(
|
|||||||
player.setMediaItems(mediaItems, startIndex, startPositionMs.toLong())
|
player.setMediaItems(mediaItems, startIndex, startPositionMs.toLong())
|
||||||
player.prepare()
|
player.prepare()
|
||||||
}
|
}
|
||||||
|
syncNotificationService()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun play() {
|
override fun play() {
|
||||||
@@ -504,7 +519,13 @@ class OmniPlayerService : MediaSessionService() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
player = OmniPlayer.notificationPlayer ?: throw Error("No player available")
|
val available = OmniPlayer.notificationPlayer
|
||||||
|
if (available == null) {
|
||||||
|
startForeground(1, createImmediateNotification())
|
||||||
|
stopSelf()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
player = available
|
||||||
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)?.apply {
|
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)?.apply {
|
||||||
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||||
}
|
}
|
||||||
@@ -540,7 +561,8 @@ class OmniPlayerService : MediaSessionService() {
|
|||||||
player = current
|
player = current
|
||||||
mediaSession.player = current
|
mediaSession.player = current
|
||||||
}
|
}
|
||||||
return super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createImmediateNotification(): Notification {
|
private fun createImmediateNotification(): Notification {
|
||||||
@@ -581,7 +603,7 @@ class OmniPlayerService : MediaSessionService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
mediaSession.release()
|
if (::mediaSession.isInitialized) mediaSession.release()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.app.PictureInPictureParams
|
|||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Rational
|
import android.util.Rational
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -11,6 +12,10 @@ import android.view.ViewGroup
|
|||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.common.VideoSize
|
||||||
|
import androidx.media3.common.text.CueGroup
|
||||||
|
import androidx.media3.ui.AspectRatioFrameLayout
|
||||||
|
import androidx.media3.ui.SubtitleView
|
||||||
import com.facebook.react.bridge.LifecycleEventListener
|
import com.facebook.react.bridge.LifecycleEventListener
|
||||||
import com.facebook.react.uimanager.ThemedReactContext
|
import com.facebook.react.uimanager.ThemedReactContext
|
||||||
import com.margelo.nitro.omni.HybridOmniPlayerSpec
|
import com.margelo.nitro.omni.HybridOmniPlayerSpec
|
||||||
@@ -68,6 +73,16 @@ class OmniView(val context: ThemedReactContext) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val contentFrame = AspectRatioFrameLayout(context).apply {
|
||||||
|
setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT)
|
||||||
|
layoutParams = FrameLayout.LayoutParams(
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
Gravity.CENTER
|
||||||
|
)
|
||||||
|
view.addView(this)
|
||||||
|
}
|
||||||
|
|
||||||
private val surfaceView = SurfaceView(context).apply {
|
private val surfaceView = SurfaceView(context).apply {
|
||||||
layoutParams = FrameLayout.LayoutParams(
|
layoutParams = FrameLayout.LayoutParams(
|
||||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
@@ -75,8 +90,19 @@ class OmniView(val context: ThemedReactContext) :
|
|||||||
)
|
)
|
||||||
holder.addCallback(this@OmniView)
|
holder.addCallback(this@OmniView)
|
||||||
addOnLayoutChangeListener(this@OmniView)
|
addOnLayoutChangeListener(this@OmniView)
|
||||||
|
contentFrame.addView(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val subtitleView = SubtitleView(context).apply {
|
||||||
|
layoutParams = FrameLayout.LayoutParams(
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
setUserDefaultStyle()
|
||||||
|
setUserDefaultTextSize()
|
||||||
view.addView(this)
|
view.addView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var surfaceReady = false
|
private var surfaceReady = false
|
||||||
private var boundPlayer: OmniPlayer? = null
|
private var boundPlayer: OmniPlayer? = null
|
||||||
private var rootContent: ViewGroup? = null
|
private var rootContent: ViewGroup? = null
|
||||||
@@ -94,6 +120,20 @@ class OmniView(val context: ThemedReactContext) :
|
|||||||
context.addLifecycleEventListener(this)
|
context.addLifecycleEventListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onVideoSizeChanged(videoSize: VideoSize) {
|
||||||
|
contentFrame.setAspectRatio(
|
||||||
|
if (videoSize.width > 0 && videoSize.height > 0) {
|
||||||
|
videoSize.width * videoSize.pixelWidthHeightRatio / videoSize.height
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCues(cueGroup: CueGroup) {
|
||||||
|
subtitleView.setCues(cueGroup.cues)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onLayoutChange(
|
override fun onLayoutChange(
|
||||||
view: View?,
|
view: View?,
|
||||||
left: Int,
|
left: Int,
|
||||||
@@ -219,6 +259,11 @@ class OmniView(val context: ThemedReactContext) :
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boundPlayer?.isCasting == true) {
|
||||||
|
clearPictureInPictureParams()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val autoEnterEnabled =
|
val autoEnterEnabled =
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
||||||
autoPip == true &&
|
autoPip == true &&
|
||||||
@@ -242,9 +287,18 @@ class OmniView(val context: ThemedReactContext) :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun clampedAspectRatio(width: Int, height: Int): Rational {
|
||||||
|
val minRatio = 0.42
|
||||||
|
val maxRatio = 2.38
|
||||||
|
val ratio = width.toDouble() / height.toDouble()
|
||||||
|
if (ratio in minRatio..maxRatio) return Rational(width, height)
|
||||||
|
val clamped = ratio.coerceIn(minRatio, maxRatio)
|
||||||
|
return Rational((clamped * 1000).toInt(), 1000)
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildPipParams(autoEnterEnabled: Boolean): PictureInPictureParams {
|
private fun buildPipParams(autoEnterEnabled: Boolean): PictureInPictureParams {
|
||||||
val aspectRatio = if (surfaceView.width > 0 && surfaceView.height > 0) {
|
val aspectRatio = if (surfaceView.width > 0 && surfaceView.height > 0) {
|
||||||
Rational(surfaceView.width, surfaceView.height)
|
clampedAspectRatio(surfaceView.width, surfaceView.height)
|
||||||
} else {
|
} else {
|
||||||
Rational(16, 9)
|
Rational(16, 9)
|
||||||
}
|
}
|
||||||
@@ -293,7 +347,7 @@ class OmniView(val context: ThemedReactContext) :
|
|||||||
child.visibility = View.GONE
|
child.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
view.removeView(surfaceView)
|
contentFrame.removeView(surfaceView)
|
||||||
root.addView(
|
root.addView(
|
||||||
surfaceView,
|
surfaceView,
|
||||||
FrameLayout.LayoutParams(
|
FrameLayout.LayoutParams(
|
||||||
@@ -317,7 +371,7 @@ class OmniView(val context: ThemedReactContext) :
|
|||||||
}
|
}
|
||||||
rootContentViews = emptyList()
|
rootContentViews = emptyList()
|
||||||
|
|
||||||
view.addView(surfaceView, FrameLayout.LayoutParams(
|
contentFrame.addView(surfaceView, FrameLayout.LayoutParams(
|
||||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
FrameLayout.LayoutParams.MATCH_PARENT
|
FrameLayout.LayoutParams.MATCH_PARENT
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -97,9 +97,11 @@ class VlcPlayer(ctx: Context) :
|
|||||||
private var mediaItems: List<MediaItem> = emptyList()
|
private var mediaItems: List<MediaItem> = emptyList()
|
||||||
private var currentMediaItemIndex: Int = INDEX_UNSET
|
private var currentMediaItemIndex: Int = INDEX_UNSET
|
||||||
private var currentTrackSelectionParameters = TrackSelectionParameters.Builder().build()
|
private var currentTrackSelectionParameters = TrackSelectionParameters.Builder().build()
|
||||||
|
@Volatile
|
||||||
private var playerError: PlaybackException? = null
|
private var playerError: PlaybackException? = null
|
||||||
private var playlistMetadata: MediaMetadata = MediaMetadata.EMPTY
|
private var playlistMetadata: MediaMetadata = MediaMetadata.EMPTY
|
||||||
private var userInitiatedTransition: Boolean = false
|
private var userInitiatedTransition: Boolean = false
|
||||||
|
@Volatile
|
||||||
private var cachedBufferedPosition: Long = 0L
|
private var cachedBufferedPosition: Long = 0L
|
||||||
private var boundSurfaceView: SurfaceView? = null
|
private var boundSurfaceView: SurfaceView? = null
|
||||||
|
|
||||||
@@ -467,7 +469,11 @@ class VlcPlayer(ctx: Context) :
|
|||||||
|
|
||||||
override fun getShuffleModeEnabled(): Boolean = false
|
override fun getShuffleModeEnabled(): Boolean = false
|
||||||
|
|
||||||
override fun isLoading() = player.playerState == IMedia.State.Opening
|
override fun isLoading(): Boolean {
|
||||||
|
val state = playbackState
|
||||||
|
if (state == STATE_IDLE || state == STATE_ENDED) return false
|
||||||
|
return player.playerState == IMedia.State.Opening
|
||||||
|
}
|
||||||
|
|
||||||
override fun seekTo(
|
override fun seekTo(
|
||||||
mediaItemIndex: Int,
|
mediaItemIndex: Int,
|
||||||
@@ -493,11 +499,11 @@ class VlcPlayer(ctx: Context) :
|
|||||||
override fun getSeekForwardIncrement(): Long = 15_000L
|
override fun getSeekForwardIncrement(): Long = 15_000L
|
||||||
|
|
||||||
override fun setPlaybackParameters(playbackParameters: PlaybackParameters) {
|
override fun setPlaybackParameters(playbackParameters: PlaybackParameters) {
|
||||||
player.rate = playbackParameters.speed.coerceAtLeast(0f)
|
player.rate = playbackParameters.speed.coerceAtLeast(0.01f)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPlaybackParameters(): PlaybackParameters =
|
override fun getPlaybackParameters(): PlaybackParameters =
|
||||||
PlaybackParameters(player.rate.coerceAtLeast(0f))
|
PlaybackParameters(player.rate.takeIf { it > 0f } ?: 1f)
|
||||||
|
|
||||||
override fun stop() {
|
override fun stop() {
|
||||||
player.stop()
|
player.stop()
|
||||||
@@ -505,6 +511,7 @@ class VlcPlayer(ctx: Context) :
|
|||||||
|
|
||||||
override fun release() {
|
override fun release() {
|
||||||
player.setEventListener(null)
|
player.setEventListener(null)
|
||||||
|
listeners.release()
|
||||||
abandonAudioFocus()
|
abandonAudioFocus()
|
||||||
player.stop()
|
player.stop()
|
||||||
clearVideoSurface()
|
clearVideoSurface()
|
||||||
@@ -518,36 +525,43 @@ class VlcPlayer(ctx: Context) :
|
|||||||
val selectedAudio = player.getSelectedTrack(IMedia.Track.Type.Audio)
|
val selectedAudio = player.getSelectedTrack(IMedia.Track.Type.Audio)
|
||||||
val selectedSubtitle = player.getSelectedTrack(IMedia.Track.Type.Text)
|
val selectedSubtitle = player.getSelectedTrack(IMedia.Track.Type.Text)
|
||||||
|
|
||||||
val videoTracks = player.getTracks(IMedia.Track.Type.Video)
|
player.getTracks(IMedia.Track.Type.Video).orEmpty()
|
||||||
if (!videoTracks.isNullOrEmpty()) {
|
.groupBy { listOf(it.language, it.name, it.description) }
|
||||||
val videoFormats = videoTracks.map { track ->
|
.values.forEach { videoTracks ->
|
||||||
Format.Builder()
|
val videoFormats = videoTracks.map { track ->
|
||||||
.setId(track.id)
|
Format.Builder()
|
||||||
.setLabel(track.name)
|
.setId(track.id)
|
||||||
.setLanguage(track.language)
|
.setLabel(track.name)
|
||||||
.setSampleMimeType("video/x-unknown")
|
.setLanguage(track.language)
|
||||||
.build()
|
.setSampleMimeType("video/x-unknown")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
val group = TrackGroup("vlc-video-${videoTracks.minOf { it.id }}", *videoFormats.toTypedArray())
|
||||||
|
val selected = BooleanArray(videoFormats.size) { idx ->
|
||||||
|
selectedVideo != null && selectedVideo.id == videoTracks[idx].id
|
||||||
|
}
|
||||||
|
val support = IntArray(videoFormats.size) { FORMAT_HANDLED }
|
||||||
|
result.add(Group(group, videoFormats.size > 1, support, selected))
|
||||||
}
|
}
|
||||||
val group = TrackGroup("vlc-video", *videoFormats.toTypedArray())
|
|
||||||
val selected = BooleanArray(videoFormats.size) { idx ->
|
|
||||||
selectedVideo != null && selectedVideo.id == videoTracks[idx].id
|
|
||||||
}
|
|
||||||
val support = IntArray(videoFormats.size) { FORMAT_HANDLED }
|
|
||||||
result.add(Group(group, videoFormats.size > 1, support, selected))
|
|
||||||
}
|
|
||||||
|
|
||||||
player.getTracks(IMedia.Track.Type.Audio)?.forEach { track ->
|
player.getTracks(IMedia.Track.Type.Audio).orEmpty()
|
||||||
val format = Format.Builder()
|
.groupBy { listOf(it.language, it.name, it.description) }
|
||||||
.setId(track.id)
|
.values.forEach { audioTracks ->
|
||||||
.setLabel(track.name)
|
val audioFormats = audioTracks.map { track ->
|
||||||
.setLanguage(track.language)
|
Format.Builder()
|
||||||
.setSampleMimeType("audio/x-unknown")
|
.setId(track.id)
|
||||||
.build()
|
.setLabel(track.name)
|
||||||
val group = TrackGroup("vlc-audio-${track.id}", format)
|
.setLanguage(track.language)
|
||||||
val selected = booleanArrayOf(selectedAudio != null && selectedAudio.id == track.id)
|
.setSampleMimeType("audio/x-unknown")
|
||||||
val support = intArrayOf(FORMAT_HANDLED)
|
.build()
|
||||||
result.add(Group(group, false, support, selected))
|
}
|
||||||
}
|
val group = TrackGroup("vlc-audio-${audioTracks.minOf { it.id }}", *audioFormats.toTypedArray())
|
||||||
|
val selected = BooleanArray(audioFormats.size) { idx ->
|
||||||
|
selectedAudio != null && selectedAudio.id == audioTracks[idx].id
|
||||||
|
}
|
||||||
|
val support = IntArray(audioFormats.size) { FORMAT_HANDLED }
|
||||||
|
result.add(Group(group, audioFormats.size > 1, support, selected))
|
||||||
|
}
|
||||||
|
|
||||||
player.getTracks(IMedia.Track.Type.Text)?.forEach { track ->
|
player.getTracks(IMedia.Track.Type.Text)?.forEach { track ->
|
||||||
val format = Format.Builder()
|
val format = Format.Builder()
|
||||||
@@ -742,9 +756,9 @@ class VlcPlayer(ctx: Context) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCurrentPeriodIndex() = currentMediaItemIndex
|
override fun getCurrentPeriodIndex() = currentMediaItemIndex.coerceAtLeast(0)
|
||||||
|
|
||||||
override fun getCurrentMediaItemIndex() = currentMediaItemIndex
|
override fun getCurrentMediaItemIndex() = currentMediaItemIndex.coerceAtLeast(0)
|
||||||
|
|
||||||
override fun getDuration(): Long = player.length.takeIf { it > 0 } ?: TIME_UNSET
|
override fun getDuration(): Long = player.length.takeIf { it > 0 } ?: TIME_UNSET
|
||||||
|
|
||||||
|
|||||||
+24
-2
@@ -57,12 +57,16 @@ function PlayerExample({
|
|||||||
trackLabel,
|
trackLabel,
|
||||||
backend,
|
backend,
|
||||||
onSwitchBackend,
|
onSwitchBackend,
|
||||||
|
hasSource,
|
||||||
|
onLoad,
|
||||||
}: {
|
}: {
|
||||||
onPrev: () => void;
|
onPrev: () => void;
|
||||||
onNext: () => void;
|
onNext: () => void;
|
||||||
trackLabel: string;
|
trackLabel: string;
|
||||||
backend: AndroidBackend;
|
backend: AndroidBackend;
|
||||||
onSwitchBackend: (backend: AndroidBackend) => void;
|
onSwitchBackend: (backend: AndroidBackend) => void;
|
||||||
|
hasSource: boolean;
|
||||||
|
onLoad: () => void;
|
||||||
}): React.JSX.Element {
|
}): React.JSX.Element {
|
||||||
const player = usePlayer();
|
const player = usePlayer();
|
||||||
const status = usePlayerState("status");
|
const status = usePlayerState("status");
|
||||||
@@ -233,7 +237,20 @@ function PlayerExample({
|
|||||||
return (
|
return (
|
||||||
<ScrollView style={styles.container}>
|
<ScrollView style={styles.container}>
|
||||||
<Text style={styles.heading}>react-native-omni</Text>
|
<Text style={styles.heading}>react-native-omni</Text>
|
||||||
<Text style={styles.subheading}>{trackLabel}</Text>
|
<Text style={styles.subheading}>
|
||||||
|
{hasSource ? trackLabel : "No media loaded"}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Pressable
|
||||||
|
style={[styles.button, hasSource && styles.selectedTrackButton]}
|
||||||
|
onPress={onLoad}
|
||||||
|
>
|
||||||
|
<Text style={styles.buttonText}>
|
||||||
|
{hasSource ? "Unload media" : "Load media"}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<Pressable
|
<Pressable
|
||||||
@@ -497,6 +514,9 @@ function PlayerExample({
|
|||||||
|
|
||||||
function App(): React.JSX.Element {
|
function App(): React.JSX.Element {
|
||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
// Start with no media loaded so we can verify the app boots (and the media
|
||||||
|
// notification stays hidden) before any source is set.
|
||||||
|
const [hasSource, setHasSource] = useState(false);
|
||||||
const [backend, setBackend] = useState<AndroidBackend>("vlc");
|
const [backend, setBackend] = useState<AndroidBackend>("vlc");
|
||||||
const [pendingBackend, setPendingBackend] = useState<AndroidBackend | null>(
|
const [pendingBackend, setPendingBackend] = useState<AndroidBackend | null>(
|
||||||
null,
|
null,
|
||||||
@@ -574,7 +594,7 @@ function App(): React.JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<OmniProvider
|
<OmniProvider
|
||||||
key={backend}
|
key={backend}
|
||||||
source={source}
|
source={hasSource ? source : undefined}
|
||||||
backend={{ android: backend }}
|
backend={{ android: backend }}
|
||||||
cast={{receiverApplicationId: "D8FB0FC1"}}
|
cast={{receiverApplicationId: "D8FB0FC1"}}
|
||||||
showNotification
|
showNotification
|
||||||
@@ -585,6 +605,8 @@ function App(): React.JSX.Element {
|
|||||||
trackLabel={PLAYLIST[currentIndex].title}
|
trackLabel={PLAYLIST[currentIndex].title}
|
||||||
backend={backend}
|
backend={backend}
|
||||||
onSwitchBackend={handleSwitchBackend}
|
onSwitchBackend={handleSwitchBackend}
|
||||||
|
hasSource={hasSource}
|
||||||
|
onLoad={() => setHasSource((v) => !v)}
|
||||||
/>
|
/>
|
||||||
</OmniProvider>
|
</OmniProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:screenOrientation="nosensor"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
|
|||||||
Reference in New Issue
Block a user