Override ringer volume in iPhone apps - iphone

I have built an app that plays lots of sounds the easy way:
AudioServicesPlaySystemSound(someSoundID);
When I use the device volume buttons to increase or decrease the volume, the volume I actually change is the phone's ringer volume. So if you decrease it and exit the app then your phone will ring quietly...
Is there an easy way to override this and actually change my application's volume?

I have found the solution to what I thought would be a common problem. So here is how your app can have its own volume, and not mess with the user's ringer volume, even if you are only playing sounds as System Sounds.
You have to import the AVFoundation framework and in an object that stays loaded the whole time your app runs (or view, or the app delegate) you have initialize a AVAudioPlayer, give it a file to play and set it to "prepareToPlay" it...
This is what i did in my main View (that is used to load other views as subviews):
in the header file:
#import <AVFoundation/AVFoundation.h>
#interface MainViewController : UIViewController {
AVAudioPlayer *volumeOverridePlayer;
}
#property (nonatomic, retain) AVAudioPlayer *volumeOverridePlayer;
In the implementation file:
#synthesize volumeOverridePlayer;
- (void)viewDidLoad
{
[super viewDidLoad];
volumeOverridePlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:[[NSBundle mainBundle] pathForResource:#"something" ofType:#"caf"]] error:nil];
[volumeOverridePlayer prepareToPlay];
//...
}
Just leave the player prepared to play your file and enjoy your own volume control without having to play the sounds through it!
And of course, remember to release it in dealloc.

From what I can see in Apple's documentation, there are two issues here. 1. Application and the OS/hardware volume can only be controlled by the hardware/user. 2. You are playing a system sound. I believe the system sound is governed by the OS, so you're out of luck here, unless you are playing actual sound files. If you can use sound files, then take a look here.
Good luck!

Related

Proper way to quickly switch between videos

I am creating an application which displays a certain video based on an external event, which may require the playing video to change quickly - once per second or more. However, there must not be a gap or lag between the videos.
What is the best way to do this? There are only four videos, each about two megabytes.
I was considering creating four MPMoviePlayerControllers, and have their views added to the main view but hidden, and switching by pausing and hiding the current video, then unhiding and playing the next video. Is there a more elegant solution?
Edit
Here's some more information for my exact sitaution:
The different video frames share mostly common pixels- so it's OK for a frame to stick during switch, but NOT okay for black frames to appear.
Each video is only about ten seconds long, and there are only four videos. The general state transitions are 1<->2<->3<->4->1.
The video playback should compatible with simultaneous AVAudioRecorder recording. As far as I can tell, MPMoviePlayerController is not.
You'll need to set up and prebuffer all the video streams to avoid hiccups, so I don't think your multiple MPMoviePlayerController solution is too far off the mark.
However, that specific solution is potentially problematic because each movie player has its own UI. The UIs do not synchronize with each other, so one might be showing the control bar, another not; one might be in full screen mode, etc. Switching among them will cause visual discontinuities.
Since it sounds like your video switching is not necessarily user-initiated, I'm guessing you don't care too much about the standard video player UI.
I would suggest dropping down to the underlying layer, AV Foundation. In theory, you can just create an AVPlayerItem for each video. This is a stream-management object with no UI overhead, so it's perfect for what you're doing. You could then -- again, in theory -- create one AVPlayer and one AVPlayerLayer to handle the display. When you wanted to switch from one AVPlayerItem stream to another, you could call the AVPlayer's replaceCurrentItemWithPlayerItem: message to swap out the data stream.
I made a little test project (GitHub) to verify this, and unfortunately the straightforward solution isn't quite perfect. There is no video flow glitching, but in the transition from AVPlayer to AVPlayer, the presentation layer seems to briefly flash the previous movie's last-seen frame at the size appropriate to the next movie. It seems to help to allocate separate AVPlayer objects for each movie and switch among them to a constant player layer. There still seems to be an instantaneous flash of background, but at least it's a subtle defect. Here's the gist of the code:
#interface ViewController : UIViewController
{
NSMutableArray *players;
AVPlayerLayer *playerLayer;
}
#property (nonatomic) IBOutlet UIView *videoView;
- (IBAction) selectVideo:(id)sender;
#end
#implementation ViewController
#synthesize videoView;
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *videoTitles = [NSArray arrayWithObjects:#"Ultimate Dog Tease",
#"Backin Up", #"Herman Cain", nil];
players = [NSMutableArray array];
for (NSString *title in videoTitles) {
AVPlayerItem *player = [AVPlayer playerWithURL:[[NSBundle mainBundle]
URLForResource:title withExtension:#"mp4"]];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
[players addObject:player];
}
playerLayer = [AVPlayerLayer playerLayerWithPlayer:[players objectAtIndex:0]];
playerLayer.frame = self.videoView.layer.bounds;
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
[self.videoView.layer addSublayer:playerLayer];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
[object removeObserver:self forKeyPath:#"status"];
for (AVPlayer *player in players) {
if (player.status != AVPlayerStatusReadyToPlay) {
return;
}
}
// All videos are ready to go
[self playItemAtIndex:0];
}
- (void) playItemAtIndex:(NSUInteger)idx
{
AVPlayer *newPlayer = [players objectAtIndex:idx];
if (newPlayer != playerLayer.player) {
[playerLayer.player pause];
playerLayer.player = newPlayer;
}
[newPlayer play];
}
- (IBAction) selectVideo:(id)sender
{
[self playItemAtIndex:((UILabel *)sender).tag];
}
#end
Half the code is there just to observe the state of the players and make sure that playback doesn't start until all videos have been buffered.
Allocating three separate AVPlayerLayers (in addition to three AVPlayers) prevents any sort of flash. Unfortunately, an AVPlayer connected to an undisplayed AVPlayerLayer seems to assume that it doesn't need to maintain a video buffer. Every switch among layers then produces a transient video stutter. So that's no good.
A couple of things to note when using AV Foundation are:
1) The AVPlayer object doesn't have built-in support for looped playback. You'll have to observe for the end of the current video and manually seek to time zero.
2) There's no UI at all other than the video frame, but again, I'm guessing that this might actually be an advantage for you.
The MPMoviePlayerController is a singleton. Four instances will share the same pointer, the same view, etc. With the native player, I think you have only two alternatives: one is to change the contentURL property when you want a transition. If the latency this way unacceptable, the other alternative is to produce a longer video with the shorter clips concatenated. You can create very quick jumps within the single, longer clip by setting currentPlaybackTime.

AVPlayerItem fails with AVStatusFailed and error code "Cannot Decode"

I'm running into a strange issue, I hope someone can help.
In my iOS app I create a video with a custom soundtrack using MutableComposition by combining a video from the user's photo library and an audio file from the app bundle. I then use an AVPlayer and AVPlayerItem to play the video back to the user using a custom video player I made.
Each time a new composition is created, the assets, the player and the composition are cleared, released and it basically starts from a clean, init state.
All works fine, until after exactly 4 successful videos created this way every other attempt to create the player fails with error Cannot Decode. It does not matter if its the same video I'm recreating, has no relation to the size/length of the video or the audio file it simply always fails exactly on the fifth attempt, like clockwork. Once it fails, it will then always fail!
This is weird, because it just decoded the same video four times with no problem, so all of a sudden it fails? So, if anyone has a clue, please let me know.
Ok everyone, I have the answer to this straight from Apple. I used one of my developer TSI lifelines to ask the question, and I'll summarize the response.
There is a limit on the number of concurrent video players that AVFoundation will allow. It is due to the limitations of iOS hardware. The limit for current devices is 4 players. If you create a 5th player, you will get the "cannot decode" error. It is not a limit on the number of instances of AVPlayer, or AVPlayerItem. Rather,it is the association of AVPlayerItem with an AVPlayer which creates a "render pipeline", and you are limited to 4 of these. For example, this causes a new render pipeline:
AVPlayer *player = [AVPlayer playerWithPlayerItem:somePlayerItem];
// assuming the AVPlayerItem is ready to go with an AVAsset that has been loaded
I was also warned that you cannot assume that you will have 4 pipelines available to you. Another App may be using one or more. Indeed, I have seen this happen on an iPad, but it was not clear which app was using a pipeline.
So, there you go, it was totally undocumented, but that is the story.
I ran into the same error message after creating 4 AVPlayer instances, the fix in my case wasn't exactly the same though. Perhaps this will help anyone else who comes across this problem.
What I eventually found is that the AVPlayers were not being released when I had thought they were. In my case I was pushing my AVPlayer View Controller onto a Navigation Controller. Even though I was only creating one AVPlayer instance at a time, when the View Controllers are popped off a nav controller they were not being released immediately. It was then very easy for me to reach 4 AVPlayer instances before the old View Controllers were cleaned up.
It wasn't until I made sure that the previous players were released that this problem went away. To be complete I released the AVPlayerItem, AVPlayer and set the player on the AVPlayerLayer to nil before releasing.
I have to wonder if there is some limit on AVPlayer instances, unintentional or not. A related bit of info from the docs:
https://developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/02_Playback.html
"Multiple player layers: You can create arbitrarily many AVPlayerLayer objects from a single AVPlayer instance, but only the most-recently-created such layer will display any video content on-screen."
This one was absolutely killing me until I figured it out, picking up clues from this thread and a few others. The biggest single problem in my code was that I was instantiating my video player controller every time I wanted to play a video. Now, it gets instantiated once in the primary controller (in this case, my DetailViewContoller):
#interface DetailViewController () {
VideoPlayerViewController *videoPlayerViewController;
}
- (void) viewDidLoad
{
[super viewDidLoad];
videoPlayerViewController = [[VideoPlayerViewController alloc] initWithNibName: nil bundle: nil];
}
When I want to show a video, I call my DetailViewController's startVideoPlayback method:
- (void) startVideoPlayback: (NSString *)videoUID
{
videoPlayerViewController.videoUID = videoUID;
[self presentModalViewController: videoPlayerViewController animated: YES];
}
(NOTE: I'm passing it 'videoUID' -- a unique identified that was used to create the video in another part of the app.)
In the VideoPlayerViewController (which is largely cribbed from Apple's AVPlayerDemo sample), the one-time screen setup (initializing the AVPlayer, setting up the toolbar, etc.) is done in viewDidLoad -- which now only get's called once, and all per-video setup gets done within viewWillAppear, which then calls prepareToPlay:
- (void) prepareToPlay
{
[self initScrubberTimer];
[self syncPlayPauseButtons];
[self syncScrubber];
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//*** Retrieve and play video at associated with this videoUID
NSString *destinationPath = [documentsDirectory stringByAppendingFormat: #"/%#.mov", videoUID];
if ([self fileExists: destinationPath]) {
//*** Show the activity indicator spinny thing
[pleaseWait startAnimating];
[self setURL: [NSURL fileURLWithPath: destinationPath]];
//*** Get things going with the first video in this session
if (isFirst) {
isFirst = NO;
//*** Subseqeunt videos replace the first one
} else {
[self.mPlayer replaceCurrentItemWithPlayerItem: [AVPlayerItem playerItemWithURL: [NSURL fileURLWithPath: destinationPath]]];
}
}
}
OK, I figured out a solution, I hope this is helpful to anyone who may stumble on something similar to this problem.
The solution in my case was to initialize the asset for the AVPlayer and the AVPlayerItem on the main thread and make sure I don't create the actual AVPlayerLayer before the playerItem and the player objects return with status "ReadyToPlay".
This proved to be tricky to isolate and I still don't know why it worked the first 4 times and then failed consistently on the 5th time.
Till, I couldn't really include the code, it wasn't a matter of one line or even a few functions. It was a complex problem that I couldn't isolate to begin with. Thanks for the comments though.
It seems like that issue can be caused by any decoding tasks, not only actual players.
I randomly had this problem when I implemented a background task to extract frames from currently playing videos with generateCGImagesAsynchronously
I need to display 4 videos on screen and a race condition would sometime cause the frame extraction to start before the video started playing and I would wait for isReadyForDisplay forever.
Not sure what a good recover strategy is if you can't avoid the condition in the first place, I would probably try to replaceCurrentItem

MPMoviePlayerController stopped working in 3.1.2

I am using MPMoviePlayerController to play a live streaming m3u8 video for older devices (3.1.2). This worked fine until this morning. I tried changing the scalingMode to resolve another issue, and now the player does not work at all. I went back to older backups that worked, and they don't work, either.
While debugging, control goes into [mMPPlayer play] and never returns. This also locks up my app.
Has something changed with MPMoviePlayerController, or did I break something in XCode?
My app was scheduled to start moving to production today, so I'm really in a bind, here. :(
Here's the warning that I get:
Warning: MPMoviePlayerController may not support file of type m3u8
And here's my code:
MyViewController.h:
#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
#interface WatchNowViewController : UIViewController {
MPMoviePlayerController *mMPPlayer;
}
#property (nonatomic, retain) MPMoviePlayerController *mMPPlayer;
#end
MyViewController.m:
mMPPlayer = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:#"http://www.mysite.com/myVideo.m3u8"]];
mMPPlayer.scalingMode=MPMovieScalingModeFill;
mMPPlayer.backgroundColor=[UIColor blackColor];
[mMPPlayer play];
NSLog("Control never returns to here");
Happened to us as well. Not sure what went wrong. The encrypted streams just stopped playing in 3.2. Try the m3u8 url in the iPad safari and check if it plays there. If it doesn't play in iPad safari as well, try an unencrypted stream. As per my experience, an unencrypted stream played in 3.2 without issues.

What is the best way to play sound quickly upon fast button presses Xcode?

I have a soundboard
it's just a screen with about 8 buttons.
each individual button will have its own sound which will be played upon button press
There are a couple of ways I could play the sound, such as using SystemSound or AVAudioPlayer
system sound so far seems have the quickest response times, avaudioplayer is quite slow, it cant keep up if the user taps on the buttons really fast, I created an audio player for each sound which was quite messy.
this is how I'm playing the sounds at the moment
the .h file
#interface MainScreenViewController : UIViewController <AVAudioPlayerDelegate, UITabBarControllerDelegate> {
AVAudioPlayer *player;
CFURLRef keNURL;
SystemSoundID keNObject;
//KE LOUD
CFURLRef keLURL;
SystemSoundID keLObject;
//GE NORMAL
CFURLRef geNURL;
SystemSoundID geNObject;
//GE LOUD
CFURLRef geLURL;
SystemSoundID geLObject;
//NA NORMAL
CFURLRef naNURL;
SystemSoundID naNObject;
//NA LOUD
CFURLRef naLURL;
SystemSoundID naLObject;
//RA
CFURLRef raURL;
SystemSoundID raObject;
//DAGGA CLICK
CFURLRef daCURL;
SystemSoundID daCObject;
//TILLI CLICK
CFURLRef tiCURL;
SystemSoundID tiCObject;
}
#property (nonatomic, retain) AVAudioPlayer *player;
#property (readwrite) CFURLRef keNURL;
#property (readonly) SystemSoundID keNObject;
#property (readwrite) CFURLRef keLURL;
#property (readonly) SystemSoundID keLObject;
#property (readwrite) CFURLRef geNURL;
#property (readonly) SystemSoundID geNObject;
#property (readwrite) CFURLRef geLURL;
#property (readonly) SystemSoundID geLObject;
#property (readwrite) CFURLRef naNURL;
#property (readonly) SystemSoundID naNObject;
#property (readwrite) CFURLRef naLURL;
#property (readonly) SystemSoundID naLObject;
#property (readwrite) CFURLRef raURL;
#property (readonly) SystemSoundID raObject;
#property (readwrite) CFURLRef daCURL;
#property (readonly) SystemSoundID daCObject;
#property (readwrite) CFURLRef tiCURL;
#property (readonly) SystemSoundID tiCObject;
}
then the actions that play the individual sounds
then the .m file, after importing the .h file and the right frame works and synthesizing all the variables, write the code for the action
and this is what's in the individual actions.
-(IBAction)geSound{
AudioServicesPlaySystemSound (self.geNObject);
}
I just wanted to know if systemsound is the way forward for me to create a soundboard.
thanks especially when the user will tap on the board really fast alternating between beats.
.
if not what is the best way to play the sound which responds really well?
Speaking from experience, AVAudioPlayer works quite well at playing multiple sounds at the same time or very quickly one after the other. The best way to use it is to just create one method that you feed in an NSString to play some sound with the name held in that NSString... in that way, you will create a new player for each sound file that you play. Be careful about releasing the allocated players though unless you know you are finished with them.
Unless you have very large sound files which might take a short second to buffer (you'll have to figure out for yourself if you can live with any latency or not), I've never had any issues with it being slow. If it's slow, you're probably doing something against Apple's recommendations in regards to decoding certain files (i.e. for multiple sounds at once, Apple recommends the CAF format since it is hardware decoded versus software decoded): http://developer.apple.com/iphone/library/documentation/AVFoundation/Reference/AVAudioPlayerClassReference/Reference/Reference.html
Update
Here is how I encode my files with a shell script. Put this in a file called batchit.sh in it's own directory. Then place whatever .WAV files you want to encode as a .CAF file in that directory. Open up the Terminal on your Mac, cd to that directory and type sh batchit.sh and let it do it's thing. It will batch convert all the files... Here's the code:
for f in *; do
if [ "$f" != "batchit.sh" ]
then
/usr/bin/afconvert -f caff -d ima4 $f
echo "$f converted"
fi
done
Notice I didn't do anything in the audioPlayerDidFinishPlaying method which is a delegate method of AVAudioPlayer. You will need to add code to properly release the players after each is finished playing (hey, I can't do all the work for you :) so that all the memory is allocated correctly. Otherwise you will eventually run out if you indefinitely keep creating players without releasing them. Since it seems like you're having a rough day.. see below for a big hint.
That's all the help I can give though. You gotta learn & earn the rest
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
// do something here if you want.. you know like maybe release the old players by uncommenting the line below :)
//[player release];
}
22/03/2014 - Update: Got rid of my overly excited post (which was written a long time ago) and updated to portray a more appropriate answer.
I had to achieve fine control over audio so opted to use OpenAL.
WOW is one way to sum it up. As soon as I took it upon myself to use OpenAL which required a bit of extra leg work, getting all the required methods implemented and setting everything up; I found myself with practically no noticeable latency at all. I was absolutely pleased with the excellent results.
I was able to achieve both fine control and no latency at all. I remember jumping in joy at the time when I had it working.
Here are the resources I used that helped ease the endeavor of implementing OpenAL for the first time:
OpenAL on the iPhone
It was because of this link this video tutorial that I was able to create an excellent sound manager singleton. This allowed me to play all the sounds that I needed from any class that I wanted with little sound management, the singleton took care of everything for me.
I humbly suggest everyone to use OpenAL if you require fine control over your audio, and more importantly if you require a low-latency on-demand audio, specially for games when you need to make sure sound is played there and then when you require and expect it to, an audio based application for example.
Just going to add for the benefit of others, even though this is 2 years after the issue.
I've found that if they are short sounds, you would be ok (ish) on your application load. buffering all your tracks by running an instance of prepare to play for every sound (in a for loop) (then releasing - just enough for your processor to remember where the file is)
This takes away the initial loading time next time you come round to it and lets the sounds start more promptly
you could create AVAudioPlayer for each sound and then just pause/play it.
Have you tried playing back each sound in its own thread? You could try a basic NSOperation

Audio volume stuck at low level and adjusting volume has no effect?

When I use my app (on the device), the actual volume works OK for a while, but after a few days it seems to get 'stuck' at a low level.
Adjusting the volume rocker has no effect - and it shows 'Ringer' text. I've noticed that other people's apps are similarly affected if they show the 'Ringer' text when adjusting the volume. But apps which don't show 'Ringer' text are not affected by this.
How would I remove the 'Ringer' text and get my app to respond properly to different volumes?
I found some code on Apple's forums which fixes it. You have to use the AVFoundation.framework and put a bit of code into your app delegate. And put an audio file called 'blank.aif' into your project. Basically, it's a hack which prepares a file to play, but then never plays it. This fools the system into thinking that sound is being played all the time, which allows the user to use the main volume control.
#import <AVFoundation/AVFoundation.h>
//...
-(void)applicationDidFinishLaunching:(UIApplication *)application {
//volume control hack
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"blanksound" ofType:#"aif"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:soundFilePath];
AVAudioPlayer *volumeHack = [[[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil]retain];
[fileURL release];
[volumeHack prepareToPlay];
// other applicationDidFinishLaunching stuff
}