I have a requirement where my app connects to a country channel (USA) and starts playing records from the channel. This is basically a channel which is run by users, the users upload their records to channel and they are played one by one. The user who connects to channel they start listening to channel.
The server sends the iOS app the URLs for the record that needs to be played via sockets, the iOS app creates AVQueuePlayer to play the URL's (using AVPlayerItems) one by one.
If I keep app in background when the channel is full of records for almost 1 day or so, the app keep running and keep playing all the records one by one. I know that AVQueuePlayer takes care of running the app all the time without killing as it receives new player items to play.
But if there are no records in channel and if user connects to channel, then app doesn't play the records in background if the idle time of the app exceeds 10 minutes.
I have written code with background task identifier which keeps my socket connect open so that new record URLs can be received all the time.
I see some of the crash reports in my device which says "AppName(my app) has active assertions beyond permitted time"
So can I know what wrong is going on here.
I am posting the background task code as well
- (void)keepBroadcastPersistentConnection {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if(self._bgTaskIdentifier)
self._bgTaskIdentifier = UIBackgroundTaskInvalid;
self._bgTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^{
[[UIApplication sharedApplication] endBackgroundTask:self._bgTaskIdentifier];
self._bgTaskIdentifier = UIBackgroundTaskInvalid;
CGLog(#"========================================end bg task at time %#", [NSDate date]);
CGLog(#"Time taken by app to run in bg is %f seconds", [[NSDate date] timeIntervalSinceDate:self.date]);
}];
[[BroadcastSocketConnecter sharedSocketConnecter].socketIO sendHeartbeat]; // this keep the socket alive
self.date = [NSDate date];
CGLog(#"========================================begin bg task at time %#", self.date);
});
}
Thanks
From the audio session programming guide:
Why a Default Audio Session Usually Isn’t What You Want
Scenario 3. You write a streaming radio application that uses Audio
Queue Services for playback. While a user is listening, a phone call
arrives and stops your sound, as expected. The user chooses to ignore
the call and dismisses the alert. The user taps Play again to resume
the music stream, but nothing happens. To resume playback, the user
must quit your application and restart it.
To handle the interruption of an audio queue gracefully, implement
delegate methods or write an audio session callback function to allow
your application to continue playing automatically or to allow the
user to manually resume playing. See “Responding to Audio Session
Interruptions.”
Shortly, the solution would be to implement the AVAudioSessionDelegate protocol'a beginInterruption and endInterruption methods. However, the delegate property of the AvAudioSession class was deprecated in iOS6 and Notifications should be used instead. Namely, you are interested in the AVAudioSessionInterruptionNotification
Solution. According to this story if the playback stops, then you should explicitly activate the audio session again to prevent your app from being terminated.
Below is the source for the delegate implementation but the logic doesn't change much with the notifications so I feel it's still a good source for info.
- (void) beginInterruption {
if (playing) {
playing = NO;
interruptedWhilePlaying = YES;
[self updateUserInterface];
}
}
NSError *activationError = nil;
- (void) endInterruption {
if (interruptedWhilePlaying) {
BOOL success = [[AVAudioSession sharedInstance] setActive: YES error: &activationError];
if (!success) { /* handle the error in activationError */ }
[player play];
playing = YES;
interruptedWhilePlaying = NO;
[self updateUserInterface];
}
}
Old response which is still valid but not an elegant solution
You cannot start playing audio in the background. This answer explains what I mentioned in my comment above: https://stackoverflow.com/a/16568437/768935 Doing tricks with the AudioSession does not seem to have an effect on this policy.
As a solution, you need to keep playing audio. If there is no item in the queue, then insert the "silence" audio track. However, I have my doubts that the app with this trick will be admitted in the App Store. It may be better to inform the user that audio playback will be resumed on if the app is started again.
Related
I want to know when my AVAudioRecorder is inaccessible (e.g when music starts playing).
As audioRecorderEndInterruption will be deprecated with iOS 9 I am focusing on AVAudioSession's interruption notification (but neither is working as expected).
The issue is that the interruption notification is never called if the app was and remains in the foreground when the interruption occurs.
E.g: The user starts and stops playing music without moving the application into the background.
To detect any interruptions I am using:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(audioSessionWasInterrupted:) name:AVAudioSessionInterruptionNotification object:nil];
...
- (void)audioSessionWasInterrupted:(NSNotification *)notification {
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
NSLog(#"Interruption notification");
if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
NSLog(#"InterruptionTypeBegan");
} else {
NSLog(#"InterruptionTypeEnded");
}
}
}
I get InterruptionTypeBegan as expected, but InterruptionTypeEnded isn't called if the app is still in the foreground (meaning it won't be called until the app is placed in the background and back into the foreground).
How may I receive InterruptionTypeEnded notification when the interruption occurs while the app is in the foreground?
This is a widespread problem affecting any app using AV framework components (the same goes for native iOS apps).
As explained in 's documentation on the subject of audio interruptions, the InterruptionTypeEnded should actually be applied in the scenario mentioned:
If the user dismisses the interruption ... the system invokes your callback method, indicating that the interruption has ended.
However, it also states that the InterruptionTypeEnded might not be called at all:
There is no guarantee that a begin interruption will have an end interruption.
Therefore, a different approach is needed in the scenario mentioned.
When it comes to handling music interruptions, the issue won't be around for long. iOS 9 effectively prevents outside audio sources to be used while the app's audio handler is invoked.
A way to handle the exact issue of media interruption could be to listen to MPMusicPlayerController's playbackState, as shown in this stackoverflow question: Detecting if music is playing?.
A more direct way to handle the issue of interruptions would be to either:
Block outside audio interruptions completely by re-invoking your audio component at the time of InterruptionTypeBegan.
Or by giving a UI indication that an outside media source has interrupted the audio session (for example showing an inactive microphone).
Hopefully will come up with a better solution to the problem, but in the meantime this should give you some options to solve the interruption issue.
If you haven't already, try setting your AVCaptureSession's property usesApplicationAudioSession to NO.
This question & answer may act as a good reference if you're looking for any more detail.
I try this and find InterruptionTypeEnded may called after music pause in some app, but other not called when pause.
My solution is update the UI to let user know record has stopped and do some related work such as file operation. When interruption ends, active AVAudioSession, if doesn't have error, start a new record.
If you want to join the file before and after interrupt, the answer to this question: AVAudioRecorder records only the audio after interruption may be helpful to you.
I think I know the answer to this question already, but I wanted to ask just to be thorough.
Consider the Apple Watch built-in Maps app. When you're using turn-by-turn directions, when it's time to turn left or right, the watch plays a custom haptic pattern - even though the screen is off and the app is backgrounded. Another example is while you're doing a workout - if you've set a goal, you'll get a light tap on your wrist when you get 50% there and 100% there, even if you're not looking at the watch at the time (screen off, app backgrounded).
In watchOS 2, is there any way for us 3rd party developers to have an app play a certain haptic pattern when the screen is off and the app is backgrounded? I know the playHaptic: method works while the app is active to let you play several different kinds of haptic patterns, and I know that while the app is inactive, you can have a notification come in - but the notification would only ever play the 'notification' haptic feeling, no choice in that.
You can only run custom code when you app is active. So I´m afraid you can't do this.
Here is how i'm playing haptic in background,
first of all you need to enable background mod in Capabilities for WatchExtensionand to enable:Workout Processing and Audio,Airplay.
Also you need to enable for WatchExtension HealthKit.
#import < HealthKit/HealthKit.h >
add HKWorkoutSessionDelegate
-(void)awakeWithContext:(id)context{
[super awakeWithContext:context];
HKHealthStore *cwHealthStore = [[HKHealthStore alloc] init];
cwConfiguration = [[HKWorkoutConfiguration alloc] init];
cwConfiguration.activityType = HKWorkoutActivityTypeOther;
NSError *error;
HKWorkoutSession *cwSession = [[HKWorkoutSession alloc] initWithConfiguration:cwConfiguration error:&error];
[cwSession setDelegate:self];
if (!error) {
[cwHealthStore startWorkoutSession:cwSession];
}
[self test];
}
#pragma mark WorkoutSession Delegates
- (void)workoutSession:(HKWorkoutSession *)workoutSession
didChangeToState:(HKWorkoutSessionState)toState
fromState:(HKWorkoutSessionState)fromState
date:(NSDate *)date{
NSLog(#"------>%ld", (long)toState);
}
- (void)workoutSession:(HKWorkoutSession *)workoutSession didFailWithError:(NSError *)error{
NSLog(#"%#", error);
}
And now you can play haptic in background.
-(void)test{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerTrick:) userInfo:nil repeats:true];
}
- (void)timerTrick:(NSTimer *)time {
[[WKInterfaceDevice currentDevice] playHaptic:WKHapticTypeStart];
}
Don't forghet to stop workout Session after leaving controller:
[cwHealthStore endWorkoutSession:cwSession];
Just to post an update to my own question a couple years later - in watchOS 3 workout apps were granted background execution, but no haptics (I think).
In watchOS 4, workout apps, audio recording apps and navigation apps have background execution; navigation apps can send haptics in the background. Furthermore, the "frontmost app" (app last used which still appears if wrist is raised within 2 minutes, or 8 if extended frontmost time is enabled) has some privileges for sending haptics at the conclusion of a WatchConnectivity or NSURLSession data transfer, or when a notification comes in. See the docs for details.
My iPad sound application (iOS 7.1) is able to record while in background. Everything is ok as far as recording is not interrupted while in background. For example if one's has the (stupid?) idea to start listening music while recording something.
I tried to manage this interruption in different ways, with no success. The problem is that the
- (void)audioRecorderEndInterruption:(AVAudioPlayer *)p withOptions:(NSUInteger)flags
is never fired when application was in background as the interruption occurred. I then tried, in another solution, to implement the AVAudioSessionInterruptionNotification and a handleInterruption: method as
- (void) viewDidAppear:(BOOL)animated
{
......
AVAudioSession *session=[AVAudioSession sharedInstance];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleInterruption:)
name:AVAudioSessionInterruptionNotification
object:session];
.......
}
// interruption handling
- (void)handleInterruption:(NSNotification *)notification
{
try {
UInt8 theInterruptionType = [[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] intValue];
NSLog(#"Session interrupted > --- %s ---\n", theInterruptionType == AVAudioSessionInterruptionTypeBegan ? "Begin Interruption" : "End Interruption");
.... MANAGE interruption begin
interruptionBeganWhileInBackgroundMode = TRUE;
}
if (theInterruptionType == AVAudioSessionInterruptionTypeEnded) {
.... MANAGE interruption end
}
} catch (CAXException e) {
char buf[256];
fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf));
}
}
In this case the handleInterruption: is fired when interruption begins, but it is not when interruption ends (shoulld be fired with theInterruptionType set to AVAudioSessionInterruptionTypeEnded)
To circumvent this problem, I decided to set a flag (interruptionBeganWhileInBackgroundMode) when interruption begins to get the application informed that an interruption has occurred when coming foreground. So that I can manage the end of interruption.
It may seem clever, but it is not! Here is why...
I tried two implementations.
Solution 1. Put a [recorder pause] in handleInterruption: when interruption begins, and manage a [recorder record] in the comingForeground: method when the interruption flag is set.
In this situation, the recording is going on immediately when application comes back to foreground, BUT instead of resuming, the recorder erase the file and restart a new recording, so that everything recorded before interruption is lost.
Solution 2. Put a [recorder stop] in handleInterruption: when interruption begins, to save the file, to be sure to preserve the recorded data. This recording is saved, BUT when application is coming foreground, it stalls for about ten seconds before the user can interact again, as if there was a process (the file saving?) that keep the UI frozen.
Last point: I have exactly the same problems in iPhone version of this application: when application is foreground and a phone call occurs, everything goes OK. But when a phone call occurs as my application is background I see the same bad behaviour as in iPad's version.
I notice that Apple's Voice Memo app in iOS7 correctly manage these background interruptions (it stops and saves the recording) despite it displays a 00:00:00 file length when coming foreground. The iTalk application manages it perfectly, automatically resuming recording when coming foreground.
Did anybody find a workaround for the audio interruption management for backgrounded recording applications? I found plenty of people looking for that in many developer websites, but no answer... Thanks!
I have been going through the same issue myself. It seems as if there is a bug in the iOS7 AVAudioRecorder in how it deals with interrupts. Instead of pausing as I believe the documentation says that is should, it closes the file. I have not been able to figure out what is stalling the app when it comes back to the foreground. In my case, I would see AVAudioRecorder finish (with the success flag set to NO), after 10 seconds.
I ended up rewriting the audio recorder using Audio Queues. I found some sample code here (git#github.com:vecter/Audio-Queue-Services-Example.git) that helped with setting it up in an Objective-C environment and the Apple SpeakHere demo has some code to handle the interrupt notifications.
Essentially, I am stopping the recording on interrupt began and opening an alert for the user to save the file. This alert is deferred until UIApplicationDidBecomeActiveNotification is passed if the interrupt started while the app was in the background.
One other thing to note, there seems to be a minor bug in Audio Queues that the AudioQueueStart method will return -50 sometimes. If you add
AudioSessionInitialize(NULL, NULL,nil,(__bridge void *)(self));
UInt32 sessionCategory = kAudioSessionCategory_PlayAndRecord;
AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
sizeof(sessionCategory),
&sessionCategory
);
AudioSessionSetActive(true);
Before any AudioQueue methods, the error goes away. These methods are marked as deprecated but seem to be necessary.
Don't know / think this is going to be relevant to the original author of the topic, but here is my experience with it:
I ended up here because of the AVAudioSessionInterruptionNotification thing; the .began flavour came as expected, but the .ended one did not.
In my case, it happened because I was using two AVPlayer instances: one to play music, and the other one to play silence while the first struggled to start streaming a new track (otherwise iOS would suspend my app while in background, if next track loading did not happen fast enough).
Turns out it's not the brightest solution, and it somehow messes up the notification system. Giving up the second AVPlayer (the silence playing one) resulted in .ended being triggered as expected.
Of course, I found the answer / solution for the notification problem, but I'm now left with the old one... :-)
I'm building a music streaming app using AVPlayer.
Everything is working fine, music keeps playing in the background, etc.
My problem is when I simulate connection loss on the phone (switch cellular data off, wait for the stream to stop and then switch back) it will take about 2 mins for the app to fill the buffer even if there's a 3G network present (I can surf the web meanwhile).
Everything is working in simulator, but not on device.
If I wait 2 mins I'll get a proper playbackLikelyToKeepUp notification, and from that on everything is fine, but that rebuffering should be just a few seconds. It's ok when I simulate low bandwidth by turning off bandwidth control on my router, but when I simulate carrier change by switching off cellular data and switch it back its that long.
I'm calling beginInterruption on "playbackBufferEmpty" and call endInterruption on "playbackLikelyToKeepUp". The main problem is "playbackLikelyToKeepUp" arrives way too late.
- (void)beginInterruption {
self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
}
- (void)endInterruptionWithFlags:(NSUInteger)flags {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
if(self.interrupted) [self play];
self.interrupted = NO;
}
Any ideas? I've spent my whole day with this issue, googled everything but I'm stuck.
Thanks in advance.
I suffered the same situation months ago, my conclusion is "playbackLikelyToKeepUp is not reliable".
My solution is checked the buffered TimeRange manually.
FYI, I made a AVPlayer class for remote media playback - HysteriaPlayer.
You can use it directly, or look up the .m file.
Hope this helps.
I am trying to keep the iOS app in active state for more than 10 mins when it enters in background state.
How can I implement this.
See "Background Execution" section of the iPhoneAppProgrammingGuide. In short, your app must be one of these types:
Apps that play audible content to the user while in the background, such as a music player app
Apps that keep users informed of their location at all times, such as a navigation app
Apps that support Voice over Internet Protocol (VoIP)
Newsstand apps that need to download and process new content
Apps that receive regular updates from external accessories
And you must add to the Info.plist as follows:
Add the UIBackgroundModes key to your
Info.plist file and set its value to an array containing one or more of the following strings:
audio—The app plays audible content to the user while in the background. (This content includes streaming audio or video content using AirPlay.)
location—The app keeps users informed of their location, even while it is running in the background.
voip—The app provides the ability for the user to make phone calls using an Internet connection.
newsstand-content—The app is aNewsstand app that downloads and processesmagazine or newspaper
content in the background.
external-accessory—The app works with a hardware accessory that needs to deliver updates on a
regular schedule through the External Accessory framework.
bluetooth-central—The app works with a Bluetooth accessory that needs to deliver updates on a
regular schedule through the CoreBluetooth framework
Note that part of the review process will be checking to make sure that your app does what it says it's doing with regard to background processing.
Here's what I've done using beginBackgroundTaskWithExpirationHandler.
Write a method that starts a background task.
Inside that background task, run a NSTimer with a scheduled (non repeating) time that is under 10 minutes. For the purposes of my situation I was using 5 minutes.
Once the NStimer's selector fires, end the background task and then instantly call the method that you wrote earlier to start off another background task.
If you want to schedule methods to run at specific times, you will have to check for them in the background task.
This solution isn't really ideal and is still power hungry but will do what you want.
Edit: Since iOS7, I suggest you read this excellent post. Note that this article was last updated in 2013 and is probably irrelevant now.
Only certain types of apps are allowed to run in the background. See the "Implementing Long-Running Background Tasks" section of this guide.
If you aren't requesting permissions to do background processing you can use UIApplication's beginBackgroundTaskWithExpirationHandler but you cannot get extra time.
This code makes your iOS app run indefinitely in the background. Copy and paste the below methods into a singleton / manager which handles the tasks you need to perform in the background.
// #interface
// Declare Private property
#property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
//#end
// ...
// Copy into
//#implementation
- (void)setupBackgrounding {
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(appBackgrounding:)
name: UIApplicationDidEnterBackgroundNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(appForegrounding:)
name: UIApplicationWillEnterForegroundNotification
object: nil];
}
- (void)appBackgrounding: (NSNotification *)notification {
[self keepAlive];
}
- (void) keepAlive {
self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
[self keepAlive];
}];
}
- (void)appForegrounding: (NSNotification *)notification {
if (self.backgroundTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
}
}
You can't. Unless your app uses audio, voip or gps. What you can do is notify the user (via local notifications) that the time is almost up and ask him to open/close the app.
Also if you just need to notify the user, you can use push notifications.
https://github.com/yarodevuci/backgroundTask Check my code here I am using audio player that plays blank wav file Works perfectly on IOS 8 Battery usage around 10% in 24 hour period
How to use:
var backgroundTask = BackgroundTask()
backgroundTask.startBackgroundTask() //Starts playing blank audio file. You can run NSTimer() or whatever you need and it will continue executing in the background.
backgroundTask.stopBackgroundTask() //Stops the task
Warning: Apple will reject this if you try to submit it!
If your App type is not one of VOIP/Audio/Location....(check Background Modes),
or you don't want to specify your App as a background App, you can implement beginBackgroundTaskWithName:expirationHandler or beginBackgroundTaskWithExpirationHandler to ask for more time to run your process in background. You can find the detailed description here
Apps moving to the background are expected to put themselves into a quiescent state as quickly as possible so that they can be suspended by the system. If your app is in the middle of a task and needs a little extra time to complete that task, it can call the beginBackgroundTaskWithName:expirationHandler: or beginBackgroundTaskWithExpirationHandler: method of the UIApplication object to request some additional execution time. Calling either of these methods delays the suspension of your app temporarily, giving it a little extra time to finish its work. Upon completion of that work, your app must call the endBackgroundTask: method to let the system know that it is finished and can be suspended.
Each call to the beginBackgroundTaskWithName:expirationHandler: or beginBackgroundTaskWithExpirationHandler: method generates a unique token to associate with the corresponding task. When your app completes a task, it must call the endBackgroundTask: method with the corresponding token to let the system know that the task is complete. Failure to call the endBackgroundTask: method for a background task will result in the termination of your app. If you provided an expiration handler when starting the task, the system calls that handler and gives you one last chance to end the task and avoid termination.