I have an app which plays Spotify track. It works perfectly when app moves to background but when it get interrupted by 'call' or 'iOS pop-ups' it stops playing.
I have added Required background modes to App plays audio in .plist.
How to resume playback when interruption ended while app is in background.
i have used audio interruption callbacks.
AudioSessionInitialize (NULL, NULL, interruptionListenerCallback,self);
AudioSessionSetActive(YES);
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
And after receiving interruption call play/pause method of playback manager.
void interruptionListenerCallback (void *inUserData, UInt32 interruptionState)
{
// This callback, being outside the implementation block, needs a reference
//to the AudioPlayer object
CustomPlayerView *controller = (__bridge CustomPlayerView *) inUserData;
if (interruptionState == kAudioSessionBeginInterruption)
{
[controller.playbackManager setIsPlaying:NO];
}
else if (interruptionState == kAudioSessionEndInterruption)
{
[controller.playbackManager setIsPlaying:YES];
}
}
i need to trigger the iphone audio route back and forth from speaker to normal mode very quickly. I've created an audio Session similar to this site:
http://atastypixel.com/blog/using-remoteio-audio-unit/
Im creating a app that can do VOIP.
So let me explain the simple issue i have. I have a button that toggles from speaker to normal mode like this:
if(speakerState){
value = kAudioSessionOverrideAudioRoute_None;
error = AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute, sizeof(value), &value);
speakerState = false;
}
else {
value = kAudioSessionOverrideAudioRoute_Speaker;
error = AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute, sizeof(value), &value);
speakerState = true;
}
... you get the idea just toggling the speaker on and off.
Here is the issue: When the end user RAPIDLY hits the button to toggle the speaker after about 10 presses the application freezes for a while as if its releasing something and after 30 seconds comes back. I need the end user to be able to constantly tap this button on/off as much as they want.
update: I tried calling the speakers in the background thread but still if i press the speakers too many times it freezes or i loose audio here is the code:
-(void) manageSpeakerState{
OSStatus error;
UInt32 value;
if(speakerState){
value = kAudioSessionOverrideAudioRoute_None;
error = AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute, sizeof(value), &value);
speakerBtn.selected = false;
speakerState = false;
}
else {
value = kAudioSessionOverrideAudioRoute_Speaker;
error = AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute, sizeof(value), &value);
speakerBtn.selected = true;
speakerState = true;
}
}
// this gets called whenever the speaker button is pressed ..it toggles the speakers on/off
-(IBAction) speaker
{
[self performSelectorInBackground:#selector(manageSpeakerState) withObject:nil ];
}
UPDATE: My audio session is running on a seperate thread using pthread and its in a .c file. It could be a concurrency issue as it seems when the main thread gets tied up this issue occurs. How can i update the audio route on the thread that the audio session is running on ?
UPDATE: i have the same issue as this: http://lists.apple.com/archives/coreaudio-api/2012/Jul/msg00129.html
Whenever i change routes (ie. speaker or headphone) my audioUnits are paused so im reading empty stuff into the buffers. Its almost like a hardware manufacturer issue. How can i get around this ? the call backs are still being called but theres no audio unit while switching routes.
On button click just change button's background image and tag.Change audio session property in background thread.
In your application delegate just add this piece of code..I hope it work fine.
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
UIBackgroundTaskIdentifier newTaskId = UIBackgroundTaskInvalid;
newTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
I have an app that streams music using AudioStreamer class by Matt Gallagher. This works fine as a background process except I want to be able to skip to the next song once the stream is finished. Unfortunately this part doesn't work.
Initially I had a timer that was monitoring the stream but realized that when the app backgrounds this timer no longer runs. So I tried adding a delegate callback in the packet read function:
void ASReadStreamCallBack(CFReadStreamRef aStream, CFStreamEventType eventType, void* inClientInfo)
{
AudioStreamer* streamer = (AudioStreamer *)inClientInfo;
double percent = [streamer progress]/[streamer duration];
if(percent>=0.98 || (percent>=0.95 && [streamer isIdle])){
if([streamer.delegate respondsToSelector:#selector(didFinishPlayingStream:)] ){
[streamer.delegate didFinishPlayingStream:streamer];
streamer.delegate = nil;
}
}
[streamer handleReadFromStream:aStream eventType:eventType];
}
This works fine when the app is in the foreground but no longer works when the app is backgrounding. The delegate method basically sends a request to get the stream URL for the next song, then once it has it creates a new AudioStreamer class
While the app is in background you can implemente the delegate to handle the different remote control states.
- (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent {
switch (receivedEvent.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
if (player.isPlaying) {
[player pause];
} else {
[player start];
}
break;
case UIEventSubtypeRemoteControlPreviousTrack:
break;
case UIEventSubtypeRemoteControlNextTrack:
[self skipSong:nil];
break;
default:
break;
} }
Something like this works for me.
I've uploaded my AudioPlayer/streamer class inspired in part by Matt Gallagher's AudioStreamer to
https://code.google.com/p/audjustable.
One of the cooler features is its support for gapless playback. This means the AudioQueue is never closed between gaps; keeping iOS from suspending your app.
You can implement AudioPlayerDelegate:didFinishBufferingSourceWithQueueItemId and AudioPlayerDelegate:didFinishPlayingQueueItemId to queue up the next track by calling AudioPlayer:queueDataSource.
Let me know if you need help using it.
How can I detect lock/unlock events on the iPhone? Assuming it's only possible for jailbroken devices, can you point me to the correct API?
By lock events, I mean showing or hiding the Lock Screen (which might need a password to unlock, or not).
You can use Darwin notifications, to listen for the events. From my testing on a jailbroken iOS 5.0.1 iPhone 4, I think that one of these events might be what you need:
com.apple.springboard.lockstate
com.apple.springboard.lockcomplete
Note: according to the poster's comments to a similar question I answered here, this should work on a non-jailbroken phone, too.
To use this, register for the event like this (this registers for just the first event above, but you can add an observer for lockcomplete, too):
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
(void*)self, // observer (can be NULL)
lockStateChanged, // callback
CFSTR("com.apple.springboard.lockstate"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
where lockStateChanged is your event callback:
static void lockStateChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
NSLog(#"event received!");
if (observer != NULL) {
MyClass *this = (MyClass*)observer;
}
// you might try inspecting the `userInfo` dictionary, to see
// if it contains any useful info
if (userInfo != nil) {
CFShow(userInfo);
}
}
The lockstate event occurs when the device is locked and unlocked, but the lockcomplete event is only triggered when the device locks. Another way to determine whether the event is for a lock or unlock event is to use notify_get_state(). You'll get a different value for lock vs. unlock, as described here.
Round about answer:
Application will resign active gets called in all sorts of scenarios... and from all my testing, even if your application stays awake while backgrounded, there are no ways to determine that the screen is locked (CPU speed doesn't report, BUS speed remains the same, mach_time denom / numer doesn't change)...
However, it seems Apple does turn off the accelerometer when the device is locked... Enable iPhone accelerometer while screen is locked
(tested iOS4.2 on iPhone 4 has this behavior)
Thus...
In your application delegate:
- (void)applicationWillResignActive:(UIApplication *)application
{
NSLog(#"STATUS - Application will Resign Active");
// Start checking the accelerometer (while we are in the background)
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:1]; // Ping every second
_notActiveTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(deviceDidLock) userInfo:nil repeats:NO]; // 2 seconds for wiggle
}
//Deprecated in iOS5
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
NSLog(#"STATUS - Update from accelerometer");
[_notActiveTimer invalidate];
_notActiveTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(deviceDidLock) userInfo:nil repeats:NO];
}
- (void)deviceDidLock
{
NSLog(#"STATUS - Device locked!");
[[UIAccelerometer sharedAccelerometer] setDelegate:nil];
_notActiveTimer = nil;
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSLog(#"STATUS - Application did become active");
[[UIAccelerometer sharedAccelerometer] setDelegate:nil];
[_notActiveTimer invalidate];
_notActiveTimer = nil;
}
I know... It's kind of a hack, but it has worked like a charm for me so far. Please update if you see any issues that prevent this from working.
There is a prettier way of telling apart task switching and screen locking-originated applicationWillResignActive: callbacks which doesn't even involve undocumented features such as the accelerometer state.
When the app is moving to the background, the app delegate is first sent an applicationWillResignActive:, then an applicationDidEnterBackground:. When the app is interrupted by pressing the Lock button or by an incoming phone call, the latter method is not called. We can use this information to distinguish between the two scenarios.
Say you want to be called back in the screenLockActivated method if the screen gets locked. Here's the magic:
- (void)applicationWillResignActive:(UIApplication*)aApplication
{
[self performSelector:#selector(screenLockActivated)
withObject:nil
afterDelay:0];
}
- (void)applicationDidEnterBackground:(UIApplication*)aApplication
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
- (void)screenLockActivated
{
NSLog(#"yaay");
}
Explanation:
By default, we assume that every call to applicationWillResignActive: is because of an active->inactive state transition (as when locking the screen) but we generously let the system prove the contrary within a timeout (in this case, a single runloop cycle) by delaying the call to screenLockActivated. In case the screen gets locked, the system finishes the current runloop cycle without touching any other delegate methods. If, however, this is an active->background state transition, it also invokes applicationDidEnterBackground: before the end of the cycle, which allows us to simply cancel the previously scheduled request from there, thus preventing it from being called when it's not supposed to.
Enjoy!
As of the time of writing there are two fairly reliable ways to detect device locking:
Data Protection
By enabling the Data Protection entitlement your app can subscribe to the applicationProtectedDataWillBecomeUnavailable: and applicationProtectedDataDidBecomeAvailable: notifications to determine with high probability when a device that uses passcode/TouchID Authentication is locked/unlocked. To determine if a device uses a passcode/TouchID LAContext can be queried.
Caveats: This method relies on the "protected data becoming unavailable" coinciding with the phone being locked. When the phone is using TouchID and the sleep/lock button is pressed then the phone is locked, protected data becomes unavailable, and a passcode will immediately be required to unlock it again. This means that protected data becoming unavailable essentially indicates that the phone has been locked. This is not necessarily true when someone is using just a passcode since they can set the "requires passcode" time to anywhere from immediately to something like 4 hours. In this case the phone will report being able to handle protected data but locking the phone will not result in protected data becoming unavailable for quite some time.
Lifecycle Timing
If your app is in the foreground there will be a noticeable change in time difference between the two lifecycle events UIApplicationWillResignActiveNotification and UIApplicationDidEnterBackgroundNotification depending on what triggers them.
(This was tested in iOS 10 and may change in future releases)
Pressing the home button results in a significant delay between the two (even when the Reduced Motion setting is enabled):
15:23:42.517 willResignActive
15:23:43.182 didEnterBackground
15:23:43.184 difference: 0.666346
Locking the device while the app is open creates a more trivial (<~0.2s) delay between the two events:
15:22:59.236 willResignActive
15:22:59.267 didEnterBackground
15:22:59.267 difference: 0.031404
in iOS 8, you lock the screen or push the home button, all of those make app push in background, but you don't know which operator result in this. My solution same with Nits007ak,use notify_register_dispatch to get state.
#import <notify.h>
int notify_token
notify_register_dispatch("com.apple.springboard.lockstate",
¬ify_token,
dispatch_get_main_queue(),
^(int token)
{
uint64_t state = UINT64_MAX;
notify_get_state(token, &state);
if(state == 0) {
NSLog(#"unlock device");
} else {
NSLog(#"lock device");
}
}
);
As long as the app is running, in foreground or background. not suspend, you can get this event.
And you can use notify_token as parameter of notify_get_state to get current state anywhere, this is useful when you want know the state and the screen state don't change.
If passcode is set, you can use these event in AppDelegate
-(void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application
{
}
- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application
{
}
Just import #import notify.h before using this code. enjoy!!
-(void)registerAppforDetectLockState {
int notify_token;
notify_register_dispatch("com.apple.springboard.lockstate", ¬ify_token,dispatch_get_main_queue(), ^(int token) {
uint64_t state = UINT64_MAX;
notify_get_state(token, &state);
if(state == 0) {
NSLog(#"unlock device");
} else {
NSLog(#"lock device");
}
NSLog(#"com.apple.springboard.lockstate = %llu", state);
UILocalNotification *notification = [[UILocalNotification alloc]init];
notification.repeatInterval = NSDayCalendarUnit;
[notification setAlertBody:#"Hello world!! I come becoz you lock/unlock your device :)"];
notification.alertAction = #"View";
notification.alertAction = #"Yes";
[notification setFireDate:[NSDate dateWithTimeIntervalSinceNow:1]];
notification.soundName = UILocalNotificationDefaultSoundName;
[notification setTimeZone:[NSTimeZone defaultTimeZone]];
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
});
}
From a lot of trial and error, discovered monitoring the blank screen, lock complete and lock state events gives a consistent lock screen indicator. You'll need to monitor a state transition.
// call back
void displayStatusChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
// notification comes in order of
// "com.apple.springboard.hasBlankedScreen" notification
// "com.apple.springboard.lockcomplete" notification only if locked
// "com.apple.springboard.lockstate" notification
AppDelegate *appDelegate = CFBridgingRelease(observer);
NSString *eventName = (__bridge NSString*)name;
NSLog(#"Darwin notification NAME = %#",name);
if([eventName isEqualToString:#"com.apple.springboard.hasBlankedScreen"])
{
NSLog(#"SCREEN BLANK");
appDelegate.bDeviceLocked = false; // clear
}
else if([eventName isEqualToString:#"com.apple.springboard.lockcomplete"])
{
NSLog(#"DEVICE LOCK");
appDelegate.bDeviceLocked = true; // set
}
else if([eventName isEqualToString:#"com.apple.springboard.lockstate"])
{
NSLog(#"LOCK STATUS CHANGE");
if(appDelegate.bDeviceLocked) // if a lock, is set
{
NSLog(#"DEVICE IS LOCKED");
}
else
{
NSLog(#"DEVICE IS UNLOCKED");
}
}
}
-(void)registerforDeviceLockNotif
{
// screen and lock notifications
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
CFBridgingRetain(self), // observer
displayStatusChanged, // callback
CFSTR("com.apple.springboard.hasBlankedScreen"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
CFBridgingRetain(self), // observer
displayStatusChanged, // callback
CFSTR("com.apple.springboard.lockcomplete"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
CFBridgingRetain(self), // observer
displayStatusChanged, // callback
CFSTR("com.apple.springboard.lockstate"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
}
To have the screen lock indicators run in the background, you need to implement background processing calling the following upon app launching.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.backgroundTaskIdentifier =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
}];
[self registerforDeviceLockNotif];
}
If your app is running and the user locks the device your app delegate will receive a call to 'application Will Resign Active:'. If your app was running when locked, it will receive a call to 'application Did Become Active:' when the device is unlocked. But you get the same calls to your app if the user gets a phone call and then chooses to ignore it. You can't tell the difference as far as I know.
And if your app wasn't running at any of these times there is no way to be notified since your app isn't running.
The simplest way to get screen lock and unlock events are by adding event observers using NSNotificationCenter in your viewcontroller. I added the following observer in the viewdidload method. This is what i did:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationEnteredForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
Then I added the following selector to the viewcontroller. This selector will get called when the screen is unlocked.
- (void)applicationEnteredForeground:(NSNotification *)notification {
NSLog(#"Application Entered Foreground");
}
If you want to detect the event when screen gets locked, you can replace UIApplicationWillEnterForegroundNotification with UIApplicationDidEnterBackgroundNotification.
I am developing an audio streamer and have declared an interruption listener to save state of a song when an interruption occurs - like an incoming call or an sms.
Here is the relevant code
In my AppDelegate, I have this
AudioSessionInitialize (NULL, NULL, interruptionListenerCallback, self);
AudioSessionSetActive(YES);
This is what the interruption listener looks like
void interruptionListenerCallback (void *inUserData, UInt32 interruptionState) {
// This callback, being outside the implementation block, needs a reference
//to the AudioPlayer object
MyPlayer *player = (MyPlayer *)inUserData;
if (interruptionState == kAudioSessionBeginInterruption) {
if ([player audioStreamer]) {
// if currently playing, pause
[player pausePlayback];
player.interruptedOnPlayback = YES;
}
} else if ((interruptionState == kAudioSessionEndInterruption) && player.interruptedOnPlayback) {
// if the interruption was removed, and the app had been playing, resume playback
[player resumePlayback];
player.interruptedOnPlayback = NO;
}
}
When I get a phone call the interruption listener is called and if the user declines the call the playback resume method is also called. But before the resumePlayback method is called, I get this error in the console for AudioQueueEnqueueBuffer
error: tca! error int: 560030580
Does anyone have an idea of how to correctly handle audio interruptions when streaming audio files.
Thanks.
This link shows what the problem is. Might help someone.
https://devforums.apple.com/message/31758#31758
!act is kAudioSessionNotActiveError, declared in AudioServices.h, with the comment
"The operation failed because the AudioSession is not active. Calling AudioSessionSetActive(true) first will fix this error in most cases."
You also get this error when you call AudioQueueStart() after an interruption (as I found out today).
It looks to me like you are setting inUserData as your appDelegate instead of your player.