CustomMessage handling: send and receive;
Code re-organization: CastDialogOpener, MethodNames;
This commit is contained in:
parent
9e10c120bc
commit
b4b8eb258a
9 changed files with 267 additions and 42 deletions
|
|
@ -7,6 +7,8 @@ import androidx.lifecycle.OnLifecycleEvent
|
|||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.mediarouter.app.MediaRouteChooserDialog
|
||||
import androidx.mediarouter.app.MediaRouteControllerDialog
|
||||
import com.gianlucaparadise.flutter_cast_framework.cast.CastDialogOpener
|
||||
import com.gianlucaparadise.flutter_cast_framework.cast.MessageCastingChannel
|
||||
import com.google.android.gms.cast.framework.CastContext
|
||||
import com.google.android.gms.cast.framework.CastSession
|
||||
import com.google.android.gms.cast.framework.SessionManager
|
||||
|
|
@ -28,23 +30,6 @@ class FlutterCastFrameworkPlugin(private val registrar: Registrar, private val c
|
|||
}
|
||||
}
|
||||
|
||||
private object MethodNames {
|
||||
const val onCastStateChanged = "CastContext.onCastStateChanged"
|
||||
const val showCastDialog = "showCastDialog"
|
||||
|
||||
// region SessionManager
|
||||
const val onSessionStarting = "SessionManager.onSessionStarting"
|
||||
const val onSessionStarted = "SessionManager.onSessionStarted"
|
||||
const val onSessionStartFailed = "SessionManager.onSessionStartFailed"
|
||||
const val onSessionEnding = "SessionManager.onSessionEnding"
|
||||
const val onSessionEnded = "SessionManager.onSessionEnded"
|
||||
const val onSessionResuming = "SessionManager.onSessionResuming"
|
||||
const val onSessionResumed = "SessionManager.onSessionResumed"
|
||||
const val onSessionResumeFailed = "SessionManager.onSessionResumeFailed"
|
||||
const val onSessionSuspended = "SessionManager.onSessionSuspended"
|
||||
// end-region
|
||||
}
|
||||
|
||||
init {
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||
|
||||
|
|
@ -57,16 +42,32 @@ class FlutterCastFrameworkPlugin(private val registrar: Registrar, private val c
|
|||
private lateinit var mSessionManager: SessionManager
|
||||
private val mSessionManagerListener = CastSessionManagerListener()
|
||||
|
||||
private val mMessageCastingChannel = MessageCastingChannel(channel)
|
||||
|
||||
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 result = NamespaceResult(oldSession = field, newSession = value)
|
||||
|
||||
field = value
|
||||
|
||||
channel.invokeMethod(MethodNames.getSessionMessageNamespaces, null, result)
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
||||
fun onCreate() {
|
||||
Log.d(TAG, "App: ON_CREATE")
|
||||
mSessionManager = CastContext.getSharedInstance(registrar.activeContext()).sessionManager
|
||||
mCastSession = mSessionManager.currentCastSession
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
fun onResume() {
|
||||
Log.d(TAG, "App: ON_RESUME")
|
||||
mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
|
||||
mCastSession = mSessionManager.currentCastSession
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
|
|
@ -76,39 +77,53 @@ class FlutterCastFrameworkPlugin(private val registrar: Registrar, private val c
|
|||
mSessionManagerListener,
|
||||
CastSession::class.java
|
||||
)
|
||||
// I can't set this to null because I need the cast session to send commands from notification
|
||||
// mCastSession = null
|
||||
}
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: Result) {
|
||||
when (call.method) {
|
||||
MethodNames.showCastDialog -> showCastDialog()
|
||||
val method = call.method
|
||||
val arguments = call.arguments
|
||||
|
||||
when (method) {
|
||||
MethodNames.showCastDialog -> CastDialogOpener.showCastDialog(registrar)
|
||||
MethodNames.sendMessage -> this.mMessageCastingChannel.sendMessage(mCastSession, arguments)
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCastDialog() {
|
||||
val castContext = CastContext.getSharedInstance(registrar.activeContext())
|
||||
val castSession = castContext.sessionManager.currentCastSession
|
||||
private inner class NamespaceResult(val oldSession: CastSession?, val newSession: CastSession?) : Result {
|
||||
override fun notImplemented() {
|
||||
Log.d(TAG, "Updating mCastSession - notImplemented")
|
||||
}
|
||||
|
||||
val activity = this.registrar.activity()
|
||||
val themeResId = activity.packageManager.getActivityInfo(activity.componentName, 0).themeResource
|
||||
override fun error(p0: String?, p1: String?, p2: Any?) {
|
||||
Log.d(TAG, "Updating mCastSession - error - $p0 $p1 $p2")
|
||||
}
|
||||
|
||||
override fun success(args: Any?) {
|
||||
Log.d(TAG, "Updating mCastSession - success - param: $args")
|
||||
if (oldSession == null && newSession == null) return // nothing to do here
|
||||
if (args == null) return // nothing to do here
|
||||
|
||||
if (args !is ArrayList<*>)
|
||||
throw IllegalArgumentException("${MethodNames.getSessionMessageNamespaces} method expects an ArrayList<String>")
|
||||
|
||||
if (!args.any()) return // nothing to do here
|
||||
|
||||
if (args[0] !is String)
|
||||
throw IllegalArgumentException("${MethodNames.getSessionMessageNamespaces} method expects an ArrayList<String>")
|
||||
|
||||
val namespaces = args as ArrayList<String>
|
||||
namespaces.forEach {
|
||||
try {
|
||||
if (castSession != null) {
|
||||
// This dialog allows the user to control or disconnect from the currently selected route.
|
||||
MediaRouteControllerDialog(registrar.activeContext(), themeResId)
|
||||
.show()
|
||||
} else {
|
||||
// This dialog allows the user to choose a route that matches a given selector.
|
||||
MediaRouteChooserDialog(registrar.activeContext(), themeResId).apply {
|
||||
routeSelector = castContext.mergedSelector
|
||||
show()
|
||||
oldSession?.removeMessageReceivedCallbacks(it)
|
||||
newSession?.setMessageReceivedCallbacks(it, mMessageCastingChannel)
|
||||
}
|
||||
catch (e: java.lang.Exception) {
|
||||
Log.e(TAG, "Updating mCastSession - Exception while creating channel", e)
|
||||
}
|
||||
}
|
||||
} catch (ex: IllegalArgumentException) {
|
||||
Log.d(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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,11 +138,15 @@ class FlutterCastFrameworkPlugin(private val registrar: Registrar, private val c
|
|||
override fun onSessionStarting(session: CastSession?) {
|
||||
Log.d(TAG, "onSessionStarting")
|
||||
channel.invokeMethod(MethodNames.onSessionStarting, null)
|
||||
|
||||
mCastSession = session
|
||||
}
|
||||
|
||||
override fun onSessionResuming(session: CastSession?, p1: String?) {
|
||||
Log.d(TAG, "onSessionResuming")
|
||||
channel.invokeMethod(MethodNames.onSessionResuming, null)
|
||||
|
||||
mCastSession = session
|
||||
}
|
||||
|
||||
override fun onSessionEnding(session: CastSession?) {
|
||||
|
|
@ -148,11 +167,15 @@ class FlutterCastFrameworkPlugin(private val registrar: Registrar, private val c
|
|||
override fun onSessionStarted(session: CastSession, sessionId: String) {
|
||||
Log.d(TAG, "onSessionStarted")
|
||||
channel.invokeMethod(MethodNames.onSessionStarted, null)
|
||||
|
||||
mCastSession = session
|
||||
}
|
||||
|
||||
override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) {
|
||||
Log.d(TAG, "onSessionResumed")
|
||||
channel.invokeMethod(MethodNames.onSessionResumed, null)
|
||||
|
||||
mCastSession = session
|
||||
}
|
||||
|
||||
override fun onSessionEnded(session: CastSession, error: Int) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.gianlucaparadise.flutter_cast_framework
|
||||
|
||||
object MethodNames {
|
||||
const val onCastStateChanged = "CastContext.onCastStateChanged"
|
||||
const val showCastDialog = "showCastDialog"
|
||||
|
||||
// region SessionManager
|
||||
const val onSessionStarting = "SessionManager.onSessionStarting"
|
||||
const val onSessionStarted = "SessionManager.onSessionStarted"
|
||||
const val onSessionStartFailed = "SessionManager.onSessionStartFailed"
|
||||
const val onSessionEnding = "SessionManager.onSessionEnding"
|
||||
const val onSessionEnded = "SessionManager.onSessionEnded"
|
||||
const val onSessionResuming = "SessionManager.onSessionResuming"
|
||||
const val onSessionResumed = "SessionManager.onSessionResumed"
|
||||
const val onSessionResumeFailed = "SessionManager.onSessionResumeFailed"
|
||||
const val onSessionSuspended = "SessionManager.onSessionSuspended"
|
||||
// end-region
|
||||
|
||||
const val getSessionMessageNamespaces = "CastSession.getSessionMessageNamespaces"
|
||||
const val onMessageReceived = "CastSession.onMessageReceived"
|
||||
const val sendMessage = "CastSession.sendMessage"
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.gianlucaparadise.flutter_cast_framework.cast
|
||||
|
||||
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
|
||||
import io.flutter.plugin.common.PluginRegistry
|
||||
|
||||
object CastDialogOpener {
|
||||
fun showCastDialog(registrar: PluginRegistry.Registrar) {
|
||||
val castContext = CastContext.getSharedInstance(registrar.activeContext())
|
||||
val castSession = castContext.sessionManager.currentCastSession
|
||||
|
||||
val activity = registrar.activity()
|
||||
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(registrar.activeContext(), themeResId)
|
||||
.show()
|
||||
} else {
|
||||
// This dialog allows the user to choose a route that matches a given selector.
|
||||
MediaRouteChooserDialog(registrar.activeContext(), 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.gianlucaparadise.flutter_cast_framework.cast
|
||||
|
||||
import android.util.Log
|
||||
import com.gianlucaparadise.flutter_cast_framework.MethodNames
|
||||
import com.google.android.gms.cast.Cast
|
||||
import com.google.android.gms.cast.CastDevice
|
||||
import com.google.android.gms.cast.framework.CastSession
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
class MessageCastingChannel(private val channel: MethodChannel) : Cast.MessageReceivedCallback {
|
||||
companion object {
|
||||
const val TAG = "MessageCastingChannel"
|
||||
}
|
||||
|
||||
override fun onMessageReceived(castDevice: CastDevice?, namespace: String?, message: String?) {
|
||||
Log.d(TAG, "Message received: $message:")
|
||||
val argsMap: HashMap<String, String?> = hashMapOf(
|
||||
"namespace" to namespace,
|
||||
"message" to message
|
||||
)
|
||||
|
||||
channel.invokeMethod(MethodNames.onMessageReceived, argsMap)
|
||||
}
|
||||
|
||||
fun sendMessage(castSession: CastSession?, arguments: Any) {
|
||||
Log.d(TAG, "Send Message arguments: $arguments:")
|
||||
val argsMap = arguments as HashMap<String, String?>
|
||||
val namespace = argsMap["namespace"]
|
||||
val message = argsMap["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())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -14,12 +14,25 @@ class MyApp extends StatefulWidget {
|
|||
class _MyAppState extends State<MyApp> {
|
||||
CastState _castState = CastState.idle;
|
||||
SessionState _sessionState = SessionState.idle;
|
||||
String _message = '';
|
||||
|
||||
final textMessageController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
FlutterCastFramework.castContext.state.addListener(_onCastStateChanged);
|
||||
FlutterCastFramework.castContext.sessionManager.state.addListener(_onSessionStateChanged);
|
||||
FlutterCastFramework.castContext.sessionManager.state
|
||||
.addListener(_onSessionStateChanged);
|
||||
FlutterCastFramework.castContext.sessionManager.onMessageReceived =
|
||||
_onMessageReceived;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Clean up the controller when the widget is disposed.
|
||||
textMessageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onCastStateChanged() {
|
||||
|
|
@ -32,10 +45,24 @@ class _MyAppState extends State<MyApp> {
|
|||
void _onSessionStateChanged() {
|
||||
debugPrint("Session state changed from example");
|
||||
setState(() {
|
||||
_sessionState = FlutterCastFramework.castContext.sessionManager.state.value;
|
||||
_sessionState =
|
||||
FlutterCastFramework.castContext.sessionManager.state.value;
|
||||
});
|
||||
}
|
||||
|
||||
void _onMessageReceived(String namespace, String message) {
|
||||
debugPrint("Message received from example");
|
||||
setState(() {
|
||||
_message = message;
|
||||
});
|
||||
}
|
||||
|
||||
void _onSendMessage() {
|
||||
String message = this.textMessageController.text;
|
||||
FlutterCastFramework.castContext.sessionManager
|
||||
.sendMessage('urn:x-cast:cast-your-instructions', message);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
|
|
@ -47,8 +74,30 @@ class _MyAppState extends State<MyApp> {
|
|||
child: Column(
|
||||
children: [
|
||||
CastButton(),
|
||||
Text(
|
||||
'States',
|
||||
style: Theme.of(context).textTheme.title,
|
||||
),
|
||||
Text('Cast State: $_castState'),
|
||||
Text('Cast State: $_sessionState'),
|
||||
Text(
|
||||
'Message',
|
||||
style: Theme.of(context).textTheme.title,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: textMessageController,
|
||||
),
|
||||
),
|
||||
RaisedButton(
|
||||
child: Text('Send'),
|
||||
onPressed: _onSendMessage,
|
||||
)
|
||||
],
|
||||
),
|
||||
Text('Received Message: $_message'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -11,4 +11,8 @@ class PlatformMethodNames {
|
|||
static const onSessionResumed = "SessionManager.onSessionResumed";
|
||||
static const onSessionResumeFailed = "SessionManager.onSessionResumeFailed";
|
||||
static const onSessionSuspended = "SessionManager.onSessionSuspended";
|
||||
|
||||
static const getSessionMessageNamespaces = "CastSession.getSessionMessageNamespaces";
|
||||
static const onMessageReceived = "CastSession.onMessageReceived";
|
||||
static const sendMessage = "CastSession.sendMessage";
|
||||
}
|
||||
|
|
@ -18,7 +18,13 @@ class CastContext {
|
|||
state.value = CastState.values[castState];
|
||||
}
|
||||
|
||||
SessionManager sessionManager = SessionManager();
|
||||
SessionManager _sessionManager;
|
||||
SessionManager get sessionManager {
|
||||
if (_sessionManager == null) {
|
||||
_sessionManager = SessionManager(_channel);
|
||||
}
|
||||
return _sessionManager;
|
||||
}
|
||||
}
|
||||
|
||||
enum CastState {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_cast_framework/MethodNames.dart';
|
||||
|
||||
class SessionManager {
|
||||
final MethodChannel _channel;
|
||||
|
||||
SessionManager(this._channel);
|
||||
|
||||
final ValueNotifier<SessionState> state = ValueNotifier(SessionState.idle);
|
||||
|
||||
void onSessionStateChanged(String method, dynamic arguments) {
|
||||
|
|
@ -35,8 +40,29 @@ class SessionManager {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MessageReceivedCallback onMessageReceived;
|
||||
|
||||
void platformOnMessageReceived(dynamic arguments) {
|
||||
if (onMessageReceived == null) return;
|
||||
final namespace = arguments['namespace'];
|
||||
final message = arguments['message'];
|
||||
|
||||
onMessageReceived(namespace, message);
|
||||
}
|
||||
|
||||
void sendMessage(String namespace, String message) {
|
||||
final argsMap = {
|
||||
'namespace': namespace,
|
||||
'message': '{"message":"$message"}'
|
||||
};
|
||||
_channel.invokeMethod(PlatformMethodNames.sendMessage, argsMap);
|
||||
}
|
||||
}
|
||||
|
||||
typedef MessageReceivedCallback = void Function(
|
||||
String namespace, String message);
|
||||
|
||||
enum SessionState {
|
||||
idle,
|
||||
session_starting,
|
||||
|
|
|
|||
|
|
@ -32,10 +32,19 @@ class FlutterCastFramework {
|
|||
castContext.sessionManager.onSessionStateChanged(method, arguments);
|
||||
break;
|
||||
|
||||
case PlatformMethodNames.getSessionMessageNamespaces:
|
||||
return ["urn:x-cast:cast-your-instructions"];
|
||||
|
||||
case PlatformMethodNames.onMessageReceived:
|
||||
castContext.sessionManager.platformOnMessageReceived(arguments);
|
||||
break;
|
||||
|
||||
default:
|
||||
debugPrint("Method not handled: $method");
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue