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) channel.setMethodCallHandler(null)
if (::dialogLauncher.isInitialized) {
dialogLauncher.dispose()
}
if (::videoOptimizer.isInitialized) { if (::videoOptimizer.isInitialized) {
videoOptimizer.dispose() videoOptimizer.dispose()
} }

View file

@ -1,6 +1,7 @@
package com.aves.google_cast.cast package com.aves.google_cast.cast
import android.app.Activity import android.app.Activity
import android.util.Log
import android.view.ViewGroup import android.view.ViewGroup
import androidx.mediarouter.app.MediaRouteButton import androidx.mediarouter.app.MediaRouteButton
import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastButtonFactory
@ -10,22 +11,74 @@ class CastDialogLauncher(
private val channel: MethodChannel private val channel: MethodChannel
) { ) {
companion object {
private const val TAG = "GCAST_NATIVE"
}
private var currentButton: MediaRouteButton? = null
fun showCastDialog(activity: Activity) { fun showCastDialog(activity: Activity) {
activity.runOnUiThread { activity.runOnUiThread {
try { try {
removeCurrentButton()
val button = MediaRouteButton(activity) val button = MediaRouteButton(activity)
CastButtonFactory.setUpMediaRouteButton(activity, button)
val root = activity.findViewById<ViewGroup>(android.R.id.content) CastButtonFactory.setUpMediaRouteButton(
root?.addView(button, ViewGroup.LayoutParams(1, 1)) 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) { } catch (e: Exception) {
removeCurrentButton()
channel.invokeMethod( channel.invokeMethod(
"onCastError", "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 // CLIENT ATTACH
// ------------------------------------------------------------ // ------------------------------------------------------------
fun attachCurrentClientIfNeeded() { fun attachClient(client: RemoteMediaClient?) {
val client = ctx.getClient() ?: run { if (client == null) {
Log.d("GCAST_NATIVE", "attachCurrentClientIfNeeded(): ctx.getClient() == null") Log.d("GCAST_NATIVE", "attachClient(): client == null")
detachCurrentClient()
return return
} }
val identity = System.identityHashCode(client) 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 { try {
client.unregisterCallback(mediaClientCallback) client.unregisterCallback(mediaClientCallback)
} catch (_: Exception) {} } catch (_: Exception) {
}
client.registerCallback(mediaClientCallback) client.registerCallback(mediaClientCallback)
@ -269,6 +281,30 @@ override fun onMediaError(mediaError: MediaError) {
lastVolume = null lastVolume = null
lastMuted = 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() { fun resetClientTracking() {

View file

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