Resuming Interrupted Radio Stream using MPMoviePlayerController - ios

I am developing an online radio app for iOS6 devices. Ive looked for various wrappers to achieve this task. AVPlayer, MPMoviePlayerController etc.
I tried using AVPlayer as it sounds more correct to use it for my purpose as it is audio only application. But soon I came across this problem : Here
Therefore I switched to MPMoviePlayerController and this is what Im trying to do :
pPlayer = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:#"http://launch.fusionradio.fm:8004"]];
pPlayer.movieSourceType = MPMovieSourceTypeStreaming;
pPlayer.view.hidden = YES;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
[pPlayer prepareToPlay];
[pPlayer play];
pPlayer.shouldAutoplay = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(StreamStateChanged) name:MPMoviePlayerLoadStateDidChangeNotification object:pPlayer];
In my StreamStateChanged method Im doing :
NSLog(#"Trying to replay");
[pPlayer pause];
[pPlayer play];
pPlayer is MPMoviePlayer. Everything is fine except when there is an interrupt Console spits out the following :
Took background task assertion (1) for playback stall.
Ending background task assertion (1) for playback stall.
The number after assertion keeps increasing. and then it recovers from it once the internet connection is stable.
My question is : Is this approach correct? Am I doing something wrong along the way? And Is it ok to ignore that assert message?.
P.S : Please suggest if there is a better approach for developing radio streaming app using different API as opposed to MPMoviePlayerController
Thank you :)

You are entirely correct in ignoring those internal assert messages. There is nothing you can do about them.

Related

Performing vibration in recording app

I am trying to perform a vibration in an app similar to Snapchat, that uses both audio output and input as well as supports audio mixing from other apps, but this seems to be a harder task that I initially thought it would be. Important to know is that I am not trying to vibrate during playback or recording. From reading all the documentation I could find on the subject, this is what I have come to understand:
In order to support both playback and recording (output and input), I need to use AVAudioSessionCategoryPlayAndRecord
Making the phone vibrate through AudioServicesPlaySystemSound (kSystemSoundID_Vibrate) is not supported in any of the recording categories, including AVAudioSessionCategoryPlayAndRecord.
Enabling other apps to play audio can be done by adding the option AVAudioSessionCategoryOptionMixWithOthers.
Therefore, I do this in my app delegate:
NSError *error = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionMixWithOthers error:&error];
The possible solutions to doing the vibration that I have tried but failed at are:
Deactivating the shared AVAudioSession before vibrating, and then activate it straight after.
[[AVAudioSession sharedInstance] setActive:NO error:nil];
AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
[[AVAudioSession sharedInstance] setActive:YES error:nil];
This successfully performs the vibration, but afterwards, when I try to record a movie, the audio is ducked (or something else is causing it to be very silent). It also gives me an error saying that I am not allowed to deactivate a session without removing its I/O devices first.
Changing category before vibrating, and then changing it back.
[[AVAudioSession sharedInstance] setActive:NO error:nil];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
[[AVAudioSession sharedInstance] setActive:NO error:nil];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionMixWithOthers error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
This solution comes up every now and then, but does not seem to work for me. No vibration occurs, even though the categories seems to be set. This might still be a valid solution if I set usesApplicationAudioSession = YES on my AVCaptureSession, but I haven't made it work yet.
Sources:
https://developer.apple.com/library/ios/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/ConfiguringanAudioSession/ConfiguringanAudioSession.html
https://developer.apple.com/library/ios/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/AudioSessionBasics/AudioSessionBasics.html
https://developer.apple.com/library/ios/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/AudioGuidelinesByAppType/AudioGuidelinesByAppType.html#//apple_ref/doc/uid/TP40007875-CH11-SW1
So, I've been recently trying to play a simple beep in my app and one of the methods of doing so I stumbled upon is:
AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID);
It's a function that plays a system sound from /System/Library/Audio/UISounds/ it's supported on all iOS versions.
The sound 1350 equals to RingerVibeChanged (vibration). So..
AudioServicesPlaySystemSound(1350);
...vibrates for about 0.5 seconds.
Incase you're interested in more sounds here is a link to all the playable sounds and what iOS versions they've been added in:
http://iphonedevwiki.net/index.php/AudioServices
You didn't say what your supported device requirements are, but for newer devices, you can use the new taptic engine APIs, such as:
UIImpactFeedbackGenerator
UISelectionFeedbackGenerator
UINotificationFeedbackGenerator
https://developer.apple.com/reference/uikit/uifeedbackgenerator#2555399
For reference, I'm not using AVCaptureSession but an AVAudioEngine to record something, and for my case I pinpointed it down to having to pause the input in order to play the vibration properly with AudioServicesPlaySystemSound during a record session.
So in my case, I did something like this
audioEngine?.pause()
AudioServicesPlaySystemSound(1519)
do {
try audioEngine.start()
} catch let error {
print("An error occurred starting audio engine. \(error.localizedDescription)")
}
I'm not sure if it would also work with doing something similar for AVCaptureSession (i.e., stopRunning() and startRunning()), but leaving this here in case someone wants to give it a try.
You may try setting the allowHapticsAndSystemSoundsDuringRecording flag on AVAudioSession to true (the flag defaults to false) by calling
sessionInstance.setAllowHapticsAndSystemSoundsDuringRecording(true)
It worked for me.
Note the system prevents haptic and sound feedback (such as those from a UIPickerView or UIDatePicker) from playing, to avoid unexpected system noise while recording.

Loading issues with AVPlayerItem from local URL

I am working on a custom video player using AVPlayer. I load videos stored on the local file system in the Cache folder (NSCache). I initialize the player like this:
self.playerItem = [[AVPlayerItem alloc] initWithURL:self.localURL];
[self.playerItem addObserver:self forKeyPath:NSStringFromSelector(#selector(status)) options:NSKeyValueObservingOptionInitial context:nil];
self.avPlayer = [[AVPlayer alloc]initWithPlayerItem:self.playerItem];
[self.avPlayer addObserver:self forKeyPath:NSStringFromSelector(#selector(status)) options:NSKeyValueObservingOptionInitial context:nil];
This generally works fine. However I have frequent fails on the status of the AVPlayerItem with this error:
NSLocalizedDescription = "The operation could not be completed";
NSLocalizedFailureReason = "An unknown error occurred (-12983)";
NSUnderlyingError = "Error Domain=NSOSStatusErrorDomain Code=-12983
The strange thing is that the same URLs which fail sometimes work just shortly after and before such fails. I would say every 10th load attempt fails. I can't figure out what causes this or where to look for answers. A search for the error code turned up empty for me. Any help or pointers are highly appreciated.
After a lengthy hunt, I was able to track down the source of the issue. The problem was an undocumented limit on the number of AVPlayer items which can exist concurrently. If there are too many instances videos can no longer be loaded failing with the AVPlayerItem error -12983.
Other people seem to have run into the same issue as well: AVPlayerItemStatusFailed error when too many AVPlayer instances created. I solved the issue by reusing player instances and making sure that there are not too many active ones at the same time.

App volume goes quiet on iPad when using GKVoiceChat

In my iOS game, I support push-to-talk using Game Center's GKVoiceChat.
When two iPhones are connected in a multiplayer match, this works as expected: the game's sounds are heard at roughly the same volume as the other player's voice (voice may be a tiny bit louder), and the game's volume is consistent whether or not the other player is using the push-to-talk function.
However, on an iPad, the volume of the game's sounds is drastically reduced; game sounds are played at roughly one quarter the volume of the voice sounds, so quiet that unless you put your ear to the speaker, you're hard pressed to tell that any game sounds are being played at all. (Voice sounds are at full volume.) In comparison, the iPhone's volume is deafening.
Here's how I'm setting up audio:
AVAudioSession* avSession = [AVAudioSession sharedInstance];
NSError *myError = nil;
[avSession setActive:YES error:&myError];
if(myError)
NSLog(#"Error initializing audio session: %#", myError);
[avSession setCategory:AVAudioSessionCategoryPlayAndRecord
error: &myError];
if(myError)
NSLog(#"Error setting audio session category: %#", myError);
[avSession setMode:AVAudioSessionModeVoiceChat
error:&myError];
if(myError)
NSLog(#"Error setting audio session mode: %#", myError);
// By default, AVAudioSessionCategoryPlayAndRecord sends audio output to the phone's earpiece; instead, we want to force it to the speakers
[avSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker
error:&myError];
if(myError)
NSLog(#"Error changing audio port to speakers: %#", myError);
Then, later, when a multiplayer match is set up, we set up the voice chat like this:
self.myVoiceChannel = [[self myMatch] voiceChatWithName:#"allPlayers"];
[[self myVoiceChannel] start];
[[self myVoiceChannel] setActive:NO];
self.myVoiceChannel.volume = 1.0;
I've confirmed that commenting out the [[self myVoiceChannel] start] statement is sufficient to restore the iPad volume to the expected levels.
What's surprising is that [[AVAudioSession sharedInstance] mode] never gets set to AVAudioSessionModeGameChat---no matter when I expect it, it's always AVAudioSessionModeVoiceChat. From the AVAudioSession documentation, it seemed like when I initiated a GKVoiceChat, this would be changed automatically.
Any ideas why the iPad's audio would be mixed so differently from the iPhone?
It looks like this is a known issue since iOS 6.
The only options for working around it is to crank up your game's sounds during voice chats (on the iPad only, obviously).

Switching avaudiosession categories then retaking control of remote control center controls

This is my first post asking a question as i never usually need help but i can't figure out if this is even possible. What i need is to switch between these two categories of avaudiosession
and when the switch is made from mixing allowed to no mixing for the app take back control of the remote controls in the control center.
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil]
and
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:nil error:nil]
Ill try explain what is occurring:
They both work independently so if i start with the first avaudiosession config it allows mixing and correctly switches the remote controls in the control center to iPod.
And if i start the second avaudiosession config the app correctly takes control of the remote control in the control center.
The issue occurs when i trying toggle these options. When i toggle the app doesn't retake control of the remote controls after mixing is turned off.
Any help would be greatly appreciated
I've found a solution that works for me, which involves calling
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents]
or
[[UIApplication sharedApplication] endReceivingRemoteControlEvents]
before setting AVAudioSession category options. eg:
NSUInteger options = ... // determine your options
// it seems that calls to beginReceivingRemoteControlEvents and endReceivingRemoteControlEvents
// need to be balanced, so we keep track of the current state in _isReceivingRemoteControlEvents
BOOL shouldBeReceivingRemoteControlEvents = ( 0 == (options & AVAudioSessionCategoryOptionMixWithOthers) );
if(_isReceivingRemoteControlEvents != shouldBeReceivingRemoteControlEvents) {
if(shouldBeReceivingRemoteControlEvents) {
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
_isReceivingRemoteControlEvents=YES;
} else {
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
_isReceivingRemoteControlEvents=NO;
}
}
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:options error:&error];
...
[[AVAudioSession sharedInstance] setActive:YES error:&error]
I've been able to achieve consistent results by using a variable to keep track of whether or not the app is currently receiving remote control events so that I can ensure that calls to (begin/end)ReceivingRemoteControlEvents are balanced. I haven't found any documentation that says that you need to do this but otherwise things don't always seem to behave as expected, particularly since I call this code multiple times throughout the course of the application.
In my implementation, the code above gets called each time the app comes to the foreground and also just before each time I begin playing audio.
I hope this helps.

iOS AVPlayer EXC_BAD_ACCESS when accessing properties

I'm working on a plugin for Apache Cordova that will allow audio streaming from a remote URL through the Media API.
The issue that I'm experiencing is that I get an EXC_BAD_ACCESS signal whenever I try to access certain properties on the AVPlayer instance. currentTime and isPlaying are the worst offenders. The player will be playing sound through the speakers, but as soon as my code reaches a player.currentTime or [player currentTime] it throws the bad access signal.
[player play];
double position = round([player duration] * 1000) / 1000;
[player currentTime]; //This will cause the signal
I am using ARC, so I'm not releasing anything that shouldn't be.
Edit:
Everything that I've done has been hacking around on the Cordova 3 CDVSound class as a proof of concept for actual streaming on iOS.
The original code can be found here: https://github.com/apache/cordova-plugin-media/tree/master/src/ios
My code can be found here:
CDVSound.h
CDVSound.m
The method startPlayingAudio will trip an exc_bad_access at line 346. Removing line 346 will cause audio to play, but it will trip a bad access later down the road when getCurrentPositionAudio and line 532 is called.
Edit / Solution
So it turns out that the best way to handle this is to use a AVPlayerItem and then access it with player.currentItem.currentTime. The real question then becomes, why isn't this behavior documented with AVPlayer and why does it behave like this?

Resources