iOS add audio playing via AVPlayer to OS default player? - ios

So the idea is - when we play audio in Music app in iPhone and press home button then swipe from bottom of the screen, we can see the audio playing in default OS player with play/pause controls and audio continue play.
Now what i need to do, i play an audio by using AVAudioPlayer in my app and if user press home button, the audio need to play continue as in case of music app.
But i have no idea how to add audio in OS default player? Any help or suggestion would be highly appreciated.
Edit: I need to play audio using streaming so i guess i need to use AVPlayer instead of AVAudioPlayer

You could use an MPMovieplayerController since you'll be more than likely be using an M3U8 playlist.
Here's the documentation http://developer.apple.com/library/ios/documentation/MediaPlayer/Reference/mpmovieplayercontroller_class/index.html
But it basically goes like this:
Initialize the MPMoviePlayerController, assign a file type (stream), put the source url, and present it in the view stack.
For the background audio, you'd have to use AudioSession like in this answer iOS MPMoviePlayerController playing audio in background

You can do this as a background audio task so that the audio keeps playing even after the user presses the home button and your app goes into the background. First you create an AVAudioSession. Then you set up an array of AVPlayerObjects and a AVQueuePlayer in your viewDidLoad method. There's a great tutorial by Ray Wenderlich that discusses all of this in detail http://www.raywenderlich.com/29948/backgrounding-for-ios. You can set up a callback method (observer method) so that the app is sent additional audio data as it streams in - (void)observeValueForKeyPath.
Here's how the code looks (from Ray Wenderlich's tutorial):
in viewDidLoad:
// Set AVAudioSession
NSError *sessionError = nil;
[[AVAudioSession sharedInstance] setDelegate:self];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&sessionError];
// Change the default output audio route
UInt32 doChangeDefaultRoute = 1;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryDefaultToSpeaker,
sizeof(doChangeDefaultRoute), &doChangeDefaultRoute);
NSArray *queue = #[
[AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:#"IronBacon" withExtension:#"mp3"]],
[AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:#"FeelinGood" withExtension:#"mp3"]],
[AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:#"WhatYouWant" withExtension:#"mp3"]]];
self.player = [[AVQueuePlayer alloc] initWithItems:queue];
self.player.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
[self.player addObserver:self
forKeyPath:#"currentItem"
options:NSKeyValueObservingOptionNew
context:nil];
in a callback method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"currentItem"])
{
AVPlayerItem *item = ((AVPlayer *)object).currentItem;
self.lblMusicName.text = ((AVURLAsset*)item.asset).URL.pathComponents.lastObject;
NSLog(#"New music name: %#", self.lblMusicName.text);
}
}
Don't forget to add the member variables in your view controller's private API in the implementation file:
#interface TBFirstViewController ()
#property (nonatomic, strong) AVQueuePlayer *player;
#property (nonatomic, strong) id timeObserver;
#end

Related

AudioQueueStart returns 561015905 (AVAudioSessionErrorCodeCannotStartPlaying)

My app uses Audio Queue Services to play sounds. On app start, I set audio session category to solo ambient:
`[[AVAudioSession sharedInstance] setCategory:AVAudioSEssionCategorySoloAmbient error:&error];`
When my app delegate gets applicationWillResignActive notification, I call AudioQueuePause(queue); for all playing sounds. And when the app delegate gets applicationDidBecomeActive, I call
OSStatus status = AudioQueueStart(queue, 0);
Sometimes (and it's hard to reproduce) status equals to 561015905. This value does not belong to Audio Queue Result Codes. It is AVAudioSessionErrorCodeCannotStartPlaying, and docs say
"The app is not allowed to start recording and/or playing, usually because of a lack of audio key in its Info.plist. This could also
happen if the app has this key but uses a category that can't record
and/or play in the background (AVAudioSessionCategoryAmbient,
AVAudioSessionCategorySoloAmbient, etc.)."
I definitely do not want to play the sound in background (that's why I am pausing audio queues when app resigns active). So, what am I doing wrong?
I had a similar problem, but I made a little in a different way...There was musical app in which background music was necessary, but under certain conditions she has to howled to play in background mode.
For me, i use this code:
#import "ViewController.h"
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVAudioPlayer.h>
#import <AVFoundation/AVAudioSession.h>
#interface ViewController ()
{
AVAudioPlayer *audioPlayer;
}
#property (assign) SystemSoundID pewPewSound;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL * pewPewURL = [[NSBundle mainBundle]URLForResource:#"Calypso" withExtension:#"caf"];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:pewPewURL error:nil];
if (audioPlayer)
{
[audioPlayer setNumberOfLoops:100];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[audioPlayer prepareToPlay];
[audioPlayer play];
}
}
#end
If u don't touch any Xcode setting, this code play looping melody, only in foreground, and if u go to background, player is stopped.
if u do this:
melody will continue to play in background, and in my case i can stop and play melody in background and foreground when i want. And I add system sound to call then u stop player, system sound stopped then U continue play music.
-(void)stopPlaying
{
[audioPlayer pause];
NSURL * pewPewURL;
pewPewURL = [[NSBundle mainBundle]URLForResource:#"Calypso" withExtension:#"caf"];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)pewPewURL, &_pewPewSound);
AudioServicesPlaySystemSound(self.pewPewSound);
}
-(void)continueToPlay
{
if ([audioPlayer prepareToPlay])
{
[audioPlayer play];
AudioServicesRemoveSystemSoundCompletion(self.pewPewSound);
AudioServicesDisposeSystemSoundID(self.pewPewSound);
}
}
I hope it will help to solve yours problems, If there are questions, set I will answer everything.

m3u8 audio stream plays in MPMoviePlayerController but not AVPlayer?

I'm kind of lost on this one. I have a class that needs to play audio and video files or streams. It has a completely custom UI so I use AVPlayer for this.
There is one live audio stream that just won't play. Every single time the AVPlayerItem observer triggers AVPlayerItemStatusFailed, the error of AVPlayer is nil.
But when I try to play the same audio stream in MPMoviePlayerController or Safari or Chrome it works just fine. Which is extremely weird since internally MPMoviePlayerController uses AVPlayer.
This is the URL of the live audio stream that fails: http://bit.ly/1gIqjV6
My AVPlayer code (doesn't work with the URL)
_currentItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:#"http://bit.ly/1gIqjV6"]];
[self startObservingPlayerItem:self.currentItem];
_avPlayer = [[AVPlayer alloc] initWithPlayerItem:self.currentItem];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.currentItem];
The AVPlayerItem observer
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([object isEqual:[self.avPlayer currentItem]])
{
if ([keyPath isEqualToString:#"status"])
{
switch ([self.avPlayer currentItem].status) {
case AVPlayerItemStatusReadyToPlay:
NSLog(#"AVPlayerItemStatusReadyToPlay");
break;
case AVPlayerItemStatusFailed:
// The live audio stream always fails, error is always nil
NSLog(#"AVPlayerItemStatusFailed");
NSError* error = self.avPlayer.error;
break;
case AVPlayerItemStatusUnknown:
NSLog(#"AVPlayerItemStatusUnknown");
break;
}
}
}
}
With MPMoviePlayerController (works fine with the URL)
MPMoviePlayerViewController* controller = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL URLWithString:#"http://bit.ly/1gIqjV6"]];
[rootViewController presentViewController:controller animated:YES completion:nil];
Has anyone ever encountered an issue like this?
Thanks in advance.
It looks like it's your m3u8 playlist from here. I tried a blank project with a simple AVPlayer, and while the test stream ("http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"), as well as the 'hi' and 'lo' streams from your m3u8 file worked, creating the AVPlayerItem with your m3u8 file always failed.
The diff in the m3u8 is as follows:
Yours:
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=35200, CODECS="mp4a.40.2"
Apple's sample:
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000
Do you have any other m3u8 playlists to test?

Can’t observe AVPlayerItem for #“status” key

I’m trying to play stream from URL, but here is an issue. ObserveValueForKeyPath:ofObject:change:context just doesn’t execute. As I understand, it is not depends on streamURL, nether it is correct or not. It must change status to AVPlayerItemStatusReadyToPlay or AVPlayerItemStatusFailed from AVPlayerItemStatusUnknown. But nonetheless I checked that URL in browser, and it is working fine. So, what’s the problem? I watched WWDC video about it, and it says, if you have audio stream, use AVPLayerItem before AVAsset.
Here is my code:
- (void) prepare
{
NSURL * trackStreamURL = [NSURL URLWithString:streamURL];
NSLog(#"STREAM URL: %#",trackStreamURL);
_playerItem = [AVPlayerItem playerItemWithURL:trackStreamURL];
[_playerItem addObserver:self forKeyPath:#"status" options:0 context:nil];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"KEY PATH : %#", keyPath);
NSLog(#"CHANGE : %#", change);
}
Thanks in advance!
I've always observed the status property on the AVPlayer instead of the AVPlayerItem since you need a player to play the player item anyway. In fact, I had never even looked at the status property of the player item.
The status of the player item probably never changes because there is no player that is ready to play it. So, Create a player with the player item and observe the status of the player (or possibly, still the status of the player item).
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];

iOS AVAudioPlayer Volume Control

I've read quite a few posts on the subject but the answers are not 100% clear. I'm looking for clarity here.
My app plays a short AVAudioPlayer sound periodically. The problem is, I can only set the volume after the first sound is played.
After reading stackoverflow, everyone seems to suggest that I play a dummy (silent) AVAudioPlayer sound at the start of the app to "link" the device's volume buttons to the "app volume".
Said another way, when the app starts, it's the "Ringer" volume that is controlled by default and only after the first sound is played will the device's volume buttons finally control the "app volume" (AVAudioPlayer volume) (it's the image without any label). Unfortunately by the time this happens, the user doesn't hear the first sound and now sees the app as broken.
My question is, is this the answer? Do I simply play a short dummy sound once at the start of the app to "link" the device's volume buttons to the app?
You don't have to play a dummy sound. Using the AudioToolbox framework you can set the AudioSessionActive as follows:
AudioSessionInitialize (NULL, NULL, NULL, NULL);
UInt32 sessionCategory = kAudioSessionCategory_AmbientSound;
AudioSessionSetProperty (kAudioSessionProperty_AudioCategory, sizeof (sessionCategory), &sessionCategory);
AudioSessionSetActive (true);
This will allow the volume buttons to control the app volume.
See this question: Cannot Control Volume of AVAudioPlayer via Hardware Buttons when AudioSessionActive is NO for more information on this approach.
Hey for future answer searchers, Since AudioSessionInitialize and AudioSessionSetActive are deprecated in iOS7 the recommended way of handling hardware audio and getting call backs is by using an AVAudioSession object. Set the session as active for your app and KVO on the #"outputVolume" property of the session.
- (id)init
{
self = [super init];
if (self)
{
self.audioSession = [AVAudioSession sharedInstance];
[_audioSession setActive:YES error:nil];
[_audioSession addObserver:self forKeyPath:#"outputVolume" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:NULL];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"outputVolume"])
{
[self setVolume:[change[#"new"] floatValue]];
}
}
- (void)dealloc
{
[_audioSession removeObserver:self forKeyPath:#"outputVolume"];
[_audioSession setActive:NO error:nil];
}

How to mute/unmute audio when playing video using MPMoviePlayerController?

I've created my own custom controls for use with the MPMoviePlayerController. So far everything works except the mute button control.
I've configured the AVAudioSession using the following code before I create my instance of the MPMoviePlayerController.
NSError *modeError = nil;
[self.audioSession setMode:AVAudioSessionModeMoviePlayback error:&modeError];
if (modeError != nil) {
NSLog(#"Error setting mode for AVAudioSession: %#", modeError);
}
NSError *categoryError = nil;
[self.audioSession setCategory:AVAudioSessionCategoryPlayback error:&categoryError];
if (categoryError != nil) {
NSLog(#"Error setting category for AVAudioSession: %#", categoryError);
}
Then in my mute button callback method I have the following code:
NSError *activeError = nil;
[self.audioSession setActive:NO error:&activeError];
if (activeError != nil) {
NSLog(#"Error setting inactive state for AVAudioSession: %#", activeError);
}
When clicking the Mute button I get the following unuseful error:
Error Domain=NSOSStatusErrorDomain Code=560030580 "The operation couldn’t be completed. (OSStatus error 560030580.)"
I am linking to the AVFoundation framework.
This is really starting to bug me as I can't for the life of me work out a way to reduce or mute the playback audio of my application.
I don't want to change the system global volume just the application level volume as defined by the AVAudioSession AVAudioSessionCategoryPlayback category.
It seems that you can set the volume of the AVAudioPlayer but not the MPMoviePlayerController. I've seen other posts here on SO that say just create an instance of AVAudioPlayer and set the volume but this just causes my app to crash and I expect it has something to do with the fact I'm not using the initWithContentsOfURL:error: or initWithData:error: and instead using `init'.
Any help would be appreciated.
After speaking to an Apple technician it turns out that it's not possible to control or mute the audio using MPMoviePlayerController.
Instead you have to create your own controller using AVFoundations AVPlayer class.
Once you're using that it's a matter of creating a custom audio mix and setting the volume level. It actually works very well.
Sample code:
AVURLAsset * asset = [AVURLAsset URLAssetWithURL:[self localMovieURL] options:nil];
NSArray *audioTracks = [asset tracksWithMediaType:AVMediaTypeAudio];
// Mute all the audio tracks
NSMutableArray * allAudioParams = [NSMutableArray array];
for (AVAssetTrack *track in audioTracks) {
AVMutableAudioMixInputParameters *audioInputParams =[AVMutableAudioMixInputParameters audioMixInputParameters];
[audioInputParams setVolume:0.0 atTime:kCMTimeZero ];
[audioInputParams setTrackID:[track trackID]];
[allAudioParams addObject:audioInputParams];
}
AVMutableAudioMix * audioZeroMix = [AVMutableAudioMix audioMix];
[audioZeroMix setInputParameters:allAudioParams];
// Create a player item
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
[playerItem setAudioMix:audioZeroMix]; // Mute the player item
// Create a new Player, and set the player to use the player item
// with the muted audio mix
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
self.mPlayer = player;
[mPlayer play];
I've written an MPMoviePlayerController replacement class that adds support for volume level. I will upload the to github shortly and add the link in this post.
I know this is an old post, but I managed to find a way to successfully control the volume of the MPMoviePlayerController control in iOS6 & iOS7, using an MPVolumeView. One gotcha is that it does NOT work in the simulator, only on the physical device. For just controlling the volume, adding a hidden MPVolumeView will work fine. However if you use a hidden one, the native OS volume display that appears when you change the volume using the physical device volume buttons will still appear centre screen. If you want to prevent this, make sure your MPVolumeView is not hidden. Instead, you can give it a very low alpha transparency and place it behind other views, so the user can't see it.
Here's the code i've used:
MPVolumeView *volumeView = [[MPVolumeView alloc]initWithFrame:CGRectZero];
[volumeView setShowsVolumeSlider:YES];
[volumeView setShowsRouteButton:NO];
// control must be VISIBLE if you want to prevent default OS volume display
// from appearing when you change the volume level
[volumeView setHidden:NO];
volumeView.alpha = 0.1f;
volumeView.userInteractionEnabled = NO;
// to hide from view just insert behind all other views
[self.view insertSubview:volumeView atIndex:0];
This allows you to control the volume by calling:
[[MPMusicPlayerController applicationMusicPlayer] setVolume:0.0];
But I was still getting the native OS volume display appearing the first time I would try to change the volume - on subsequent loads it did not show this display, so figuring it was something to do with the stage in the viewcontroller life cycle, I moved it from my viewDidLoad method to the viewDidAppear method - it worked - the volume muted and the native volume display did not appear, but I now was able to hear a split second of audio before the video started playing. So I hooked into the playback state did change delegate of the MPMoviePlayerController. In viewDidload I added:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(videoPlaybackStateDidChange:)
name:MPMoviePlayerPlaybackStateDidChangeNotification object:nil];
And the delegate callback method:
-(void)videoPlaybackStateDidChange:(NSNotification *)aNotification
{
// Note, this doesn't work in simulator (even in iOS7), only on actual device!
if ([moviePlayerController playbackState] == MPMoviePlaybackStatePlaying)
{
[[MPMusicPlayerController applicationMusicPlayer] setVolume:0.0];
}
}
This muted the audio of the video before it started playing, but after the viewDidLoad in the life cycle, so no native OS volume muted display.
In my app, I retrieved and stored the current volume level before muting (using [MPMusicPlayerController applicationMusicPlayer].volume property), and then restored the volume to this level when the view controller was closed, meaning the user would be unaware that their device volume level was modified and reverted.
Also, if your MPMoviePlayerController is using a non-standard audio route in iOS7, calling [[MPMusicPlayerController applicationMusicPlayer] setVolume:0.0] may not work for you - in this case you can loop through the subviews of your MPVolumeView control until you find a view which subclasses UISlider. You can then call [sliderView setValue:0 animated:NO] which should work for you. This method isn't using any private Apple APIs so shouldn't get your app rejected - after all there are so many legitimate reasons why you would offer this functionality, and it was possible in iOS6 without having to go to these lengths! In fact, I was bamboozled to discover that Apple had removed the functionality to set the volume on MPMoviePlayerController in iOS7 in the first place.. enforced migration to AVPlayer?
Update: My iPad app has now been approved using this method and is live on the app store.

Resources