21

I am building a MP3 player for iOS that plays audio files hosted on the web. I want to offer the ability to play the files offline so I have the files downloading using ASIHTTP but I am cannot seem to find info on initiallzing AVPlayer with a mp3 in the app documents directory. Has anyone done this before? Is it even possible?

*I posted an answer below that shows how to use the iOS AvPlayer for both local and http files. Hope this helps!

stitz
  • 1,429
  • 1
  • 16
  • 33

7 Answers7

36

I decided to answer my own question because I felt like there is very little documentation on how to use the Apple provided AVPlayer for both local and stream (over http) files. To help understand the solution, I put together a sample project on GitHub in Objective-C and Swift The code below is Objective-C but you can download my Swift example to see that. It is very similar!

What I found is that the two ways of setting up the files are almost identical except for how you instantiate your NSURL for the Asset > PlayerItem > AVPlayer chain.

Here is an outline of the core methods

.h file (partial code)

-(IBAction) BtnGoClick:(id)sender;
-(IBAction) BtnGoLocalClick:(id)sender;
-(IBAction) BtnPlay:(id)sender;
-(IBAction) BtnPause:(id)sender;
-(void) setupAVPlayerForURL: (NSURL*) url;

.m file (partial code)

-(IBAction) BtnGoClick:(id)sender {

    NSURL *url = [[NSURL alloc] initWithString:@""];

    [self setupAVPlayerForURL:url];
}

-(IBAction) BtnGoLocalClick:(id)sender {

    // - - - Pull media from documents folder

    //NSString* saveFileName = @"MyAudio.mp3";
    //NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    //NSString *documentsDirectory = [paths objectAtIndex:0];
    //NSString *path = [documentsDirectory stringByAppendingPathComponent:saveFileName];

    // - - -

    // - - - Pull media from resources folder

    NSString *path = [[NSBundle mainBundle] pathForResource:@"MyAudio" ofType:@"mp3"];

    // - - -

    NSURL *url = [[NSURL alloc] initFileURLWithPath: path];

    [self setupAVPlayerForURL:url];
}

-(void) setupAVPlayerForURL: (NSURL*) url {
    AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
    AVPlayerItem *anItem = [AVPlayerItem playerItemWithAsset:asset];

    player = [AVPlayer playerWithPlayerItem:anItem];
    [player addObserver:self forKeyPath:@"status" options:0 context:nil];
}


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if (object == player && [keyPath isEqualToString:@"status"]) {
        if (player.status == AVPlayerStatusFailed) {
            NSLog(@"AVPlayer Failed");
        } else if (player.status == AVPlayerStatusReadyToPlay) {
            NSLog(@"AVPlayer Ready to Play");
        } else if (player.status == AVPlayerItemStatusUnknown) {
            NSLog(@"AVPlayer Unknown");
        }
    }
}

-(IBAction) BtnPlay:(id)sender {
    [player play];
}

-(IBAction) BtnPause:(id)sender {
    [player pause];
}

Check out the Objective-C source code for a working example of this. Hope this helps!

-Update 12/7/2015 I now have a Swift example of the source code you can view here.

stitz
  • 1,429
  • 1
  • 16
  • 33
  • NSString *path = [[NSBundle mainBundle] pathForResource:@"MyAudio" ofType:@"mp3"]; can u explain this line please.. from where u r fetching mp3.. – ChenSmile Mar 04 '14 at 05:32
19

I got AVPlayer to work with local URL by prepending file:// to my local url

NSURL * localURL = [NSURL URLWithString:[@"file://" stringByAppendingString:YOUR_LOCAL_URL]];
AVPlayer * player = [[AVPlayer alloc] initWithURL:localURL];
Sudo
  • 991
  • 9
  • 24
  • This fixed my issue. Make sure to check if adding "file://" to the beginning of your path fixes the issue. – Jonathan Mar 12 '17 at 00:08
  • 1
    In stead of NSURL * localURL = [NSURL URLWithString:[@"file://" stringByAppendingString:YOUR_LOCAL_URL]]; you can write NSURL *localURL = [NSURL fileURLWithString:YOUR_LOCAL_URL]; – Martin Sep 07 '17 at 10:49
3

Try this

NSString*thePath=[[NSBundle mainBundle] pathForResource:@"yourVideo" ofType:@"MOV"];
NSURL*theurl=[NSURL fileURLWithPath:thePath];
ZaEeM ZaFaR
  • 1,508
  • 17
  • 22
3

Yes,thats possible to download and save the .mp3(or any kind of file)into NSDocument directory and then you can retrive from that and play by using AVAudioPlayer.

NSString *downloadURL=**your url to download .mp3 file**

NSURL *url = [NSURLURLWithString:downloadURL];

NSURLConnectionalloc *downloadFileConnection = [[[NSURLConnectionalloc] initWithRequest:      [NSURLRequestrequestWithURL:url] delegate:self] autorelease];//initialize NSURLConnection

NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,  YES) objectAtIndex:0];

NSString *fileDocPath = [NSStringstringWithFormat:@"%@/",docDir];//document directory path

[fileDocPathretain];

NSFileManager *filemanager=[ NSFileManager defaultManager ];

NSError *error;

if([filemanager fileExistsAtPath:fileDocPath])
{

//just check existence of files in document directory
}

NSURLConnection is used to download the content.NSURLConnection Delegate methods are used to  support downloading.

(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{

}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSFileManager *filemanager=[NSFileManagerdefaultManager];
if(![filemanager fileExistsAtPath:filePath])
{
[[NSFileManagerdefaultManager] createFileAtPath:fileDocPath contents:nil attributes:nil];

}
NSFileHandle *handle = [NSFileHandlefileHandleForWritingAtPath:filePath];

[handle seekToEndOfFile];

[handle writeData:data];

[handle closeFile];
 }

-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
 {
 UIAlertView *alertView=[[UIAlertViewalloc]initWithTitle:@”"message:
 [NSStringstringWithFormat:@"Connection failed!\n Error - %@ ", [error localizedDescription]]   delegate:nilcancelButtonTitle:@”Ok”otherButtonTitles:nil];
  [alertView show];
  [alertView release];
  [downloadFileConnectioncancel];//cancel downloding
  }

Retrieve the downloaded Audio and Play:

   NSString *docDir1 = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,   NSUserDomainMask, YES) objectAtIndex:0];

   NSString *myfilepath = [docDir1 stringByAppendingPathComponent:YourAudioNameinNSDOCDir];

   NSLog(@”url:%@”,myfilepath);

   NSURL *AudioURL = [[[NSURLalloc]initFileURLWithPath:myfilepath]autorelease];

Just write your code to play Audio by using AudioURL

I Like to know if u have any clarification in this regard.

Thank you

iphonecool
  • 99
  • 12
  • Thank you for the sample code. I need to use AVPlayer instead of AVAudioPlayer for this project. – stitz Mar 08 '12 at 12:26
  • Will it work fine if I'll try to play audio with AVPlayer immediately after starting request (or receiving first chunk)? What will happen if my download speed is not enough for realtime playback? – Pavel Alexeev Mar 24 '13 at 17:00
  • 1
    Hey @PavelAlexeev I'm trying to do a very similar thing now. Did you ever get something like this to work? – Joshua Book Feb 07 '15 at 00:48
  • No, but I've came across a library called “OrigamiEngine” – it is said to support HTTP data caching. Need to have a look on how it is implemented. – Pavel Alexeev Feb 07 '15 at 20:15
  • One more link: http://code4app.net/ios/NCMusicEngine-Pro/518657716803facd2d000000 – Pavel Alexeev Feb 10 '15 at 09:50
  • the question is about AVPlayer, NOT AVAudioPlayer – Fattie Feb 23 '20 at 12:44
1

Swift local playback version, assuming I have a file "shelter.mp3" in my bundle:

@IBAction func button(_ sender: Any?) {
    guard let url = Bundle.main.url(forResource: "shelter", withExtension: "mp3") else {
        return
    }

    let player = AVPlayer(url: url)

    player.play()
    playerView?.player = player;
}

See here for details about playerView or playing a remote url.

owenfi
  • 2,471
  • 1
  • 24
  • 37
0

What I have observed is that you need to have the proper file extension, else you will see a black screen with crossed out player icon. You need to add a file extension to your downloaded file like .m4v or .mov.

JuliusBahr
  • 91
  • 1
  • 3
0

its very difficult to play a song using a Avplayer why you are not use a MPMoviePlayerController player . i have play song from document directory .i am posting a code pls refer this .its working fine .and also u directly from url live .

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *publicDocumentsDir = [paths objectAtIndex:0];   
NSString *dataPath = [publicDocumentsDir stringByAppendingPathComponent:@"Ringtone"];
NSString *fullPath = [dataPath stringByAppendingPathComponent:[obj.DownloadArray objectAtIndex:obj.tagvalue]];
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO];


NSURL *url = [NSURL fileURLWithPath:fullPath];

videoPlayer =[[MPMoviePlayerController alloc] initWithContentURL: url];
[[videoPlayer view] setFrame: [self.view bounds]]; 
[vvideo addSubview: [videoPlayer view]];


videoPlayer.view.frame=CGRectMake(0, 0,260, 100);
videoPlayer.view.backgroundColor=[UIColor clearColor];
videoPlayer.controlStyle =   MPMovieControlStyleFullscreen;
videoPlayer.shouldAutoplay = YES;  
[videoPlayer play];
videoPlayer.repeatMode=YES;


NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(moviePlayerEvent:) name:MPMoviePlayerLoadStateDidChangeNotification object:videoPlayer];


/*  NSNotificationCenter *notificationCenter1 = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(moviePlayerEvent1:) name:MPMoviePlaybackStateStopped object:videoPlayer];
*/
[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(playbackStateChange:)
                                             name:MPMoviePlayerLoadStateDidChangeNotification
                                           object:videoPlayer];
}

-(void)playbackStateChange:(NSNotification*)notification{

if([[UIApplication sharedApplication]respondsToSelector:@selector(setStatusBarHidden: withAnimation:)])
  { 
      [[UIApplication sharedApplication] setStatusBarHidden:NO 
                                            withAnimation:UIStatusBarAnimationNone];
   }
  else 
   {

       [[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
   }
}

 -(void)moviePlayerEvent:(NSNotification*)aNotification{


   [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:NO];


}

  -(void)moviePlayerEvent1:(NSNotification*)aNotification{

[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:NO];

 }
parag
  • 657
  • 1
  • 6
  • 17
  • The MPMoviePlayerController class is deprecated in iOS 9. https://developer.apple.com/reference/mediaplayer/mpmovieplayercontroller – RenniePet Mar 11 '17 at 17:53