Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ build/

.idea/
example/ios/Flutter/flutter_export_environment.sh

example/ios/Flutter/ephemeral/
9 changes: 9 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
namespace 'com.gianlucaparadise.flutter_cast_framework'
compileSdkVersion 33

sourceSets {
Expand All @@ -34,6 +35,13 @@ android {
minSdkVersion 21
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
lintOptions {
disable 'InvalidPackage'
}
Expand All @@ -47,6 +55,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

api "com.google.android.gms:play-services-cast-framework:21.3.0"
implementation "androidx.mediarouter:mediarouter:1.3.0"

// Lifecycle
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
Expand Down
5 changes: 4 additions & 1 deletion android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
<manifest package="com.gianlucaparadise.flutter_cast_framework">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.gianlucaparadise.flutter_cast_framework">
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package com.gianlucaparadise.flutter_cast_framework

import android.app.Activity
import android.content.Context
import android.net.wifi.WifiManager
import android.util.Log
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.*
import androidx.mediarouter.media.MediaRouter
import com.gianlucaparadise.flutter_cast_framework.cast.CastDialogOpener
import com.gianlucaparadise.flutter_cast_framework.cast.MessageCastingChannel
import com.gianlucaparadise.flutter_cast_framework.media.*
Expand All @@ -25,24 +27,72 @@ 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


class FlutterCastFrameworkPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, DefaultLifecycleObserver {
companion object {
const val TAG = "AndroidCastPlugin"

@JvmStatic
fun registerWith(registrar: Registrar) {
val plugin = FlutterCastFrameworkPlugin()
plugin.onAttachedToEngine(registrar.context(), registrar.messenger())
}
}

init {
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}

// MediaRouter is used to keep an active discovery running while the app is
// in foreground. Without an active scan (normally kept alive by a native
// MediaRouteButton), CastContext.getCastState() stays at NO_DEVICES_AVAILABLE
// even when there are Cast devices reachable on the network.
private var mMediaRouter: MediaRouter? = null
private val mMediaRouterCallback = object : MediaRouter.Callback() {}

// Some devices (notably several Wi-Fi chipsets / MIUI builds) filter inbound
// multicast frames when no app holds a MulticastLock, which silently blocks
// mDNS (224.0.0.251) Cast discovery even though unicast to the device works.
private var mMulticastLock: WifiManager.MulticastLock? = null

private fun startActiveDiscovery(context: Context, castContext: CastContext) {
try {
val selector = castContext.mergedSelector ?: return
acquireMulticastLock(context)
val mediaRouter = mMediaRouter ?: MediaRouter.getInstance(context).also { mMediaRouter = it }
mediaRouter.addCallback(
selector,
mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN
)
Log.d(TAG, "Active cast discovery started")
} catch (e: Exception) {
Log.e(TAG, "Failed to start active cast discovery", e)
}
}
Comment on lines +51 to +65

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

O acquireMulticastLock(context) é chamado antes da verificação de nulidade de castContext.mergedSelector. Se mergedSelector for nulo, o método retorna antecipadamente, mas o MulticastLock permanece ativo sem que nenhuma descoberta ativa esteja ocorrendo. Isso pode causar um consumo desnecessário de bateria, pois impede que o chip Wi-Fi entre no modo de baixo consumo. Recomenda-se mover a chamada de acquireMulticastLock(context) para depois da verificação de nulidade do selector.

Suggested change
private fun startActiveDiscovery(context: Context, castContext: CastContext) {
try {
acquireMulticastLock(context)
val mediaRouter = mMediaRouter ?: MediaRouter.getInstance(context).also { mMediaRouter = it }
val selector = castContext.mergedSelector ?: return
mediaRouter.addCallback(
selector,
mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN
)
Log.d(TAG, "Active cast discovery started")
} catch (e: Exception) {
Log.e(TAG, "Failed to start active cast discovery", e)
}
}
private fun startActiveDiscovery(context: Context, castContext: CastContext) {
try {
val selector = castContext.mergedSelector ?: return
acquireMulticastLock(context)
val mediaRouter = mMediaRouter ?: MediaRouter.getInstance(context).also { mMediaRouter = it }
mediaRouter.addCallback(
selector,
mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN
)
Log.d(TAG, "Active cast discovery started")
} catch (e: Exception) {
Log.e(TAG, "Failed to start active cast discovery", e)
}
}


private fun stopActiveDiscovery() {
mMediaRouter?.removeCallback(mMediaRouterCallback)
releaseMulticastLock()
Log.d(TAG, "Active cast discovery stopped")
}

private fun acquireMulticastLock(context: Context) {
try {
if (mMulticastLock?.isHeld == true) return
val wifi = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager ?: return
mMulticastLock = wifi.createMulticastLock("flutter_cast_framework").apply {
setReferenceCounted(false)
acquire()
}
Log.d(TAG, "MulticastLock acquired")
} catch (e: Exception) {
Log.e(TAG, "Failed to acquire MulticastLock", e)
}
}

private fun releaseMulticastLock() {
try {
mMulticastLock?.let { if (it.isHeld) it.release() }
mMulticastLock = null
} catch (e: Exception) {
Log.e(TAG, "Failed to release MulticastLock", e)
}
}

//region FlutterPlugin interface
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
Log.d(TAG, "onAttachedToEngine")
Expand Down Expand Up @@ -183,12 +233,15 @@ class FlutterCastFrameworkPlugin : FlutterPlugin, MethodCallHandler, ActivityAwa
Log.d(TAG, "App: ON_RESUME - missing context")
return
}
startActiveDiscovery(context, castContext)

val castState = castContext.castState
flutterApi?.onCastStateChanged(castState.toLong()) { }
}

override fun onPause(owner: LifecycleOwner) {
Log.d(TAG, "App: ON_PAUSE")
stopActiveDiscovery()
mSessionManager?.removeSessionManagerListener(
mSessionManagerListener,
CastSession::class.java
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
package com.gianlucaparadise.flutter_cast_framework.cast

import android.content.Context
import com.google.android.gms.cast.CastMediaControlIntent
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
* Example OptionsProvider. Copy this class into your app and set OPTIONS_PROVIDER_CLASS_NAME
* in your AndroidManifest.xml to point to it.
*
* Receiver IDs for reference:
* Debug/test (any Chromecast, no Cast Console setup needed):
* CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID // = "CC1AD845"
* Production (must be published or device registered in Cast Console):
* "<YOUR_APP_ID>" // e.g. "6D8B5531"
*/
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")
// Use DEFAULT_MEDIA_RECEIVER_APPLICATION_ID for quick testing with any Chromecast.
// Replace with your production app ID once published in the Cast Console.
.setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
.build()
}

Expand Down
4 changes: 3 additions & 1 deletion example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import GoogleCast

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, GCKLoggerDelegate {
let kReceiverAppID = "CC1AD845"
// Use kGCKDefaultMediaReceiverApplicationID ("CC1AD845") for quick testing with any Chromecast.
// Replace with your production app ID once published in the Cast Console. e.g: "6D8B5531"
let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID
let kDebugLoggingEnabled = true

override func application(
Expand Down
90 changes: 57 additions & 33 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -29,42 +29,42 @@ packages:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.4.1"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.17.1"
version: "1.19.1"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
version: "1.0.9"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
version: "1.3.3"
flutter:
dependency: "direct main"
description: flutter
Expand All @@ -90,46 +90,62 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
js:
leak_tracker:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "0.6.7"
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
matcher:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.18"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.13.0"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.17.0"
path:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.1"
path_parsing:
dependency: transitive
description:
Expand Down Expand Up @@ -158,7 +174,7 @@ packages:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
version: "0.0.0"
source_span:
dependency: transitive
description:
Expand All @@ -171,18 +187,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.4"
string_scanner:
dependency: transitive
description:
Expand All @@ -203,10 +219,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.7.9"
transparent_image:
dependency: "direct main"
description:
Expand Down Expand Up @@ -243,10 +259,18 @@ packages:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
url: "https://pub.dev"
source: hosted
version: "15.2.0"
xml:
dependency: transitive
description:
Expand All @@ -256,5 +280,5 @@ packages:
source: hosted
version: "6.2.2"
sdks:
dart: ">=3.0.0-0 <4.0.0"
flutter: ">=3.7.0-0"
dart: ">=3.9.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
Loading