aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-03 13:44:47 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-03 14:24:54 +0200
commitab0ba3db76d1accec1ca944e62b325c8d92592a6 (patch)
tree817dab4d087e8f9b43cfbb0f2c4269aecec1c143 /packages/meshbay-android/app/src/main/kotlin/org/meshbay/client
parenteac753c436d64adf921ae2cbdd0d09e095a5e5a3 (diff)
downloadmeshbay-ab0ba3db76d1accec1ca944e62b325c8d92592a6.tar.gz
Cast: receiver position and pause/play on both clients
The player's remote mode reads where the television is instead of the local playhead, which drifts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-android/app/src/main/kotlin/org/meshbay/client')
-rw-r--r--packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/cast/CastChannels.kt2
-rw-r--r--packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/cast/CastControl.kt30
2 files changed, 32 insertions, 0 deletions
diff --git a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/cast/CastChannels.kt b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/cast/CastChannels.kt
index ecad8ec..74a086b 100644
--- a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/cast/CastChannels.kt
+++ b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/cast/CastChannels.kt
@@ -56,6 +56,8 @@ class CastChannels(
val o = a.optJSONObject(0) ?: JSONObject()
reported { control.reload(relayUrl(o), subtitleOf(o)) }
}
+ "cast:chromecast:pause" -> control.pause()
+ "cast:chromecast:play" -> control.play()
"cast:chromecast:disconnect" -> control.disconnect()
else -> throw Refused("Refused: no such channel")
}
diff --git a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/cast/CastControl.kt b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/cast/CastControl.kt
index 9f47f42..f574c89 100644
--- a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/cast/CastControl.kt
+++ b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/cast/CastControl.kt
@@ -283,6 +283,28 @@ class CastControl(private val context: Context) {
return onMain { stateName(session.remoteMediaClient?.playerState ?: MediaStatus.PLAYER_STATE_UNKNOWN) }
}
+ /** The player as a remote: pause and play the receiver, answered once it has. */
+ fun pause(): JSONObject = command { it.pause() }
+ fun play(): JSONObject = command { it.play() }
+
+ private fun command(send: (com.google.android.gms.cast.framework.media.RemoteMediaClient) ->
+ com.google.android.gms.common.api.PendingResult<com.google.android.gms.cast.framework.media.RemoteMediaClient.MediaChannelResult>): JSONObject {
+ need()
+ val done = CountDownLatch(1)
+ var ok = false
+ onMain {
+ val c = CastContext.getSharedInstance(context).sessionManager.currentCastSession?.remoteMediaClient
+ ?: throw Refused("Not connected")
+ send(c).setResultCallback { r -> ok = r.status.isSuccess; done.countDown() }
+ }
+ if (!done.await(10, TimeUnit.SECONDS)) throw IllegalStateException("The receiver did not answer")
+ if (!ok) throw IllegalStateException("The receiver refused the command")
+ return JSONObject().put("playerState", onMain {
+ stateName(CastContext.getSharedInstance(context).sessionManager.currentCastSession?.remoteMediaClient?.playerState
+ ?: MediaStatus.PLAYER_STATE_UNKNOWN)
+ })
+ }
+
fun disconnect(): Boolean {
if (!available()) return true
onMain { CastContext.getSharedInstance(context).sessionManager.endCurrentSession(true) }
@@ -295,7 +317,15 @@ class CastControl(private val context: Context) {
else onMain {
val s = CastContext.getSharedInstance(context).sessionManager.currentCastSession
val on = s != null && s.isConnected
+ val c = if (on) s!!.remoteMediaClient else null
JSONObject().put("connected", on).put("deviceName", if (on) (deviceName ?: s!!.castDevice?.friendlyName) else JSONObject.NULL)
+ // Where the television is, on the stream's timeline (zero at
+ // the relay's start): the page adds that start. Not the local
+ // playhead, which started earlier and drifts.
+ .put("playerState", c?.let { stateName(it.playerState) } ?: JSONObject.NULL)
+ .put("idleReason", c?.takeIf { it.playerState == MediaStatus.PLAYER_STATE_IDLE }
+ ?.let { idleName(it.idleReason) } ?: JSONObject.NULL)
+ .put("position", c?.let { it.approximateStreamPosition / 1000.0 } ?: JSONObject.NULL)
}
} catch (e: Exception) {
Log.w("MeshBay", "cast status", e)