In my app, I have multiple tabs and on each tab I have an instance of AVPlayer. When I activate AirPlay, however, "the first player wins". That means that the player on the currently active tab connects to AirPlay and when I switch to a different tab and press play, nothing happens. So only the first instance of AVPlayer that connects to AirPlay can actually play through AirPlay and no players on the other tabs work. What to do?
The solution is quite easy: When your view controller that contains a player appears, you set allowsExternalPlayback on the AVPlayer instance to YES, when in disappears you set it to NO.
Example:
- (void)viewWillAppear:(BOOL)animated
{
// _player is an instance of AVPlayer
if ([_player respondsToSelector:#selector(setAllowsExternalPlayback:)]) {
// iOS 6+
_player.allowsExternalPlayback = YES;
} else {
// iOS 5
_player.allowsAirPlayVideo = YES;
}
[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
// _player is an instance of AVPlayer
if ([_player respondsToSelector:#selector(setAllowsExternalPlayback:)]) {
// iOS 6+
_player.allowsExternalPlayback = NO;
} else {
// iOS 5
_player.allowsAirPlayVideo = NO;
}
[super viewWillDisappear:animated];
}
Enjoy.
Related
I'm integrating MPNowPlayingInfoCenter into a music playing app. The integration of that and MPRemoteCommandCenter work great when I run it in the iOS simulator. When I run the same code on a device the music controls in Control Center do not change at all. It's as if the app isn't event registering with MPNow and MPRemote centers.
MPNowPlayingInfoCenter *np = [MPNowPlayingInfoCenter defaultCenter];
np.nowPlayingInfo = #{MPMediaItemPropertyTitle:[tracks objectAtIndex:self.currentTrack],
MPMediaItemPropertyArtist:currentArtist,
MPMediaItemPropertyAlbumTitle:currentAlbum,
MPMediaItemPropertyMediaType:#(MPMediaTypeMusic),
MPMediaItemPropertyPlaybackDuration:#(self.audioHandler.audioDuration),
MPNowPlayingInfoPropertyElapsedPlaybackTime:#(self.audioHandler.position),
MPNowPlayingInfoPropertyPlaybackRate:rate
};
I've got all this good stuff in my view controller life cycle methods:
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
// Turn off remote control event delivery
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
// Resign as first responder
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
A couple notes:
I'm not using AVPlayer. I'm using CoreAudio/Audio units to play
the audio. This part of the app is working fine.
I'm testing on iOS 7 and iOS 8 only.
I'm using the Amazing Audio Engine as a CoreAudio wrapper: http://theamazingaudioengine.com
Found it. When initializing the amazing audio engine (a great framework by the way) one needs to tell the framework that it doesn't want to share the audio output with other apps if one wants to use the MPNowPlayingInfoCenter. To do this I did:
self.audioController = [[AEAudioController alloc]
initWithAudioDescription:[AEAudioController nonInterleaved16BitStereoAudioDescription]
inputEnabled:NO];
self.audioController.allowMixingWithOtherApps = NO;
I have a tableview of songs on the ios device and if I select a row, I push to a new view where AVPlayer starts playing the selected song. Now if I go back and select another row, pushing to the view the app will start playing the new song, while continue to play the one that was running already.
EDIT:
I tried using the rate value like this without success, as it will always output "not playing" if i put it in my viewdidload method, even if a song is playing:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
appDelegate = (SimpleTableAppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.audioPlayer = [[AVPlayer alloc] init];
if (appDelegate.audioPlayer.rate == 0.0f)
{
NSLog(#"not playing");
} else {
NSLog(#"already playing");
}
}
In this line
appDelegate.audioPlayer = [[AVPlayer alloc] init];
you seem to be alloc'ing and init'ing a new AVPlayer. As such it's not surprising that you get a "not playing" result. Simply leave out that line.
AVPlayer has a rate property which indicates the speed of playback as a float. 0.0f indicates stopped. The play and stop methods just change the rate property.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
appDelegate = (SimpleTableAppDelegate *)[[UIApplication sharedApplication] delegate];
//Stop potential existing audioPlayer prior to creating new player
[appDelegate.audioPlayer stop];
//Create new audio player
appDelegate.audioPlayer = [[AVPlayer alloc] init];
}
I am developing an application for iOS which works in two different modes, one of them plays music from the iPod library, and the other one synthesizes sounds (eg: pure tones).
In order to synthesize sounds I use an audio unit, so through its callback I can pass the desired signal. As far as sounds must be played at an specific volume, I set the device volume using iPodMusicPlayer.
The issue I see is that the first time the application is run on an iPod Touch 5g after turning it on, once the iPodMusicPlayer is used to set the volume of the device, applicationMusicPlayer will not respond to stop.
I came up with the following code. Runing it on an iPod Touch 5g after turning it on, music will not stop after touching stop.
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
MPMusicPlayerController *mp = [MPMusicPlayerController iPodMusicPlayer];
mp.volume = 0.3;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)playAction:(id)sender {
MPMusicPlayerController *mp = [MPMusicPlayerController applicationMusicPlayer];
MPMediaQuery *query = [MPMediaQuery songsQuery];
[mp setQueueWithQuery:query];
[mp play];
}
- (IBAction)stopAction:(id)sender {
MPMusicPlayerController *mp = [MPMusicPlayerController applicationMusicPlayer];
[mp stop];
}
#end
PS: It is also possible to reproduce the issue by entering the music app, the quitting it (double press on home button and quit music from the multitasking bar), and finally run the code above.
In the other devices I have tested so far (iPad 2nd and 3rd generation, iPod Touch 4 generation) the application works well.
Thanks in advance.
Does anyone know the best way to mute game sounds on my app.
I am currently using SystemSoundID which I know cannot have the volume adjusted and need to move to AVAudioPlayer.
Ideally I need a settings page where the volume can be changed and the volume level saved so when coming back out or leaving and re-opening the app it remembers to volume level?
I have tried it various ways using AVAudioPlayer but had no success at present and am receiving some bad reviews for my app for people saying they hate that they cant control the game volume... HELP!!
for AVAudioPlayer you can use this code to mute volume
in .h file
#interface ViewController : UIViewController
{
BOOL muted;
}
and in .m file
- (void)viewDidLoad
{
[super viewDidLoad];
muted = NO;
}
- (IBAction)speakerOnOff:(id)sender
{
if (muted) {
muted = NO;
[player setVolume:1.0];
} else {
muted = YES;
[player setVolume:0.0];
}
}
this code is from this great answer.
I want to implement the following things,
App is running a music or video (using MPMoviePlayerController) in background.
User double clicks the home button and go to the first screen showing playback controls (fast rewind, play or pause, fast forward buttons)
User click fast rewind or fast forward button.
Then app play previous or next music or video.
For the 3rd step, I should know which button is clicked.
(As I naturally know, the currently playing item is paused, stopped.. using MPMoviePlayerPlaybackStateDidChangeNotification notification).
Which notification should I register? Or are there any other approaches?
I got the answer by myself.
That is using UIApplication's beginReceivingRemoteControlEvents.
In an appropriate place (like viewWillAppear:) put the following code
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
And the view controller should implement the following method returning YES
- (BOOL)canBecomeFirstResponder {
return YES;
}
And then you can receive remote controller event in the following method.
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
if( event.type == UIEventTypeRemoteControl ) {
NSLog(#"sub type: %d", event.subtype);
}
}
And event.subtype is as below,
typedef enum {
// available in iPhone OS 3.0
UIEventSubtypeNone = 0,
// for UIEventTypeMotion, available in iPhone OS 3.0
UIEventSubtypeMotionShake = 1,
// for UIEventTypeRemoteControl, available in iPhone OS 4.0
UIEventSubtypeRemoteControlPlay = 100,
UIEventSubtypeRemoteControlPause = 101,
UIEventSubtypeRemoteControlStop = 102,
UIEventSubtypeRemoteControlTogglePlayPause = 103,
UIEventSubtypeRemoteControlNextTrack = 104,
UIEventSubtypeRemoteControlPreviousTrack = 105,
UIEventSubtypeRemoteControlBeginSeekingBackward = 106,
UIEventSubtypeRemoteControlEndSeekingBackward = 107,
UIEventSubtypeRemoteControlBeginSeekingForward = 108,
UIEventSubtypeRemoteControlEndSeekingForward = 109,
} UIEventSubtype;
This might be a very late answer, but as I notice, there aren't many Q/As about audio playing and remote controls, so I hope my answer helps the others who have the same problem:
I'm using AVAudioPlayer at the moment, but the remote controlling method which is - (void)remoteControlReceivedWithEvent:(UIEvent *)event must not be involved with the type of the player you're using.
To get the forward and rewind buttons on lock screen work, follow this:
In your view controller's viewDidLoad method add the following code:
//Make sure the system follows our playback status - to support the playback when the app enters the background mode.
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
Then add these methods:
viewDidAppear:: (if not implemented already)
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
//Once the view has loaded then we can register to begin recieving controls and we can become the first responder
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
viewWillDisappear: (if not implemented already)
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
//End recieving events
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
}
And:
//Make sure we can recieve remote control events
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
//if it is a remote control event handle it correctly
if (event.type == UIEventTypeRemoteControl)
{
if (event.subtype == UIEventSubtypeRemoteControlPlay)
{
[self playAudio];
}
else if (event.subtype == UIEventSubtypeRemoteControlPause)
{
[self pauseAudio];
}
else if (event.subtype == UIEventSubtypeRemoteControlTogglePlayPause)
{
[self togglePlayPause];
}
else if (event.subtype == UIEventSubtypeRemoteControlBeginSeekingBackward)
{
[self rewindTheAudio]; //You must implement 15" rewinding in this method.
}
else if (event.subtype == UIEventSubtypeRemoteControlBeginSeekingForward)
{
[self fastForwardTheAudio]; //You must implement 15" fastforwarding in this method.
}
}
}
This is working fine in my app, however if you want to be able to receive remote control events in all view controllers, then you should set it in the AppDelegate.
NOTE! This code is working fine at the moment, but I see two more subtypes called UIEventSubtypeRemoteControlEndSeekingBackward and UIEventSubtypeRemoteControlEndSeekingBackward. I'm not sure if they have to be implemented or not, if someone knows about it, let us know.