MPMusicPlayerController slow to respond when systemMusicPlayer, fast when application - ios11

I have a music app that uses the MPMusicPlayerController.
I originally wrote it using the systemMusicPlayer option under iOS 9.
I had some trouble with the player not shutting down correctly under certain circumstances so I switched to the appplicationMusicPlayer
(see Quitting app causes error "Message from debugger: Terminated due to signal 9")
However, as an application player, I can't get a lot of the benefits like control center handling, bluetooth data display, etc.
So, I switched back to the systemMusicPlayer.
I have also changed to Xcode 9.2 and a compile target of iOS 10.3.
Now when I run my app, it can take several seconds for it to respond to controls like play/pause or next/previous. My whole UI is painfully unresponsive.
I tried switching back to applicationMusicPlayer, recompiled, and sure enough - the UI is at normal speed.
So now I'm in a crappy position - with systemMusicPlayer, the app is barely usable, but with applicationMusicPlayer I lose a ton of capabilities.
This seems directly related to either iOS 11.2.2 on my iPhone, or something to do with targeting iOS 10.3+
Does anyone have any information about what is going on and how to fix it
EDIT:
I created a very basic player and it seems to work fine in either mode, so now I'm puzzled - I'll be testing other MP commands to see what the
issue is but since even my UI slows down I'm not sure.
EDIT 2:
I believe I've found the culprit to be NotificationCenter, and also getting States from the MPMusicPlayerController. I've updated my sample code below which shows the problem. Once running, clicking the 'next' button will be slow sometimes, but clicking 'previous' can cause delays of up to two seconds!!
Here is the basic code if you want to create a simple player.
Be sure to add three buttons in the storyboard and connect them accordingly.
//
// ViewController.swift
// junkplayer
//
import UIKit
import MediaPlayer
let notificationCenter = NotificationCenter.default
let myMP:MPMusicPlayerController = MPMusicPlayerController.systemMusicPlayer
//let myMP:MPMusicPlayerController = MPMusicPlayerController.applicationMusicPlayer()
class ViewController: UIViewController {
#IBOutlet weak var xxx: UIButton!
#IBOutlet weak var nextbut: UIButton!
#IBOutlet weak var prevbut: UIButton!
var qrySongs = MPMediaQuery()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
myMP.repeatMode = MPMusicRepeatMode.none
myMP.shuffleMode = MPMusicShuffleMode.off
myMP.prepareToPlay()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
stopMPNotifications()
startMPNotifications()
}
#IBAction func nextbut(_ sender: Any) {
myMP.skipToNextItem()
}
#IBAction func prevbut(_ sender: Any) {
myMP.skipToPreviousItem()
}
#IBAction func playbut(_ sender: UIButton) {
qrySongs = MPMediaQuery.songs()
myMP.setQueue(with: qrySongs)
myMP.play()
}
func startMPNotifications(){
notificationCenter.addObserver(self, selector: #selector(showNowPlaying), name: .MPMusicPlayerControllerNowPlayingItemDidChange, object: myMP)
notificationCenter.addObserver(self, selector: #selector(handlePlayState), name: .MPMusicPlayerControllerPlaybackStateDidChange, object: myMP)
myMP.beginGeneratingPlaybackNotifications()
}
func stopMPNotifications(){
notificationCenter.removeObserver(self, name: .MPMusicPlayerControllerPlaybackStateDidChange, object: myMP)
notificationCenter.removeObserver(self, name: .MPMusicPlayerControllerNowPlayingItemDidChange, object: myMP)
myMP.endGeneratingPlaybackNotifications()
}
#objc func handlePlayState(){
if myMP.playbackState == .playing {
print("handlePlayState playback state = playing")
}else{
print("handlePlayState playback state NOT playing")
}
print("handlePlayState going to shownowplaying")
showNowPlaying()
}
#objc func showNowPlaying(){
if myMP.nowPlayingItem != nil {
print("shownowplaying nowplaying not null")
}
}
}

The app seems to lock up once you start playing, but if you swipe up to show the Control Centre then dismiss it, the app starts working fine immediately.

I am having the same problem. I think it’s something wrong with the API. It's especially slow for large queries. But you can put a predicate on the quart, that allows no cloud music to go on it.

For anyone browsing this thread in the future, this was a known bug within all versions of iOS 11.2. It affected anyone using the systemMusicPlayer.
Apple sure does love to discourage us third party music app developers, eh? ;)
Regardless of my conspiracy theories (and I'm sure you have yours too), this bug was fixed in iOS 11.3.
We warn users on iOS 11.2 about the bug and recommend that they upgrade to iOS 11.3 for a (more or less) lag-free experience.

I ran into this problem today - working code just stopped running correctly, but I found a (partial) workaround:
\\Do anything that updates (like changing the song title in the UI) in the selector called by this notification
NotificationCenter.default.addObserver(self, selector: #selector(remoteMusicChange), name: NSNotification.Name.MPMusicPlayerControllerNowPlayingItemDidChange, object: nil)
The issue is that calls to say go to the next track for systemMusicPlayer are no longer (always) happening immediately. If you wait for the notification, at least you can be sure systemMusicPlayer has updated.
The problem with this is sometimes it can take a perceptibly long time for that notification to fire (and sometimes it's instant).
EDIT: https://forums.developer.apple.com/thread/96287 I am guessing this is related to these issues
EDIT2: Tested another related issue quickly in iOS12 and the problem no longer existed (changing playback speed) and the pauses when changing songs went away.

Related

Unable to connect #IBSegueAction

I have single view controller with a UIButton containing the following code:
import UIKit
class ViewController: UIViewController {
required init?(coder: NSCoder) {
super.init(coder: coder)
}
#IBSegueAction
func makeAnotherController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ViewController? {
return ViewController(coder: coder)
}
}
According to Apple's docs, this is the correct signature for a #IBSegueAction, and it compiles OK, but I'm unable to connect the button to the #IBSegueAction by dragging from the storyboard scene to the assistant editor.
I can connect without issue to other outlets or actions.
Any thoughts? Xcode 11 bug or am I doing something wrong?
Update
Briefly, Apple's docs say…
Create a connection from a segue to an #IBSegueAction method on its source view controller. On new OS versions that support Segue Actions, that method will be called … An IBSegueAction method takes up to three parameters: a coder, the sender, and the segue’s identifier. The first parameter is required, and the other parameters can be omitted from your method’s signature if desired
I'm unable to complete the first step (create a connection)
This was my misunderstanding of Apple's docs. When they say…
Create a connection from a segue to an #IBSegueAction method on its source view controller.
they literally mean drag from the line that represents the segue to the view controller.

Application getting crashed on setting the vocabulary for carName using Siri Kit

I am trying to develop an application using SiriKit to get the car door lock status and set the same from Siri. I followed this blog https://www.appcoda.com/sirikit-introduction/ and did all the setup replacing the INStartWorkoutIntent with INGetCarLockStatusIntent.
But when i try to set the vocabulary for carName, the application is getting crashed with following exception,
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Illegal attempt to provide vocabulary of type INVocabularyStringTypeCarName by an app that does not handle any intents that could use that type of vocabulary'
The source code that i am using to set the vocabulary is,
INPreferences.requestSiriAuthorization { (status) in
}
INVocabulary.shared().setVocabularyStrings(["benz", "bmw", "audi"], of: .carName)
In AppDelegate, i have added the following method,
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
guard let intent = userActivity.interaction?.intent as? INGetCarLockStatusIntent else {
print("AppDelegate: Start Workout Intent - FALSE")
return false
}
print("AppDelegate: Start Workout Intent - TRUE")
print(intent)
return true
}
Also created the extension for intent handler and implemented INSetCarLockStatusIntentHandling, INGetCarLockStatusIntentHandling protocols. I am getting this issue when i try to run it in iPhone 10.
Check, if in TARGETS of your project in Build Phases->Embed App Extensions added your Siri Extension. Maybe if you replace the INStartWorkoutIntent with INGetCarLockStatusIntent, old INStartWorkoutIntent remained there.
My crash fix this.
I was facing a similar issue. Make sure your extension's Deployment Target is set to appropriate iOS version. Creating an extension with the latest Xcode (at the moment 10.1) will set the Deployment Target to 12.1 and thus cause crash when run on iOS 10. So you should change it to your desired minimum.

UIActivityViewController showing "Unable to Load" row in iOS 11

I was using a UIActivityViewController instance in several previous versions of an app in order to share content through AirDrop, and other services. And now, without even recompiling, when running the app in iOS 11 Beta, the dialog (UIActivityViewController instance) shows the following row with the message "Unable to load":
Here is the code:
let handlerOk = { () -> Void in
let activityItems = [NSURL(fileURLWithPath: fullPath, isDirectory: true)]
self.viewControllerShare = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
if let viewControllerShare = self.viewControllerShare {
viewControllerShare.popoverPresentationController?.sourceView = sender
let excludedActivities = [
UIActivityType.postToFacebook,
UIActivityType.postToTwitter,
UIActivityType.postToWeibo,
//UIActivityType.message,
//UIActivityType.mail,
UIActivityType.print,
UIActivityType.copyToPasteboard,
UIActivityType.assignToContact,
UIActivityType.saveToCameraRoll,
UIActivityType.addToReadingList,
UIActivityType.postToFlickr,
UIActivityType.postToVimeo,
UIActivityType.postToTencentWeibo,
UIActivityType.openInIBooks
]
viewControllerShare.excludedActivityTypes = excludedActivities
self.present(viewControllerShare, animated: true, completion: nil)
}
}
Right now I'm compiling with XCode 9 in Swift 4, but I don't think this is relevant since as said, if an older version of the app is run in iOS 11 (compiled with XCode 8.somenthing or even downloading the binary from TestFlight) the problem is the same. So this really looks like an iOS 11 API problem...
Is there anything new regarding the dialog initialization that may prevent the "Unable to load" row to appear?
EDIT: I tested a third party app (from Apple) that uses the same sharing dialog, and found that that row belongs to iCloud. But I'm not using iCloud in my app, so the previous UIActivityViewController behaviour was correct: it just did not show anything. So I made an experiment: disabled from iOS' Settings the iCloud permission for that third party app, and it showed the same unkind "Unable to load" message. I hope this is not the way that the final iOS 11 version works, and that it's just a Beta problem.
That said, workarounds are still welcome! Thanks!

UIActivityViewController.completionWithItemsHandler is not called on iOS 10.0 when I select "Print" menu and cancel it

I created a simple application which uses UIActivityViewController as below.
let text = "Test Text"
let printData = UISimpleTextPrintFormatter(text: text)
let vc = UIActivityViewController(activityItems: [text, printData], applicationActivities: nil)
vc.completionWithItemsHandler = { (type,completed,items,error) in
print("completed. type=\(type) completed=\(completed) items=\(items) error=\(error)")
}
vc.modalPresentationStyle = .popover
vc.popoverPresentationController?.sourceView = self.openActivityButton
vc.popoverPresentationController?.sourceRect = self.openActivityButton.bounds
vc.popoverPresentationController?.permittedArrowDirections = .up
vc.popoverPresentationController?.delegate = self
self.present(vc, animated: true) { () in
}
and I run this application on iOS 10 (Xcode 8.0 beta 6).
When I close activity dialog, the completionWithItemsHandler is called.
When I select "Copy" activity, the completionWithItemsHandler is called.
When I select "Mail" activity and cancel it, the completionWithItemsHandler is called.
But, when I select "Print" activity and cancel it, the completionWithItemsHandler is not called.
This strange behavior occurred on iOS 10 but not occurred on iOS9 (the handler was called on iOS9)
Is this iOS 10's bug? If so, are there any workarounds to detect the UIActivityController is dismissed?
I pushed this sample app on https://github.com/kunichiko/ios10-activity-bug
I had a similar problem. In my case, I noticed that the completion handler was not called because the UIActivityController was already dismissed and deallocated. What I did was just add a property to hold a strong reference and set it to nil later. Then the completion handler was called properly.

3d touch navigate to specific viewController

I am trying to implement 3d Touch and I want to navigate to a specific viewController in a Master - Detail app.
The viewController I am aiming to get to is the third controller in the hierarchy:
MasterViewController ----> SettingsViewController ----> ThirdViewController
I can access the ThirdViewController in normal operation through TabBarItem (connected via segue to SettingsViewController right from the IB) and then via a UIButton (connected via segue to ThirdViewController right from the IB) to the ThirdViewController.
i.e in MasterViewController ---> (tap barButtonItem) ----> SettingsViewControler ----> (tap uiButton) ---> ThirdViewController.
I have already amended the info.plist and 3D touch works fine and can call my action:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
var shortcutDictionary = shortcutItem.userInfo;
let shortcutString1 = shortcutDictionary!["key1"] as! String
if (shortcutString1 == "value1") {
// how do i get from here to the settingsController??
}
}
EDIT:
already have tried:
let viewController = MasterViewController() viewController.settingsButton.sendActionsForControlEvents(.TouchUpInside)
which gives me an error:fatal error trying to unwrap an optional value!
although this exists in MasterViewController
#IBOutlet weak var settingsButton: UIButton!
This is the code I use for the shortcut....
func actionFromShortcut(sender: AnyObject!) {
let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.performSegueWithIdentifier("shotcutToSettings", sender: nil)
//some other stuff here.....
})
Also as per #MattLong's comment I have duplicated the segues to my controllers (one with and one without animation). That way the shortcut transition seems simultaneous when the action is called from the shortcut.
You will notice a delay of 0.1. It is to give the MasterViewController time to load cause whenever I tried to call the shortcut from cold (app not running) I would end up with the viewController I requested but there would be an empty MasterViewController to return to and hence crash....

Resources