I'am trying to add remote control events to a music player app, but can't get it working.
Some facts:
I'm using [MPMusicPlayerController applicationMusicPlayer];
I added Audio, AirPlay and Picture in Picture background modes;
The example is playing audio;
It does not receive remote notifications from the lock screen and control center;
I've made an example project which can be downloaded here:
example project
The main source:
#import "ViewController.h"
#import <MediaPlayer/MPMediaPickerController.h>
#import <MediaPlayer/MPMediaQuery.h>
#import <MediaPlayer/MediaPlayer.h>
#interface ViewController ()
#property (strong, nonatomic) MPMusicPlayerController *MusicPlayer;
#property (strong, nonatomic) MPMediaItemCollection *MusicPlayerSongs;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_MusicPlayerSongs = [MPMediaItemCollection alloc];
_MusicPlayer = [MPMusicPlayerController applicationMusicPlayer];
[_MusicPlayer setShuffleMode: MPMusicShuffleModeOff];
[_MusicPlayer setRepeatMode: MPMusicRepeatModeNone];
[_MusicPlayer beginGeneratingPlaybackNotifications];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
NSMutableArray *selectedTracks = [NSMutableArray new];
//Find all tracks that contains an 'a'
MPMediaPropertyPredicate *songPredicate =
[MPMediaPropertyPredicate predicateWithValue:#"a"
forProperty:MPMediaItemPropertyTitle
comparisonType:MPMediaPredicateComparisonContains];
MPMediaQuery *mediaQuery = [[MPMediaQuery alloc] init];
[mediaQuery addFilterPredicate:songPredicate];
[selectedTracks addObjectsFromArray:[mediaQuery items]];
NSLog(#"Number of tracks containing an 'a': %lu",(unsigned long)selectedTracks.count);
self.MusicPlayerSongs = [[MPMediaItemCollection alloc]initWithItems:selectedTracks];
[self.MusicPlayer setQueueWithItemCollection:self.MusicPlayerSongs];
[self.MusicPlayer play];
[self.MusicPlayer beginGeneratingPlaybackNotifications];
[self basicSetup];
[self updateNowPlayingCenter];
}
- (void)basicSetup {
//Listen to remote control events
[MPRemoteCommandCenter sharedCommandCenter].previousTrackCommand.enabled = NO;
[MPRemoteCommandCenter sharedCommandCenter].nextTrackCommand.enabled = NO;
[MPRemoteCommandCenter sharedCommandCenter].playCommand.enabled = YES;
[MPRemoteCommandCenter sharedCommandCenter].pauseCommand.enabled = YES;
[MPRemoteCommandCenter sharedCommandCenter].togglePlayPauseCommand.enabled = YES;
[[MPRemoteCommandCenter sharedCommandCenter].nextTrackCommand addTarget:self action:#selector(remoteNext)];
[[MPRemoteCommandCenter sharedCommandCenter].previousTrackCommand addTarget:self action:#selector(remotePrevious)];
[[MPRemoteCommandCenter sharedCommandCenter].playCommand addTarget:self action:#selector(remotePlay)];
[[MPRemoteCommandCenter sharedCommandCenter].pauseCommand addTarget:self action:#selector(remotePlay)];
[[MPRemoteCommandCenter sharedCommandCenter].togglePlayPauseCommand addTarget:self action:#selector(remoteTogglePlayState)];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
-(void)remotePlay{
NSLog(#"remotePlay");
[self.MusicPlayer play];
}
-(void)remoteNext{
NSLog(#"remoteNext");
[self.MusicPlayer skipToNextItem];
[self updateNowPlayingCenter];
}
-(void)remotePrevious{
NSLog(#"remotePrevious");
[self.MusicPlayer skipToPreviousItem];
[self updateNowPlayingCenter];
}
-(void)remotePause{
NSLog(#"remotePause");
[self.MusicPlayer pause];
}
-(void)remoteTogglePlayState{
NSLog(#"remoteTogglePlayState");
if([self.MusicPlayer playbackState] == MPMusicPlaybackStatePlaying){
[self.MusicPlayer pause];
}else{
[self.MusicPlayer play];
}
}
-(void)updateNowPlayingCenter{
MPNowPlayingInfoCenter *center = [MPNowPlayingInfoCenter defaultCenter];
NSMutableDictionary *songInfo = [NSMutableDictionary dictionaryWithDictionary:#{
MPMediaItemPropertyArtist: [self.MusicPlayer nowPlayingItem].artist,
MPMediaItemPropertyTitle: [self.MusicPlayer nowPlayingItem].title,
MPMediaItemPropertyAlbumTitle: [self.MusicPlayer nowPlayingItem].albumTitle,
}];
center.nowPlayingInfo = songInfo;
}
I just can't figure out why it is not working :(
PROGRESS UPDATE'S
What I found out so far, if I reboot my iPhone, it works. But every time after that it won't.
The lock screen and control center both show the right track, but still can't control the app.
Not sure if this helps you at all, but here is how I received any changes (in Swift).
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.getNowPlayingItem), name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification, object: nil)
player.beginGeneratingPlaybackNotifications()
}
deinit {
player.endGeneratingPlaybackNotifications()
NSNotificationCenter.defaultCenter().removeObserver(self)
}
It is necessary to (addObserver and beginGeneratingPlaybackNotifications), but also to (removeObserver and endGeneratingPlaybackNotification). I am not yet familiar with remote control events, but you may want to try ending those notifications as well. Hope this helps!
Related
I am using AVPlayerLayer to play video and playing the audio of video when app goes in background. Is there any way I can manage controls when screen is locked. User wants to stop play the audio.
My app is working in background but controls are not visible so user have to open the app and stop the video.
I got the solution simply used MPRemoteCommandCenter and added details about video see code in Objetive C
// to setup the playing info
- (void) setupNowPlayingInfoCenter{
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.togglePlayPauseCommand setEnabled:YES];
[commandCenter.playCommand setEnabled:YES];
[commandCenter.pauseCommand setEnabled:YES];
[commandCenter.nextTrackCommand setEnabled:NO];
[commandCenter.previousTrackCommand setEnabled:NO];
[commandCenter.playCommand addTargetWithHandler: ^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
[self->_player play];
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.pauseCommand addTargetWithHandler: ^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
[self->_player pause];
return MPRemoteCommandHandlerStatusSuccess;
}];
}
// to update the playing info when enter in background
- (void) updateNowPlayingInfoCenter {
NSDictionary *metadata = [self.media metaData];
MPNowPlayingInfoCenter *playingInfoCenter = [MPNowPlayingInfoCenter defaultCenter];
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
[songInfo setObject:metadata[MIBMediaMetaDataTrackNameKey] forKey:MPMediaItemPropertyTitle];
[songInfo setObject:metadata[MIBMediaMetaDataTrackNameKey] forKey:MPMediaItemPropertyArtist];
[songInfo setObject:metadata[MIBMediaMetaDataTrackDurationKey] forKey:MPMediaItemPropertyPlaybackDuration];
[songInfo setObject:[NSNumber numberWithDouble:(!self.playing ? 0.0f : 1.0f)] forKey:MPNowPlayingInfoPropertyPlaybackRate];
[playingInfoCenter setNowPlayingInfo:songInfo];
}
// add this line of code in init or viewdidload
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(applicationDidEnterBackgroundNotification:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
Called these 2 methods when enter in background
- (void)applicationDidEnterBackgroundNotification:(NSNotification *)notification {
[self setupNowPlayingInfoCenter];
[self updateNowPlayingInfoCenter];
}
I created a very simple music player using MPMusicPlayerController that is working fine except the stop button (see picture below). When the music is playing and stop button is pressed, the app doesn't play anymore. I tried to terminate it when in background but the app doesn't work. I just can use the app again after press play on center control (picture two).
And my code
#import "ViewController.h"
#import <MediaPlayer/MediaPlayer.h>
#interface ViewController ()
{
MPMusicPlayerController *playerController;
MPMediaQuery *query;
}
#end
#implementation ViewController
#synthesize musicTitleLabel;
- (void)viewDidLoad {
[super viewDidLoad];
playerController = [[MPMusicPlayerController alloc] init];
}
- (IBAction)playPressed:(id)sender {
query = [MPMediaQuery songsQuery];
MPMediaItem *item = [[query collections] objectAtIndex:0];
[playerController setNowPlayingItem:item];
[playerController play];
NSString *titleString = [item valueForProperty:MPMediaItemPropertyTitle];
musicTitleLabel.text = [NSString stringWithFormat:#"%#",titleString];
}
- (IBAction)pausePressed:(id)sender {
[playerController pause];
}
- (IBAction)stopPressed:(id)sender {
[playerController stop];
}
How can I fix that problem? What's the function o the Stop Method?
try this:
MPMusicPlayerController *player = [MPMusicPlayerController systemMusicPlayer];
Playing media items with the systemMusicPlayer will replace the user's current Music state.
I have an online music player. I want to add a feature to it that if the song is being played and a call (incoming or outgoing) is made, it should pause the music which call is going on and after the call is disconnected, the music should start again.
here is the code I have:
//
// FirstViewController.m
#import "FirstViewController.h"
CM_EXPORT const CMTime kCMTimeZero;
#interface FirstViewController ()
#end
#implementation FirstViewController
#synthesize metadatas;
#synthesize toggleButton;
#synthesize slider;
#synthesize mpVolumeView = _mpVolumeView;
#synthesize viewVolume;
- (void)viewDidLoad
{
//[super viewDidLoad];
//slider.transform = CGAffineTransformRotate(slider.transform,270.0/180*M_PI);
//[slider setMaximumValue:2];
//[slider setMinimumValue:0];
//[slider setSelected:YES];
//[[self mpVolumeView] setBackgroundColor:[UIColor clearColor]];
//MPVolumeView *myVolumeView = [[MPVolumeView alloc] initWithFrame: [[self mpVolumeView] bounds]];
//[[self mpVolumeView] addSubview:myVolumeView];
//toggleIsOn =TRUE;
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
toggleIsOn=TRUE;
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:self.viewVolume.bounds] ;
[self.viewVolume addSubview:volumeView];
[volumeView sizeToFit];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
-(IBAction)playButtonPressed:(id)sender
{
if(toggleIsOn){
toggleIsOn=!toggleIsOn;
player = nil;
NSString *stringurl = #"";
stringurl = #"http://majestic.wavestreamer.com:6221/listen.pls";
NSURL *url = [NSURL URLWithString:stringurl];
asset = [AVURLAsset URLAssetWithURL:url options:nil];
playerItem = [AVPlayerItem playerItemWithAsset:asset];
player = [AVPlayer playerWithPlayerItem:playerItem];
player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[playerItem addObserver:self forKeyPath:#"timedMetadata" options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:nil];
[player play];
[self.toggleButton setImage:[UIImage imageNamed:#"reload.png"] forState:UIControlStateNormal];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
else {
[self.toggleButton setImage:[UIImage imageNamed:#"playMusic.png"] forState:UIControlStateNormal];
self->player.rate=0.0;
toggleIsOn=!toggleIsOn;
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
[playerItem removeObserver:self forKeyPath:keyPath];
if ([keyPath isEqualToString:#"status"]) {
AVPlayerItem *pItem = (AVPlayerItem *)object;
if (pItem.status == AVPlayerItemStatusReadyToPlay)
{
metadatas.text = #"";
}
}
if ([keyPath isEqualToString:#"timedMetadata"]) {
for (AVAssetTrack *track in playerItem.tracks) {
for (AVPlayerItemTrack *item in player.currentItem.tracks) {
if ([item.assetTrack.mediaType isEqual:AVMediaTypeAudio]) {
NSArray *meta = [playerItem timedMetadata];
for (AVMetadataItem *metaItem in meta) {
NSString *source = metaItem.stringValue;
metadatas.text = source;
}
}
}
}
}
[self.toggleButton setImage:[UIImage imageNamed:toggleIsOn ? #"playMusic.png" :#"stop.png"] forState:UIControlStateNormal];
}
-(IBAction)fbButtonPressed:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://www.facebook.com"];
if (![[UIApplication sharedApplication] openURL:url])
NSLog(#"%#%#",#"Failed to open url:",[url description]);
}
-(IBAction)inButtonPressed:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://www.linkedin.com"];
if (![[UIApplication sharedApplication] openURL:url])
NSLog(#"%#%#",#"Failed to open url:",[url description]);
}
-(IBAction)tweetButtonPressed:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://www.twitter.com"];
if (![[UIApplication sharedApplication] openURL:url])
NSLog(#"%#%#",#"Failed to open url:",[url description]);
}
-(IBAction) sliderChanged:(id)sender
{
}
- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
#end
Also, the code for inserting volume view is there, then also there is no volume controller in the UI. Why so.?
// // FirstViewController.m
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>
#class AVPlayer;
#class AVPlayerItem;
#interface FirstViewController : UIViewController
{
UIView *viewVolume;
AVAsset *asset;
AVPlayerItem *playerItem;
AVPlayer *player;
NSURL *mURL;
MPVolumeView *_mpVolumeView;
IBOutlet UILabel *metadatas;
IBOutlet UIButton *toggleButton;
BOOL toggleIsOn;
IBOutlet UISlider *slider;
}
-(IBAction)playButtonPressed:(id)sender;
-(IBAction)fbButtonPressed:(id)sender;
-(IBAction)inButtonPressed:(id)sender;
-(IBAction)tweetButtonPressed:(id)sender;
-(IBAction) sliderChanged:(id)sender;
#property (strong, nonatomic) IBOutlet UISlider *slider;
#property (nonatomic, retain) IBOutlet MPVolumeView *mpVolumeView;
#property (nonatomic, retain) IBOutlet UILabel *metadatas;
#property (nonatomic, retain) IBOutlet UIButton *toggleButton;
#property (nonatomic, strong) IBOutlet UIView *viewVolume;
#end
am unable to start the music after call. Please help with possible solutions.
You need to add observer in FirstViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(resumePlayMusic) name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (void) resumePlayMusic
{
.......
}
And don't forget to remove observer.
Go to your appDelegate file and there you will find the UIApplicationDelegate methods automatically having been implemented for you.
Simply add your music pause and resume code in the methods and everything else will be handled accordingly. Just make sure that you can access your music instance player from the app delegate.
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
I've been pulling out my hair past three days to fix this problem. I've checked lots of sample codes, read lots of tutorials, and googled and checked lots and lots of questions and answers on stackoverflow, but I still cannot fix the problem. There are several similar questions like this or this but they don't have any solutions either.
So a little bit about my project:
I have a NIKMasterViewController and a NIKDetailViewController. In the first one I have a list of audio files in a table view; selecting a row, it navigates to the NIKDetailViewController where the user can see some info about the file and play the audio file.
I've defined an AVAudioPlayer property in the NIKMasterViewController and have set it like this:
NIKMasterViewController.h:
#property (nonatomic, strong) AVAudioPlayer *audioPlayer;
NIKMasterViewController.m:
#synthesize audioPlayer;
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showDetail"])
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NIKDetailViewController *detailViewController = (NIKDetailViewController *) segue.destinationViewController;
[detailViewController setAudioPlayer:audioPlayer];
[detailViewController setFeedEntry:[[[self feedParser] feedItems] objectAtIndex:indexPath.row]];
} else {
NSLog(#"Segue Identifier: %#", segue.identifier);
}
}
And that's all about the AVAudioPlayer in NIKMasterViewController. Now in my NIKDetailViewController I have another property of AVAudioPlayer:
NIKDetailViewController.h:
#property (nonatomic, strong) AVAudioPlayer *audioPlayer;
Now in my .m file I have a method called streamAudio which is called in viewDidLoad to prepare the audio playback, and I have an if condition asking to check if the audioPlayer is nill and if not, if the audioPlayer.isPlaying is true so that it stops the player, but it's never called, and when i navigate back to the Master VC to tap on another row to play another file, the second file starts playing while the first file is being played and everything gets mixed up.
Any help will be truly appreciated, since I'm almost about to stop programming after being unable to fix this issue after hours and days!
NIKDetailViewController.m:
#synthesize audioPlayer;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
selectedItem = [[NSString alloc]init];
}
return self;
}
#pragma mark - Managing the Audio Playback
- (IBAction)togglePlayingState:(id)button
{
//Handle the button pressing
[self togglePlayPause];
}
- (void)playAudio
{
//Play the audio and set the button to represent the audio is playing
[audioPlayer play];
[playPauseButton setImage:[UIImage imageNamed:#"player_pause"] forState:UIControlStateNormal];
}
- (void)pauseAudio
{
//Pause the audio and set the button to represent the audio is paused
[audioPlayer pause];
[playPauseButton setImage:[UIImage imageNamed:#"player_play"] forState:UIControlStateNormal];
}
- (void)togglePlayPause
{
//Toggle if the music is playing or paused
if (!audioPlayer.playing)
{
[self playAudio];
}
else if (audioPlayer.playing)
{
[self pauseAudio];
}
}
- (void)streamAudio
{
currentFileName = [[feedEntry podcastDownloadURL] lastPathComponent];
NSString* documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* path = [documentPath stringByAppendingPathComponent:currentFileName];
NSURL* audioURL = [NSURL fileURLWithPath: path];
if (audioPlayer != nil)
{
if (audioPlayer.isPlaying)
{
[audioPlayer stop]; //THIS IS NEVER CALLED
}
audioPlayer = nil; //THIS IS NEVER CALLED
}
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioURL error:nil];
// Set a timer which keep getting the current music time and update the UISlider in 1 sec interval
playbackTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateSlider) userInfo:nil repeats:YES];
// Set the maximum value of the UISlider
seekSlider.maximumValue = audioPlayer.duration;
currentTime.text = [NSString stringWithFormat:#"%d:%02d", (int)audioPlayer.currentTime / 60, (int)audioPlayer.currentTime % 60, nil];
remainingTime.text = [NSString stringWithFormat:#"%d:%02d", (int)(audioPlayer.duration - audioPlayer.currentTime) / 60, (int)(audioPlayer.duration - audioPlayer.currentTime) % 60, nil];
// Set the valueChanged target
[seekSlider addTarget:self action:#selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
audioPlayer.delegate = self;
[audioPlayer prepareToPlay]; //Add the audio to the memory.
}
- (void)updateSlider
{
// Update the slider about the music time
seekSlider.value = audioPlayer.currentTime;
}
- (IBAction)sliderChanged:(UISlider *)sender {
// Fast skip the music when user scrolls the slider
[audioPlayer stop];
[audioPlayer setCurrentTime:seekSlider.value];
audioPlayer.delegate = self;
[audioPlayer prepareToPlay];
[audioPlayer play];
}
// Stop the timer when the music is finished (Need to implement the AVAudioPlayerDelegate in the Controller header)
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
// Music completed
if (flag) {
[playbackTimer invalidate];
}
}
- (IBAction)forwardAudio:(id)sender
{
int currentTime = [audioPlayer currentTime];
[audioPlayer setCurrentTime:currentTime+10];
}
- (IBAction)rewindAudio:(id)sender
{
int currentTime = [audioPlayer currentTime];
[audioPlayer setCurrentTime:currentTime-10];
}
//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];
}
}
}
#pragma mark - view life cycle
- (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];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
//End recieving events
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self streamAudio];
//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];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
NOTE: I have tried setting the property in the Detail VC as weak but then, I get a warning, and the property is release before I can play the file.
So... I could finally fix this problem by creating a singleton of the audioplayer. This is how:
First of all, I removed all the code related to the audioPlayer from my NIKMasterViewController class, that includes the audioPlayer declaration and setting it in prepareForSegue.
I created a new class called NIKAudioPlayer.
In NIKAudioPlayer.h:
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#interface NIKAudioPlayer : NSObject <AVAudioPlayerDelegate>
{
AVAudioPlayer *currentPlayer;
}
#property (nonatomic, strong) AVAudioPlayer *currentPlayer;
+(NIKAudioPlayer *) sharedPlayer;
-(void)playURL:(NSURL*)url;
#end
In NIKAudioPlayer.m:
#import "NIKAudioPlayer.h"
#implementation NIKAudioPlayer
#synthesize currentPlayer;
+(NIKAudioPlayer *) sharedPlayer
{
static NIKAudioPlayer* sharedPlayer;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPlayer = [[NIKAudioPlayer alloc] init];
});
return sharedPlayer;
}
-(void)playURL:(NSURL*)url
{
[currentPlayer stop];
currentPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[currentPlayer prepareToPlay];
}
#end
Now in everywhere else in the code (in my case in NIKDetailViewController) whenever I need to play an audio file, I call the sharedPlayer from NIKAudioPlayer:
[[NIKPlayer sharedPlayer] playURL:audioURL];
[[NIKPlayer sharedPlayer].currentPlayer prepareToPlay];
To put in a nutshell, replace all audioPlayers in NIKDetailViewController with [NIKPlayer sharedPlayer].currentPlayer, or even cast it and use it everywhere:
audioPlayer = [NIKPlayer sharedPlayer].currentPlayer
The following code plays a song from the user's music library. Works fine on devices running iOS6, but I get no sound at all on devices running iOS5. What am I doing wrong? A search for AVAudioPlayer issues on iOS5 doesn't turn up much.
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:self.songUrl error:nil];
[self.audioPlayer setNumberOfLoops:-1];
[self.audioPlayer play];
self.songUrl is valid. (ipod-library://item/item.m4a?id=557492601628322780)
This is how I have implemented in my apps compatible with both iOS 5.0 and iOS 6.0
In your *.h file
#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#interface v1MusicPicController : UIViewController <MPMediaPickerControllerDelegate, AVAudioPlayerDelegate>
{
MPMusicPlayerController *musicPlayer;
MPMediaItemCollection *userMediaItemCollection;
}
#property (nonatomic, retain) MPMusicPlayerController *musicPlayer;
#property (nonatomic, retain) MPMediaItemCollection *userMediaItemCollection;
--------- In your *.m file
#implementation v1MusicPicController
#synthesize musicPlayer;
#synthesize userMediaItemCollection;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[self setMusicPlayer: [MPMusicPlayerController applicationMusicPlayer]];
// By default, an application music player takes on the shuffle and repeat modes
// of the built-in iPod app. Here they are both turned off.
[musicPlayer setShuffleMode: MPMusicShuffleModeOff];
[musicPlayer setRepeatMode: MPMusicRepeatModeNone];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (IBAction) DoneButtonMusic:(id)sender
{
MPMediaPickerController *mediaPicker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeMusic];
mediaPicker.delegate = self;
mediaPicker.allowsPickingMultipleItems = YES; // this is the default
[self presentModalViewController:mediaPicker animated:YES];
}
// To learn about notifications, see "Notifications" in Cocoa Fundamentals Guide.
- (void) registerForMediaPlayerNotifications {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver: self
selector: #selector (handle_NowPlayingItemChanged:)
name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification
object: musicPlayer];
[notificationCenter addObserver: self
selector: #selector (handle_PlaybackStateChanged:)
name: MPMusicPlayerControllerPlaybackStateDidChangeNotification
object: musicPlayer];
[musicPlayer beginGeneratingPlaybackNotifications];
}
- (void) updatePlayerQueueWithMediaCollection: (MPMediaItemCollection *) mediaItemCollection
{
if (userMediaItemCollection == nil)
{
//NSLog(#"Went here 4");
// apply the new media item collection as a playback queue for the music player
[self setUserMediaItemCollection: mediaItemCollection];
[musicPlayer setQueueWithItemCollection: userMediaItemCollection];
[musicPlayer play];
}
}
// Media picker delegate methods
- (void)mediaPicker: (MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
//NSLog(#"Went here 2");
// We need to dismiss the picker
[self dismissModalViewControllerAnimated:YES];
// Apply the chosen songs to the music player's queue.
[self updatePlayerQueueWithMediaCollection: mediaItemCollection];
}
- (void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker
{
// User did not select anything
// We need to dismiss the picker
[self dismissModalViewControllerAnimated:YES];
}
You may already know this but I use AVAudioPlayer for playing sounds that are in my app bundle.
#property (nonatomic, retain) AVAudioPlayer *myAudioPlayer1;
#synthesize myAudioPlayer1;
// ************************
// PLAY AUDIO
// ************************
[myAudioPlayer1 stop];
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"funny_sound" ofType: #"mp3"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
myAudioPlayer1 = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
[myAudioPlayer1 play];
I'm seeing the same behavior with ipod-library URLs. More specifically, initWithContentsOfURL sets an error code of -43, which is similar to a file not found error. Apparently either this was fixed, or support was added, in iOS 6, but I haven't found any official explanation.
Loading the audio into an NSData and using AVAudioPlayer initWithData also failed for me with a similar error (NSData code 256).
The best solution I've found so far is to use AVPlayer instead of AVAudioPlayer, but it's going to take some rework because the methods and properties are slightly different.