Start Audio streaming in the background - objective-c

I'm developing an application that allows to stream a list of audio files that will be played one after the other. I'm using mattgallagher's audiostreamer from github.
This works fine when the app is active, but when it goes in the background and one song finished, the next one fails to start.In other words, the initialization of the audio streamer is not working in the background.
In my info.plist I have set the "Required background modes" to "App plays audio" but this is not helping to start a stream when the app is in the background.
Its been like a week now I'm trying to search for a solution, but didn't find any. Can anyone help me solving this problem?

You application will be stopped as soon as you stop playing the audio file, so the second one does not have a chance to start playing.
Initialise one more audio session, that will play a short silent audio file in a loop. You can keep it as long as you need.
The session should allow mixing the audio, which will have no effect on your audio because the audio file is silent.
AudioSessionInitialize(NULL, NULL, NULL, NULL);
AudioSessionSetActive(YES);
UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(sessionCategory), &sessionCategory);
UInt32 allowMixing = true;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof(allowMixing), &allowMixing);
NSString* path = [[NSBundle mainBundle] pathForResource:#"silentAudio" ofType:#"m4a"];
NSURL* fileURL = [NSURL fileURLWithPath:path];
NSError* error = nil;
self.beatPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&error];
if (error != nil)
{
//process the error
}
_beatPlayer.numberOfLoops = -1;
[_beatPlayer play];
Stopping the playback will also stop the application threads:
[_beatPlayer stop];

You should use UIBackgroundModes: iPhoneOSKeys.
In your .plist file:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
Update:
Add this to your init code:
NSError *setCategoryErr = nil;
NSError *activationErr = nil;
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error:&setCategoryErr];
[[AVAudioSession sharedInstance] setActive:YES error:&activationErr];

Related

AVFoundation to reproduce a video loop

I need to reproduce a video indefinitely (restarting the video when it ends) in my OpenGL application.
To do so I'm trying to utilize AV foundation.
I created an AVAssetReader and an AVAssetReaderTrackOutput and I utilize the copyNextSampleBuffer method to get CMSampleBufferRef and create an OpenGL texture for each frame.
NSString *path = [[NSBundle mainBundle] pathForResource:videoFileName ofType:type];
_url = [NSURL fileURLWithPath:path];
//Create the AVAsset
_asset = [AVURLAsset assetWithURL:_url];
//Get the asset AVAssetTrack
NSArray *arrayAssetTrack = [_asset tracksWithMediaType:AVMediaTypeVideo];
_assetTrackVideo = [arrayAssetTrack objectAtIndex:0];
//create the AVAssetReaderTrackOutput
NSDictionary *dictCompressionProperty = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id) kCVPixelBufferPixelFormatTypeKey];
_trackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:_assetTrackVideo outputSettings:dictCompressionProperty];
//Create the AVAssetReader
NSError *error;
_assetReader = [[AVAssetReader alloc] initWithAsset:_asset error:&error];
if(error){
NSLog(#"error in AssetReader %#", error);
}
[_assetReader addOutput:_trackOutput];
//_assetReader.timeRange = CMTimeRangeMake(kCMTimeZero, _asset.duration);
//Asset reading start reading
[_assetReader startReading];
And in -update method of my GLKViewController I call the following:
if (_assetReader.status == AVAssetReaderStatusReading){
if (_trackOutput) {
CMSampleBufferRef sampleBuffer = [_trackOutput copyNextSampleBuffer];
[self createNewTextureVideoFromOutputSampleBuffer:sampleBuffer]; //create the new texture
}
}else if (_assetReader.status == AVAssetReaderStatusCompleted) {
NSLog(#"restart");
[_assetReader startReading];
}
All work fine until the AVAssetReader is in the reading status but when it finished reading and I tried to restart the AVAssetReading with a new call [_assetReader startReading], the application crash without output.
What I'm doing wrong? It is correct to restart an AVAssetReading when it complete his reading?
AVAssetReader doesn't support seeking or restarting, it is essentially a sequential decoder. You have to create a new AVAssetReader object to read the same samples again.
Thanks Costique! Your suggestion brings me back on the problem. I finally restarted the reading by creating a new AVAssetReader. However in order to do that I noted that a new AVAssetReaderTrackOutput must be created and added to the new AVAssetReader
e.g.
[newAssetReader addOutput:newTrackOutput];

AVAudioPlayer returns success but no sound from device

I have a very simple app that I have built to test out AVAudioPlayer. I have a 16s aif file that plays just fine on my Mac. Put it in a simple app and ran on my iPhone but no sound. Sending 'play' to the object returns success and audioPlayerDidFinishPlaying comes back with success after 16 seconds. Still no sound. Have checked the sound volume on the iPhone, just fine. Have checked to make sure my AVAudioPlayer object has volume set at 1.0 - it does. Can anybody help?
Here's my code
NSString *soundPath = [[NSBundle mainBundle] pathForResource:#"Soothing" ofType:#"aif"];
NSURL *playURL = [NSURL fileURLWithPath:soundPath];
NSError *error;
self.myAudioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:playURL error:&error];
NSLog(#"About to play sound");
self.myAudioPlayer.delegate = self;
[self.myAudioPlayer prepareToPlay];
bool success = [self.myAudioPlayer play];
if (!success) {
NSLog(#"Failed to play file");
} else NSLog(#"Looks like it succeded");
OK - got it figured out. I had not configured the AVAudioSession for my app. Did something very simple as follows (copied from Apple's example code):
[[AVAudioSession sharedInstance] setDelegate: self];
NSError *setCategoryError = nil;
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: &setCategoryError];
if (setCategoryError)
NSLog(#"Error setting category! %#", setCategoryError);
Sound now plays - plus learned a lot about how to control playback through screen locks, etc., which will be valuable.
Did you check if you get anything from the error?
if(error) {
NSLog( #"%#", [error localizedDescription] );
}

Record Audio/Video with AVCaptureSession and Playback Audio simultaneously?

I'm trying to create an iOS app which can record audio and video while simultaneously outputting audio to the speakers.
To do the recording and preview, I'm using AVCaptureSession, an AVCaptureConnection each for both video and audio, and an AVAssetWriterInput each for both video and audio. I basically achieved this by following the RosyWriter example code.
Prior to setting up recording in this fashion, I was using AVAudioPlayer to play audio.
Now, if I am in the middle of capturing (not even recording, just capturing for preview), and attempt to use AVAudioPlayer, my captureOutput callbacks on my AVCaptureAudioDataOutputSampleBufferDelegate class stop getting called.
Is there a workaround for this?
Is there another, lower level service I should use to be playing audio that won't stop my capture?
mc.
You first set the AVAudioSession and its category. For example in viewDidLoad method,
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers|AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
Therefore you can play BGM
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:music_url error:nil];
[self.player setDelegate:self];
[self.player play];
and record the movie
self.captureSession = [[AVCaptureSession alloc] init];
/* ... addInput AVCaptureDeviceInput AVMediaTypeVideo & AVMediaTypeAudio */
/* ... addOutput */
[self.captureSession startRunning];
AVCaptureMovieFileOutput *m_captureFileOutput =[self.captureSession.outputs objectAtIndex:0];
[m_captureFileOutput startRecordingToOutputFileURL:saveMovieURL recordingDelegate:self];

(iOS) Why does AVAudioPlayer initWithContentsOfURL always fail with error code=-43

NSString *songNameEscaped = [songName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *songURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#%#", #"http://somerootpath/Music/", songNameEscaped]];
NSLog(#"songURL = %#", songURL);
NSError *avPlayerError = nil;
AVAudioPlayer *avPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:songURL error:&avPlayerError];
if (avPlayerError)
{
NSLog(#"Error: %#", [avPlayerError description]);
}
else
{
[avPlayer play];
}
If I copy the NSLog output from NSLog(#"songURL = %#", songURL); and paste it in safari, Quicktime plugin plays the files no problem so I know the URLs are valid. I've tried with .m4a and .mp3 files and have tried removing the spaces from songName but not matter what I always get Error:
Error Domain=NSOSStatusErrorDomain Code=-43 "The operation couldn’t be completed. (OSStatus error -43.)".
But they are just standard .m4a / .mp3 files created in iTunes.
Apparently AVAudioPlayer doesn't support streaming. Apple recommends AVPlayer for this although it is not as conveinient for finding things like current time and duration .
Error -43 indicates an inability to find the data. This could be because the url was in fact malformed, or because the server doesn't support streaming. You should try to pre-load the song data with an NSData object.
NSString *songNameEscaped = [songName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *songURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#%#", #"http://somerootpath/Music/", songNameEscaped]];
NSLog(#"songURL = %#", songURL);
NSError *avPlayerError = nil;
NSData* songData = [NSData dataWithContentsOfURL:songURL error:&avPlayerError];
if (songData)
{
AVAudioPlayer *avPlayer = [[AVAudioPlayer alloc] initWithData error:&avPlayerError];
if (avPlayer)
{
[avPlayer prepareToPlay];
[avPlayer play];
}
else
{
NSLog(#"Error initializing data for AVAudioPlayer. Possibly an Unsupported Format");
NSLog(#"Error: %#", [avPlayerError description]);
}
}
else
{
NSLog(#"Error initializing data for AVAudioPlayer. Possibly Malformed URL");
NSLog(#"Error: %#", [avPlayerError description]);
}
I had the same problem yesterday. Turns out my url was wrong.
I had something like you here:
NSURL *songURLID = [NSURL URLWithString:[NSString stringWithFormat:#"%#%d", #"http://somerootpath/Music/", songNameEscaped]];
NSLog(#"songURL = **%d**", songURL**ID**);
But, my songURL was of NSString type. My NSLog wrote it correctly but when I put %d in url it turn out wrong. I suggest you try to check out your AVAudioPlayer url again.
try something like described here to see the players url.
You may want to try checking the targets on your audio file. I had a similar issue and fixed it by making sure that my mp4 file target membership check box was checked. I must not have checked it when I was importing the file originally.
in Apple documentation
An instance of the AVAudioPlayer class, called an audio player,
provides playback of audio data from a file or memory.
Apple recommends that you use this class for audio playback unless you
are playing audio captured from a network stream or require very low
I/O latency.

iphone iOS5 audioPlayerDidFinishPlaying not called screen lock?

I have a series of audio files which I want to play in sequence. I setup audioPlayerDidFinishPlaying delegate method to start the next file in the sequence. This works with no problem if I turn off screen lock or if I run it on the emulator or on an iOS4 device. However, when I run it on a disconnected device, iOS5, screen locked, it stops after the first track. Is there some way to start a new sound track after the screen lock has come on?
Here is my basic code
// Registers this class as the delegate of the audio session.
[[AVAudioSession sharedInstance] setDelegate: self];
// Use this code instead to allow the app sound to continue to play when the screen is locked.
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: nil];
UInt32 doSetProperty = 0;
AudioSessionSetProperty ( kAudioSessionProperty_OverrideCategoryMixWithOthers,
sizeof (doSetProperty),
&doSetProperty
);
// Registers the audio route change listener callback function
AudioSessionAddPropertyListener (
kAudioSessionProperty_AudioRouteChange,
audioRouteChangeListenerCallback,
self
);
// Activates the audio session.
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive: YES error: &activationError];
NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:#"m4a"];
float vol = [appSoundPlayer volume];
AVAudioPlayer * newAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
self.appSoundPlayer = newAudio; // automatically retain audio and dealloc old file if new file is loaded
[newAudio release]; // release the audio safely
appSoundPlayer.delegate = self;
[appSoundPlayer prepareToPlay];
[appSoundPlayer setNumberOfLoops:0];
[appSoundPlayer play];
[appSoundPlayer setVolume:vol];
[progTime setProgress:0];
}
- (void) audioPlayerDidFinishPlaying: (AVAudioPlayer *) lappSoundPlayer successfully: (BOOL) flag {
//... code here to select nex sound track
[lappSoundPlayer play];
}
I have already made it so the app continues to play the current track even when screen lock comes on, I just can't get it to start up the next track.
Note: I have verified that this was working in iOS4.
Have you set up an audio session category, specifically AVAudioSessionCategoryPlayback, as described here?
[[AVAudioSession sharedInstance] setDelegate: self];
NSError *error = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
If that doesn't work, another thing to try, according to user ascanio in this message in the Apple dev forums, is to set your app up to listen for remote control events:
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];