Improve Google Cast connection speed

This commit is contained in:
FabioMich66 2026-10-29 16:22:45 +01:00
parent 055719330d
commit af8a793e05
4 changed files with 283 additions and 60 deletions

View file

@ -69,6 +69,10 @@ override fun onDetachedFromEngine(
) {
channel.setMethodCallHandler(null)
if (::dialogLauncher.isInitialized) {
dialogLauncher.dispose()
}
if (::videoOptimizer.isInitialized) {
videoOptimizer.dispose()
}

View file

@ -1,6 +1,7 @@
package com.aves.google_cast.cast
import android.app.Activity
import android.util.Log
import android.view.ViewGroup
import androidx.mediarouter.app.MediaRouteButton
import com.google.android.gms.cast.framework.CastButtonFactory
@ -10,22 +11,74 @@ class CastDialogLauncher(
private val channel: MethodChannel
) {
companion object {
private const val TAG = "GCAST_NATIVE"
}
private var currentButton: MediaRouteButton? = null
fun showCastDialog(activity: Activity) {
activity.runOnUiThread {
try {
removeCurrentButton()
val button = MediaRouteButton(activity)
CastButtonFactory.setUpMediaRouteButton(activity, button)
val root = activity.findViewById<ViewGroup>(android.R.id.content)
root?.addView(button, ViewGroup.LayoutParams(1, 1))
CastButtonFactory.setUpMediaRouteButton(
activity,
button
)
button.performClick()
val root = activity.findViewById<ViewGroup>(
android.R.id.content
)
root?.addView(
button,
ViewGroup.LayoutParams(1, 1)
)
currentButton = button
val clicked = button.performClick()
Log.d(
TAG,
"Cast dialog performClick=$clicked"
)
button.postDelayed(
{ removeCurrentButton() },
1_000L
)
} catch (e: Exception) {
removeCurrentButton()
channel.invokeMethod(
"onCastError",
mapOf("message" to (e.message ?: "unknown"))
mapOf(
"message" to
(e.message ?: "Unknown Cast dialog error")
)
)
}
}
}
fun dispose() {
currentButton?.post {
removeCurrentButton()
}
}
private fun removeCurrentButton() {
val button = currentButton ?: return
try {
(button.parent as? ViewGroup)?.removeView(button)
} catch (_: Exception) {
}
currentButton = null
}
}

View file

@ -243,20 +243,32 @@ override fun onMediaError(mediaError: MediaError) {
// ------------------------------------------------------------
// CLIENT ATTACH
// ------------------------------------------------------------
fun attachCurrentClientIfNeeded() {
val client = ctx.getClient() ?: run {
Log.d("GCAST_NATIVE", "attachCurrentClientIfNeeded(): ctx.getClient() == null")
fun attachClient(client: RemoteMediaClient?) {
if (client == null) {
Log.d("GCAST_NATIVE", "attachClient(): client == null")
detachCurrentClient()
return
}
val identity = System.identityHashCode(client)
Log.d("GCAST_NATIVE", "attachCurrentClientIfNeeded(): attaching to client identity=$identity")
Log.d(
"GCAST_NATIVE",
"attachClient(): direct client identity=$identity",
)
if (mediaClientListenerAdded && lastClientIdentity == identity) {
Log.d(
"GCAST_NATIVE",
"attachClient(): client already attached",
)
return
}
if (!mediaClientListenerAdded || lastClientIdentity != identity) {
try {
client.unregisterCallback(mediaClientCallback)
} catch (_: Exception) {}
} catch (_: Exception) {
}
client.registerCallback(mediaClientCallback)
@ -269,6 +281,30 @@ override fun onMediaError(mediaError: MediaError) {
lastVolume = null
lastMuted = null
}
fun detachCurrentClient() {
val client = ctx.getClient()
if (client != null) {
try {
client.unregisterCallback(mediaClientCallback)
} catch (_: Exception) {
}
}
resetClientTracking()
}
fun attachCurrentClientIfNeeded() {
val client = ctx.getClient() ?: run {
Log.d(
"GCAST_NATIVE",
"attachCurrentClientIfNeeded(): ctx.getClient() == null",
)
return
}
attachClient(client)
}
fun resetClientTracking() {

View file

@ -2,6 +2,7 @@ package com.aves.google_cast.cast
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.util.Log
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.SessionManagerListener
@ -11,90 +12,219 @@ class CastSessionHandler(
private val channel: MethodChannel
) : SessionManagerListener<CastSession> {
companion object {
private const val TAG = "GCAST_NATIVE"
}
private val mainHandler = Handler(Looper.getMainLooper())
private var readySent = false
private var currentSession: CastSession? = null
private var connectionStartedAtMs: Long = 0L
// ⭐ mediaClient viene assegnato DOPO la creazione
private var mediaClient: CastMediaClient? = null
fun setMediaClient(client: CastMediaClient) {
mediaClient = client
}
private fun beginConnectionTiming() {
if (connectionStartedAtMs == 0L) {
connectionStartedAtMs = SystemClock.elapsedRealtime()
}
}
private fun elapsedMs(): Long {
if (connectionStartedAtMs == 0L) return 0L
return SystemClock.elapsedRealtime() - connectionStartedAtMs
}
override fun onSessionStarting(session: CastSession) {
Log.d("GCAST_NATIVE", "onSessionStarting")
currentSession = session
beginConnectionTiming()
mediaClient?.attachCurrentClientIfNeeded() // ⭐ PATCH
trySendReadyWhenClientExists()
Log.i(
TAG,
"CAST_TIMING onSessionStarting t=${elapsedMs()}ms"
)
currentSession = session
readySent = false
session.remoteMediaClient?.let {
mediaClient?.attachClient(it)
}
}
override fun onSessionStarted(session: CastSession, sessionId: String) {
Log.d("GCAST_NATIVE", "onSessionStarted sessionId=$sessionId")
override fun onSessionStarted(
session: CastSession,
sessionId: String
) {
beginConnectionTiming()
Log.i(
TAG,
"CAST_TIMING onSessionStarted " +
"sessionId=$sessionId t=${elapsedMs()}ms"
)
currentSession = session
mediaClient?.attachCurrentClientIfNeeded() // ⭐ PATCH
trySendReadyWhenClientExists()
mediaClient?.attachClient(session.remoteMediaClient)
sendReadyIfPossible(session)
}
override fun onSessionResuming(session: CastSession, sessionId: String) {
Log.d("GCAST_NATIVE", "onSessionResuming sessionId=$sessionId")
override fun onSessionResuming(
session: CastSession,
sessionId: String
) {
beginConnectionTiming()
Log.i(
TAG,
"CAST_TIMING onSessionResuming " +
"sessionId=$sessionId t=${elapsedMs()}ms"
)
currentSession = session
readySent = false
}
override fun onSessionResumed(
session: CastSession,
wasSuspended: Boolean
) {
beginConnectionTiming()
Log.i(
TAG,
"CAST_TIMING onSessionResumed " +
"wasSuspended=$wasSuspended t=${elapsedMs()}ms"
)
currentSession = session
mediaClient?.attachCurrentClientIfNeeded() // ⭐ PATCH
trySendReadyWhenClientExists()
mediaClient?.attachClient(session.remoteMediaClient)
sendReadyIfPossible(session)
}
override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) {
Log.d("GCAST_NATIVE", "onSessionResumed wasSuspended=$wasSuspended")
override fun onSessionSuspended(
session: CastSession,
reason: Int
) {
Log.i(
TAG,
"onSessionSuspended reason=$reason t=${elapsedMs()}ms"
)
currentSession = session
readySent = false
mediaClient?.attachCurrentClientIfNeeded() // ⭐ PATCH
trySendReadyWhenClientExists()
mediaClient?.detachCurrentClient()
}
override fun onSessionSuspended(session: CastSession, reason: Int) {
Log.d("GCAST_NATIVE", "onSessionSuspended reason=$reason")
currentSession = session
mediaClient?.attachCurrentClientIfNeeded() // ⭐ PATCH
trySendReadyWhenClientExists()
}
override fun onSessionEnded(session: CastSession, error: Int) {
Log.d("GCAST_NATIVE", "onSessionEnded error=$error")
override fun onSessionEnded(
session: CastSession,
error: Int
) {
Log.i(
TAG,
"onSessionEnded error=$error t=${elapsedMs()}ms"
)
currentSession = null
readySent = false
connectionStartedAtMs = 0L
mediaClient?.detachCurrentClient()
mainHandler.post {
Log.d("GCAST_NATIVE", "invokeMethod onCastStopped")
Log.d(TAG, "invokeMethod onCastStopped")
channel.invokeMethod("onCastStopped", null)
}
}
override fun onSessionStartFailed(session: CastSession, error: Int) {
Log.d("GCAST_NATIVE", "onSessionStartFailed error=$error")
override fun onSessionStartFailed(
session: CastSession,
error: Int
) {
Log.e(
TAG,
"onSessionStartFailed error=$error t=${elapsedMs()}ms"
)
currentSession = null
readySent = false
connectionStartedAtMs = 0L
mediaClient?.detachCurrentClient()
mainHandler.post {
channel.invokeMethod(
"onCastError",
mapOf(
"message" to "Cast session start failed",
"errorCode" to error
)
)
}
}
override fun onSessionResumeFailed(session: CastSession, error: Int) {
Log.d("GCAST_NATIVE", "onSessionResumeFailed error=$error")
override fun onSessionResumeFailed(
session: CastSession,
error: Int
) {
Log.e(
TAG,
"onSessionResumeFailed error=$error t=${elapsedMs()}ms"
)
currentSession = null
readySent = false
connectionStartedAtMs = 0L
mediaClient?.detachCurrentClient()
mainHandler.post {
channel.invokeMethod(
"onCastError",
mapOf(
"message" to "Cast session resume failed",
"errorCode" to error
)
)
}
}
override fun onSessionEnding(session: CastSession) {
Log.d("GCAST_NATIVE", "onSessionEnding")
Log.d(TAG, "onSessionEnding")
}
private fun trySendReadyWhenClientExists() {
val session = currentSession ?: return
val client = session.remoteMediaClient ?: return
private fun sendReadyIfPossible(session: CastSession) {
val client = session.remoteMediaClient ?: run {
Log.d(
TAG,
"sendReadyIfPossible(): remoteMediaClient == null"
)
return
}
if (readySent) return
if (readySent) {
Log.d(
TAG,
"sendReadyIfPossible(): ready already sent"
)
return
}
mediaClient?.attachClient(client)
readySent = true
mainHandler.post {
Log.d("GCAST_NATIVE", "invokeMethod onCastReady")
Log.i(
TAG,
"CAST_TIMING invokeMethod onCastReady " +
"t=${elapsedMs()}ms"
)
channel.invokeMethod("onCastReady", null)
}
}