I have just started developing for iOS. To get right to the point…I created a UITableViewController populated with some audio files I want to play. I have successfully passed the selected rows string, and change views to the ‘Now Play View” which is a UIViewController. From there I play the audio using the avaudioplayer. After returning to the previous screen with the embedded navigationcontroller, and selecting a different audio track it creates a whole new instance of the avaudioplayer and plays the newly selected audio on top of the old selected audio with no way of stopping the old one. How do I create a “Now Playing” view where I can load the newly selected audio and clear the old audio??
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
@interface NowPlayingViewController : UIViewController{
AVAudioPlayer *audioPlayer;
//playPause button images
UIImage *playImg;
UIImage *pauseImg;
IBOutlet UIButton *playPauseButton;
//Volume slider
NSTimer *volumeTimer;
IBOutlet UISlider *volumeSlider;
}
//playPause button action
- (IBAction)playPause:(id)sender;
@end
Here is the implementation file.
#import "NowPlayingViewController.h"
@implementation NowPlayingViewController
#pragma mark - My actions
//Audio Player Controllers
//Play the selected audio.
- (IBAction)playPause:(id)sender
{
if (![audioPlayer isPlaying]) {
[playPauseButton setImage:pauseImg forState:UIControlStateNormal];
[audioPlayer play];
} else {
[playPauseButton setImage:playImg forState:UIControlStateNormal];
[audioPlayer pause];
}
}
-(void)updateVolumeSlider
{
[audioPlayer setVolume:volumeSlider.value];
}
#pragma mark - Initialization
- (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];
//Instantiate needed variables
playImg = [UIImage imageNamed:@"Play.png"];
pauseImg = [UIImage imageNamed:@"Pause.png"];
//Prepare the audio player
NSString *path = [[NSBundle mainBundle] pathForResource:self.title ofType:@"m4a"];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
[audioPlayer prepareToPlay];
//Setup the volume slider
volumeTimer = [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(updateVolumeSlider) userInfo:nil repeats:YES];
}
MainView Header
#import <UIKit/UIKit.h>
@interface MainTableViewController : UITableViewController{
NSMutableArray *performanceArray;
NSMutableArray *recoveryArray;
}
@end
MainView Implementation File
#import "MainTableViewController.h"
#import "NowPlayingViewController.h"
@implementation MainTableViewController
#pragma mark - Table view
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 2;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
if(section == 0)
return @"Performance";
else
return @"Recovery";
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(section == 0)
return [performanceArray count];
else
return [recoveryArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
if(indexPath.section == 0)
cell.textLabel.text = [performanceArray objectAtIndex:indexPath.row];
else
cell.textLabel.text = [recoveryArray objectAtIndex:indexPath.row];
return cell;
}
#pragma mark - Prepare data to pass
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:@"Audio Selection Segue"])
{
// Get reference to the destination view controller
NowPlayingViewController *np = [segue destinationViewController];
//Pass the selected audio title to the Now Playing View
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:self.tableView.indexPathForSelectedRow];
np.title = cell.textLabel.text;
}
}
#pragma mark - didSelectRowAtIndexPath
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:@"Audio Selection Segue" sender:self];
}
#pragma mark - Initialization
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
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];
//Instantiate performanceArray
performanceArray = [[NSMutableArray alloc]initWithObjects:@"Centering", nil];
//Instantiate recoveryArray
recoveryArray = [[NSMutableArray alloc]initWithObjects:@"Power Nap", nil];
}
First part is simple. Instead of instantiating a new NowPlayingViewController each time you want to push it to the navigation stack, just reuse an existing one. You will then need to pass the audio url through a property on the NowPlayingViewController.
Edit: the above was only relevant if you were instantiating the viewController as was implied. Turns out you were using storyboarding, which automatically instantiates new instances of the viewsControllers in segues. Therefore the problem is you are not stopping the music (see below).
Regarding the 2nd portion (music not stopping), you do not seem to be STOPPING the music anywhere. Do so, in “viewDidUnload”, or “viewWillDisappear” delegate call, which gets called whenever the view is no longer in use.
New edit based on new requirements:
In this case, you must separate the AudioPlayer from the Now Playing view.
You should pass in the AudioPlayer from externally, each time the NowPlaying view is created. Optimally, you should have a class that maintains the application state of the music player.
Then, when you do your segue, instead of just telling your NowPlaying view which track to play, pass it the MusicPlayer instance instead.
Now, instead of your buttons controlling what to play etc, let your buttons simply send messages to your music player. With some NSNotification, you can even let the musicplayer send events to your views to update things like album name, track name, album art etc.