Why does iOS not inform my application about audio session interruptions? - objective-c

I am using AVPlayer to play sound from different sources (including iPod music library). Due to the fact that AVPlayer is more low level AVAudioPlayer I have to handle interruptions myself. Using AVAudioPlayer is not an option!
In the Apple developer documents they mention to either listen AVAudioSessionInterruptionNotificationor setup a listener with AudioSessionInitialize. But when doing so I only receive notifications when the interruption ended, but due to their documents my app should be able to handle both.
I am using the following code to initialize my audio session: (simplified version, removed unimportant lines)
AudioSessionInitialize(NULL, NULL, ARInterruptionListenerCallback, nil);
UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(sessionCategory), &sessionCategory);
AudioSessionSetActive(true);
And here is how the listener looks like:
void ARInterruptionListenerCallback(void *inUserData, UInt32 interruptionState) {
if (interruptionState == kAudioSessionBeginInterruption) {
// Here is the code which is never called...
}
else if (interruptionState == kAudioSessionEndInterruption) {
// But this case will be executed!
}
}
Btw. I am expecting this code executed when there is an interruption like a phone call or similar. Am I misunderstand what Apple declares as interruption?

There is a bug in iOS 6. The interrupt code never seems to be called for begin interrupt - That includes both the old deprecated method (using AVAudioSession delegate) and the new notification method.
Myself and a few others have raised a high severity bug request with Apple so I suggest you do the same (bugreport.apple.com) to get their attention.
I'm not sure what you're using the interrupt for, but I was using it to allow audio to resume after the interrupt if necessary, and also update the play/pause button in the interface. I had to change my method to watch the AVPlayer rate property to toggle the play/pause button, and not allow audio to resume after an interruption.
Edit: see this thread

Related

"[GCController controllers]" does not contain any controllers that were connected prior to application launch

"[GCController controllers]" does not contain any controllers that were connected prior to application launch
TLDR;
I am trying to implement gamepad input on macOS using the Game Controller Framework. When invoked in my code, [GameController controllers] always returns an empty list until new controllers are connected. It never reflects gamepads connected to macOS prior to application launch, except if you disconnect them and reconnect them while the app is running. Does anyone know what I need to do to make controllers populate with pre-launch connections?
Full question
Now that Apple has added support for Xbox and Playstation controllers to the GameController framework, I'm trying to use it for gamepad input on a C++ game engine I'm developing. I'm using the framework instead of IOKit in order to "future-proof" my games to support additional controller types in the future, as well as to simplify my own input handling code.
Like many other game engines, I've foregone using NSApplicationMain() and nib files in favor of implementing my own event loop and setting up my game window programmatically. While my "Windows style" event loop appears to be working correctly, I've discovered that [GCController controllers] does not. The array it returns is always empty at launch, and will only ever reflect controllers that are connected while the game is running. Disconnecting a pre-connected controller does not trigger my GCControllerDidDisconnectNotification callback.
Here is a simplified version of my event loop:
int main(int argc, const char * argv[])
{
#autoreleasepool
{
// Create application
[NSApplication sharedApplication];
// Set up custom app delegate
CustomAppDelegate * delegate = [[CustomAppDelegate alloc] init];
[NSApp setDelegate:delegate];
// Activate and launch app
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp setPresentationOptions:NSApplicationPresentationDefault];
[NSApp activateIgnoringOtherApps:YES]; // Strictly speaking, not necessary
[NSApp finishLaunching]; // NSMenu is set up at this point in applicationWillFinishLaunching:.
// Initialize game engine (window is created here)
GenericEngineCode_Init(); // <-- Where I want to call [GCController controllers]
NSEvent *e;
do
{
do
{
// Pump messages
e = [NSApp nextEventMatchingMask: NSEventMaskAny
untilDate: nil
inMode: NSDefaultRunLoopMode
dequeue: YES];
if (e)
{
[NSApp sendEvent: e];
[NSApp updateWindows];
}
} while (e);
} while (GenericEngineCode_Run()); // Steps the engine, returns false when quitting.
GenericEngineCode_Cleanup();
}
return 0;
}
I've confirmed that even when using [NSApp run] instead of [NSApp finishLaunching], the behavior is the same. As best as I can tell, the problem is that there's something NSApplicationMain() does that I'm not doing, but that function is a black box -- I can't identify what I need to do to get controllers to populate correctly. Does anyone know what I'm missing?
The closest thing I could find to an explanation of this problem is this answer, which suggests that my app isn't getting didBecomeActive notifications, or that at the least, the private _GCControllerManager isn't getting a CBApplicationDidBecomeActive message. I'm not a professional macOS developer, though: I don't know if this actually applies to my situation, or how I'd go about correcting the problem if it does.
After a huge amount of time searching, I found the answer on my own. It turns out that my code wasn't the problem -- the problem was that my Info.plist file was having its CFBundleIdentifier value stripped out due to a problem with my build system. It appears that the Game Controller Framework needs the bundle identifier to correctly populate [GCController controllers] at launch. While a missing CFBundleIdentifier would have been a problem anyway, as a Windows person it didn't occur to me that the identifier might be used for things besides the App Store, so I let it slide until now.
If someone else has this problem, make sure that CFBundleIdentifier isn't missing or empty in Info.plist in your assembled app bundle. In my case with Premake, I had to manually set PRODUCT_BUNDLE_IDENTIFIER with xcodebuildsettings so that $(PRODUCT_BUNDLE_IDENTIFIER) would get properly replaced in Info.plist.

How do I detect when a Quick Look panel is dismissed?

I've written a Quick Look plugin that attempts to play music like this:
OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options)
{
NSURL *fileURL = (__bridge NSURL*)url;
AudioPlayer *player = // load player with fileURL
// Create a semaphore
sema = dispatch_semaphore_create(0);
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// Start playback and signal the semaphore once finished
[player play:^{
dispatch_semaphore_signal(sema);
}];
// Wait here until the player completion block signals the semaphore to stop waiting
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
NSLog(#"%#", #"done!");
return kQLReturnNoError;
}
For various reasons, it's not practical for me to transcode these audio files into a format that macOS knows, or else I could just hand the OS an MP3 file and get the system's plugin to play it for me. So instead I'm using a dirty hack with semaphores to halt execution to keep my player object around, or else it'd abruptly stop immediately after starting playback.
The problem with that is that the file will just continue playing after the Quick Look panel stops previewing it due to the quicklookd process still running.
Is there a way to stop playback the way the system plugins do when they're dismissed?
Have you tried to use following delegate methods:
According to Apple Documentation:
func previewControllerWillDismiss(QLPreviewController)
Called before the preview controller is closed.
func previewControllerDidDismiss(QLPreviewController)
Called after the preview controller is closed.

why isn't didReceiveRemoteNotification being fired in my react-native app?

I'm building a react-native app. Every time our cloud sends an APNS message, we want to increment the badge by 1.
I'm using the react-native PushNotificationIOS module to handle messages, but there's a known limitation with react-native, where the JS bridge is deactivated when the app is in the background. As a result, react-native application code that wants to manipulate the badge isn't executed, because the notification event is never delivered to the javascript.
It's important that this badge operation occur even when the app is not in the foreground, so I decided to implement the badge-incrementing logic in Objective-C, directly in AppDelegate.m. (Note: I don't understand ObjC, and can only loosely follow it.)
Here's the entirety of didReceiveRemoteNotification from AppDelegate.m:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
{
// --- BEGIN CODE I ADDED --- //
// increment badge number; must be written in ObjC b/c react-native JS bridge is deactivated when app in background
NSLog(#"remote notification LINE 1");
NSUInteger currentBadgeNum = [UIApplication sharedApplication].applicationIconBadgeNumber;
NSLog(#"currentBadgeNum = %d", currentBadgeNum);
NSUInteger newBadgeNum = currentBadgeNum + 1; // to increment by .badge: ... + [[[notification objectForKey:#"aps"] objectForKey: #"badge"] intValue]
NSLog(#"newBadgeNum = %d", newBadgeNum);
[UIApplication sharedApplication].applicationIconBadgeNumber = newBadgeNum;
NSLog(#"done updating badge; now notifying react-native");
// --- END CODE I ADDED --- //
// forward notification on to react-native code
[RCTPushNotificationManager didReceiveRemoteNotification:notification];
}
When I test it, none of the log statements appear in Xcode when the app is backgrounded. However, when the app is active, the log statements appear and the routine works exactly as expected (and the notification is delivered to the javascript code as well).
I believe I've already configured the app to receive push-notifications; here's the relevant section from my Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
What am I missing? Is there a problem with my code, or am I misunderstanding the nature of didReceiveRemoteNotification?
Any help is appreciated.

How to detect microphone input permission refused in iOS 7

I would like to detect when a user refused the microphone permission on my iOS application.
I only get this value when I try to record the microphone: -120.000000 db
But before to get this I have to set up an AVAudioSession. Is there another function?
And I got this message in the output:
Microphone input permission refused - will record only silence
Thanks.
If you are still compiling with iOS SDK 6.0 (as I am) you have to be a bit more indirect than #Luis E. Prado, as the requestRecordPermission method doesn't exist.
Here's how I did it. Remove the autorelease bit if you're using ARC. On iOS6 nothing happens, and on iOS7 either the 'microphone is enabled' message is logged or the alert is popped up.
AVAudioSession *session = [AVAudioSession sharedInstance];
if ([session respondsToSelector:#selector(requestRecordPermission:)]) {
[session performSelector:#selector(requestRecordPermission:) withObject:^(BOOL granted) {
if (granted) {
// Microphone enabled code
NSLog(#"Microphone is enabled..");
}
else {
// Microphone disabled code
NSLog(#"Microphone is disabled..");
// We're in a background thread here, so jump to main thread to do UI work.
dispatch_async(dispatch_get_main_queue(), ^{
[[[[UIAlertView alloc] initWithTitle:#"Microphone Access Denied"
message:#"This app requires access to your device's Microphone.\n\nPlease enable Microphone access for this app in Settings / Privacy / Microphone"
delegate:nil
cancelButtonTitle:#"Dismiss"
otherButtonTitles:nil] autorelease] show];
});
}
}];
}
EDIT: It turns out that the withObject block is executed in a background thread, so DO NOT do any UI work in there, or your app may hang. I've adjusted the code above. A client pointed this out on what was thankfully a beta release. Apologies for the mistake.
Please note that this will only work if built with Xcode 5, and not with 4.6
Add the AVFoundation Framework to your project
Then import the AVAudioSession header file, from the AVFoundation framework, where you intend to check if the microphone setting is enabled
#import <AVFoundation/AVAudioSession.h>
Then simply call this method
[[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
if (granted) {
// Microphone enabled code
}
else {
// Microphone disabled code
}
}];
The first time this method runs, it will show the prompt to allow microphone access and based on the users response it will execute the completion block. From the second time onwards it will just act based on the stored setting on the device.
Swift answer:
if AVAudioSession.sharedInstance().recordPermission() == .Denied {
print("Microphone permission refused");
}
Or you can use framework like PermissionScope which permit to easily check permissions. https://github.com/nickoneill/PermissionScope
Edit: Swift 3 answer:
import AVFoundation
...
if AVAudioSession.sharedInstance().recordPermission() == .denied {
print("Microphone permission refused");
}
I'm not 100% certain if we're allowed to talk about iOS 7 outside of Apple's devforums, but I found the answer you're looking for there.
In short, you'll find your solution in the AVAudioSession.h header file in the SDK. And if you want to make use of it while still supporting iOS 6, make certain to use "respondsToSelector:" to check for the API availability.

Graceful termination of NSApplication with Core Data and Grand Central Dispatch (GCD)

I have an Cocoa Application (Mac OS X SDK 10.7) that is performing some processes via Grand Central Dispatch (GCD). These processes are manipulating some Core Data NSManagedObjects (non-document-based) in a manner that I believe is thread safe (creating a new managedObjectContext for use in this thread).
The problem I have is when the user tries to quit the application while the dispatch queue is still running.
The NSApplication delegate is being called before actually quitting.
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
I get an error "Could not merge changes." Which is somewhat expected since there are still operations being performed through the different managedObjectContext. I am then presented with the NSAlert from the template that is generated with a core data application.
In the Threading Programming Guide there is a section called "Be Aware of Thread Behaviors at Quit Time" which alludes to using replyToApplicationShouldTerminate: method. I'm having a little trouble implementing this.
What I would like is for my application to complete processing the queued items and then terminate without presenting an error message to the user. It would also be helpful to update the view or use a sheet to let the user know that the app is performing some action and will terminate when the action is complete.
Where and how would I implement this behavior?
Solution:
So I had a few different issues here.
I had blocks that were accessing core data in a dispatch_queue preventing my application from terminating gracefully.
When I tried to add a new item to the dispatch_queue a new instance of the dispatch_queue was started on a new thread.
What I did to solve this was use NSNotificationCenter in my AppDelegate (where (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender was being called. In the template code that Core Data generates add the following:
// Customize this code block to include application-specific recovery steps.
if (error) {
// Do something here to add queue item in AppController
[[NSNotificationCenter defaultCenter] postNotificationName:#"TerminateApplicationFromQueue" object:self];
return NSTerminateLater;
}
Then in AppController add an observer for the notification (I added this to awakeFromNib):
- (void)awakeFromNib {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(terminateApplicationFromQueue:) name:#"TerminateApplicationFromQueue" object:nil];
// Set initial state of struct that dispatch_queue checks to see if it should terminate the application.
appTerminating.isAppTerminating = NO;
appTerminating.isTerminatingNow = NO;
}
I have also created a struct that can be checked against to see if the user wants to terminate the application. (I set the initial state of the struct in awakeFromNib above). Place the struct after your #synthesize statements:
struct {
bool isAppTerminating;
bool isTerminatingNow;
} appTerminating;
Now for the long-running dispatch_queue that is preventing the app from gracefully terminating. When I initially create this dispatch_queue, a for loop is used to add the items that need updating. After this for loop is executed, I have tacked on another queue item that will check the struct to see if the app should terminate:
// Additional queue item block to check if app should terminate and then update struct to terminate if required.
dispatch_group_async(refreshGroup, trackingQueue, ^{
NSLog(#"check if app should terminate");
if (appTerminating.isAppTerminating) {
NSLog(#"app is terminating");
appTerminating.isTerminatingNow = YES;
}
});
dispatch_release(refreshGroup);
And the method to be called when the notification is received:
- (void)terminateApplicationFromQueue:(NSNotification *)notification {
// Struct to check against at end of dispatch_queue to see if it should shutdown.
if (!appTerminating.isAppTerminating) {
appTerminating.isAppTerminating = YES;
dispatch_queue_t terminateQueue = dispatch_queue_create("com.example.appname.terminate", DISPATCH_QUEUE_SERIAL); // or NULL
dispatch_group_t terminateGroup = dispatch_group_create();
dispatch_group_async(terminateGroup, terminateQueue, ^{
NSLog(#"termination queued until after operation is complete");
while (!appTerminating.isTerminatingNow) {
// add a little delay before checking termination status again
[NSThread sleepForTimeInterval:0.5];
}
NSLog(#"terminate now");
[NSApp replyToApplicationShouldTerminate:YES];
});
dispatch_release(terminateGroup);
}
}
I haven't dealt with this myself, but just from my reading of the docs, it looks like what you should do is:
Return NSTerminateLater from applicationShouldTerminate:. This lets the system know that your app isn't ready to terminate just yet, but will do so shortly.
Enqueue a "final" block on your dispatch queue. (You need to make sure that other blocks are not enqueued after this. This block will then be run after all the other work has been performed. Note the queue must be serial -- not one of the concurrent queues) for this to work correctly.) The "final" block should do [NSApp replyToApplicationShouldTerminate:YES];, which will complete the normal termination process.
There isn't any direct way to find out whether a GCD queue is still working. The only other thing that you can do (that I know of) to handle this is to put all of the blocks into a dispatch group, and then wait on the group in applicationShouldTerminate: (using dispatch_group_wait().