feat(cast): clicking on the notif opens specified path

This commit is contained in:
2026-08-14 23:42:34 +02:00
parent 0545e06a04
commit 336db26fc1
10 changed files with 82 additions and 15 deletions
+7
View File
@@ -16,5 +16,12 @@
<action android:name="androidx.media3.session.MediaSessionService" />
</intent-filter>
</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>
</manifest>
@@ -26,11 +26,7 @@ class OmniCastOptionsProvider : OptionsProvider {
.setSmallIconDrawableResId(
androidx.media3.session.R.drawable.media3_notification_small_icon
)
.apply {
context.packageManager.getLaunchIntentForPackage(context.packageName)
?.component?.className
?.let { setTargetActivityClassName(it) }
}
.setTargetActivityClassName(OmniCastTapActivity::class.java.name)
.build()
val mediaOptions = CastMediaOptions.Builder()
@@ -0,0 +1,30 @@
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()
}
}
@@ -128,6 +128,12 @@ class OmniPlayer(
val player: Player = runOnMainThreadSync {
castOptions?.receiverApplicationId?.let { receiverApplicationId = it }
// the cast notification outlives the app, so OmniCastTapActivity has to find the
// 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 {
CastContext.getSharedInstance(ctx)
} catch (_: Throwable) {
@@ -648,6 +654,10 @@ class OmniPlayer(
// Receiver application id passed at runtime via OmniProvider's `cast`
// prop; read by OmniCastOptionsProvider when the Cast SDK initializes.
var receiverApplicationId: String? = null
// `cast.notificationUrl`, read back by OmniCastTapActivity.
const val OMNI_PREFS = "dev.zoriya.omni"
const val NOTIFICATION_URL_PREF = "notificationUrl"
}
}
+4 -1
View File
@@ -511,7 +511,10 @@ function App(): React.JSX.Element {
<OmniProvider
source={hasSource ? source : undefined}
backend={{ android: backend }}
cast={{ receiverApplicationId: "D8FB0FC1" }}
cast={{
receiverApplicationId: "D8FB0FC1",
notificationUrl: "omniexample://remote",
}}
showNotification
>
<PlayerExample
@@ -25,6 +25,12 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="omniexample" />
</intent-filter>
</activity>
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
+7 -3
View File
@@ -34,8 +34,11 @@ namespace margelo::nitro::omni {
static const auto clazz = javaClassStatic();
static const auto fieldReceiverApplicationId = clazz->getField<jni::JString>("receiverApplicationId");
jni::local_ref<jni::JString> receiverApplicationId = this->getFieldValue(fieldReceiverApplicationId);
static const auto fieldNotificationUrl = clazz->getField<jni::JString>("notificationUrl");
jni::local_ref<jni::JString> notificationUrl = this->getFieldValue(fieldNotificationUrl);
return CastOptions(
receiverApplicationId != nullptr ? std::make_optional(receiverApplicationId->toStdString()) : std::nullopt
receiverApplicationId != nullptr ? std::make_optional(receiverApplicationId->toStdString()) : std::nullopt,
notificationUrl != nullptr ? std::make_optional(notificationUrl->toStdString()) : std::nullopt
);
}
@@ -45,12 +48,13 @@ namespace margelo::nitro::omni {
*/
[[maybe_unused]]
static jni::local_ref<JCastOptions::javaobject> fromCpp(const CastOptions& value) {
using JSignature = JCastOptions(jni::alias_ref<jni::JString>);
using JSignature = JCastOptions(jni::alias_ref<jni::JString>, jni::alias_ref<jni::JString>);
static const auto clazz = javaClassStatic();
static const auto create = clazz->getStaticMethod<JSignature>("fromCpp");
return create(
clazz,
value.receiverApplicationId.has_value() ? jni::make_jstring(value.receiverApplicationId.value()) : nullptr
value.receiverApplicationId.has_value() ? jni::make_jstring(value.receiverApplicationId.value()) : nullptr,
value.notificationUrl.has_value() ? jni::make_jstring(value.notificationUrl.value()) : nullptr
);
}
};
@@ -20,7 +20,10 @@ import java.util.Objects
data class CastOptions(
@DoNotStrip
@Keep
val receiverApplicationId: String?
val receiverApplicationId: String?,
@DoNotStrip
@Keep
val notificationUrl: String?
) {
/* primary constructor */
@@ -28,11 +31,13 @@ data class CastOptions(
if (this === other) return true
if (other !is CastOptions) return false
return Objects.deepEquals(this.receiverApplicationId, other.receiverApplicationId)
&& Objects.deepEquals(this.notificationUrl, other.notificationUrl)
}
override fun hashCode(): Int {
return arrayOf<Any?>(
receiverApplicationId
receiverApplicationId,
notificationUrl
).contentDeepHashCode()
}
@@ -44,8 +49,8 @@ data class CastOptions(
@Keep
@Suppress("unused")
@JvmStatic
private fun fromCpp(receiverApplicationId: String?): CastOptions {
return CastOptions(receiverApplicationId)
private fun fromCpp(receiverApplicationId: String?, notificationUrl: String?): CastOptions {
return CastOptions(receiverApplicationId, notificationUrl)
}
}
}
+6 -2
View File
@@ -41,10 +41,11 @@ namespace margelo::nitro::omni {
struct CastOptions final {
public:
std::optional<std::string> receiverApplicationId SWIFT_PRIVATE;
std::optional<std::string> notificationUrl SWIFT_PRIVATE;
public:
CastOptions() = default;
explicit CastOptions(std::optional<std::string> receiverApplicationId): receiverApplicationId(receiverApplicationId) {}
explicit CastOptions(std::optional<std::string> receiverApplicationId, std::optional<std::string> notificationUrl): receiverApplicationId(receiverApplicationId), notificationUrl(notificationUrl) {}
public:
friend bool operator==(const CastOptions& lhs, const CastOptions& rhs) = default;
@@ -60,12 +61,14 @@ namespace margelo::nitro {
static inline margelo::nitro::omni::CastOptions fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
jsi::Object obj = arg.asObject(runtime);
return margelo::nitro::omni::CastOptions(
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "receiverApplicationId")))
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "receiverApplicationId"))),
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "notificationUrl")))
);
}
static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::omni::CastOptions& arg) {
jsi::Object obj(runtime);
obj.setProperty(runtime, PropNameIDCache::get(runtime, "receiverApplicationId"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.receiverApplicationId));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "notificationUrl"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.notificationUrl));
return obj;
}
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
@@ -77,6 +80,7 @@ namespace margelo::nitro {
return false;
}
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "receiverApplicationId")))) return false;
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "notificationUrl")))) return false;
return true;
}
};
+2
View File
@@ -16,6 +16,8 @@ export interface Source {
export interface CastOptions {
receiverApplicationId?: string;
// deep link opened when the cast notification is tapped (android only)
notificationUrl?: string;
}
export interface VideoSrc {