fix(cast): use custom notif instead of builtin one

This commit is contained in:
2026-08-15 02:23:59 +02:00
parent 336db26fc1
commit 709264ec1c
16 changed files with 80 additions and 157 deletions
-7
View File
@@ -16,12 +16,5 @@
<action android:name="androidx.media3.session.MediaSessionService" /> <action android:name="androidx.media3.session.MediaSessionService" />
</intent-filter> </intent-filter>
</service> </service>
<activity
android:name="dev.zoriya.omni.OmniCastTapActivity"
android:exported="false"
android:excludeFromRecents="true"
android:noHistory="true"
android:taskAffinity=""
android:theme="@android:style/Theme.NoDisplay" />
</application> </application>
</manifest> </manifest>
@@ -4,8 +4,6 @@ import android.content.Context
import com.google.android.gms.cast.CastMediaControlIntent import com.google.android.gms.cast.CastMediaControlIntent
import com.google.android.gms.cast.framework.CastOptions import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.media.CastMediaOptions import com.google.android.gms.cast.framework.media.CastMediaOptions
import com.google.android.gms.cast.framework.media.MediaIntentReceiver
import com.google.android.gms.cast.framework.media.NotificationOptions
import com.google.android.gms.cast.framework.OptionsProvider import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.SessionProvider import com.google.android.gms.cast.framework.SessionProvider
@@ -14,23 +12,9 @@ class OmniCastOptionsProvider : OptionsProvider {
val appId = OmniPlayer.receiverApplicationId val appId = OmniPlayer.receiverApplicationId
?: CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID ?: CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID
val notificationOptions = NotificationOptions.Builder()
.setActions(
listOf(
MediaIntentReceiver.ACTION_SKIP_PREV,
MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK,
MediaIntentReceiver.ACTION_SKIP_NEXT,
),
intArrayOf(0, 1, 2),
)
.setSmallIconDrawableResId(
androidx.media3.session.R.drawable.media3_notification_small_icon
)
.setTargetActivityClassName(OmniCastTapActivity::class.java.name)
.build()
val mediaOptions = CastMediaOptions.Builder() val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions) .setMediaSessionEnabled(false)
.setNotificationOptions(null)
.build() .build()
return CastOptions.Builder() return CastOptions.Builder()
@@ -1,30 +0,0 @@
package dev.zoriya.omni
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.core.net.toUri
class OmniCastTapActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val url = getSharedPreferences(OmniPlayer.OMNI_PREFS, Context.MODE_PRIVATE)
.getString(OmniPlayer.NOTIFICATION_URL_PREF, null)
val intent = url
?.let { Intent(Intent.ACTION_VIEW, it.toUri()).setPackage(packageName) }
?: packageManager.getLaunchIntentForPackage(packageName)
try {
startActivity(
intent?.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
)
} catch (e: Throwable) {
android.util.Log.w("OmniPlayer", "could not open $url", e)
}
finish()
}
}
@@ -104,7 +104,8 @@ class OmniPlayer(
private val castStateListener = CastStateListener { private val castStateListener = CastStateListener {
eventMap.emitCastStatus(computeCastStatus()) eventMap.emitCastStatus(computeCastStatus())
listenToReceiver() listenToReceiver()
syncNotificationService() // we were only kept alive to hold this cast, it is over now
if (abandoned && !isCasting) release()
} }
// receivers have no queue to skip in (a queue would make them play things on // receivers have no queue to skip in (a queue would make them play things on
@@ -128,12 +129,7 @@ class OmniPlayer(
val player: Player = runOnMainThreadSync { val player: Player = runOnMainThreadSync {
castOptions?.receiverApplicationId?.let { receiverApplicationId = it } castOptions?.receiverApplicationId?.let { receiverApplicationId = it }
// the cast notification outlives the app, so OmniCastTapActivity has to find the notificationUrl = castOptions?.notificationUrl
// url back even when nothing of ours ran in that process yet.
ctx.getSharedPreferences(OMNI_PREFS, Context.MODE_PRIVATE)
.edit()
.putString(NOTIFICATION_URL_PREF, castOptions?.notificationUrl)
.apply()
val cc = try { val cc = try {
CastContext.getSharedInstance(ctx) CastContext.getSharedInstance(ctx)
} catch (_: Throwable) { } catch (_: Throwable) {
@@ -172,24 +168,26 @@ class OmniPlayer(
private var serviceRunning = false private var serviceRunning = false
private fun syncNotificationService() { private fun syncNotificationService() {
val shouldShow = showNotification == true && source != null && if (abandoned) return
!runOnMainThreadSync { isCasting } val shouldShow = showNotification == true && source != null
when { when {
shouldShow && !serviceRunning -> { shouldShow && !serviceRunning -> {
val otherIsPlaying = notificationPlayer?.let { other -> val otherIsPlaying = notificationPlayer?.let { other ->
other !== player && runOnMainThreadSync { other.isPlaying } // an abandoned one only holds it until the cast it kept alive ends
other !== this && !other.abandoned &&
runOnMainThreadSync { other.player.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 = player notificationPlayer = this
ctx.startForegroundService(Intent(ctx, OmniPlayerService::class.java)) ctx.startForegroundService(Intent(ctx, OmniPlayerService::class.java))
serviceRunning = true serviceRunning = true
} }
!shouldShow && serviceRunning -> { !shouldShow && serviceRunning -> {
ctx.stopService(Intent(ctx, OmniPlayerService::class.java)) ctx.stopService(Intent(ctx, OmniPlayerService::class.java))
if (notificationPlayer == player) notificationPlayer = null if (notificationPlayer === this) notificationPlayer = null
serviceRunning = false serviceRunning = false
} }
} }
@@ -229,22 +227,36 @@ class OmniPlayer(
} }
} }
// the app let go of us. while casting we stay alive anyway: OmniPlayerService holds the
// process (and thus the session) up, and its notification is the only remote left - we
// keep owning both until castStateListener sees the cast end.
@Volatile @Volatile
private var released = false private var abandoned = false
override fun release() { override fun abandon() {
if (released) return if (abandoned) return
released = true abandoned = true
showNotification = false if (!runOnMainThreadSync { isCasting }) release()
}
// drop everything. this ends the cast session, so it only runs once there is no cast
// left to hold (either there was none, or it just ended).
private fun release() {
runOnMainThread { runOnMainThread {
eventMap.dispose()
castContext?.removeCastStateListener(castStateListener) castContext?.removeCastStateListener(castStateListener)
if (!isCasting) player.release() if (notificationPlayer === this) {
// syncNotificationService ignores us now, hand the service back ourselves
ctx.stopService(Intent(ctx, OmniPlayerService::class.java))
notificationPlayer = null
serviceRunning = false
}
eventMap.dispose()
player.release()
} }
} }
override fun dispose() { override fun dispose() {
release() abandon()
super.dispose() super.dispose()
} }
@@ -641,7 +653,7 @@ class OmniPlayer(
} }
companion object { companion object {
var notificationPlayer: Player? = null var notificationPlayer: OmniPlayer? = null
// MediaItem RequestMetadata extras keys carrying cast-only data. // MediaItem RequestMetadata extras keys carrying cast-only data.
const val CAST_ID_EXTRA = "dev.zoriya.omni.castId" const val CAST_ID_EXTRA = "dev.zoriya.omni.castId"
@@ -655,9 +667,8 @@ class OmniPlayer(
// prop; read by OmniCastOptionsProvider when the Cast SDK initializes. // prop; read by OmniCastOptionsProvider when the Cast SDK initializes.
var receiverApplicationId: String? = null var receiverApplicationId: String? = null
// `cast.notificationUrl`, read back by OmniCastTapActivity. // `cast.notificationUrl`, opened when our notification is tapped.
const val OMNI_PREFS = "dev.zoriya.omni" var notificationUrl: String? = null
const val NOTIFICATION_URL_PREF = "notificationUrl"
} }
} }
@@ -668,24 +679,14 @@ class OmniPlayerService : MediaSessionService() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
val available = OmniPlayer.notificationPlayer val available = OmniPlayer.notificationPlayer?.player
if (available == null) { if (available == null) {
startForeground(1, createImmediateNotification()) startForeground(1, createImmediateNotification())
stopSelf() stopSelf()
return return
} }
player = available player = available
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)?.apply { val sessionActivity = openIntent()
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
val sessionActivity = launchIntent?.let {
PendingIntent.getActivity(
this,
0,
it,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
}
mediaSession = MediaSession.Builder(this, player) mediaSession = MediaSession.Builder(this, player)
.apply { .apply {
sessionActivity?.let { setSessionActivity(it) } sessionActivity?.let { setSessionActivity(it) }
@@ -701,7 +702,7 @@ class OmniPlayerService : MediaSessionService() {
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val current = OmniPlayer.notificationPlayer val current = OmniPlayer.notificationPlayer?.player
if (current != null && ::mediaSession.isInitialized && mediaSession.player !== current) { if (current != null && ::mediaSession.isInitialized && mediaSession.player !== current) {
player = current player = current
mediaSession.player = current mediaSession.player = current
@@ -721,25 +722,29 @@ class OmniPlayerService : MediaSessionService() {
manager.createNotificationChannel(channel) manager.createNotificationChannel(channel)
} }
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)?.apply {
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
val pendingIntent = PendingIntent.getActivity(
this,
0,
launchIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
return NotificationCompat.Builder(this, "omni_playback") return NotificationCompat.Builder(this, "omni_playback")
.setSmallIcon(androidx.media3.session.R.drawable.media3_notification_small_icon) .setSmallIcon(androidx.media3.session.R.drawable.media3_notification_small_icon)
.setContentTitle("Omni Player") .setContentTitle("Omni Player")
.setContentText("Preparing playback...") .setContentText("Preparing playback...")
.setContentIntent(pendingIntent) .setContentIntent(openIntent())
.setOngoing(true) .setOngoing(true)
.build() .build()
} }
private fun openIntent(): PendingIntent? {
val intent = OmniPlayer.notificationUrl
?.let { Intent(Intent.ACTION_VIEW, it.toUri()).setPackage(packageName) }
?: packageManager.getLaunchIntentForPackage(packageName)
?: return null
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
return PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession
override fun onTaskRemoved(rootIntent: Intent?) { override fun onTaskRemoved(rootIntent: Intent?) {
@@ -6,16 +6,12 @@ import com.margelo.nitro.omni.CastOptions
import com.margelo.nitro.omni.HybridOmniPlayerFactorySpec import com.margelo.nitro.omni.HybridOmniPlayerFactorySpec
import com.margelo.nitro.omni.HybridOmniPlayerSpec import com.margelo.nitro.omni.HybridOmniPlayerSpec
import com.margelo.nitro.omni.PlayerBackend import com.margelo.nitro.omni.PlayerBackend
import com.margelo.nitro.omni.Source
class OmniPlayerFactory(val context: ThemedReactContext) : HybridOmniPlayerFactorySpec() { class OmniPlayerFactory(val context: ThemedReactContext) : HybridOmniPlayerFactorySpec() {
override fun createPlayer( override fun createPlayer(
props: Source?,
backend: PlayerBackend?, backend: PlayerBackend?,
cast: CastOptions?, cast: CastOptions?,
): HybridOmniPlayerSpec { ): HybridOmniPlayerSpec {
return OmniPlayer(backend?.android ?: AndroidBackend.VLC, cast).apply { return OmniPlayer(backend?.android ?: AndroidBackend.VLC, cast)
source = props
}
} }
} }
@@ -9,16 +9,6 @@
// Forward declaration of `HybridOmniPlayerSpec` to properly resolve imports. // Forward declaration of `HybridOmniPlayerSpec` to properly resolve imports.
namespace margelo::nitro::omni { class HybridOmniPlayerSpec; } namespace margelo::nitro::omni { class HybridOmniPlayerSpec; }
// Forward declaration of `Source` to properly resolve imports.
namespace margelo::nitro::omni { struct Source; }
// Forward declaration of `VideoSrc` to properly resolve imports.
namespace margelo::nitro::omni { struct VideoSrc; }
// Forward declaration of `Subtitle` to properly resolve imports.
namespace margelo::nitro::omni { struct Subtitle; }
// Forward declaration of `Metadata` to properly resolve imports.
namespace margelo::nitro::omni { struct Metadata; }
// Forward declaration of `MixAudioMode` to properly resolve imports.
namespace margelo::nitro::omni { enum class MixAudioMode; }
// Forward declaration of `PlayerBackend` to properly resolve imports. // Forward declaration of `PlayerBackend` to properly resolve imports.
namespace margelo::nitro::omni { struct PlayerBackend; } namespace margelo::nitro::omni { struct PlayerBackend; }
// Forward declaration of `AndroidBackend` to properly resolve imports. // Forward declaration of `AndroidBackend` to properly resolve imports.
@@ -29,26 +19,14 @@ namespace margelo::nitro::omni { struct CastOptions; }
#include <memory> #include <memory>
#include "HybridOmniPlayerSpec.hpp" #include "HybridOmniPlayerSpec.hpp"
#include "JHybridOmniPlayerSpec.hpp" #include "JHybridOmniPlayerSpec.hpp"
#include "Source.hpp"
#include <optional>
#include "JSource.hpp"
#include "VideoSrc.hpp"
#include "JVideoSrc.hpp"
#include <string>
#include <unordered_map>
#include "Subtitle.hpp"
#include <vector>
#include "JSubtitle.hpp"
#include "Metadata.hpp"
#include "JMetadata.hpp"
#include "MixAudioMode.hpp"
#include "JMixAudioMode.hpp"
#include "PlayerBackend.hpp" #include "PlayerBackend.hpp"
#include <optional>
#include "JPlayerBackend.hpp" #include "JPlayerBackend.hpp"
#include "AndroidBackend.hpp" #include "AndroidBackend.hpp"
#include "JAndroidBackend.hpp" #include "JAndroidBackend.hpp"
#include "CastOptions.hpp" #include "CastOptions.hpp"
#include "JCastOptions.hpp" #include "JCastOptions.hpp"
#include <string>
namespace margelo::nitro::omni { namespace margelo::nitro::omni {
@@ -83,9 +61,9 @@ namespace margelo::nitro::omni {
// Methods // Methods
std::shared_ptr<HybridOmniPlayerSpec> JHybridOmniPlayerFactorySpec::createPlayer(const std::optional<Source>& props, const std::optional<PlayerBackend>& backend, const std::optional<CastOptions>& cast) { std::shared_ptr<HybridOmniPlayerSpec> JHybridOmniPlayerFactorySpec::createPlayer(const std::optional<PlayerBackend>& backend, const std::optional<CastOptions>& cast) {
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JHybridOmniPlayerSpec::JavaPart>(jni::alias_ref<JSource> /* props */, jni::alias_ref<JPlayerBackend> /* backend */, jni::alias_ref<JCastOptions> /* cast */)>("createPlayer"); static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JHybridOmniPlayerSpec::JavaPart>(jni::alias_ref<JPlayerBackend> /* backend */, jni::alias_ref<JCastOptions> /* cast */)>("createPlayer");
auto __result = method(_javaPart, props.has_value() ? JSource::fromCpp(props.value()) : nullptr, backend.has_value() ? JPlayerBackend::fromCpp(backend.value()) : nullptr, cast.has_value() ? JCastOptions::fromCpp(cast.value()) : nullptr); auto __result = method(_javaPart, backend.has_value() ? JPlayerBackend::fromCpp(backend.value()) : nullptr, cast.has_value() ? JCastOptions::fromCpp(cast.value()) : nullptr);
return __result->getJHybridOmniPlayerSpec(); return __result->getJHybridOmniPlayerSpec();
} }
@@ -54,7 +54,7 @@ namespace margelo::nitro::omni {
public: public:
// Methods // Methods
std::shared_ptr<HybridOmniPlayerSpec> createPlayer(const std::optional<Source>& props, const std::optional<PlayerBackend>& backend, const std::optional<CastOptions>& cast) override; std::shared_ptr<HybridOmniPlayerSpec> createPlayer(const std::optional<PlayerBackend>& backend, const std::optional<CastOptions>& cast) override;
private: private:
jni::global_ref<JHybridOmniPlayerFactorySpec::JavaPart> _javaPart; jni::global_ref<JHybridOmniPlayerFactorySpec::JavaPart> _javaPart;
+2 -2
View File
@@ -241,8 +241,8 @@ namespace margelo::nitro::omni {
} }
// Methods // Methods
void JHybridOmniPlayerSpec::release() { void JHybridOmniPlayerSpec::abandon() {
static const auto method = _javaPart->javaClassStatic()->getMethod<void()>("release"); static const auto method = _javaPart->javaClassStatic()->getMethod<void()>("abandon");
method(_javaPart); method(_javaPart);
} }
void JHybridOmniPlayerSpec::play() { void JHybridOmniPlayerSpec::play() {
+1 -1
View File
@@ -78,7 +78,7 @@ namespace margelo::nitro::omni {
public: public:
// Methods // Methods
void release() override; void abandon() override;
void play() override; void play() override;
void pause() override; void pause() override;
void seekBy(double offset) override; void seekBy(double offset) override;
@@ -30,7 +30,7 @@ abstract class HybridOmniPlayerFactorySpec: HybridObject() {
// Methods // Methods
@DoNotStrip @DoNotStrip
@Keep @Keep
abstract fun createPlayer(props: Source?, backend: PlayerBackend?, cast: CastOptions?): HybridOmniPlayerSpec abstract fun createPlayer(backend: PlayerBackend?, cast: CastOptions?): HybridOmniPlayerSpec
// Default implementation of `HybridObject.toString()` // Default implementation of `HybridObject.toString()`
override fun toString(): String { override fun toString(): String {
@@ -116,7 +116,7 @@ abstract class HybridOmniPlayerSpec: HybridObject() {
// Methods // Methods
@DoNotStrip @DoNotStrip
@Keep @Keep
abstract fun release(): Unit abstract fun abandon(): Unit
@DoNotStrip @DoNotStrip
@Keep @Keep
@@ -15,8 +15,6 @@
// Forward declaration of `HybridOmniPlayerSpec` to properly resolve imports. // Forward declaration of `HybridOmniPlayerSpec` to properly resolve imports.
namespace margelo::nitro::omni { class HybridOmniPlayerSpec; } namespace margelo::nitro::omni { class HybridOmniPlayerSpec; }
// Forward declaration of `Source` to properly resolve imports.
namespace margelo::nitro::omni { struct Source; }
// Forward declaration of `PlayerBackend` to properly resolve imports. // Forward declaration of `PlayerBackend` to properly resolve imports.
namespace margelo::nitro::omni { struct PlayerBackend; } namespace margelo::nitro::omni { struct PlayerBackend; }
// Forward declaration of `CastOptions` to properly resolve imports. // Forward declaration of `CastOptions` to properly resolve imports.
@@ -24,9 +22,8 @@ namespace margelo::nitro::omni { struct CastOptions; }
#include <memory> #include <memory>
#include "HybridOmniPlayerSpec.hpp" #include "HybridOmniPlayerSpec.hpp"
#include "Source.hpp"
#include <optional>
#include "PlayerBackend.hpp" #include "PlayerBackend.hpp"
#include <optional>
#include "CastOptions.hpp" #include "CastOptions.hpp"
namespace margelo::nitro::omni { namespace margelo::nitro::omni {
@@ -60,7 +57,7 @@ namespace margelo::nitro::omni {
public: public:
// Methods // Methods
virtual std::shared_ptr<HybridOmniPlayerSpec> createPlayer(const std::optional<Source>& props, const std::optional<PlayerBackend>& backend, const std::optional<CastOptions>& cast) = 0; virtual std::shared_ptr<HybridOmniPlayerSpec> createPlayer(const std::optional<PlayerBackend>& backend, const std::optional<CastOptions>& cast) = 0;
protected: protected:
// Hybrid Setup // Hybrid Setup
+1 -1
View File
@@ -39,7 +39,7 @@ namespace margelo::nitro::omni {
prototype.registerHybridGetter("audios", &HybridOmniPlayerSpec::getAudios); prototype.registerHybridGetter("audios", &HybridOmniPlayerSpec::getAudios);
prototype.registerHybridGetter("subtitles", &HybridOmniPlayerSpec::getSubtitles); prototype.registerHybridGetter("subtitles", &HybridOmniPlayerSpec::getSubtitles);
prototype.registerHybridGetter("renditions", &HybridOmniPlayerSpec::getRenditions); prototype.registerHybridGetter("renditions", &HybridOmniPlayerSpec::getRenditions);
prototype.registerHybridMethod("release", &HybridOmniPlayerSpec::release); prototype.registerHybridMethod("abandon", &HybridOmniPlayerSpec::abandon);
prototype.registerHybridMethod("play", &HybridOmniPlayerSpec::play); prototype.registerHybridMethod("play", &HybridOmniPlayerSpec::play);
prototype.registerHybridMethod("pause", &HybridOmniPlayerSpec::pause); prototype.registerHybridMethod("pause", &HybridOmniPlayerSpec::pause);
prototype.registerHybridMethod("seekBy", &HybridOmniPlayerSpec::seekBy); prototype.registerHybridMethod("seekBy", &HybridOmniPlayerSpec::seekBy);
+1 -1
View File
@@ -91,7 +91,7 @@ namespace margelo::nitro::omni {
public: public:
// Methods // Methods
virtual void release() = 0; virtual void abandon() = 0;
virtual void play() = 0; virtual void play() = 0;
virtual void pause() = 0; virtual void pause() = 0;
virtual void seekBy(double offset) = 0; virtual void seekBy(double offset) = 0;
+6 -3
View File
@@ -35,7 +35,7 @@ export const OmniProvider = ({
const [player, setPlayer] = useState<NativeOmniPlayer | null>(null); const [player, setPlayer] = useState<NativeOmniPlayer | null>(null);
const createPlayer = useEffectEvent((aBackend: AndroidBackend) => const createPlayer = useEffectEvent((aBackend: AndroidBackend) =>
ProviderFactory.createPlayer(source, { android: aBackend }, cast), ProviderFactory.createPlayer({ android: aBackend }, cast),
); );
useEffect(() => { useEffect(() => {
@@ -44,11 +44,14 @@ export const OmniProvider = ({
useEffect(() => { useEffect(() => {
if (!player) return; if (!player) return;
return () => player.release(); return () => player.abandon();
}, [player]); }, [player]);
useEffect(() => { useEffect(() => {
if (player) player.source = source; if (!player) return;
// early return on init to keep running cast sessions active
if (source == null && player.source == null) return;
player.source = source;
}, [player, source]); }, [player, source]);
useEffect(() => { useEffect(() => {
+3 -6
View File
@@ -81,13 +81,10 @@ export interface OmniPlayer
OmniPlayerT { OmniPlayerT {
readonly eventMap: OmniEventMap; readonly eventMap: OmniEventMap;
release(): void; // let go of the player. it stays alive as long as it holds a cast session (android).
abandon(): void;
} }
export interface OmniPlayerFactory extends HybridObject<{ android: "kotlin" }> { export interface OmniPlayerFactory extends HybridObject<{ android: "kotlin" }> {
createPlayer( createPlayer(backend?: PlayerBackend, cast?: CastOptions): OmniPlayer;
props?: Source,
backend?: PlayerBackend,
cast?: CastOptions,
): OmniPlayer;
} }