I'm looking for a way to utilize the didReceiveMemoryWarning to fade out the music playing in my app before killing the audio player. I want to fade the music and remove the player from memory without affecting the system's volume (or at least by resetting the systems volume when I'm done.) I only need to support iOS 5+.
The reason I ask the question here is:
I do not have access to the code actually playing the music, it's in a 3rd party framework (so I think my only solution is to fall back on adjusting the system volume.)
All the solutions I've found so far are quite old, hackish, and/or come with caveats about not being accepted by the App store, etc. etc.
Is there an acceptable way to fade the iPhone's audios?
If you do end up finding a way to at least access the player, here's a couple of ways this can be done.
Running in a while-loop
-(void)fadeOut
{
while (data.volume > 0) {
data.volume = data.volume - 0.1;
}
[data stop];
data = nil;
}
Or, using a timer:
- (void)fadeOutWithTimer
{
if (data.volume > 0.1) {
data.volume = data.volume - 0.1;
[self performSelector:#selector(fadeOutWithTimer) withObject:nil afterDelay:0.05];
} else {
[data stop];
data = nil;
}
}
Within the realm of allowed APIs, only the user can control the system volume. There is no way for your app to adjust it. As you mention, accessing these other APIs will not be allowed in the app store, and may require a jailbroken device.
Which 3rd party framework are you using? Can you check to see if it is using a standard audio player inside of it, such as AVAudioPlayer? If so, you may be able to modify it that way.
Update: as Randy points out below, you can actually do this by controlling the iPod's volume:
[[MPMusicPlayerController iPodMusicPlayer] setVolume:0.05];
Related
I am developing an Xcode/Swift/SwiftUI app for real-time music visualization. I allow the user to push a button to toggle between microphone-input and file-play input (but never both at the same time). My app runs fine on my Mac and on my iPad, but on my iPhone, the speaker audio is only at half-volume (and appears to be only coming from the back speakers) - even when I am in file-play mode. I have traced the problem to one offending line in my code - namely the declaration
let mic = engine.inputNode // where engine = AVAudioEngine()
When I comment-out this line, the iPhone speaker level (for file-play mode) is fine. But when I un-comment it, the iPhone speaker level is barely audible. Even when I wrap this line inside a conditional if(micEnabled){} construct, the sound level is fine at first; but as soon as I select the microphone and then toggle back to file-play, the volume again decreases.
I suspect that iOS detects when a microphone is declared and automatically reduces the speaker volume to avoid audio feedback. This would make sense because nobody wants music playing when they are speaking on a telephone call. But it would also make sense to provide developers a way to override this feature if they want to handle it themselves. In my case, for the microphone-input case, I purposely assign the audio stream a zero-volume after it is tapped and before going to the speaker.
My source code is available here. All of the audio code is inside the MuVis / Shared / AudioManager.swift class.
Can anyone help me to get the file-play mode to work with full volume on my iPhone - while also allowing the user the option to select microphone-input mode?
Many thanks to Rob Napier for pointing me in the right direction for solving my problem.
As a macOS-only developer, I had ignored AVAudioSession (since it caused compiler errors on macOS). When I converted my MuVis app from macOS-only to multiplatform, I simply started a new Xcode project with the appropriate multiplatform settings, and then pasted my existing code into the shared folder. After cleaning up a few errors (mostly calls to NSObject), it magically worked on all Apple platforms - except for the iPhone audio problem described in my question. After a little research and a lot of trial-and-error, I found that my audio-volume problem is solved by inserting the following code into my setupAudio() function:
#if os(iOS)
// For iOS devices, set the audioSession category, mode, and options:
let session = AVAudioSession.sharedInstance() // Get the singleton instance of an AVAudioSession.
do {
if(filePlayEnabled) {
// This is required by iOS to prevent output audio from going only to the iPhone's rear speaker.
try session.setCategory(AVAudioSession.Category.playAndRecord, mode: AVAudioSession.Mode.default, options: [.defaultToSpeaker])
}
else {
try session.setCategory(AVAudioSession.Category.playAndRecord, mode: AVAudioSession.Mode.default, options: [])
}
} catch { print("Failed to set audioSession category.") }
#endif
Again, thank you Rob.
Every time security of Apps comes up, it turns out a lot of people are unaware of this being an issue. For instance, iOS takes screen-shot of visible screen every time our App gets backgrounded and it is stored in local storage.
Now that's the thing I want to get rid of. I am developing an App that does online financial transactions and I want my App be very powerful in terms of security aspect. Here is the path where the screenshot is being stored when my App gets backgrounded.
Path: /private/var/mobile/Applications/15980ADD-B269-4EBE-9F52- B6275AFB195A/Library/Caches/Snapshots/com.ABC.myAppName/screenshotName.PNG
This is the image which is being stored that looks very critical:
Even more critical scenario will be if user has entered his/her Credit/Debit card number including CVV2 number and other essential information and might have forced App in background for a while.
I have been doing a little search on that and I got to know that, for an attacker to be able to leverage this attack, there are two ways for him to gain access to that:
The attacker needs physical access to the device with the intent of
jail breaking.
Needs to be on the same network as user who has jail broken the
device and attempt to access the device remotely.
What could have I done to avoid this being possible? Is there any solution that can avoid an attacker getting access to the sensitive information in this way?
Also I have gotten advice to enable a blank screenshot or delete the screenshot for the application, when the application is backgrounded. But, I don't have any idea what to choose and how to do it properly. Is there any other alternative?
I can suggest a couple of things:
1) you know when your app is about to be put into the background, via the application delegate method:
- (void) applicationDidEnterBackground:(UIApplication *)application
That's the exact moment the snapshot is generated. Why not change your view to be something different or more "secure"?
2)
If you want the "secure" (or bogus) snapshot to be ignored when you bring the app back to foreground, you can use "[UIApplication ignoreSnapshotOnNextApplicationLaunch]".
3)
You can also add "UIApplicationExitsOnSuspend" into your app's Info.plist when putting your app into the background, which will kill your app entirely and not save a snapshot.
Apple told us to hide secure info before going to background, so just give it a image to hide everything:
-(void)applicationWillResignActive:(UIApplication *)application
{
if(needToHide){
_imageView = [[UIImageView alloc]initWithFrame:[self.window frame]];
[_imageView setImage:[UIImage imageNamed:#"HideME.png"]];
[self.window addSubview:_imageView];
}
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
if(_imageView != nil) {
[_imageView removeFromSuperview];
_imageView = nil;
}
}
I am using the MPMoviePlayerController(MPMPC) to stream audio into an application and thats working really fine. Just with one exception, during low network connectivity the app becomes unresponsive.
Now I have even tried to use AVPlayer too but with more or less same experience. And for some reason I cannot find any issues related to this on the internet. So I am not sure if this is from my end or is it how MPMoviePlayerController behaves during low connectivity.
I even tried to log any function that is being called after giving URL to the MPMPC but none of the functions are called.
I have used below three notification to get events of the MPMPC
MPMoviePlayerLoadStateDidChangeNotification
MPMoviePlayerPlaybackDidFinishNotification
MPMoviePlayerPlaybackStateDidChangeNotification
Once the available networking bandwidth is becoming too low to keep up proper playback, MPMoviePlayerController will trigger the MPMoviePlayerLoadStateDidChangeNotification and the loadState will have MPMovieLoadStateStalled set.
You may then mask the load-state within your notification handler and run any actions needed by your app for this state:
if ((movieController_.loadState & MPMovieLoadStateStalled) == MPMovieLoadStateStalled)
{
NSLog(#"playback stalled - make sure we don't block now!");
}
Once the player has recovered, once again the MPMoviePlayerLoadStateDidChangeNotification is triggered and the loadState property will have the bits for MPMovieLoadStatePlaythroughOK set:
if ((movieController_.loadState & MPMovieLoadStatePlaythroughOK) == MPMovieLoadStatePlaythroughOK)
{
NSLog(#"playback should run uninterrupted from now on.");
}
However, I never experienced any interface slowdowns of my app caused by the MPMovieLoadStateStalled state. I'ld say that must be your code acting weird, it is not MPMoviePlayerController as I know it. Additionally, those notifications are always sent, I never experienced scenarios in which they were not properly triggered.
I can only recommend to recreate this issue within a minimal test-case and work your way up from that one towards your app (possibly from both sides, test-case and your app).
For simulating bandwidth breakdowns, I would recommend using Charles proxy.
I am using the accelerometer in IOS and I want it to run in background mode.
I tried to use CMMotionManager like this but it didn't work:
CMMotionManager*manager= [[CMMotionManager alloc] init];
if(!manager.accelerometerAvailable) {
NSLog(#"Accelerometer not available");
} else {
manager.accelerometerUpdateInterval = 0.1;
NSOperationQueue *motionQueue = [[NSOperationQueue alloc] init];
[manager startAccelerometerUpdatesToQueue: motionQueue withHandler:
^(CMAccelerometerData *data, NSError *error) {
NSLog(#"Accelerometer data: %#", [data description]);
}
];
}
How can I do this?
Not sure this solves your problem, but a glance at the docs (http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html) says:
"Implementing Long-Running Background Tasks
For tasks that require more execution time to implement, you must request specific permissions to run them in the background without their being suspended. In iOS, only specific app types are allowed to run in the background:
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
Apps that implement these services must declare the services they support and use system frameworks to implement the relevant aspects of those services. Declaring the services lets the system know which services you use, but in some cases it is the system frameworks that actually prevent your application from being suspended."
Not sure if you're trying to use the accelerometer to do any of these things (communicate with a blue tooth device perhaps?), but if so, you'll need to declare the services you support in the app. To do that, you need to add the UIBackgroundModes key to your Info.plist and then add an array containing the relevant string(s) for the services you're trying to use. See the docs page linked above for the full list of the strings.
If you're not trying to use the accelerometer to do any of those things, it looks as if you may be out of luck. Though I'd love to be wrong there.. (anybody?)
I've asked this question before but I feel I should start a new thread since my other thread is dated and probably poorly worded. I'm wondering what the best approach would be for adding volume control to an iOS app the is mostly silent. A good example would be a navigation app that only plays audio when you approach or miss a turn. In such an app, hearing a turn prompt which is not loud enough, the user would want the volume for the prompts to be audible and would naturally used the side volume controls to adjust prompts to their liking.
There are several problems here. One is that audio is not currently playing so the user has no reference as to how much it has been increased. This is more or less expected however there are technical issues that I am more interested in. To link the side volume control to your app you have to start and manage an audio session. I have not found an authoritative reference for such a situation as most documentation assumes you are currently playing or in the process of starting audio. Managing an audio session for a mostly silent app seems to be an edge case, though I find it rather common in that two of the major apps I've worked on require such functionality.
Of the various problems associated with audio session management, you have to address killing and restoring the audio session as you move in and out of the background. You have to consider other apps playing audio as you begin and stop the session. Depending on your type of app, you may have other more advanced needs such as custom override routing to the speakers, custom mute controls, etc. If you have any experience with such an app could you elaborate on how you addressed such challenges and expound on other issues?
One very common merhod is to set the audio session category appropriate for the type of app at launch, no matter whether sound is immanent or won't be played till tomorrow (as long as the purpose and settings of the app is to play such).
Added:
One way to allow to user to adjust the volume when the app is silent is to provide some means for the user to have your app to immediately start (and/or maybe stop) playing some sound with an amplitude typical for your app: some calibration tone/talk, your copyright notice, trademark jingle, or a safety message, for instance.
The main issue I see when developing apps that are mostly silent regard moving in/out of the foreground and playing nicely with other audio. To give a better idea of what I usually do I'll give some snippets from a recent project. (These are intentionally incomplete and only meant to illustrate a point.) For the sake of argument let's assume we have an AudioManager class that is responsible for maintaining the audio sessions. This class is what we use to instantiate our custom audio player. In such a class we put:
#interface MyAudioManager ()
#property (nonatomic, retain) BOOL alwaysMaintainAudioSession;
#property (nonatomic, retain) MyCustomAudioPlayer *player;
#end
#implementation MyAudioManager
#synthesize alwaysMaintainAudioSession;
#synthesize player;
-(void) applicationWillEnterForeground
{
isInBackground = NO;
if (NO==[self anyAudioIsPlaying] && self.alwaysMaintainAudioSession) {
[self activateAudioSession];
}
}
-(void) activateAudioSession
{
AudioSessionSetActive(TRUE);
AudioSessionAddPropertyListener ( kAudioSessionProperty_AudioRouteChange, AudioPropertyListener, self);
}
-(BOOL) anyAudioIsPlaying
{
return [self otherAudioIsPlaying] || [player isPlaying];
}
-(BOOL) otherAudioIsPlaying
{
UInt32 yesNo;
UInt32 propertySize = sizeof(yesNo);
OSStatus status = AudioSessionGetProperty(kAudioSessionProperty_OtherAudioIsPlaying, &propertySize, &yesNo);
if (kAudioSessionUnsupportedPropertyError == status) {
return MPMusicPlaybackStatePlaying == [theiPodMusicPlayer playbackState];
} else {
return MPMusicPlaybackStatePlaying == [theiPodMusicPlayer playbackState] || yesNo;
}
}
The manager allows you to set a property that always keeps the volume control linked to the app sounds which means we always make sure either a session is active or some other app is playing audio. In any other case the volume control reverts to controlling the ringer. So when entering the foreground we have to check for any other audio playing and conditionally activate the audio session. We also need to close the session when moving to the background to restore the ringer volume control.
-(void) applicationDidEnterBackground
{
if (NO==[self anyAudioIsPlaying]) {
AudioSessionSetActive(NO);
}
}
In my solution I include a bunch of other code to handle things like responding intelligently when bluetooth audio devices are connected, factory methods for creating the custom player, custom audio compression and more. The main idea, however, is handling other apps playing audio while attempting to keep the volume control linked to app volume while in the foreground.