I am using a singleton for AVAudioPlayer, it works great.
I wish to add a check before initializing the player and stop the player if it already playing something.
I added this code :
SoundPlayer *player = [SoundPlayer sharedInstance];
if([player isPlaying]){
[player stop];
}
But it gives me a EXE_BAD_ACCESS on the if([player isPlaying]) line.
2 Questions:
If this is a singleton and I am getting back the same player then why doesn't it stopes by itself?
Why am I getting the error?
HERE IS THE FULL CODE
#import "SoundPlayer.h"
#implementation SoundPlayer
#synthesize helpMode;
static SoundPlayer *sharedInstance = nil;
+ (SoundPlayer *)sharedInstance {
if (sharedInstance == nil) {
sharedInstance = [[super allocWithZone:NULL] init];
}
return sharedInstance;
}
+(BOOL)playSound:(NSURL*)url{
NSError *error;
SoundPlayer *player = [SoundPlayer sharedInstance];
if([player isPlaying]){
[player stop];
}
player = [[SoundPlayer sharedInstance] initWithContentsOfURL:url error:&error];
[player play];
if (player == nil){
NSLog(#"error %# \n for file %#",[error description],[url path]);
return NO;
}else {
if (![player helpMode]) {
[player play];
}else{
NSLog(#"help mode");
}
}
return YES;
}
-(void)stopSound{
if ([self isPlaying]) {
[self stop];
}
}
#end
Im not sure if this is whats causing the error, but figured I would post my response to the discussion here to make my point clearer since Ill have more room and code formatting.
No you dont have to override all the methods, i was just asking to make sure I understood everything right.
The other piece of what im saying is that just like in stopSound{}, you should be using self not
SoundPlayer *player = [SoundPlayer sharedInstance];
so change your code to this, run it and see if its fixed, post a comment below to let me know the outcome.
-(BOOL)playSound:(NSURL*)url{
NSError *error;
// SoundPlayer *player = [SoundPlayer sharedInstance];
if([self isPlaying]){
[self stop];
}
//not sure if this should be self or super (i think super, try both)
//if you overrided the init then definitely self
//[self initWithContentsOfURL:url error:&error];
[super initWithContentsOfURL:url error:&error];
[self play];
if (self == nil){
NSLog(#"error %# \n for file %#",[error description],[url path]);
return NO;
}else {
if (![self helpMode]) {
[self play];
}else{
NSLog(#"help mode");
}
}
return YES;
}
Really all your doing is creating a pointer to self with you create a player object since it is a singleton. With that said Im not sure why that would make it crash other then the fact that you "should" be using self instead.
+ (SoundPlayer *)sharedInstance {
if (sharedInstance == nil) {
sharedInstance = [[super allocWithZone:NULL] init];
}
return sharedInstance;
}
see here that AVAudioPlayer only has 2 init methods and neither are just init hence you are not completely initializing your super class. You need to override initWithContentsOfURL:url method and initialize this object and its super with the url parameter. Just make sure you put the initWithContentsOfURL:url in both the .h and .m. Also idk if it matters but try just alloc not allocWithZone.
Related
I'm building AudioKit into an experimental psychology add testing interoception (internal body sense), and it's very simple for now, I just have an AudioManager.m class that declares:
#implementation AudioManager {
AKAudioPlayer* player;
bool canPlay;
}
+(AudioManager *)sharedManager {
static dispatch_once_t pred = 0;
static AudioManager *instance = nil;
dispatch_once(&pred, ^{
instance = [[AudioManager alloc] init];
[instance setup];
});
return instance;
}
little player methods like:
-(NSError*)playHTTIntro {
return [self playWavInResources:#"Htt1"];
}
are then invoked from particular view controllers as:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[AudioManager sharedManager] playHTTGuess];
}
the weird problem I'm having is that as files get called and played, increasing amplitude and distortion accumulates
by the time I've played 10 or 15 files, the voice audio files are almost impossible to understand
the playWavInResourcesabove is implemented as:
-(NSError*)playWavInResources:(NSString*)wavName {
if(player)
[player stop];
NSError* error;
AKAudioFile* audioFile = [[AKAudioFile alloc] initForReading:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:wavName ofType:#"wav"]] error:&error];
if(!error) {
if(player)
[player replaceWithFile:audioFile error:&error];
else
player = [[AKAudioPlayer alloc] initWithFile:audioFile looping:false lazyBuffering:false error:&error completionHandler:nil];
AudioKit.output = player;
// Start audio engine if not already
NSError* error;
[AudioKit startAndReturnError:&error];
if(!error)
[player start];
}
return error;
}
Has anyone seem this problem before?
I'm developing an iOS application for a news website, the website has a live audio news channel, i have tried using AVAudioPlayer and did the following:
.h file:
#interface ViewController : UIViewController <AVAudioPlayerDelegate>
#property (strong, nonatomic) AVAudioPlayer *audioPlayer;
.m file (viewDidLoad):
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL fileURLWithPath:#"http://198.178.123.23:8662/stream/1/;listen.mp3"];
NSError *error;
_audioPlayer = [[AVAudioPlayer alloc]
initWithContentsOfURL:url
error:&error];
if (error)
{
NSLog(#"Error in audioPlayer: %#",
[error localizedDescription]);
} else {
_audioPlayer.delegate = self;
[_audioPlayer prepareToPlay];
}
}
But the application is giving me the following error:
2015-05-25 15:03:12.353 AudioPlayer[2043:421434] Error in audioPlayer: The operation couldn’t be completed. (OSStatus error 2003334207.)
I don't know what's wrong with this code.
Can anyone tell me where's the error in the code ?
Many thanks.
I think there is a better way to play the audio link in the default MPMoviePlayer with audio url. Rest of the things will be managed by default player.
EDIT :
NSURL *audioPath = [NSURL URLWithString:#"Your Audio URL"];
MPMoviePlayerViewController *mpViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:audioPath];
// Remove the movie player view controller from the "playback did finish" notification observers
[[NSNotificationCenter defaultCenter] removeObserver:mpViewController
name:MPMoviePlayerPlaybackDidFinishNotification
object:mpViewController.moviePlayer];
// Register this class as an observer instead
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:mpViewController.moviePlayer];
// Set the modal transition style of your choice
mpViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
// Present the movie player view controller
[self presentViewController:mpViewController animated:YES completion:nil];
// Start playback
[mpViewController.moviePlayer prepareToPlay];
[mpViewController.moviePlayer play];
You're getting an error because AVAudioPlayer doesn't support HTTP (meaning that you can't play media from the internet), so what you can do is use AVPlayer it's pretty much similar, here's an example :
- (void)viewDidLoad {
[super viewDidLoad];
NSString *urlAddress = #"http://198.178.123.23:8662/stream/1/;listen.mp3";
urlStream = [NSURL URLWithString:urlAddress];
self.audioPlayer = [AVPlayer playerWithURL:urlStream];
NSError *error;
if (error)
{
NSLog(#"Error in audioPlayer: %#",
[error localizedDescription]);
} else {
[_audioPlayer prepareForInterfaceBuilder];
}
}
Hope my answer helped you :)
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
I can play an mp3 file with AVAudioPlayer framework directly in the ViewController as an action from a button, but if I want to play the same mp3 calling a selector in a NSObject Class, failed. Thanks for your advice.
Playing an mp3 from the ViewController (audioPlayerDemo.xcodeproj) and it works:
AVFoundation.framework included in the frameworks folder
APDViewController.h
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface APDViewController : UIViewController <AVAudioPlayerDelegate>
{
}
#property (strong,nonatomic) AVAudioPlayer *audioPlay;
- (IBAction)play:(id)sender;
APDViewController.m
#implementation APDViewController
#synthesize audioPlay;
- (IBAction)play:(id)sender
{
NSError *error = nil;
NSData *dataAudio = [[NSData alloc] initWithContentsOfFile:
#"/Users/User/Documents/AudioPlayerDemo/AudioPlayerDemo/hello world.mp3"options:0 error:&error];
if(error){
NSLog(#" error loading data: %# ", [error localizedDescription]);
}
else{
audioPlay = [[AVAudioPlayer alloc] initWithData:dataAudio error:&error];
if(error){
NSLog(#" error loading audio: %# ", [error localizedDescription]);
}
else{
audioPlay.delegate = self;
[audioPlay prepareToPlay];
[audioPlay play];
}
}
}
-(void)audioPlayerDidFinishPlaying:
(AVAudioPlayer *)player successfully:(BOOL)flag
{
if (player == audioPlay){
audioPlay = nil;
NSLog(#"audioplay = nil");
}
}
But, if I want to play the same .mp3 calling a NSObject class (AudioPlayClass.xcodeproj), there is no error, neither any sound. Here the code that doesn`t work:
AVFoundation.framework included in the frameworks folder
APCplayObject.h
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#interface APCplayObject : NSObject <AVAudioPlayerDelegate>
{
}
#property (strong,nonatomic) AVAudioPlayer *audioPlay;
- (void)playAudio;
#end
APCplayObject.m
#import "APCplayObject.h"
#implementation APCplayObject
#synthesize audioPlay;
-(void)playAudio{
NSError *error = nil;
NSData *dataAudio = [[NSData alloc] initWithContentsOfFile:
#"/Users/User/Documents/AudioPlayerClass/AudioPlayerClass/hello world.mp3"options:0 error:&error];
if(error){
NSLog(#" error loading data: %# ", [error localizedDescription]);
}
else{
audioPlay = [[AVAudioPlayer alloc] initWithData:dataAudio error:&error];
if(error){
NSLog(#" error loading audio: %# ", [error localizedDescription]);
}
else{
audioPlay.delegate = self;
[audioPlay prepareToPlay];
[audioPlay play];
}
}
}
-(void)audioPlayerDidFinishPlaying:
(AVAudioPlayer *)player successfully:(BOOL)flag
{
if (player == audioPlay){
audioPlay = nil;
NSLog(#"audioplay = nil");
}
}
APCviewController.m, the caller as an action from a button:
- (IBAction)callPlay:(id)sender {
APCplayObject *a = [[APCplayObject alloc] init];
[a playAudio];
}
Thanks in advance.
The problem is that after you start the audio playing, the variable a goes out of scope and is therefore destroyed.
You need to create a declared property to hold your APCplayObject when the method exits.
So, in APCviewController.h you can do something like this:
#property (strong,nonatomic) APCplayObject *playObject;
And then change callPlay to:
- (IBAction)callPlay:(id)sender {
if (!self.playObject) // Use this line if you only want to create one playObject, take it out to create a new one every time.
self.playObject = [[APCplayObject alloc] init];
[self.playObject playAudio];
}
I would like to check whether the audio has ended playing. If so, I need to change the button to a play button instead of a pause button. How do i do it in iOS?
My code looks like this:
-(void)playAudioFile:(BOOL)status
{
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"audio" ofType:#"mp3"];
NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];
NSError *error;
if(status)
{
if(!audioPlayer)
{
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:&error];
audioPlayer.currentTime = currentPlayTime;
audioPlayer.numberOfLoops = 0; //Infinite
audioDuration = audioPlayer.duration;
audioPlaySlider.minimumValue = 0;
audioPlaySlider.maximumValue = audioDuration;
audioPlaySlider.value = currentPlayTime;
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(audioTimer:) userInfo:[NSDictionary dictionaryWithObject:audioPlayer forKey:#"playerObject"] repeats:YES];
}
[audioPlayer play];
if([audioPlayer rate] != 1.0 )
{
[audioPlayer play];
}
else
{
[self->audioPlayBtn setBackgroundImage:[UIImage imageNamed:#"playBtn.png"] forState:UIControlStateNormal];
}
NSLog(#"Play duration : %f",audioDuration);
}
else
{
if(audioPlayer && audioPlayer.playing)
{
[audioPlayer pause];
}
}
NSLog(#"Error for file : %#",error);
}
I tried this:
if([audioPlayer rate] != 1.0 )
{
[audioPlayer play];
}
else
{
[self->audioPlayBtn setBackgroundImage:[UIImage imageNamed:#"playBtn.png"] forState:UIControlStateNormal];
}
But not working.. Need some guidance...
Use the AVAudioPlayerDelegate's method
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
Your viewcontroller/class with the audioplayer instance must be the delegate for the audio player. The method will be called when the audio player finishes playback.
http://developer.apple.com/library/ios/#documentation/AVFoundation/Reference/AVAudioPlayerDelegateProtocolReference/Reference/Reference.html