I need help! Once i connected the device to chromecast, it crashed right when it's about to load. I have no clue why it cause that. I followed GoogleCast for Docs and few examples and seems that it's missing something. Could you guys help me?
This my code
This is on my OnCreate
override func viewDidLoad() {
sessionManager = GCKCastContext.sharedInstance().sessionManager
sessionManager?.add(self)
castMediaController = GCKUIMediaController()
if isCastEnabled() {
playSelectedItemRemotely()
}
}
//////////////////////////////////////////////////////
// //
// Start du Google ChromeCast //
// //
//////////////////////////////////////////////////////
private func buildMediaInformation() -> GCKMediaInformation {
let metadata = GCKMediaMetadata(metadataType: GCKMediaMetadataType.generic)
metadata.setString("Title", forKey: kGCKMetadataKeyTitle)
metadata.setString("Studio", forKey: kGCKMetadataKeyStudio)
let mediaInfo = GCKMediaInformation(contentID: "streamURL",
streamType: GCKMediaStreamType.none,
contentType: "video/m3u",
metadata: metadata,
streamDuration: 60,
mediaTracks: nil,
textTrackStyle: nil,
customData: nil)
print(mediaInfo.contentID)
return mediaInfo
}
func isCastEnabled() -> Bool {
switch GCKCastContext.sharedInstance().castState {
case GCKCastState.connected:
print("cast connected")
return true
case GCKCastState.connecting:
print("cast connecting")
return true
case GCKCastState.notConnected:
print("cast notConnected")
return false
case GCKCastState.noDevicesAvailable:
print("cast noDevicesAvailable")
return false
}
}
func playSelectedItemRemotely() {
let castSession = GCKCastContext.sharedInstance().sessionManager.currentCastSession
if (castSession != nil) {
castSession?.remoteMediaClient?.loadMedia(self.buildMediaInformation(), autoplay: true)
self.dismiss(animated: true, completion: nil)
}
else {
print("no castSession!")
}
}
func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKSession) {
playSelectedItemRemotely()
}
func sessionManager(_ sessionManager: GCKSessionManager, didResumeSession session: GCKSession) {
}
func sessionManager(_ sessionManager: GCKSessionManager, didEnd session: GCKSession, withError error: Error?) {
let castSession = GCKCastContext.sharedInstance().sessionManager.currentCastSession
castSession?.endAndStopCasting(true)
}
func sessionManager(_ sessionManager: GCKSessionManager, didFailToStart session: GCKSession, withError error: Error) {
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// playerView.player?.replaceCurrentItem(with: nil)
//
// //this stops the session manager sending callbacks to your VideoVC
// sessionManager?.remove(self)
}
And this is on my AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let castAppID = "myKey"
let options = GCKCastOptions.init(receiverApplicationID: castAppID)
GCKCastContext.setSharedInstanceWith(options)
GCKLogger.sharedInstance().delegate = self
I got it. My issue was in the Google Cast Console i had a remote receiver. Need to have a custom style receiver or media. So make sure you have that right!! Or just use a kGCKMediaDefaultReceiverApplicationID on your AppID to get the default one.
Related
When I try to connect to Facebook in my iOS application, the app redirects me to the Facebook app and then there is a loop between the two apps, meaning that the user is being sent back and forth between the two apps without being able to complete the login process. I suspect that there is something missing in my code to properly handle the redirect URLs for the PianoID SDK and I would like to know what needs to be done to fix this issue.
this is my appDelegate class :
#objc class AppDelegate: FlutterAppDelegate, PianoIDDelegate {
private var flutterResult: FlutterResult? = nil
#Published private(set) var initialized = false
#Published private(set) var token: PianoIDToken?
var cookiessseion = ""
var startdate = ""
var enddate = ""
var pianoToken=""
func pianoID(_ pianoID: PianoOAuth.PianoID, didSignInForToken token: PianoOAuth.PianoIDToken!, withError error: Error!) {}
func pianoID(_ pianoID: PianoOAuth.PianoID, didSignOutWithError error: Error!) {}
func pianoIDSignInDidCancel(_ pianoID: PianoOAuth.PianoID) {}
override func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
PianoID.shared.endpointUrl = Settings.endpoint.api
PianoID.shared.aid = Settings.aid
PianoID.shared.isSandbox = true
PianoID.shared.signUpEnabled = true
PianoID.shared.googleClientId = Settings.clientId
PianoID.shared.delegate = self
PianoOAuth.PianoIDApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
GeneratedPluginRegistrant.register(with: self)
let name = Bundle.main.infoDictionary?["CFBundleName"]
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"]
GEMConfig.sharedInstance().setAppInfo(name as? String, version: version as? String)
//FirebaseApp.configure()
guard let controller = window?.rootViewController as? FlutterViewController else {
fatalError("rootViewController is not type FlutterViewController")
}
let Channel = FlutterMethodChannel(name: "flutter.native/helper", binaryMessenger: controller.binaryMessenger)
Channel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: #escaping FlutterResult) -> Void in
if(call.method == "testpiano"){
self?.initPianoID()
self?.flutterResult = result
} else {
result(FlutterMethodNotImplemented)
}
})
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
/// Sign In callback
func signIn(result: PianoIDSignInResult!, withError error: Error!) {
if let r = result {
token = r.token
do {
let PianoIDTokenString = try JSONParserSwift.getJSON(object: token)
self.flutterResult?(PianoIDTokenString)
} catch {
print("Unexpected data error: \(error)")
}
} else {
}
}
/// Sign Out callback
func signOut(withError error: Error!) {
token = nil
}
/// Cancel callback
func cancel() {
}
private func initPianoID() {
PianoID.shared.signIn()
}
func openSettings(alert: UIAlertAction!) {
if let url = URL.init(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
override public func applicationDidBecomeActive(_ application: UIApplication) {
debugPrint("applicationDidBecomeActive")
if #available(iOS 14, *) {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
ATTrackingManager.requestTrackingAuthorization { status in
}
}
}
}
}
extension Date {
func currentTimeMillis() -> Int64 {
return Int64(self.timeIntervalSince1970)
}
}
And this is the documentation : link
I am developing an application to send the current user location every 15 minutes via the MQTT client framework. When the application is in the foreground it works fine but when the application is in background, the MQTT delegate function "messageDelivered" doesn't get called
We want to use the MQTT client framework to publish message in background in iOS swift.
import UIKit
import MQTTClient
class MainViewController: UIViewController {
let MQTT_HOST = "next.nanolink.com" // or IP address e.g. "192.168.0.194"
//let MQTT_HOST = "tnclicks.free.beeceptor.com" // or IP address e.g. "192.168.0.194"
let MQTT_PORT: UInt32 = 1883
private var transport = MQTTCFSocketTransport()
fileprivate var session = MQTTSession()
fileprivate var completion: (()->())?
override func viewDidLoad() {
super.viewDidLoad()
//notification observer
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveData(_:)), name: .didReceiveData, object: nil)
//MQTT
self.session?.delegate = self
self.transport.host = MQTT_HOST
self.transport.port = MQTT_PORT
session?.transport = transport
updateUI(for: self.session?.status ?? .created)
session?.connect() { error in
print("connection completed with status \(String(describing: error?.localizedDescription))")
if error != nil {
self.updateUI(for: self.session?.status ?? .created)
} else {
self.updateUI(for: self.session?.status ?? .error)
}
}
}
private func subscribe() {
self.session?.subscribe(toTopic: "test/message", at: .exactlyOnce) { error, result in
print("subscribe result error \(String(describing: error)) result \(result!)")
}
}
private func updateUI(for clientStatus: MQTTSessionStatus) {
DispatchQueue.main.async {
switch clientStatus {
case .connected:
print("Connected")
self.publishMessage("on", onTopic: "test/message")
case .connecting,
.created:
print ("Trying to connect...")
default:
print ("Connetion Failed...")
}
}
}
private func publishMessage(_ message: String, onTopic topic: String)
{
session?.publishData(message.data(using: .utf8, allowLossyConversion: false), onTopic: topic, retain: false, qos: .exactlyOnce)
}
#objc func onDidReceiveData(_ notification:Notification) {
print("check return")
guard session?.status == .connected else {
self.updateUI(for: self.session?.status ?? .error)
return
}
let obj = notification.object! as! NSMutableDictionary
print(notification.object!)
let notificationLatitude = obj.value(forKey: "latitude")!
let notificationLongitude = obj.value(forKey: "longitude")!
//let notificationLongitude = notification.object
// print(" Saved latitude:", latitude!)
// print(" Saved longitude:", longitude!)
print(" notification latitude:", notificationLatitude)
print(" notification longitude:", notificationLongitude)
guard session?.status == .connected else {
return
}
publishMessage("on", onTopic: "test/message")
}
}
extension MainViewController: MQTTSessionManagerDelegate, MQTTSessionDelegate {
func newMessage(_ session: MQTTSession!, data: Data!, onTopic topic: String!, qos: MQTTQosLevel, retained: Bool, mid: UInt32) {
if let msg = String(data: data, encoding: .utf8) {
print("topic \(topic!), msg \(msg)")
}
}
func messageDelivered(_ session: MQTTSession, msgID msgId: UInt16) {
print("delivered")
DispatchQueue.main.async {
self.completion?()
}
}
}
extension Notification.Name {
static let didReceiveData = Notification.Name("didReceiveData")
}
We have implemented update location in Background so update your code with update location to send Message in background.
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,CLLocationManagerDelegate {
var window: UIWindow?
var locationManager = CLLocationManager()
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
var bgtimer = Timer()
var latitude: Double = 0.0
var longitude: Double = 0.0
var current_time = NSDate().timeIntervalSince1970
var timer = Timer()
var f = 0
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.doBackgroundTask()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
}
func applicationWillEnterForeground(_ application: UIApplication) {
print("Entering foreBackground")
}
func applicationDidBecomeActive(_ application: UIApplication) {
}
func applicationWillTerminate(_ application: UIApplication) {
}
func applicationDidEnterBackground(_ application: UIApplication) {
print("Entering Background")
// self.doBackgroundTask()
}
func doBackgroundTask() {
DispatchQueue.main.async {
self.beginBackgroundUpdateTask()
self.StartupdateLocation()
self.bgtimer = Timer.scheduledTimer(timeInterval:-1, target: self, selector: #selector(AppDelegate.bgtimer(_:)), userInfo: nil, repeats: true)
RunLoop.current.add(self.bgtimer, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
self.endBackgroundUpdateTask()
}
}
func beginBackgroundUpdateTask() {
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
func StartupdateLocation() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestAlwaysAuthorization()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error while requesting new coordinates")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
self.latitude = locValue.latitude
self.longitude = locValue.longitude
f+=1
print("New Coordinates: \(f) ")
print(self.latitude)
print(self.longitude)
}
#objc func bgtimer(_ timer:Timer!){
sleep(2)
/* if UIApplication.shared.applicationState == .active {
timer.invalidate()
}*/
self.updateLocation()
}
func updateLocation() {
self.locationManager.startUpdatingLocation()
self.locationManager.stopUpdatingLocation()
}}
I am attempting to start an HKWorkoutSession on the Apple Watch and send the heart rate data near-instantaneously to the iPhone for near real-time display. To accomplish this, I'm using WatchConnectivity's sendMessage method. I do not need replies from the phone/watch, so I'm using the version of sendMessage that doesn't use a reply handler.
The heart rate data isn't making it to the phone, and I'm struggling to understand why. I'm not getting any sorts of error messages that are helpful. How do I find out what is going wrong? I have attached both the phone app and the watch app to the debugger, but since the message never gets to the phone, I'm having a ton of trouble figuring out what's going on because obviously my breakpoints in the phone code aren't being reached. Earlier, I was using the replyHandler version of sendMessage, which has a closure for error handling. Using that, I was able to see that my errors were (sometimes) due a timeout in receiving a return message. The didReceiveMessage delegate on the phone was never reached, though, so I'm not surprised that it never sent its return message. I assume there's something wrong with the connection between the phone app and watch app, but I've checked the isReachable boolean, as well as isPaired and isWatchAppInstalled.
So I guess I'm wondering if you have any tips about debugging techniques for Watch Connectivity. And, if you want to take a look at my code too, here it is. Let me know if you see anything glaringly wrong about it. (Sorry it's a tad sloppy--just trying to get things to work right now.)
ViewController:
class ViewController: UIViewController, WCSessionDelegate {
#IBOutlet weak var bpmLabel: UILabel!
let healthStore = HKHealthStore()
var bpmArray = [Double]()
var wcSession: WCSession?
// WC Session Delegate methods
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("WC Session activation failed with error: \(error.localizedDescription)")
return
}
}
func sessionDidBecomeInactive(_ session: WCSession) {
print("WC has become inactive")
}
func sessionDidDeactivate(_ session: WCSession) {
print("WC was deactivated")
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
guard let bpm = message["bpm"] as? Double else { return }
DispatchQueue.main.async {
self.bpmArray.append(bpm)
self.bpmLabel.text = String(bpm)
}
override func viewDidLoad() {
super.viewDidLoad()
if WCSession.isSupported() {
wcSession = WCSession.default
wcSession?.delegate = self
wcSession?.activate()
}
if !(wcSession?.isPaired)! || !(wcSession?.isWatchAppInstalled)! {
print("PAIRING PROBLEM")
}
}
InterfaceController:
var wcSession: WCSession?
#IBOutlet var bpm: WKInterfaceLabel!
let healthStore = HKHealthStore()
var workoutSession: HKWorkoutSession?
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("WC Session activation failed with error: \(error.localizedDescription)")
return
}
}
func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) {
print("here")
}
func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) {
print("Workout session failure")
}
override func awake(withContext context: Any?) {
super.awake(withContext: context)
workoutSession?.delegate = self
guard let wcSess = self.wcSession else { return }
wcSess.activate()
}
// modified from https://github.com/coolioxlr/watchOS-2-heartrate/blob/master/VimoHeartRate%20WatchKit%20App%20Extension/InterfaceController.swift
func createHeartRateStreamingQuery() -> HKQuery? {
if !HKHealthStore.isHealthDataAvailable() {
print("health data not available")
}
guard let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else { return nil }
let heartRateQuery = HKAnchoredObjectQuery(type: quantityType, predicate: nil, anchor: nil, limit: Int(HKObjectQueryNoLimit)) { (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
self.updateHeartRate(samples: sampleObjects)
}
heartRateQuery.updateHandler = {(query, samples, deleteObjects, newAnchor, error) -> Void in
self.updateHeartRate(samples: samples)
}
return heartRateQuery
}
// modified from https://github.com/coolioxlr/watchOS-2-heartrate/blob/master/VimoHeartRate%20WatchKit%20App%20Extension/InterfaceController.swift
func updateHeartRate(samples: [HKSample]?) {
guard let heartRateSamples = samples as? [HKQuantitySample] else { return }
guard let sample = heartRateSamples.first else { return }
let value = sample.quantity.doubleValue(for: HKUnit(from: "count/min"))
DispatchQueue.main.async() {
self.bpm.setText(String(UInt16(value)))
}
let dataToSendToPhone = ["bpm":String(value)]
if (wcSession?.isReachable)! {
self.wcSession?.sendMessage(dataToSendToPhone, replyHandler: nil)
}
else {
print("WC Session not reachable")
}
}
override func willActivate() {
super.willActivate()
if WCSession.isSupported() {
wcSession = WCSession.default
wcSession?.delegate = self
wcSession?.activate()
}
}
override func didDeactivate() {
super.didDeactivate()
}
#IBAction func recordIsTapped() {
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .other
workoutConfiguration.locationType = .unknown
// Inspired by https://developer.apple.com/library/content/samplecode/SpeedySloth/Introduction/Intro.html
do {
try self.workoutSession = HKWorkoutSession(configuration: workoutConfiguration)
healthStore.start(self.workoutSession!)
if HKHealthStore.isHealthDataAvailable() {
if let query = createHeartRateStreamingQuery() {
self.healthStore.execute(query)
}
}
else {
print("Healthkit unavailable")
}
}
catch {
fatalError(error.localizedDescription)
}
}
On the networking side, DNS Proxy is one of the biggest features of iOS 11. But they haven't provided much documentation or samples regarding it. There's a talk on it as well where they have just given a description of what is possible with DNS Proxy.
I want to create a working sample of it but didn't get success till now. So I have created a Network Extension with DNS Proxy entitlements and added a DNS Proxy Provider. Here's the code:
class DNSProxyProvider: NEDNSProxyProvider {
let defaults = UserDefaults(suiteName: "group.com.securly.dnsProxy")
override init() {
NSLog("QNEDNSProxy.Provider: init")
super.init()
// +++ might want to set up KVO on `systemDNSSettings`
}
override func startProxy(options:[String: Any]? = nil, completionHandler: #escaping (Error?) -> Void) {
NSLog("QNEDNSProxy.Provider: start")
// self.defaults?.set("DidStart", forKey: "DidStart")
completionHandler(nil)
}
override func stopProxy(with reason: NEProviderStopReason, completionHandler: #escaping () -> Void) {
NSLog("QNEDNSProxy.Provider: stop")
completionHandler()
}
override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
NSLog("QNEDNSProxy.Provider: new flow (denied)")
// self.defaults?.set("DidHandleNewFlow", forKey: "DidHandleNewFlow")
return true
}
}
Then in AppDelegate, I declare a NEDNSProxyManager and use it as:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let manager = NEDNSProxyManager.shared()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.enable()
return true
}
private func enable() {
self.update {
self.manager.localizedDescription = "DNSProxySample"
let proto = NEDNSProxyProviderProtocol()
// proto.providerConfiguration = +++
proto.providerBundleIdentifier = "com.securly.dnsProxy"
self.manager.providerProtocol = proto
self.manager.isEnabled = true
}
}
private func disable() {
self.update {
self.manager.isEnabled = false
}
}
private func update(_ body: #escaping () -> Void) {
self.manager.loadFromPreferences { (error) in
guard error == nil else {
NSLog("DNSProxySample.App: load error")
return
}
body()
self.manager.saveToPreferences { (error) in
guard error == nil else {
NSLog("DNSProxySample.App: save error")
return
}
NSLog("DNSProxySample.App: saved")
}
}
}
}
Questions/Issues:
Why isn't startProxy or handleNewFlow called? Is there anything wrong in the setup?
How do I mention custom DNS address?
I managed to trigger startProxy and handleFlow on DNSProxyProvider by system. My configurations are like this:
Entitlements on app target
Entitlements on DNSProxy Extension
Red line is something similar to: group.com.xzy.project_name
Info.plist file on Extension
AppDelegate
import UIKit
import NetworkExtension
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let manager = NEDNSProxyManager.shared()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.enable()
return true
}
private func enable() {
self.update {
self.manager.localizedDescription = "DNS"
let proto = NEDNSProxyProviderProtocol()
proto.providerBundleIdentifier = "EXTENSION_BUNDLE_IDENTIFIER_WHICH_HAS_DNS_PROXY"
self.manager.providerProtocol = proto
self.manager.isEnabled = true
}
}
private func disable() {
self.update {
self.manager.isEnabled = false
}
}
private func update(_ body: #escaping () -> Void) {
self.manager.loadFromPreferences { (error) in
guard error == nil else {
NSLog("DNS Test App: load error")
return
}
body()
self.manager.saveToPreferences { (error) in
guard error == nil else {
NSLog("DNS Test App: save error")
return
}
NSLog("DNS Test App: saved")
}
}
}
}
DO NOT FORGET TO CHANGE BUNDLE IDENTIFIER at here proto.providerBundleIdentifier = "EXTENSION_BUNDLE_IDENTIFIER_WHICH_HAS_DNS_PROXY"
DNSProxyProvider
import NetworkExtension
class DNSProxyProvider: NEDNSProxyProvider {
override init() {
NSLog("DNSProxyProvider: init")
super.init()
}
override func startProxy(options:[String: Any]? = nil, completionHandler: #escaping (Error?) -> Void) {
NSLog("DNSProxyProvider: startProxy")
completionHandler(nil)
}
override func stopProxy(with reason: NEProviderStopReason, completionHandler: #escaping () -> Void) {
NSLog("DNSProxyProvider: stopProxy")
completionHandler()
}
override func sleep(completionHandler: #escaping () -> Void) {
NSLog("DNSProxyProvider: sleep")
completionHandler()
}
override func wake() {
NSLog("DNSProxyProvider: wake")
}
override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
NSLog("DNSProxyProvider: handleFlow")
if let tcpFlow = flow as? NEAppProxyTCPFlow {
let remoteHost = (tcpFlow.remoteEndpoint as! NWHostEndpoint).hostname
let remotePort = (tcpFlow.remoteEndpoint as! NWHostEndpoint).port
NSLog("DNSProxyProvider TCP HOST : \(remoteHost)")
NSLog("DNSProxyProvider TCP PORT : \(remotePort)")
} else if let udpFlow = flow as? NEAppProxyUDPFlow {
let localHost = (udpFlow.localEndpoint as! NWHostEndpoint).hostname
let localPort = (udpFlow.localEndpoint as! NWHostEndpoint).port
NSLog("DNSProxyProvider UDP HOST : \(localHost)")
NSLog("DNSProxyProvider UDP PORT : \(localPort)")
}
return true
}
}
As a last step run the app on a real iOS Device.
If you want to display extension logs open Console.app from your Mac.
To debug the extension: Your main app should be selected from run menu. Select Attach to Process by PID or Name... from Xcode's Debug menu and then type your extension's name, press Attach button. After you see the Waiting to attach to EXTENSION_NAME on XYZ's iPhone. Run your app target on a iOS device.
I'm working on a project with custom video player, based on AVPlayer. Trying to integrate google cast. I've made integration based on google tuts: https://codelabs.developers.google.com/codelabs/cast-videos-ios/
But with conversion to swift. Everything seems to work fine, when cast, if video player opens, and there is connected device (or if I connect from panel), I form meta info for file, and it's passed to google cast - everything works fine.
But, i have strange behavior:
1) Start casting, open video, then another video, then third video.
2) Stop casting
3) Go to another video, enable casting, but it doesn't start this video. It start casting the first video I opened earlier....
I tried to find any method that clears cache or queue, but there is no.. Please, help
class VideoVC: UIViewController, UIGestureRecognizerDelegate, GCKSessionManagerListener {
var filmTitle: String!
var toPass: String!
var film: MovieDetails!
var filmDetails: Movie!
var sessionManager: GCKSessionManager?
var castSession: GCKCastSession?
var castMediaController: GCKUIMediaController?
var checkPlayed = 0
override func viewDidLoad() {
super.viewDidLoad()
sessionManager = GCKCastContext.sharedInstance().sessionManager
sessionManager?.add(self)
castMediaController = GCKUIMediaController()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let videoURL = toPass {
if let video = URL(string: videoURL) {
player = AVPlayer(url: video)
player.allowsExternalPlayback = true
player.usesExternalPlaybackWhileExternalScreenIsActive = true
playerController.player = player
self.addChildViewController(playerController)
self.view.addSubview(playerController.view)
playerController.view.frame = self.view.frame
self.view.sendSubview(toBack: playerController.view)
}
}
if isCastEnabled() {
playSelectedItemRemotely()
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
player.replaceCurrentItem(with: nil)
}
func buildMediaInformation() -> GCKMediaInformation {
let metaData = GCKMediaMetadata(metadataType: GCKMediaMetadataType(rawValue: 1)!)
metaData.setString(filmTitle, forKey: kGCKMetadataKeyTitle)
if let imageUrl = URL(string: filmDetails.poster_cast!) {
let image = GCKImage(url: imageUrl, width: 340, height: 450)
metaData.addImage(image)
}
if let episode = film.serial_episode, let season = film.serial_season, season != "", episode != "", let title = film.title, title != "" {
let subtitle = "\(title) \(episode) серия \(season) сезон"
metaData.setString(subtitle, forKey: kGCKMetadataKeySubtitle)
}
let duration = Double(film.duration!)
let mediaInfo = GCKMediaInformation(contentID: toPass!,
streamType: GCKMediaStreamType.buffered,
contentType: film.contentType!,
metadata: metaData as GCKMediaMetadata,
streamDuration: duration,
mediaTracks: nil,
textTrackStyle: nil,
customData: nil)
print("toPass: \(toPass!)")
print("duration: \(duration)")
return mediaInfo
}
func playSelectedItemRemotely() {
let castSession = GCKCastContext.sharedInstance().sessionManager.currentCastSession
if (castSession != nil) {
castSession?.remoteMediaClient?.loadMedia(self.buildMediaInformation(), autoplay: true)
self.dismiss(animated: true, completion: nil)
}
else {
print("no castSession!")
}
}
func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKSession) {
playSelectedItemRemotely()
}
func sessionManager(_ sessionManager: GCKSessionManager, didResumeSession session: GCKSession) {
}
func sessionManager(_ sessionManager: GCKSessionManager, didEnd session: GCKSession, withError error: Error?) {
let castSession = GCKCastContext.sharedInstance().sessionManager.currentCastSession
castSession?.endAndStopCasting(true)
}
func sessionManager(_ sessionManager: GCKSessionManager, didFailToStart session: GCKSession, withError error: Error) {
Utils.showOverAnyVC("Ошибка подключения", message: "Попробуйте еще раз!")
}
func isCastEnabled() -> Bool {
switch GCKCastContext.sharedInstance().castState {
case GCKCastState.connected:
print("cast connected")
return true
case GCKCastState.connecting:
print("cast connecting")
return true
case GCKCastState.notConnected:
print("cast notConnected")
return false
case GCKCastState.noDevicesAvailable:
print("cast noDevicesAvailable")
return false
}
}}
and my appdelegate:
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let options = GCKCastOptions(receiverApplicationID: "F443E49F")
GCKCastContext.setSharedInstanceWith(options)
GCKLogger.sharedInstance().delegate = self
let appStoryboard = UIStoryboard(name: "NewMain", bundle: nil)
let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
let castContainerVC: GCKUICastContainerViewController = GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
castContainerVC.miniMediaControlsItemEnabled = true
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = castContainerVC
self.window?.makeKeyAndVisible()
GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
return true
}
func logMessage(_ message: String, fromFunction function: String) {
print("message: \(function)")
}}
On possible solution could be due to:
sessionManager?.add(self)
You add the delegate but at no point do you clear it. As a result, the VideoVC is never destroyed due to the retained reference from the session manager. When you reopen the VideoVC the session manager is still also accessing the delegate from the first time you loaded it.
Because of this, when the following is called:
func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKSession) {
This is being called in your first instance of VideoVC which now has the wrong file information.
You can monitor this by putting a print(self) into the above method and look at the memory pointer value. Check that it also matches the same memory pointer value that is called in viewDidLoad
Update
To better manage the delegate change the following method: viewDidDisappear()
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
player.replaceCurrentItem(with: nil)
//this stops the session manager sending callbacks to your VideoVC
sessionManager.remove(self)
}