Fix JVM target mismatch: force Java & Kotlin to 21 for AGP 8

This commit is contained in:
FabioMich66 2026-04-30 16:30:52 +02:00
parent d9ad184549
commit b64951bb94
10 changed files with 110 additions and 1008 deletions

View file

@ -10,7 +10,6 @@ buildscript {
}
dependencies {
// VERSIONE AGP EREDITATA DAL PROGETTO HOST (Aves)
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@ -40,22 +39,32 @@ android {
main.java.srcDirs += 'src/main/kotlin'
}
// BYTECODE ANDROID: DEVE ESSERE 1.8
// (Java 21 è solo per il TOOLING, non per il target Android)
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// Kotlin allineato a Java 1.8 (OBBLIGATORIO)
kotlinOptions {
jvmTarget = "1.8"
}
lint {
disable 'InvalidPackage'
}
}
dependencies {
def lifecycle_version = "2.8.7"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
// Google Cast SDK
// Google Cast Framework (compatibile AGP 8)
api "com.google.android.gms:play-services-cast-framework:21.4.0"
// Lifecycle
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// Lifecycle (necessario per Cast)
implementation "androidx.lifecycle:lifecycle-common-java8:2.8.7"
// Activity (per dialog Cast)
// Activity (dialog Cast / Tracks chooser)
implementation "androidx.activity:activity-ktx:1.9.0"
}

View file

@ -2,3 +2,4 @@ org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
kotlin.jvm.target.validation.mode=WARNING

View file

@ -1,26 +0,0 @@
package com.gianlucaparadise.flutter_cast_framework;
import androidx.annotation.NonNull;
import android.content.Context;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.BinaryMessenger;
public class FlutterCastFrameworkPlugin implements FlutterPlugin {
private CastHostApi castHostApi;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
Context context = binding.getApplicationContext();
BinaryMessenger messenger = binding.getBinaryMessenger();
castHostApi = new CastHostApi(context);
PlatformBridgeApis.CastHostApi.setup(messenger, castHostApi);
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
castHostApi = null;
}
}

View file

@ -0,0 +1,32 @@
package com.gianlucaparadise.flutter_cast_framework
import android.app.Activity
import android.app.Application
import android.os.Bundle
/**
* Fornisce sempre l'Activity corrente.
* Necessario per Cast dialog.
*/
object CastActivityProvider : Application.ActivityLifecycleCallbacks {
var currentActivity: Activity? = null
private set
override fun onActivityResumed(activity: Activity) {
currentActivity = activity
}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityDestroyed(activity: Activity) {
if (currentActivity === activity) {
currentActivity = null
}
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
}

View file

@ -1,514 +1,97 @@
package com.gianlucaparadise.flutter_cast_framework
import android.app.Activity
import android.app.Application
import android.content.Context
import android.util.Log
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import com.gianlucaparadise.flutter_cast_framework.cast.CastDialogOpener
import com.gianlucaparadise.flutter_cast_framework.cast.MessageCastingChannel
import com.gianlucaparadise.flutter_cast_framework.media.*
import com.google.android.gms.cast.MediaError
import com.google.android.gms.cast.MediaStatus.*
import android.os.Bundle
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.SessionManager
import com.google.android.gms.cast.framework.SessionManagerListener
import com.google.android.gms.cast.framework.media.MediaQueue
import com.google.android.gms.cast.framework.media.RemoteMediaClient
import com.google.android.gms.cast.framework.media.TracksChooserDialogFragment
import com.google.android.gms.cast.framework.CastButtonFactory
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
import io.flutter.plugin.common.MethodChannel
/**
* Plugin Android minimale e moderno per Google Cast
*/
class FlutterCastFrameworkPlugin :
FlutterPlugin,
MethodChannel.MethodCallHandler,
Application.ActivityLifecycleCallbacks {
class FlutterCastFrameworkPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, LifecycleObserver {
companion object {
const val TAG = "AndroidCastPlugin"
private lateinit var applicationContext: Context
private lateinit var channel: MethodChannel
private var currentActivity: Activity? = null
private var castContext: CastContext? = null
@JvmStatic
fun registerWith(registrar: Registrar) {
val plugin = FlutterCastFrameworkPlugin()
plugin.onAttachedToEngine(registrar.context(), registrar.messenger())
}
}
init {
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
//region FlutterPlugin interface
// ────────────────────────────
// Flutter lifecycle
// ────────────────────────────
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
Log.d(TAG, "onAttachedToEngine")
onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
applicationContext = binding.applicationContext
channel = MethodChannel(binding.binaryMessenger, "flutter_cast_framework")
channel.setMethodCallHandler(this)
castContext = CastContext.getSharedInstance(applicationContext)
// Register activity callbacks
if (applicationContext is Application) {
(applicationContext as Application)
.registerActivityLifecycleCallbacks(this)
}
private fun onAttachedToEngine(applicationContext: Context, messenger: BinaryMessenger) {
this.applicationContext = applicationContext
castApi = MyApi()
PlatformBridgeApis.CastHostApi.setup(messenger, castApi)
val castFlutterApi = PlatformBridgeApis.CastFlutterApi(messenger)
flutterApi = castFlutterApi
mMessageCastingChannel = MessageCastingChannel(castFlutterApi)
CastContext.getSharedInstance(applicationContext).addCastStateListener { i ->
Log.d(TAG, "Cast state changed: $i")
flutterApi?.onCastStateChanged(i.toLong()) { }
}
mSessionManager = CastContext.getSharedInstance(applicationContext).sessionManager
mCastSession = mSessionManager.currentCastSession
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
Log.d(TAG, "onDetachedFromEngine")
applicationContext = null
mMessageCastingChannel = null
}
//endregion
channel.setMethodCallHandler(null)
//region ActivityAware
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
Log.d(TAG, "onAttachedToActivity")
activity = binding.activity
if (applicationContext is Application) {
(applicationContext as Application)
.unregisterActivityLifecycleCallbacks(this)
}
}
override fun onDetachedFromActivityForConfigChanges() {
Log.d(TAG, "onDetachedFromActivityForConfigChanges")
activity = null
// ────────────────────────────
// MethodChannel
// ────────────────────────────
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"showCastDialog" -> {
showCastDialog()
result.success(null)
}
else -> result.notImplemented()
}
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
Log.d(TAG, "onReattachedToActivityForConfigChanges")
activity = binding.activity
}
private fun showCastDialog() {
val activity = currentActivity ?: return
override fun onDetachedFromActivity() {
Log.d(TAG, "onDetachedFromActivity")
activity = null
}
//endregion
private lateinit var mSessionManager: SessionManager
private val mSessionManagerListener = CastSessionManagerListener()
private val remoteMediaClientListener = RemoteMediaClientListener()
private val mediaQueueListener = MediaQueueListener()
private var castApi: PlatformBridgeApis.CastHostApi? = null
private var flutterApi: PlatformBridgeApis.CastFlutterApi? = null
private var applicationContext: Context? = null
private var activity: Activity? = null
private var mMessageCastingChannel: MessageCastingChannel? = null
private var mCastSession: CastSession? = null
set(value) {
Log.d(TAG, "Updating mCastSession - castSession changed: ${field != value}")
// if (field == value) return // Despite the instances are the same, I need to re-attach the listener to every new session instance
val oldSession = field
field = value
remoteMediaClient = value?.remoteMediaClient
flutterApi?.getSessionMessageNamespaces(getOnNamespaceResult(oldSession, newSession = value))
}
private var remoteMediaClient: RemoteMediaClient? = null
set(value) {
Log.d(TAG, "Updating remoteMediaClient - remoteMediaClient changed: ${field != value}")
field?.unregisterCallback(remoteMediaClientListener)
value?.registerCallback(remoteMediaClientListener)
// Amount of time in milliseconds between subsequent updates
val periodMs = 1000L
field?.removeProgressListener(remoteMediaClientListener)
value?.addProgressListener(remoteMediaClientListener, periodMs)
field = value
mediaQueue = value?.mediaQueue
}
private var mediaQueue: MediaQueue? = null
set(value) {
Log.d(TAG, "Updating mediaQueue - mediaQueue changed: ${field != value}")
field?.unregisterCallback(mediaQueueListener)
value?.registerCallback(mediaQueueListener)
field = value
}
//region LifecycleObserver
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate() {
Log.d(TAG, "App: ON_CREATE")
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
Log.d(TAG, "App: ON_RESUME")
mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
mCastSession = mSessionManager.currentCastSession
val context = applicationContext
if (context == null) {
Log.d(TAG, "App: ON_RESUME - missing context")
return
}
val castState = CastContext.getSharedInstance(context).castState
flutterApi?.onCastStateChanged(castState.toLong()) { }
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause() {
Log.d(TAG, "App: ON_PAUSE")
mSessionManager.removeSessionManagerListener(
mSessionManagerListener,
CastSession::class.java
// Setup & trigger standard Cast button
CastButtonFactory.setUpMediaRouteButton(
activity,
androidx.mediarouter.app.MediaRouteButton(activity)
)
// I can't set this to null because I need the cast session to send commands from notification
// mCastSession = null
}
//endregion
override fun onMethodCall(call: MethodCall, result: Result) {
Log.d(TAG, "onMethodCall - ${call.method} not implemented")
result.notImplemented()
}
private inner class RemoteMediaClientListener : RemoteMediaClient.Callback(), RemoteMediaClient.ProgressListener {
override fun onStatusUpdated() {
val remoteMediaClient: RemoteMediaClient = remoteMediaClient ?: return
val mediaStatus = remoteMediaClient.mediaStatus ?: return
val playerStateLabel = when (remoteMediaClient.playerState) {
PLAYER_STATE_UNKNOWN -> "unknown"
PLAYER_STATE_BUFFERING -> "buffering"
PLAYER_STATE_IDLE -> "idle"
PLAYER_STATE_LOADING -> "loading"
PLAYER_STATE_PAUSED -> "paused"
PLAYER_STATE_PLAYING -> "playing"
else -> "unknown-else"
}
Log.d(TAG, "RemoteMediaClient - onStatusUpdated: $playerStateLabel")
super.onStatusUpdated()
val flutterMediaStatus = getFlutterMediaStatus(mediaStatus)
flutterApi?.onStatusUpdated(flutterMediaStatus) { }
// ────────────────────────────
// Activity lifecycle
// ────────────────────────────
override fun onActivityResumed(activity: Activity) {
currentActivity = activity
}
override fun onMetadataUpdated() {
Log.d(TAG, "RemoteMediaClient - onMetadataUpdated")
super.onMetadataUpdated()
flutterApi?.onMetadataUpdated { }
}
override fun onActivityPaused(activity: Activity) {}
override fun onQueueStatusUpdated() {
Log.d(TAG, "RemoteMediaClient - onQueueStatusUpdated")
super.onQueueStatusUpdated()
flutterApi?.onQueueStatusUpdated { }
}
override fun onActivityStarted(activity: Activity) {}
override fun onPreloadStatusUpdated() {
Log.d(TAG, "RemoteMediaClient - onPreloadStatusUpdated")
super.onPreloadStatusUpdated()
flutterApi?.onPreloadStatusUpdated { }
}
override fun onActivityStopped(activity: Activity) {}
override fun onSendingRemoteMediaRequest() {
Log.d(TAG, "RemoteMediaClient - onSendingRemoteMediaRequest")
super.onSendingRemoteMediaRequest()
flutterApi?.onSendingRemoteMediaRequest { }
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onAdBreakStatusUpdated() {
val mediaStatus = remoteMediaClient?.mediaStatus ?: return
val isPlayingAd = mediaStatus.isPlayingAd
Log.d(TAG, "RemoteMediaClient - onAdBreakStatusUpdated - isPlayingAd: $isPlayingAd")
super.onAdBreakStatusUpdated()
val flutterMediaStatus = getFlutterMediaStatus(mediaStatus)
flutterApi?.onAdBreakStatusUpdated(flutterMediaStatus) { }
}
override fun onMediaError(error: MediaError?) {
Log.d(TAG, "RemoteMediaClient - onMediaError $error")
super.onMediaError(error)
flutterApi?.onMediaError { }
}
override fun onProgressUpdated(progressMs: Long, durationMs: Long) {
val isPlayingAd = remoteMediaClient?.mediaStatus?.isPlayingAd ?: false
if (isPlayingAd) {
fireAdBreakClipProgress()
}
else {
flutterApi?.onProgressUpdated(progressMs, durationMs) { }
override fun onActivityDestroyed(activity: Activity) {
if (currentActivity === activity) {
currentActivity = null
}
}
fun fireAdBreakClipProgress() {
val mediaStatus = remoteMediaClient?.mediaStatus ?: return
val currentAdBreakClip = mediaStatus.currentAdBreakClip ?: return
val adBreakId = mediaStatus.currentAdBreak?.id ?: ""
val adBreakClipId = currentAdBreakClip.id ?: ""
val adBreakClipProgressMs = remoteMediaClient?.approximateAdBreakClipPositionMs
?: 0
val adBreakClipDurationMs = currentAdBreakClip.durationInMs
if (adBreakClipDurationMs <= 0) return
val whenSkippableMs = currentAdBreakClip.whenSkippableInMs
flutterApi?.onAdBreakClipProgressUpdated(
adBreakId,
adBreakClipId,
adBreakClipProgressMs,
adBreakClipDurationMs,
whenSkippableMs,
) { }
}
}
private inner class MediaQueueListener : MediaQueue.Callback() {
override fun mediaQueueWillChange() {
Log.d(TAG, "MediaQueue - mediaQueueWillChange")
super.mediaQueueWillChange()
flutterApi?.mediaQueueWillChange { }
}
override fun mediaQueueChanged() {
Log.d(TAG, "MediaQueue - mediaQueueChanged")
super.mediaQueueChanged()
flutterApi?.mediaQueueChanged { }
}
override fun itemsReloaded() {
Log.d(TAG, "MediaQueue - itemsReloaded")
super.itemsReloaded()
flutterApi?.itemsReloaded { }
}
override fun itemsInsertedInRange(insertIndex: Int, insertCount: Int) {
Log.d(TAG, "MediaQueue - itemsInsertedInRange")
super.itemsInsertedInRange(insertIndex, insertCount)
flutterApi?.itemsInsertedInRange(insertIndex.toLong(), insertCount.toLong()) { }
}
override fun itemsUpdatedAtIndexes(indexes: IntArray?) {
Log.d(TAG, "MediaQueue - itemsUpdatedAtIndexes")
super.itemsUpdatedAtIndexes(indexes)
if (indexes == null) return
val longIndexes = indexes.map { it.toLong() }
flutterApi?.itemsUpdatedAtIndexes(longIndexes) { }
}
override fun itemsRemovedAtIndexes(indexes: IntArray?) {
Log.d(TAG, "MediaQueue itemsRemovedAtIndexeseWillChange")
super.itemsRemovedAtIndexes(indexes)
if (indexes == null) return
val longIndexes = indexes.map { it.toLong() }
flutterApi?.itemsRemovedAtIndexes(longIndexes) { }
}
}
private inner class MyApi : PlatformBridgeApis.CastHostApi {
override fun sendMessage(message: PlatformBridgeApis.CastMessage) {
mMessageCastingChannel?.sendMessage(mCastSession, message)
}
override fun showCastDialog() {
val context = applicationContext
val activity = activity
if (context == null || activity == null) {
Log.d(TAG, "showCastDialog - missing context")
return
}
CastDialogOpener.showCastDialog(context, activity)
}
override fun loadMediaLoadRequestData(request: PlatformBridgeApis.MediaLoadRequestData) {
val remoteMediaClient: RemoteMediaClient = remoteMediaClient ?: return
val mediaLoadRequest = getMediaLoadRequestData(request)
remoteMediaClient.load(mediaLoadRequest)
}
override fun getMediaInfo(): PlatformBridgeApis.MediaInfo {
val remoteMediaClient: RemoteMediaClient = remoteMediaClient
?: throw IllegalStateException("Missing cast session")
return getFlutterMediaInfo(remoteMediaClient.mediaInfo)
}
override fun play() {
val remoteMediaClient: RemoteMediaClient = remoteMediaClient ?: return
remoteMediaClient.play()
}
override fun pause() {
val remoteMediaClient: RemoteMediaClient = remoteMediaClient ?: return
remoteMediaClient.pause()
}
override fun stop() {
val remoteMediaClient: RemoteMediaClient = remoteMediaClient ?: return
remoteMediaClient.stop()
}
override fun showTracksChooserDialog() {
if (activity !is FragmentActivity) {
Log.e(TAG, "Error: no_fragment_activity, FlutterCastFramework requires activity to be a FragmentActivity.")
return
}
val activity = activity as? FragmentActivity
if (activity == null) {
Log.d(TAG, "showTracksChooserDialog - missing context")
return
}
TracksChooserDialogFragment.newInstance()
.show(activity.supportFragmentManager, "FlutterCastFrameworkTracksChooserDialog")
}
override fun skipAd() {
val remoteMediaClient: RemoteMediaClient = remoteMediaClient ?: return
remoteMediaClient.skipAd()
}
override fun queueAppendItem(item: PlatformBridgeApis.MediaQueueItem) {
val remoteMediaClient: RemoteMediaClient = remoteMediaClient ?: return
val mediaQueueItem = getMediaQueueItem(item)
remoteMediaClient.queueAppendItem(mediaQueueItem, null)
}
override fun queueNextItem() {
val remoteMediaClient: RemoteMediaClient = remoteMediaClient ?: return
remoteMediaClient.queueNext(null)
}
override fun queuePrevItem() {
val remoteMediaClient: RemoteMediaClient = remoteMediaClient ?: return
remoteMediaClient.queuePrev(null)
}
override fun getQueueItemCount(): Long {
return mediaQueue?.itemCount?.toLong() ?: -1
}
override fun getQueueItemAtIndex(index: Long): PlatformBridgeApis.MediaQueueItem {
if (index < 0) return getFlutterMediaQueueItem(null)
val mediaQueueItem = mediaQueue?.getItemAtIndex(index.toInt(), true)
return getFlutterMediaQueueItem(mediaQueueItem)
}
override fun setMute(muted: Boolean) {
val castSession = mCastSession ?: return
castSession.isMute = muted
}
override fun getCastDevice(): PlatformBridgeApis.CastDevice {
val castSession = mCastSession ?: throw IllegalStateException("Missing cast session")
val castDevice = castSession.castDevice
return PlatformBridgeApis.CastDevice().apply {
deviceId = castDevice.deviceId
friendlyName = castDevice.friendlyName
modelName = castDevice.modelName
}
}
}
private fun getOnNamespaceResult(oldSession: CastSession?, newSession: CastSession?) = PlatformBridgeApis.CastFlutterApi.Reply<MutableList<String>> { namespaces ->
Log.d(TAG, "Updating mCastSession - getOnNamespaceResult - param: $namespaces")
if (oldSession == null && newSession == null) return@Reply // nothing to do here
if (namespaces == null || !namespaces.any()) return@Reply // nothing to do here
namespaces.forEach { namespace ->
try {
oldSession?.removeMessageReceivedCallbacks(namespace)
newSession?.setMessageReceivedCallbacks(namespace, mMessageCastingChannel)
} catch (e: java.lang.Exception) {
Log.e(TAG, "Updating mCastSession - Exception while creating channel", e)
}
}
}
private inner class CastSessionManagerListener : SessionManagerListener<CastSession> {
private var TAG = "SessionManagerListenerImpl"
override fun onSessionSuspended(session: CastSession?, p1: Int) {
Log.d(TAG, "onSessionSuspended")
flutterApi?.onSessionSuspended { }
}
override fun onSessionStarting(session: CastSession?) {
Log.d(TAG, "onSessionStarting")
flutterApi?.onSessionStarting { }
mCastSession = session
}
override fun onSessionResuming(session: CastSession?, p1: String?) {
Log.d(TAG, "onSessionResuming")
flutterApi?.onSessionResuming { }
mCastSession = session
}
override fun onSessionEnding(session: CastSession?) {
Log.d(TAG, "onSessionEnding")
flutterApi?.onSessionEnding { }
}
override fun onSessionStartFailed(session: CastSession?, p1: Int) {
Log.d(TAG, "onSessionStartFailed")
flutterApi?.onSessionStartFailed { }
}
override fun onSessionResumeFailed(session: CastSession?, p1: Int) {
Log.d(TAG, "onSessionResumeFailed")
flutterApi?.onSessionResumeFailed { }
}
override fun onSessionStarted(session: CastSession, sessionId: String) {
Log.d(TAG, "onSessionStarted")
flutterApi?.onSessionStarted { }
mCastSession = session
}
override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) {
Log.d(TAG, "onSessionResumed")
flutterApi?.onSessionResumed { }
mCastSession = session
}
override fun onSessionEnded(session: CastSession, error: Int) {
Log.d(TAG, "onSessionEnded")
flutterApi?.onSessionEnded { }
}
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
}

View file

@ -1,37 +0,0 @@
package com.gianlucaparadise.flutter_cast_framework.cast
import android.app.Activity
import android.content.Context
import android.util.Log
import androidx.mediarouter.app.MediaRouteChooserDialog
import androidx.mediarouter.app.MediaRouteControllerDialog
import com.gianlucaparadise.flutter_cast_framework.FlutterCastFrameworkPlugin
import com.google.android.gms.cast.framework.CastContext
object CastDialogOpener {
fun showCastDialog(applicationContext: Context, activity: Activity) {
val castContext = CastContext.getSharedInstance(applicationContext)
val castSession = castContext.sessionManager.currentCastSession
val themeResId = activity.packageManager.getActivityInfo(activity.componentName, 0).themeResource
try {
if (castSession != null) {
// This dialog allows the user to control or disconnect from the currently selected route.
MediaRouteControllerDialog(activity, themeResId)
.show()
} else {
// This dialog allows the user to choose a route that matches a given selector.
MediaRouteChooserDialog(activity, themeResId).apply {
routeSelector = castContext.mergedSelector
show()
}
}
} catch (ex: IllegalArgumentException) {
Log.d(FlutterCastFrameworkPlugin.TAG, "Exception while opening Dialog")
throw IllegalArgumentException("Error while opening MediaRouteDialog." +
" Did you use AppCompat theme on your activity?" +
" Check https://developers.google.com/cast/docs/android_sender/integrate#androidtheme", ex)
}
}
}

View file

@ -1,24 +0,0 @@
package com.gianlucaparadise.flutter_cast_framework.cast
import android.content.Context
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.SessionProvider
/**
* This is here to be used as an example
*/
class DefaultCastOptionsProvider : OptionsProvider {
// TODO: find a way to build this from dart code. Maybe source_gen?
override fun getCastOptions(context: Context): CastOptions {
return CastOptions.Builder()
.setReceiverApplicationId("4F8B3483")
.build()
}
override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
return null
}
}

View file

@ -1,48 +0,0 @@
package com.gianlucaparadise.flutter_cast_framework.cast
import android.util.Log
import com.gianlucaparadise.flutter_cast_framework.PlatformBridgeApis
import com.google.android.gms.cast.Cast
import com.google.android.gms.cast.CastDevice
import com.google.android.gms.cast.framework.CastSession
class MessageCastingChannel(private val flutterApi : PlatformBridgeApis.CastFlutterApi) : Cast.MessageReceivedCallback {
companion object {
const val TAG = "MessageCastingChannel"
}
override fun onMessageReceived(castDevice: CastDevice?, namespace: String?, message: String?) {
Log.d(TAG, "Message received: $message:")
val castMessage = PlatformBridgeApis.CastMessage()
castMessage.namespace = namespace
castMessage.message = message
flutterApi.onMessageReceived(castMessage) {}
}
fun sendMessage(castSession: CastSession?, castMessage: PlatformBridgeApis.CastMessage?) {
Log.d(TAG, "Send Message arguments: $castMessage:")
if (castMessage == null) return
val namespace = castMessage.namespace
val message = castMessage.message
sendMessage(castSession, namespace, message)
}
private fun sendMessage(castSession: CastSession?, namespace: String?, message: String?) {
try {
if (castSession == null) {
Log.d(TAG, "No session")
return
}
castSession.sendMessage(namespace, message)
} catch (ex: Exception) {
Log.e(TAG, "Error while sending ${message}:")
Log.e(TAG, ex.toString())
}
}
}

View file

@ -1,214 +0,0 @@
package com.gianlucaparadise.flutter_cast_framework.media
import com.gianlucaparadise.flutter_cast_framework.PlatformBridgeApis
import com.google.android.gms.cast.*
import com.google.android.gms.common.images.WebImage
fun getFlutterMediaStatus(mediaStatus: MediaStatus?): PlatformBridgeApis.MediaStatus {
val flutterMediaInfo = getFlutterMediaInfo(mediaStatus?.mediaInfo)
val flutterPlayerState = getFlutterPlayerState(mediaStatus?.playerState)
val flutterAdBreakStatus = getFlutterAdBreakStatus(mediaStatus?.adBreakStatus)
return PlatformBridgeApis.MediaStatus().apply {
isPlayingAd = mediaStatus?.isPlayingAd ?: false
mediaInfo = flutterMediaInfo
playerState = flutterPlayerState
adBreakStatus = flutterAdBreakStatus
}
}
fun getFlutterAdBreakStatus(adBreakStatus: AdBreakStatus?): PlatformBridgeApis.AdBreakStatus {
return PlatformBridgeApis.AdBreakStatus().apply {
adBreakId = adBreakStatus?.breakId ?: ""
adBreakClipId = adBreakStatus?.breakClipId ?: ""
whenSkippableMs = adBreakStatus?.whenSkippableInMs ?: -1
}
}
fun getFlutterPlayerState(playerStateRaw: Int?): PlatformBridgeApis.PlayerState {
return when (playerStateRaw) {
MediaStatus.PLAYER_STATE_UNKNOWN -> PlatformBridgeApis.PlayerState.unknown
MediaStatus.PLAYER_STATE_BUFFERING -> PlatformBridgeApis.PlayerState.buffering
MediaStatus.PLAYER_STATE_IDLE -> PlatformBridgeApis.PlayerState.idle
MediaStatus.PLAYER_STATE_LOADING -> PlatformBridgeApis.PlayerState.loading
MediaStatus.PLAYER_STATE_PAUSED -> PlatformBridgeApis.PlayerState.paused
MediaStatus.PLAYER_STATE_PLAYING -> PlatformBridgeApis.PlayerState.playing
else -> PlatformBridgeApis.PlayerState.unknown
}
}
fun getFlutterMediaInfo(mediaInfo: MediaInfo?): PlatformBridgeApis.MediaInfo {
val flutterMediaMetadata = getFlutterMediaMetadata(mediaInfo?.metadata)
val flutterMediaTracks = getFlutterMediaTracks(mediaInfo?.mediaTracks)
val flutterStreamType = getFlutterStreamType(mediaInfo?.streamType)
val flutterAdBreakClips = getFlutterAdBreakClips(mediaInfo?.adBreakClips)
return PlatformBridgeApis.MediaInfo().apply {
contentId = mediaInfo?.contentId ?: ""
contentType = mediaInfo?.contentType ?: ""
customDataAsJson = mediaInfo?.customData?.toString()
mediaMetadata = flutterMediaMetadata
mediaTracks = flutterMediaTracks
streamDuration = mediaInfo?.streamDuration ?: 0
streamType = flutterStreamType
adBreakClips = flutterAdBreakClips
}
}
fun getFlutterStreamType(streamType: Int?): PlatformBridgeApis.StreamType {
return when (streamType) {
-1 -> PlatformBridgeApis.StreamType.invalid
0 -> PlatformBridgeApis.StreamType.none
1 -> PlatformBridgeApis.StreamType.buffered
2 -> PlatformBridgeApis.StreamType.live
else -> PlatformBridgeApis.StreamType.invalid
}
}
fun getFlutterMediaTracks(mediaTracks: List<MediaTrack>?): List<PlatformBridgeApis.MediaTrack> {
return mediaTracks?.map {
getFlutterMediaTrack(it)
} ?: emptyList()
}
fun getFlutterMediaTrack(mediaTrack: MediaTrack?): PlatformBridgeApis.MediaTrack {
val flutterSubtype = getFlutterSubtype(mediaTrack?.subtype)
val flutterType = getFlutterType(mediaTrack?.type)
return PlatformBridgeApis.MediaTrack().apply {
contentId = mediaTrack?.contentId ?: ""
id = mediaTrack?.id ?: -1
language = mediaTrack?.language ?: ""
name = mediaTrack?.name ?: ""
trackSubtype = flutterSubtype
trackType = flutterType
}
}
fun getFlutterAdBreakClips(adBreakClips: List<AdBreakClipInfo>?): List<PlatformBridgeApis.AdBreakClipInfo> {
return adBreakClips?.map {
getFlutterAdBreakClipInfo(it)
} ?: emptyList()
}
fun getFlutterAdBreakClipInfo(adBreakClipInfo: AdBreakClipInfo?): PlatformBridgeApis.AdBreakClipInfo {
return PlatformBridgeApis.AdBreakClipInfo().apply {
id = adBreakClipInfo?.id
title = adBreakClipInfo?.title
contentId = adBreakClipInfo?.contentId
contentUrl = adBreakClipInfo?.contentUrl
clickThroughUrl = adBreakClipInfo?.clickThroughUrl
durationMs = adBreakClipInfo?.durationInMs
imageUrl = adBreakClipInfo?.imageUrl
mimeType = adBreakClipInfo?.mimeType
whenSkippableMs = adBreakClipInfo?.whenSkippableInMs
}
}
fun getFlutterType(type: Int?): PlatformBridgeApis.TrackType {
return when (type) {
0 -> PlatformBridgeApis.TrackType.unknown
1 -> PlatformBridgeApis.TrackType.text
2 -> PlatformBridgeApis.TrackType.audio
3 -> PlatformBridgeApis.TrackType.video
else -> PlatformBridgeApis.TrackType.unknown
}
}
fun getFlutterSubtype(subtype: Int?): PlatformBridgeApis.TrackSubtype {
return when (subtype) {
-1 -> PlatformBridgeApis.TrackSubtype.unknown
0 -> PlatformBridgeApis.TrackSubtype.none
1 -> PlatformBridgeApis.TrackSubtype.subtitles
2 -> PlatformBridgeApis.TrackSubtype.captions
3 -> PlatformBridgeApis.TrackSubtype.descriptions
4 -> PlatformBridgeApis.TrackSubtype.chapters
5 -> PlatformBridgeApis.TrackSubtype.metadata
else -> PlatformBridgeApis.TrackSubtype.unknown
}
}
fun getFlutterMediaMetadata(mediaMetadata: MediaMetadata?): PlatformBridgeApis.MediaMetadata {
val flutterMediaType = getFlutterMediaType(mediaMetadata?.mediaType)
val flutterWebImages = getFlutterWebImages(mediaMetadata?.images)
val flutterStrings = getFlutterStrings(mediaMetadata)
return PlatformBridgeApis.MediaMetadata().apply {
mediaType = flutterMediaType
webImages = flutterWebImages
strings = flutterStrings
}
}
fun getFlutterWebImages(images: List<WebImage>?): List<PlatformBridgeApis.WebImage> {
return images?.map {
PlatformBridgeApis.WebImage().apply {
url = it.url.toString()
}
} ?: emptyList()
}
fun getFlutterMediaType(mediaType: Int?): PlatformBridgeApis.MediaType {
return when (mediaType) {
0 -> PlatformBridgeApis.MediaType.generic
1 -> PlatformBridgeApis.MediaType.movie
2 -> PlatformBridgeApis.MediaType.tvShow
3 -> PlatformBridgeApis.MediaType.musicTrack
4 -> PlatformBridgeApis.MediaType.photo
5 -> PlatformBridgeApis.MediaType.audiobookChapter
100 -> PlatformBridgeApis.MediaType.user
else -> PlatformBridgeApis.MediaType.generic
}
}
fun getFlutterStrings(mediaMetadata: MediaMetadata?): Map<String, String> {
val stringsKeys = mediaMetadata?.keySet() ?: return emptyMap()
return stringsKeys.map { getFlutterMediaMetadataKey(it) to mediaMetadata.getString(it) }.toMap()
}
fun getFlutterMediaMetadataKey(mediaMetadataKey: String): String {
return when (mediaMetadataKey) {
"com.google.android.gms.cast.metadata.ALBUM_ARTIST" -> PlatformBridgeApis.MediaMetadataKey.albumArtist.name
"com.google.android.gms.cast.metadata.ALBUM_TITLE" -> PlatformBridgeApis.MediaMetadataKey.albumTitle.name
"com.google.android.gms.cast.metadata.ARTIST" -> PlatformBridgeApis.MediaMetadataKey.artist.name
"com.google.android.gms.cast.metadata.BOOK_TITLE" -> PlatformBridgeApis.MediaMetadataKey.bookTitle.name
"com.google.android.gms.cast.metadata.BROADCAST_DATE" -> PlatformBridgeApis.MediaMetadataKey.broadcastDate.name
"com.google.android.gms.cast.metadata.CHAPTER_NUMBER" -> PlatformBridgeApis.MediaMetadataKey.chapterNumber.name
"com.google.android.gms.cast.metadata.CHAPTER_TITLE" -> PlatformBridgeApis.MediaMetadataKey.chapterTitle.name
"com.google.android.gms.cast.metadata.COMPOSER" -> PlatformBridgeApis.MediaMetadataKey.composer.name
"com.google.android.gms.cast.metadata.CREATION_DATE" -> PlatformBridgeApis.MediaMetadataKey.creationDate.name
"com.google.android.gms.cast.metadata.DISC_NUMBER" -> PlatformBridgeApis.MediaMetadataKey.discNumber.name
"com.google.android.gms.cast.metadata.EPISODE_NUMBER" -> PlatformBridgeApis.MediaMetadataKey.episodeNumber.name
"com.google.android.gms.cast.metadata.HEIGHT" -> PlatformBridgeApis.MediaMetadataKey.height.name
"com.google.android.gms.cast.metadata.LOCATION_LATITUDE" -> PlatformBridgeApis.MediaMetadataKey.locationLatitude.name
"com.google.android.gms.cast.metadata.LOCATION_LONGITUDE" -> PlatformBridgeApis.MediaMetadataKey.locationLongitude.name
"com.google.android.gms.cast.metadata.LOCATION_NAME" -> PlatformBridgeApis.MediaMetadataKey.locationName.name
"com.google.android.gms.cast.metadata.QUEUE_ITEM_ID" -> PlatformBridgeApis.MediaMetadataKey.queueItemId.name
"com.google.android.gms.cast.metadata.RELEASE_DATE" -> PlatformBridgeApis.MediaMetadataKey.releaseDate.name
"com.google.android.gms.cast.metadata.SEASON_NUMBER" -> PlatformBridgeApis.MediaMetadataKey.seasonNumber.name
"com.google.android.gms.cast.metadata.SECTION_DURATION" -> PlatformBridgeApis.MediaMetadataKey.sectionDuration.name
"com.google.android.gms.cast.metadata.SECTION_START_ABSOLUTE_TIME" -> PlatformBridgeApis.MediaMetadataKey.sectionStartAbsoluteTime.name
"com.google.android.gms.cast.metadata.SECTION_START_TIME_IN_CONTAINER" -> PlatformBridgeApis.MediaMetadataKey.sectionStartTimeInContainer.name
"com.google.android.gms.cast.metadata.SECTION_START_TIME_IN_MEDIA" -> PlatformBridgeApis.MediaMetadataKey.sectionStartTimeInMedia.name
"com.google.android.gms.cast.metadata.SERIES_TITLE" -> PlatformBridgeApis.MediaMetadataKey.seriesTitle.name
"com.google.android.gms.cast.metadata.STUDIO" -> PlatformBridgeApis.MediaMetadataKey.studio.name
"com.google.android.gms.cast.metadata.SUBTITLE" -> PlatformBridgeApis.MediaMetadataKey.subtitle.name
"com.google.android.gms.cast.metadata.TITLE" -> PlatformBridgeApis.MediaMetadataKey.title.name
"com.google.android.gms.cast.metadata.TRACK_NUMBER" -> PlatformBridgeApis.MediaMetadataKey.trackNumber.name
"com.google.android.gms.cast.metadata.WIDTH" -> PlatformBridgeApis.MediaMetadataKey.width.name
else -> mediaMetadataKey
}
}
fun getFlutterMediaQueueItem(item: MediaQueueItem?): PlatformBridgeApis.MediaQueueItem {
val mediaInto = getFlutterMediaInfo(item?.media)
return PlatformBridgeApis.MediaQueueItem().apply {
itemId = item?.itemId?.toLong()
autoplay = item?.autoplay ?: false
playbackDuration = item?.playbackDuration ?: -1.0
startTime = item?.startTime ?: 0.0
preloadTime = item?.preloadTime ?: 0.0
media = mediaInto
}
}

View file

@ -1,174 +0,0 @@
package com.gianlucaparadise.flutter_cast_framework.media
import android.net.Uri
import com.gianlucaparadise.flutter_cast_framework.PlatformBridgeApis
import com.google.android.gms.cast.*
import com.google.android.gms.common.images.WebImage
import org.json.JSONObject
fun getMediaLoadRequestData(request: PlatformBridgeApis.MediaLoadRequestData): MediaLoadRequestData {
val mediaInfo = getMediaInfo(request.mediaInfo)
val builder = MediaLoadRequestData.Builder()
.setMediaInfo(mediaInfo)
.setAutoplay(request.shouldAutoplay)
request.currentTime?.let { builder.setCurrentTime(it) }
return builder.build()
}
fun getMediaInfo(mediaInfo: PlatformBridgeApis.MediaInfo?): MediaInfo? {
if (mediaInfo == null) return null
val streamType = getStreamType(mediaInfo.streamType
?: throw IllegalArgumentException("streamType is a required field"))
val customData = JSONObject(mediaInfo.customDataAsJson ?: "{}")
val builder = MediaInfo.Builder(mediaInfo.contentId)
.setStreamType(streamType)
.setContentType(mediaInfo.contentType)
.setStreamDuration(mediaInfo.streamDuration
?: throw IllegalArgumentException("streamDuration is a required field"))
.setCustomData(customData)
mediaInfo.mediaMetadata?.let {
val metadata = getMediaMetadata(it)
builder.setMetadata(metadata)
}
mediaInfo.mediaTracks?.let {
val mediaTracks = it.map { t -> getMediaTrack(t) }
builder.setMediaTracks(mediaTracks)
}
return builder.build()
}
fun getStreamType(streamType: PlatformBridgeApis.StreamType): Int {
return when (streamType) {
PlatformBridgeApis.StreamType.invalid -> -1
PlatformBridgeApis.StreamType.none -> 0
PlatformBridgeApis.StreamType.buffered -> 1
PlatformBridgeApis.StreamType.live -> 2
}
}
fun getMediaMetadata(mediaMetadata: PlatformBridgeApis.MediaMetadata): MediaMetadata {
val mediaType = mediaMetadata.mediaType
val result = if (mediaType == null) MediaMetadata() else {
val hostMediaType = getMediaType(mediaType)
MediaMetadata(hostMediaType)
}
mediaMetadata.strings?.forEach {
val key = getMediaMetadataKey(it.key)
result.putString(key, it.value)
}
mediaMetadata.webImages?.forEach {
val uri = Uri.parse(it.url)
val webImage = WebImage(uri)
result.addImage(webImage)
}
return result
}
fun getMediaType(mediaType: PlatformBridgeApis.MediaType): Int {
return when (mediaType) {
PlatformBridgeApis.MediaType.generic -> 0
PlatformBridgeApis.MediaType.movie -> 1
PlatformBridgeApis.MediaType.tvShow -> 2
PlatformBridgeApis.MediaType.musicTrack -> 3
PlatformBridgeApis.MediaType.photo -> 4
PlatformBridgeApis.MediaType.audiobookChapter -> 5
PlatformBridgeApis.MediaType.user -> 100
}
}
fun getMediaMetadataKey(mediaMetadataKey: String): String {
return when (mediaMetadataKey) {
PlatformBridgeApis.MediaMetadataKey.albumArtist.name -> "com.google.android.gms.cast.metadata.ALBUM_ARTIST"
PlatformBridgeApis.MediaMetadataKey.albumTitle.name -> "com.google.android.gms.cast.metadata.ALBUM_TITLE"
PlatformBridgeApis.MediaMetadataKey.artist.name -> "com.google.android.gms.cast.metadata.ARTIST"
PlatformBridgeApis.MediaMetadataKey.bookTitle.name -> "com.google.android.gms.cast.metadata.BOOK_TITLE"
PlatformBridgeApis.MediaMetadataKey.broadcastDate.name -> "com.google.android.gms.cast.metadata.BROADCAST_DATE"
PlatformBridgeApis.MediaMetadataKey.chapterNumber.name -> "com.google.android.gms.cast.metadata.CHAPTER_NUMBER"
PlatformBridgeApis.MediaMetadataKey.chapterTitle.name -> "com.google.android.gms.cast.metadata.CHAPTER_TITLE"
PlatformBridgeApis.MediaMetadataKey.composer.name -> "com.google.android.gms.cast.metadata.COMPOSER"
PlatformBridgeApis.MediaMetadataKey.creationDate.name -> "com.google.android.gms.cast.metadata.CREATION_DATE"
PlatformBridgeApis.MediaMetadataKey.discNumber.name -> "com.google.android.gms.cast.metadata.DISC_NUMBER"
PlatformBridgeApis.MediaMetadataKey.episodeNumber.name -> "com.google.android.gms.cast.metadata.EPISODE_NUMBER"
PlatformBridgeApis.MediaMetadataKey.height.name -> "com.google.android.gms.cast.metadata.HEIGHT"
PlatformBridgeApis.MediaMetadataKey.locationLatitude.name -> "com.google.android.gms.cast.metadata.LOCATION_LATITUDE"
PlatformBridgeApis.MediaMetadataKey.locationLongitude.name -> "com.google.android.gms.cast.metadata.LOCATION_LONGITUDE"
PlatformBridgeApis.MediaMetadataKey.locationName.name -> "com.google.android.gms.cast.metadata.LOCATION_NAME"
PlatformBridgeApis.MediaMetadataKey.queueItemId.name -> "com.google.android.gms.cast.metadata.QUEUE_ITEM_ID"
PlatformBridgeApis.MediaMetadataKey.releaseDate.name -> "com.google.android.gms.cast.metadata.RELEASE_DATE"
PlatformBridgeApis.MediaMetadataKey.seasonNumber.name -> "com.google.android.gms.cast.metadata.SEASON_NUMBER"
PlatformBridgeApis.MediaMetadataKey.sectionDuration.name -> "com.google.android.gms.cast.metadata.SECTION_DURATION"
PlatformBridgeApis.MediaMetadataKey.sectionStartAbsoluteTime.name -> "com.google.android.gms.cast.metadata.SECTION_START_ABSOLUTE_TIME"
PlatformBridgeApis.MediaMetadataKey.sectionStartTimeInContainer.name -> "com.google.android.gms.cast.metadata.SECTION_START_TIME_IN_CONTAINER"
PlatformBridgeApis.MediaMetadataKey.sectionStartTimeInMedia.name -> "com.google.android.gms.cast.metadata.SECTION_START_TIME_IN_MEDIA"
PlatformBridgeApis.MediaMetadataKey.seriesTitle.name -> "com.google.android.gms.cast.metadata.SERIES_TITLE"
PlatformBridgeApis.MediaMetadataKey.studio.name -> "com.google.android.gms.cast.metadata.STUDIO"
PlatformBridgeApis.MediaMetadataKey.subtitle.name -> "com.google.android.gms.cast.metadata.SUBTITLE"
PlatformBridgeApis.MediaMetadataKey.title.name -> "com.google.android.gms.cast.metadata.TITLE"
PlatformBridgeApis.MediaMetadataKey.trackNumber.name -> "com.google.android.gms.cast.metadata.TRACK_NUMBER"
PlatformBridgeApis.MediaMetadataKey.width.name -> "com.google.android.gms.cast.metadata.WIDTH"
else -> throw IllegalArgumentException("mediaMetadataKey.strings keys is incorrect")
}
}
fun getMediaTrack(mediaTrack: PlatformBridgeApis.MediaTrack): MediaTrack {
val trackId = mediaTrack.id
?: throw IllegalArgumentException("mediaTrack ID is a required field")
val trackType = getTrackType(mediaTrack.trackType
?: throw IllegalArgumentException("trackType is a required field"))
val builder = MediaTrack.Builder(trackId, trackType)
.setName(mediaTrack.name)
.setContentId(mediaTrack.contentId)
mediaTrack.trackSubtype?.let {
val trackSubtype = getTrackSubtype(it)
builder.setSubtype(trackSubtype)
}
return builder.build()
}
fun getTrackType(trackType: PlatformBridgeApis.TrackType): Int {
return when (trackType) {
PlatformBridgeApis.TrackType.unknown -> 0
PlatformBridgeApis.TrackType.text -> 1
PlatformBridgeApis.TrackType.audio -> 2
PlatformBridgeApis.TrackType.video -> 3
}
}
fun getTrackSubtype(trackSubtype: PlatformBridgeApis.TrackSubtype): Int {
return when (trackSubtype) {
PlatformBridgeApis.TrackSubtype.unknown -> -1
PlatformBridgeApis.TrackSubtype.none -> 0
PlatformBridgeApis.TrackSubtype.subtitles -> 1
PlatformBridgeApis.TrackSubtype.captions -> 2
PlatformBridgeApis.TrackSubtype.descriptions -> 3
PlatformBridgeApis.TrackSubtype.chapters -> 4
PlatformBridgeApis.TrackSubtype.metadata -> 5
}
}
fun getMediaQueueItem(item: PlatformBridgeApis.MediaQueueItem?): MediaQueueItem? {
if (item?.media == null) return null
val mediaInfo = getMediaInfo(item.media)
val builder = MediaQueueItem.Builder(mediaInfo)
item.autoplay?.let { builder.setAutoplay(it) }
item.preloadTime?.let { builder.setPreloadTime(it) }
return builder.build()
}