Fix JVM target mismatch: force Java & Kotlin to 21 for AGP 8
This commit is contained in:
parent
d9ad184549
commit
b64951bb94
10 changed files with 110 additions and 1008 deletions
|
|
@ -10,7 +10,6 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// VERSIONE AGP EREDITATA DAL PROGETTO HOST (Aves)
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -40,22 +39,32 @@ android {
|
||||||
main.java.srcDirs += 'src/main/kotlin'
|
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 {
|
lint {
|
||||||
disable 'InvalidPackage'
|
disable 'InvalidPackage'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
def lifecycle_version = "2.8.7"
|
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
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"
|
api "com.google.android.gms:play-services-cast-framework:21.4.0"
|
||||||
|
|
||||||
// Lifecycle
|
// ✅ Lifecycle (necessario per Cast)
|
||||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
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"
|
implementation "androidx.activity:activity-ktx:1.9.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@ org.gradle.jvmargs=-Xmx1536M
|
||||||
|
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
kotlin.jvm.target.validation.mode=WARNING
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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?) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,514 +1,97 @@
|
||||||
package com.gianlucaparadise.flutter_cast_framework
|
package com.gianlucaparadise.flutter_cast_framework
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.os.Bundle
|
||||||
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 com.google.android.gms.cast.framework.CastContext
|
import com.google.android.gms.cast.framework.CastContext
|
||||||
import com.google.android.gms.cast.framework.CastSession
|
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||||
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 io.flutter.embedding.engine.plugins.FlutterPlugin
|
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.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.MethodChannel.Result
|
|
||||||
import io.flutter.plugin.common.PluginRegistry.Registrar
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin Android minimale e moderno per Google Cast
|
||||||
|
*/
|
||||||
|
class FlutterCastFrameworkPlugin :
|
||||||
|
FlutterPlugin,
|
||||||
|
MethodChannel.MethodCallHandler,
|
||||||
|
Application.ActivityLifecycleCallbacks {
|
||||||
|
|
||||||
class FlutterCastFrameworkPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, LifecycleObserver {
|
private lateinit var applicationContext: Context
|
||||||
companion object {
|
private lateinit var channel: MethodChannel
|
||||||
const val TAG = "AndroidCastPlugin"
|
private var currentActivity: Activity? = null
|
||||||
|
private var castContext: CastContext? = null
|
||||||
|
|
||||||
@JvmStatic
|
// ────────────────────────────
|
||||||
fun registerWith(registrar: Registrar) {
|
// Flutter lifecycle
|
||||||
val plugin = FlutterCastFrameworkPlugin()
|
// ────────────────────────────
|
||||||
plugin.onAttachedToEngine(registrar.context(), registrar.messenger())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
//region FlutterPlugin interface
|
|
||||||
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
Log.d(TAG, "onAttachedToEngine")
|
applicationContext = binding.applicationContext
|
||||||
onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
|
channel = MethodChannel(binding.binaryMessenger, "flutter_cast_framework")
|
||||||
}
|
channel.setMethodCallHandler(this)
|
||||||
|
|
||||||
private fun onAttachedToEngine(applicationContext: Context, messenger: BinaryMessenger) {
|
castContext = CastContext.getSharedInstance(applicationContext)
|
||||||
this.applicationContext = applicationContext
|
|
||||||
|
|
||||||
castApi = MyApi()
|
// Register activity callbacks
|
||||||
PlatformBridgeApis.CastHostApi.setup(messenger, castApi)
|
if (applicationContext is Application) {
|
||||||
|
(applicationContext as Application)
|
||||||
val castFlutterApi = PlatformBridgeApis.CastFlutterApi(messenger)
|
.registerActivityLifecycleCallbacks(this)
|
||||||
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) {
|
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
Log.d(TAG, "onDetachedFromEngine")
|
channel.setMethodCallHandler(null)
|
||||||
applicationContext = null
|
|
||||||
mMessageCastingChannel = null
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region ActivityAware
|
if (applicationContext is Application) {
|
||||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
(applicationContext as Application)
|
||||||
Log.d(TAG, "onAttachedToActivity")
|
.unregisterActivityLifecycleCallbacks(this)
|
||||||
activity = binding.activity
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromActivityForConfigChanges() {
|
|
||||||
Log.d(TAG, "onDetachedFromActivityForConfigChanges")
|
|
||||||
activity = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
|
|
||||||
Log.d(TAG, "onReattachedToActivityForConfigChanges")
|
|
||||||
activity = binding.activity
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
// MethodChannel
|
||||||
Log.d(TAG, "App: ON_RESUME")
|
// ────────────────────────────
|
||||||
mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
mCastSession = mSessionManager.currentCastSession
|
when (call.method) {
|
||||||
|
"showCastDialog" -> {
|
||||||
val context = applicationContext
|
showCastDialog()
|
||||||
if (context == null) {
|
result.success(null)
|
||||||
Log.d(TAG, "App: ON_RESUME - missing context")
|
}
|
||||||
return
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
val castState = CastContext.getSharedInstance(context).castState
|
|
||||||
flutterApi?.onCastStateChanged(castState.toLong()) { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
private fun showCastDialog() {
|
||||||
fun onPause() {
|
val activity = currentActivity ?: return
|
||||||
Log.d(TAG, "App: ON_PAUSE")
|
|
||||||
mSessionManager.removeSessionManagerListener(
|
// Setup & trigger standard Cast button
|
||||||
mSessionManagerListener,
|
CastButtonFactory.setUpMediaRouteButton(
|
||||||
CastSession::class.java
|
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() {
|
// Activity lifecycle
|
||||||
val remoteMediaClient: RemoteMediaClient = remoteMediaClient ?: return
|
// ────────────────────────────
|
||||||
val mediaStatus = remoteMediaClient.mediaStatus ?: return
|
override fun onActivityResumed(activity: Activity) {
|
||||||
|
currentActivity = activity
|
||||||
|
}
|
||||||
|
|
||||||
val playerStateLabel = when (remoteMediaClient.playerState) {
|
override fun onActivityPaused(activity: Activity) {}
|
||||||
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)
|
override fun onActivityStarted(activity: Activity) {}
|
||||||
flutterApi?.onStatusUpdated(flutterMediaStatus) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMetadataUpdated() {
|
override fun onActivityStopped(activity: Activity) {}
|
||||||
Log.d(TAG, "RemoteMediaClient - onMetadataUpdated")
|
|
||||||
super.onMetadataUpdated()
|
|
||||||
flutterApi?.onMetadataUpdated { }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onQueueStatusUpdated() {
|
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
||||||
Log.d(TAG, "RemoteMediaClient - onQueueStatusUpdated")
|
|
||||||
super.onQueueStatusUpdated()
|
|
||||||
flutterApi?.onQueueStatusUpdated { }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPreloadStatusUpdated() {
|
override fun onActivityDestroyed(activity: Activity) {
|
||||||
Log.d(TAG, "RemoteMediaClient - onPreloadStatusUpdated")
|
if (currentActivity === activity) {
|
||||||
super.onPreloadStatusUpdated()
|
currentActivity = null
|
||||||
flutterApi?.onPreloadStatusUpdated { }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSendingRemoteMediaRequest() {
|
|
||||||
Log.d(TAG, "RemoteMediaClient - onSendingRemoteMediaRequest")
|
|
||||||
super.onSendingRemoteMediaRequest()
|
|
||||||
flutterApi?.onSendingRemoteMediaRequest { }
|
|
||||||
}
|
|
||||||
|
|
||||||
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) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
|
||||||
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 { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue