Quoted from Xcode doc,
AVAudioSessionSilenceSecondaryAudioHintNotification Posted on the main
thread when the primary audio from other applications starts and
stops.
Subscribe to this notification to ensure that your app is notified
when optional secondary audio muting should begin or end.
However, when my app's audio is playing, and I press the remote-control to start playing music from the Music app. This notification is not triggered in my observer callback. I believe the registration was successful.
Am I having the wrong expectation? Is it supposed to be triggered in a different scenario? Any examples?
You need to set your AVAudioSession Category to AVAudioSessionCategoryAmbient, then your app will allow background apps (such as music or podcasts) to play. If you set it to AVAudioSessionCategorySoloAmbient then it will not accept background music
You can do it like this:
NSError *categoryError = nil;
if ([[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&categoryError]) {
printf("Setting AVAudioSession CategoryAmbient Succeeded\n");
} else {
printf("Setting AVAudioSession CategoryAmbient Failed\n");
}
Check out Audio Session Categories for more details
Related
I am trying to find a way to pause any playing media on the device, so I was thinking of triggering the same logic that is fired when a user press the headphone "middle button"
I managed to prevent music from resuming (after I pause it within my app, which basically start an AVAudioSession for recording) by NOT setting the AVAudioSession active property to false and leave it hanging, but I am pretty sure thats a bad way to do it. If I deactivate it the music resumes. The other option I am thinking of is playing some kind of silent loop that would "imitate" the silence I need to do. But I think if what I am seeking is doable, it would be the best approach as I understood from this question it cannot be done using the normal means
func stopAudioSession() {
let audioSession = AVAudioSession.sharedInstance(
do {
if audioSession.secondaryAudioShouldBeSilencedHint{
print("someone is playing....")
}
try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
isSessionActive = false
} catch let error as NSError {
print("Unable to deactivate audio session: \(error.localizedDescription)")
print("retying.......")
}
}
In this code snippet as the function name implies I set active to false, tried to find other options but I could not find another way of stopping my recording session and prevent resume of the other app that was already playing
If someone can guide me to which library I should look into, if for example I can tap into the H/W part and trigger it OR if I can find out which library is listening to this button press event and handling the pause/play functionality
A friend of mine who is more experienced in IOS development suggested the following workaround and it worked - I am posting it here as it might help someone trying to achieve a similar behaviour.
In order to stop/pause what is currently being played on a user device, you will need to add a music player into your app. then at the point where you need to pause/stop the current media, you just initiate the player, play and then pause/stop it - simple :)
like so:
let musicPlayer = MPMusicPlayerApplicationController.applicationQueuePlayer
func stopMedia(){
MPMediaLibrary.requestAuthorization({(newPermissionStatus: MPMediaLibraryAuthorizationStatus) in
self.musicPlayer.setQueue(with: .songs())
self.musicPlayer.play()
print("Stopping music player")
self.musicPlayer.pause()
print("Stopped music player")
})
}
the part with MPMediaLibrary.requestAuthorization is needed to avoid an authorisation error when accessing user's media library.
and of course you will need to add the Privacy - Media Library Usage Description
key into your Info.plist file
I have an app, where audio recording is the main and the most important part. However user can switch to table view controller where all records are displayed and no recording is performed.
The question is what approach is better: "start & stop audio system or just start it". It may seem obvious that the first one is more correct, like "allocate when you need it, deallocate when used it". I will show my thoughts on this question and I hope to find approval or disapproval with arguments among skilled people.
When I constructed AudioController.m the first time I implemented methods to open/close audio session and to start/stop audio unit. I wanted to stop audio system when recording is not active. I used the following code:
- (BOOL)startAudioSystem {
// open audio session
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
NSError *err = nil;
if (![audioSession setActive:YES error:&err] ) {
NSLog(#"Couldn't activate audio session: %#", err);
}
// start audio unit
OSStatus status;
status = AudioOutputUnitStart([self audioUnit]);
BOOL noErrors = err == nil && status == noErr;
return noErrors;
}
and
- (BOOL)stopAudioSystem {
// stop audio unit
BOOL result;
result = AudioOutputUnitStop([self audioUnit]) == noErr;
HANDLE_RESULT(result);
// close audio session
NSError *err;
HANDLE_RESULT([[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&err]);
HANDLE_ERROR(err);
BOOL noErrors = err == nil && result;
return noErrors;
}
I found this approach problematic because of the following reasons:
Audio system starts with delay. That means, recording_callback() not called for some time. I suspect it is AudioOutputUnitStart, which is responsible for that. I tried to comment out the line with this function call and move it to initialization. the delay was gone.
If user performs switching between recording view and table view very very fast (audio system's starts and stops are very fast too), it cause the death of media service (I know that observing AVAudioSessionMediaServicesWereResetNotification could help here, but it is not the point).
To resolve these issues I modified AudioController.m with other approach which I managed to discover: start audio system when application becomes active and do not stop it before the app is terminated In this case there are also several issues:
CPU usage
If audio category is set to recording only, then no other audio could be played when user explores table view controller.
The first one surprisingly is not a big deal, if cancel any kind of processing in recording_callback() like this:
static OSStatus recordingCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
AudioController *input = (__bridge AudioController*)inRefCon;
if(!input->shouldPerformProcessing)
return noErr;
// processing
// ...
//
return noErr;
}
By doing this CPU usage equals to 0% on real device, when no recording is needed and no other actions are performed.
And the second issue can be solved by switching audio category to RecordAndPlay and enable mixing or just ignore the problem. For example in my case app requires mini Jack to be used by external device, so no headphones can be used in parallel.
Despite all this, the first approach is more close to me since I like to close/clean every stream/resource when it is no longer needed. And I want to be sure that there is indeed no other option than just start audio system. Please make me sure that I'm not the only one who came to this solution and it is the correct one.
The key to solving this problem is to note that the audio system actually runs in another (real-time) thread. And you can't really stop and deallocate something running in another thread exactly when you (or the app's main UI thread) "don't need it", but have to delay in order to allow the other thread to realize it needs to do something and then finish and clean up itself. This can potentially take up to many 100's of milliseconds for audio.
Given that, strategy 2 (just start) is safer and more realistic.
Or perhaps set a delay of many many seconds of non-use before attempting to stop audio, and possibly another short delay after that before attempting any restart.
Is it possible to change the session category and options when a specific behavior is needed in app? For example setting:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&sessionError];
according to apple docs will not honor the silent switch.
The app records and plays audio. On audio playback, the app needs to honor the silent switch. Question is: Can I set the category like so:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&sessionError];
when playback begins so that the app honors the silent switch. And if I do so, how will this affect routing? Apple Docs state:
The session's category and mode together define how the application
intends to use audio. Typically, you should set the category and mode
before activating the session. You may also set the category or mode
while the session is active, but this will result in an immediate
route change.
It is possible to change, quoting the docs:
Each app running in iOS has a single audio session, which in turn has
a single category. You can change your audio session’s category while
your app is running.
https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVAudioSession_ClassReference/#//apple_ref/doc/constant_group/Audio_Session_Categories
So it is just a matter of calling the setCategory: method when you want the app to change mode.
For example, you start your app while allowing sound to play from other apps:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil]
(...)
}
And when a user presses a play button on your UI, change to Playback mode:
- (void)playAudio {
if ([AVAudioSession sharedInstance].otherAudioPlaying) {
// you can check and play only if there is no other audio playing
// maybe use another category, or enable mixing or duck option
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionDuckOthers error:nil];
} else {
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
}
[[AVAudioSession sharedInstance] setActive:YES error:nil];
}
If you want to resume the other app audio after your playback you can do a notification, or just close the session to have the other app audio to continue stopped:
- (void)stopAudio {
if (self.otherAudioShouldResume) {
[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];
} else {
[[AVAudioSession sharedInstance] setActive:NO error:nil];
}
This code is an overview only, you might need to perform more functions to achieve a working example, also remember to check the return of these functions (BOOL) and log the errors for debugging.
Based on the Apple document, about AVAudioSession -setMode:error:.
Discussion
The session's category and mode together define how the
application intends to use audio. Typically, you set the category and
mode before activating the session. You can also set the category or
mode while the session is active, but this results in an immediate
route change.
It sounds like the setCategory:error: and '-setMode:error:' are called before setActive:error: generally. But if those methods are called when the session is active, the audio route will change immediately.
In my case,
- (void)playAudio
{
/// call `-setMode:error:` for playing audio
}
- (void)stopAudio
{
/// call `-setMode:error:` back to original configuration, or set to another mode. It will result in route change immediately.
}
When recording audio with an AVAudioSession audio playback from the music app or others is stopped. This is described in the audio session documentation:
AVAudioSessionCategoryRecord
For recording audio; this category silences playback audio.
Is there any way to change this behavior and do one of the following?
1) Continue playing other apps' audio and allow the phone to record the audio that is being played.
2) Resume other apps' audio once a brief recording is finished. There are notifications apps can respond to when audio interruptions begin or end. Is there something specific that must be done for other apps to receive these notifications?
from 24 hours of diligent searching and piecemealing things together, this is surprisingly simple:
let session = AVAudioSession.sharedInstance()
session.setCategory(AVAudioSessionCategoryPlayAndRecord, with: [.mixWithOthers, .allowBluetoothA2DP])
And that's it for anyone still searching how to do this.
You can use
Obj-C
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord
withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
or
Swift
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord,
with: AVAudioSessionCategoryOptions.mixWithOthers)
} catch let error as NSError {
print("audioSession error: \(error.localizedDescription)")
}
1) Not possible for AVAudioSession. All you can do is check [[AVAudioSession sharedInstance] isOtherAudioPlaying] and ask user would he like to stop music streaming or not.
2) App can't receive any notifications when iPod state changed, we can't even observe isOtherAudioPlaying property... Solution I use on my projects: add a NSTimer and once in sec check [[AVAudioSession sharedInstance] isOtherAudioPlaying] BOOL value.
I am using the MPMoviePlayerController to play an audio stream. To verify that there isn't a problem with playback, I set a movie playback error timer and I implement moviePreloadDidFinish. When moviePreloadDidFinish is called, I check the loadState for MPMovieLoadStatePlaythroughOK. If it is not called and my timer expires, I assume the download has failed.
- (void) moviePreloadDidFinish:(NSNotification*)notification
{
if (self.moviePlayer.loadState & MPMovieLoadStatePlaythroughOK) {
NSLog(#"The movie or mp3 finished loading and will now start playing");
// cancel movie playback error timer.
}
}
Occasionally, I do not receive this notification, yet audio keeps playing until my movie playback error timer expires (30 seconds). Does the absence of this moviePreloadDidFinish imply that the download of the audio stream is going to fail soon? If not, is there a better way to programmatically determine that there is a playback problem?