I am trying to play a song from my iPhone music library using AVPlayer. Everything seems ready to play, but the player simply won't make any sound. I've been struggling with this for a while, any help would be greatly appreciated!
Note: I realize I could use AVAudioPlayer, but I would like to read the file right from my music library and, to my understanding, AVAudioPlayer doesn't support that (I would have to export the song first, taking up more time). I cannot use MPMusicPlayerController because the end goal is to turn the song into NSData and play it on another device.
Above all, I would like to know WHY this code isn't playing:
NSArray *itemsFromQuery = [[MPMediaQuery songsQuery] items];
MPMediaItem *song = [itemsFromQuery objectAtIndex:29];
NSURL *songURL = [song valueForProperty:MPMediaItemPropertyAssetURL];
AVURLAsset *urlAsset = [[AVURLAsset alloc] initWithURL:songURL options:nil];
NSArray *keyArray = [[NSArray alloc] initWithObjects:#"tracks", nil];
[urlAsset loadValuesAsynchronouslyForKeys:keyArray completionHandler:^{
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:urlAsset];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
while (true) {
if (player.status == AVPlayerStatusReadyToPlay && playerItem.status == AVPlayerItemStatusReadyToPlay) {
break;
}
}
if (player.status == AVPlayerStatusReadyToPlay && playerItem.status == AVPlayerItemStatusReadyToPlay) {
NSLog(#"Ready to play");
[player play];
}
else
NSLog(#"Not ready to play");
}];
The output is "Ready to play", and the "rate" property of the AVPlayer is 1.0 after I call the play method. The MPMediaItem exists, and I can use the valueForProperty method to obtain the correct title, artist, etc. Any ideas why no sound is coming from the player?
Found something that worked:
I made the AVPlayer a property (thanks for the tip meggar!)
I made sure the AVPlayer was nil before using the initWithAsset method.
NSArray *itemsFromQuery = [[MPMediaQuery songsQuery] items];
MPMediaItem *song = [itemsFromQuery objectAtIndex:0];
NSURL *songURL = [song valueForProperty:MPMediaItemPropertyAssetURL];
AVURLAsset *urlAsset = [[AVURLAsset alloc] initWithURL:songURL options:nil];
NSArray *keyArray = [[NSArray alloc] initWithObjects:#"tracks", nil];
[urlAsset loadValuesAsynchronouslyForKeys:keyArray completionHandler:^{
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:urlAsset];
player = nil;
player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
while (true) {
if (player.status == AVPlayerStatusReadyToPlay && playerItem.status == AVPlayerItemStatusReadyToPlay)
break;
}
[player play];
}];
Hope this helps someone out!
Another reason is configuring AVAudioSession. Worked for me.
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
Related
I'm working on application which has record audio and play that recorded file which is store in document directory.
Here is my code:
Record button method.
(IBAction)stopButtonPressed:(id)sender{
UIButton *button = (UIButton *)sender;
if (button.selected) { // Play recorded file.
self.stopButton.selected = NO;
NSError *error;
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioRecorder.url error:&error];
self.audioPlayer.delegate = self;
self.audioPlayer.volume = 1.0;
[self.audioPlayer prepareToPlay];
[self.audioPlayer play];
}
else{ // Stop recording.
self.stopButton.selected = YES;
if (recordTimer) {
[recordTimer invalidate];
}
[self.audioPlayer stop];
self.recordButton.selected = NO;
[audioRecorder stop];
self.readyLabel.text = #"READY";
[self insertNewRecording];
}
}
I take it you're using ARC, in which case you'll need to retain the AVAudioPlayer as it sets to nil automatically. In your header file enter the following`
#property (nonatomic, strong) AVAudioPlayer *audioPlayer;
Hope this works for you,
Cheers Jim
um..I am not sure. But as a test, maybe you can try if you can play the file by specifying its name? E.g. you got a forest.mp3 to play and the key part of the code can be like this:
path = [[NSBundle mainBundle] pathForResource:#"forest" ofType:#"mp3"];
NSData *sampleData = [[NSData alloc] initWithContentsOfFile:path];
NSError *audioError = nil;
av = [[AVAudioPlayer alloc] initWithData:sampleData error:&audioError];
If everything went well, maybe it's something to do with audioRecorder.url in your code?
Hope this gives you some ideas.
Can the Above be Done using only AVFoundation.framework?
i Tried doing a few experiment but i can't really get it to work
(sorry newbie here please do tell me where to put the codes at Example: ViewController or the MainView)
These are my code,(UI Components are inside as i'm do not want use interface builder)
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
NSError *error;
buttonPlay = [[UILabel alloc] initWithFrame:CGRectMake(0, 350, 100, 50)];
buttonPlay.text = #"Play";
// Get the file path to the song to play.
NSString* path;
NSURL* url;
path = [[NSBundle mainBundle] pathForResource:#"Bring_Me_To_Life.mp3" ofType:nil];
url = [NSURL fileURLWithPath:path];
NSData *_objectData = [NSData dataWithContentsOfURL:url];
//Initialize the AVAudioPlayer.
audioPlayer = [[AVAudioPlayer alloc] initWithData:_objectData error:&error];
// Preloads the buffer and prepares the audio for playing.
audioPlayer.numberOfLoops = 0;
audioPlayer.volume = 1.0f;
[audioPlayer prepareToPlay];
if (audioPlayer == nil)
NSLog(#"%#", [error description]);
else
[audioPlayer play];
[self addSubview:buttonPlay];
}
return self;
}
-(void)touchesBegan:(CGPoint)location{
if(CGRectContainsPoint(buttonPlay.frame, location)){
NSLog(#"Dec from Chl %f",[audioPlayer peakPowerForChannel:0]);
NSLog(#"Length of Clip %f",[audioPlayer duration]);
}
}
try this
audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil];
I want to access music files which are available on the iPhone and get it listed (or) get the file into my iPhone application some delegats and start playing it. Is it possible to do it ? Similar to how we access images from device photo album using UIImagePickerController delegate methods.
Thank you!
You can reference MPMediaPickerController class. It functions same as UIImagePickerController class.
-(void)loadDeviceMusic{
MPMediaQuery *everything = [[MPMediaQuery alloc] init];
[everything addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:[NSNumber numberWithBool:NO] forProperty:MPMediaItemPropertyIsCloudItem]];
NSArray *itemsFromGenericQuery = [everything items];
for (MPMediaItem *song in itemsFromGenericQuery) {
NSURL *assetURL = [song valueForProperty:MPMediaItemPropertyAssetURL];
AVAsset *asset = [AVAsset assetWithURL:assetURL];
NSLog(#"SONGS URL=%#",assetURL);
if ([asset hasProtectedContent] == NO) {
MP3ObjectsClass *objMp3=[[MP3ObjectsClass alloc] init];
objMp3.mp3Url=[song valueForProperty:MPMediaItemPropertyAssetURL];
objMp3.mp3Duration=[song valueForProperty: MPMediaItemPropertyPlaybackDuration];
if([song valueForProperty: MPMediaItemPropertyTitle]){
objMp3.mp3Name=[song valueForProperty: MPMediaItemPropertyTitle];
}else{
objMp3.mp3Name=#"Unknown";
}
if([song valueForProperty: MPMediaItemPropertyArtist]){
objMp3.mp3ArtistName=[song valueForProperty: MPMediaItemPropertyArtist];
}else{
objMp3.mp3ArtistName=#"Unknown";
}
if([song valueForProperty: MPMediaItemPropertyAlbumTitle]){
objMp3.mp3AlbumTitle=[song valueForProperty: MPMediaItemPropertyAlbumTitle];
}else{
objMp3.mp3AlbumTitle=#"Unknown";
}
UIImage *mp3Image=[self getAlbumnArtWorkImage:assetURL];
if(mp3Image){
objMp3.mp3Image=mp3Image;
}else{
objMp3.mp3Image=[UIImage imageNamed:#"DefaultImage"];
}
[mp3Array addObject:objMp3];
}
}
}
-(UIImage *)getAlbumnArtWorkImage :(NSURL *)mp3Url{
AVAsset *asset = [AVURLAsset URLAssetWithURL:mp3Url options:nil];
UIImage *img = nil;
for (NSString *format in [asset availableMetadataFormats]) {
for (AVMetadataItem *item in [asset metadataForFormat:format]) {
if ([[item commonKey] isEqualToString:#"artwork"]) {
img = [UIImage imageWithData:[item.value copyWithZone:nil]];
}
}
}
return img;
}
*** MP3ObjectsClass is a NSObject class. Using above function you can access device music files from iPhone.
First import the AVFoundation/AVFoundation.h framework.
#import <AVFoundation/AVFoundation.h>
-(void)pickAudioFiles
{
MPMediaPickerController *soundPicker=[[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeAnyAudio];
soundPicker.delegate=self;
soundPicker.allowsPickingMultipleItems=NO;
[self presentViewController:soundPicker animated:YES completion:nil];
}
-(void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
MPMediaItem *item = [[mediaItemCollection items] objectAtIndex:0];
NSURL *url = [item valueForProperty:MPMediaItemPropertyAssetURL];
[mediaPicker dismissViewControllerAnimated:YES completion:nil];
AVPlayerItem *playerItem=[AVPlayerItem playerItemWithURL:url];
AVPlayer *player=[[AVPlayer alloc] initWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer=[AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame=CGRectMake(0, 0, 10, 10);
[self.view.layer addSublayer:playerLayer];
}
and play with [player play];
If you want to use AVAudioPlayer then import AudioToolbox/AudioToolbox.h
I am getting a memory leak when i click the play button....
I am testing with that "Leak" tool under "Run and performance tool"....on simulator
I am getting that leak when i click the play button first time.....
Here is my code....
-(IBAction)play
{
[self setPlayer];
[self playme];
}
-(IBAction)stop
{
[self stopme];
[self releasePlayer];
}
-(void)setPlayer
{
NSURL *file = [[NSURL alloc] initFileURLWithPath:
[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"shut up.mp3"]];
NSError *err = nil;
player = [[AVAudioPlayer alloc] initWithContentsOfURL:file error:&err];
[file release];
player.numberOfLoops = -1;
[player prepareToPlay];
player.volume=1.0;
}
-(void)playme
{
if (!isPlaying)
{
[player play];
isPlaying=YES;
}
}
-(void)stopme
{
if (isPlaying)
{
[player stop];
isPlaying=NO;
}
}
-(void)releasePlayer
{
if(!isPlaying)
{
[player release];
player=nil;
}
isPlaying=NO;
}
I think, the below statement is the source of memory leak,
player = [[AVAudioPlayer alloc] initWithContentsOfURL:file error:&err];
Here is the SO posts which has discussed the same issue.
AVAudioPlayer memory leak
AVAudioPlayer memory leak
AVAudioPlayer Memory Leak - Media Player Framework
Here is the blog post
AVAudioPlayer Memory Leak
EDITED:
As per the blog tutorial your code must be look like below.
-(void)setPlayer
{
NSURL *file = [[NSURL alloc] initFileURLWithPath:
[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"shut up.mp3"]];
NSError *err = nil;
NSData *data = [NSData dataWithContentsOfFile:file];
AVAudioPlayer *player = [AVAudioPlayer alloc];
if([player initWithData:audioData error:NULL])
{
player.numberOfLoops = -1;
[player prepareToPlay];
player.volume=1.0;
[player autorelease];
}
else
{
[player release];
player = nil;
}
[file release];
}
The leak-free version stores the pointer returned by alloc, rather than the pointer returned by initWithData:error:. That way, whatever happens, the player can still be released.
The blog post in Jhaliya's answer describes a leak that's specific to the situation when your player can't init the audio, for example when it can't find the file.
The real problem with your code is that you only release the player if the user explicitly stops the audio. If the audio plays through to the end, you have a player instance with a retainCount of 1. Then if the user hits play again, you create a new player and assign it to the player variable, leaking the old one.
The easiest solution to this is to make player a retained property:
#property(nonatomic,retain)AVAudioPlayer *player;
Then, instead of assigning to the ivar directly, use the mutator to set the player, which will implicitly release the previously set instance, if there is one:
[self setPlayer:[[[AVAudioPlayer alloc] initWithContentsOfURL:file error:&err] autorelease];
And don't forget to release it in your dealloc:
-(void)dealloc {
[player release];
[super dealloc];
}
I'm calling playSoundFromBundle from the code below to play sounds (aif files). I have a sound that does a single click and then a fading sound. Both sounds are in the same file. Sometimes I get two clicks and then the fade. Meaning, click, click...fade. A single click isn't what should play. I'm guessing the sound starts (click sound), gets interrupted and then restarts (full sound...click/fade) because of other processing that may be going on. It seems random when it will occur. I put the sound on its own thread to try and avoid the double clicking. Is there anything else I can do to ensure the sound plays correctly?
- (void) playSoundFromBundleThreaded:(NSArray*)arr{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *fileName = (NSString*)[arr objectAtIndex:0];
NSString *fileExt = (NSString*)[arr objectAtIndex:1];
NSError *err;
AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource: fileName ofType: fileExt inDirectory:#"/"]] error: &err];
self.audioPlayer = newPlayer;
self.audioPlayer.numberOfLoops = 0;
self.audioPlayer.volume = .5;
if (self.audioPlayer == nil)
{
NSLog(#"Problem initializing Sound - %#", [err description]);
}
else
{
[self.audioPlayer play];
}
[newPlayer release];
[pool release];
}
- (void) playSoundFromBundle:(NSString*)fileName fileExtension:(NSString*)fileExt{
NSArray *arr = [NSArray arrayWithObjects:fileName, fileExt, nil];
[NSThread detachNewThreadSelector: #selector(playSoundFromBundleThreaded:) toTarget:self withObject:arr];
}
No idea if this would work, but check out the Audio Session stuff. It's intended to manage things like whether the playing of other sounds (such as by the music player) will interrupt audio from your application.