iOS: CastSession management

This commit is contained in:
gianlucaparadise 2019-11-20 06:15:26 +01:00
parent 9dc03055de
commit 2f61c1682c
3 changed files with 149 additions and 19 deletions

View file

@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
@ -38,8 +36,8 @@
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions> <Testables>
</AdditionalOptions> </Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
@ -61,8 +59,6 @@
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Profile" buildConfiguration = "Profile"

View file

@ -10,4 +10,16 @@ import Foundation
enum MethodNames : String { enum MethodNames : String {
case onCastStateChanged = "CastContext.onCastStateChanged" case onCastStateChanged = "CastContext.onCastStateChanged"
case showCastDialog = "showCastDialog" case showCastDialog = "showCastDialog"
// region SessionManager
case onSessionStarting = "SessionManager.onSessionStarting"
case onSessionStarted = "SessionManager.onSessionStarted"
case onSessionStartFailed = "SessionManager.onSessionStartFailed"
case onSessionEnding = "SessionManager.onSessionEnding"
case onSessionEnded = "SessionManager.onSessionEnded"
case onSessionResuming = "SessionManager.onSessionResuming"
case onSessionResumed = "SessionManager.onSessionResumed"
case onSessionResumeFailed = "SessionManager.onSessionResumeFailed"
case onSessionSuspended = "SessionManager.onSessionSuspended"
// end-region
} }

View file

@ -2,28 +2,90 @@ import Flutter
import UIKit import UIKit
import GoogleCast import GoogleCast
public class SwiftFlutterCastFrameworkPlugin: NSObject, FlutterPlugin { public class SwiftFlutterCastFrameworkPlugin: NSObject, FlutterPlugin, GCKSessionManagerListener {
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "flutter_cast_framework", binaryMessenger: registrar.messenger()) let channel = FlutterMethodChannel(name: "flutter_cast_framework", binaryMessenger: registrar.messenger())
let instance = SwiftFlutterCastFrameworkPlugin(channel: channel) let instance = SwiftFlutterCastFrameworkPlugin(channel: channel)
registrar.addMethodCallDelegate(instance, channel: channel) registrar.addMethodCallDelegate(instance, channel: channel)
} }
let castContext: GCKCastContext private let castContext: GCKCastContext
var stateObserver: NSKeyValueObservation? private var castStateObserver: NSKeyValueObservation?
var channel: FlutterMethodChannel private let channel: FlutterMethodChannel
private let sessionManager: GCKSessionManager
var _castSession: GCKCastSession?
var castSession: GCKCastSession? {
get { return _castSession }
set {
print("Updating castSession - castSession changed: \(_castSession != newValue)")
// if (_castSession == newValue) 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)
_castSession = newValue
// channel.invokeMethod(MethodNames.getSessionMessageNamespaces, null, result)
}
}
init(channel: FlutterMethodChannel) { init(channel: FlutterMethodChannel) {
self.channel = channel self.channel = channel
self.castContext = GCKCastContext.sharedInstance() self.castContext = GCKCastContext.sharedInstance()
self.stateObserver = GCKCastContext.sharedInstance().observe(\.castState, options: [.new, .old, .initial]){ (state, change) in self.sessionManager = GCKCastContext.sharedInstance().sessionManager
let castStateRaw = GCKCastContext.sharedInstance().castState.rawValue
// Android CastStates are 1-to-4, while iOS CastStates are 0-to-3. I align iOS to Android by adding 1
let castStateRawAdjusted = castStateRaw + 1
print("cast state change to: \(castStateRawAdjusted)")
channel.invokeMethod(MethodNames.onCastStateChanged.rawValue, arguments: castStateRawAdjusted)
}
super.init() super.init()
self.castSession = GCKCastContext.sharedInstance().sessionManager.currentCastSession
self.castStateObserver = GCKCastContext.sharedInstance().observe(\.castState, changeHandler: onCastStateChanged)
let notificationCenter = NotificationCenter.default
let app = UIApplication.shared
notificationCenter.addObserver(self, selector: #selector(appDidBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: app)
notificationCenter.addObserver(self, selector: #selector(appWillResignActive), name: NSNotification.Name.UIApplicationWillResignActive, object: app)
notificationCenter.addObserver(self, selector: #selector(appDidEnterBackground), name: NSNotification.Name.UIApplicationDidEnterBackground, object: app)
notificationCenter.addObserver(self, selector: #selector(appWillEnterForeground), name: NSNotification.Name.UIApplicationWillEnterForeground, object: app)
notificationCenter.addObserver(self, selector: #selector(appWillTerminate), name: NSNotification.Name.UIApplicationWillTerminate, object: app)
}
@objc func appDidBecomeActive() {
print("AppLife: appDidBecomeActive - App moved to foreground!")
self.sessionManager.add(self)
self.castSession = self.sessionManager.currentCastSession
notifyCastState(castState: GCKCastContext.sharedInstance().castState)
}
@objc func appWillResignActive() {
print("AppLife: appWillResignActive - App moved to background!")
self.sessionManager.remove(self)
self.castSession = nil
}
@objc func appDidEnterBackground() {
print("AppLife: appDidEnterBackground")
}
@objc func appWillEnterForeground() {
print("AppLife: appWillEnterForeground")
}
@objc func appWillTerminate() {
print("AppLife: appWillTerminate")
}
private func onCastStateChanged(state: GCKCastContext, change: NSKeyValueObservedChange<GCKCastState>) {
let castState = GCKCastContext.sharedInstance().castState
print("cast state change to: \(castState.rawValue)")
notifyCastState(castState: castState)
}
private func notifyCastState(castState: GCKCastState) {
let castStateRaw = castState.rawValue
// Android CastStates are 1-to-4, while iOS CastStates are 0-to-3. I align iOS to Android by adding 1
let castStateRawAdjusted = castStateRaw + 1
self.channel.invokeMethod(MethodNames.onCastStateChanged.rawValue, arguments: castStateRawAdjusted)
} }
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@ -36,7 +98,67 @@ public class SwiftFlutterCastFrameworkPlugin: NSObject, FlutterPlugin {
} }
deinit { deinit {
stateObserver?.invalidate() castStateObserver?.invalidate()
stateObserver = nil castStateObserver = nil
}
// MARK: - GCKSessionManagerListener
// onSessionSuspended
public func sessionManager(_ sessionManager: GCKSessionManager, didSuspend session: GCKCastSession, with reason: GCKConnectionSuspendReason) {
print("SessionListener: didSuspend")
channel.invokeMethod(MethodNames.onSessionSuspended.rawValue, arguments: nil)
}
// onSessionStarting
public func sessionManager(_ sessionManager: GCKSessionManager, willStart session: GCKCastSession) {
print("SessionListener: willStart")
channel.invokeMethod(MethodNames.onSessionStarting.rawValue, arguments: nil)
castSession = session
}
// onSessionResuming
public func sessionManager(_ sessionManager: GCKSessionManager, willResumeCastSession session: GCKCastSession) {
print("SessionListener: willResumeCastSession")
channel.invokeMethod(MethodNames.onSessionResuming.rawValue, arguments: nil)
castSession = session
}
// onSessionEnding
public func sessionManager(_ sessionManager: GCKSessionManager, willEnd session: GCKCastSession) {
print("SessionListener: willEnd")
channel.invokeMethod(MethodNames.onSessionEnding.rawValue, arguments: nil)
}
// onSessionStartFailed
public func sessionManager(_ sessionManager: GCKSessionManager, didFailToStart session: GCKCastSession, withError error: Error) {
print("SessionListener: didFailToStart")
channel.invokeMethod(MethodNames.onSessionStartFailed.rawValue, arguments: nil)
}
// onSessionResumeFailed - Can't find this on iOS
// onSessionStarted
public func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKCastSession) {
print("SessionListener: didStart")
channel.invokeMethod(MethodNames.onSessionStarted.rawValue, arguments: nil)
castSession = session
}
// onSessionResumed
public func sessionManager(_ sessionManager: GCKSessionManager, didResumeCastSession session: GCKCastSession) {
print("SessionListener: didResumeCastSession")
channel.invokeMethod(MethodNames.onSessionResumed.rawValue, arguments: nil)
castSession = session
}
// onSessionEnded
public func sessionManager(_ sessionManager: GCKSessionManager, didEnd session: GCKCastSession, withError error: Error?) {
print("SessionListener: didEnd")
channel.invokeMethod(MethodNames.onSessionEnded.rawValue, arguments: nil)
} }
} }