Improve Google Cast connection speed
This commit is contained in:
parent
055719330d
commit
af8a793e05
4 changed files with 283 additions and 60 deletions
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -243,32 +243,68 @@ 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) {
|
if (mediaClientListenerAdded && lastClientIdentity == identity) {
|
||||||
|
Log.d(
|
||||||
|
"GCAST_NATIVE",
|
||||||
|
"attachClient(): client already attached",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
client.unregisterCallback(mediaClientCallback)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
|
||||||
|
client.registerCallback(mediaClientCallback)
|
||||||
|
|
||||||
|
mediaClientListenerAdded = true
|
||||||
|
lastClientIdentity = identity
|
||||||
|
|
||||||
|
lastSentIndex = null
|
||||||
|
lastHandledFinishedItemId = null
|
||||||
|
lastPlaybackState = null
|
||||||
|
lastVolume = null
|
||||||
|
lastMuted = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun detachCurrentClient() {
|
||||||
|
val client = ctx.getClient()
|
||||||
|
|
||||||
|
if (client != null) {
|
||||||
try {
|
try {
|
||||||
client.unregisterCallback(mediaClientCallback)
|
client.unregisterCallback(mediaClientCallback)
|
||||||
} catch (_: Exception) {}
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
client.registerCallback(mediaClientCallback)
|
|
||||||
|
|
||||||
mediaClientListenerAdded = true
|
|
||||||
lastClientIdentity = identity
|
|
||||||
|
|
||||||
lastSentIndex = null
|
|
||||||
lastHandledFinishedItemId = null
|
|
||||||
lastPlaybackState = null
|
|
||||||
lastVolume = null
|
|
||||||
lastMuted = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetClientTracking()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun attachCurrentClientIfNeeded() {
|
||||||
|
val client = ctx.getClient() ?: run {
|
||||||
|
Log.d(
|
||||||
|
"GCAST_NATIVE",
|
||||||
|
"attachCurrentClientIfNeeded(): ctx.getClient() == null",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
attachClient(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetClientTracking() {
|
fun resetClientTracking() {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue