Reputation: 6610
I'm trying to download & play remote mp3s.
Basically:
Everything works almost fine - the download is performed by AFHTTPRequestOperation
which is downloading the mp3 directly into a file and I get progress updates every 1000 Bytes or so.
The problem is I don't know exactly when to start playback via
AVAudioPlayer
.
AVAudioPlayer
can play unfinished mp3, but if there's not enough data it fires fatalError (EXC_BREAKPOINT) and my app crashes
- it doesn't even return an error.
I know in advance the duration of the mp3 from which I can determine approx. how many seconds of playback are downloaded like - (sizeDownloaded/totalSize)*duration
, but that's just silly and sometimes it fails (I'm guessing thanks to the fact that there's ID3 Tag in the beginning of the file and it can contain huge images my approximation is pointless).
My current code:
op.setDownloadProgressBlock({bytesRead, totalBytesRead, totalBytesExpectedToRead in
let approxBytesForOneSecond = totalBytesExpectedToRead/Int64(downloadableItem.length)
let approxSecondsDownloaded = totalBytesRead/approxBytesForOneSecond
if approxSecondsDownloaded > kMinimumSecondsDownloadedForPlay { //min. secs = 5
var error: NSError?
self.player = AVAudioPlayer(contentsOfURL: url, error: &error) // This is the line where I sometimes get EXC_Breakpoint. The reason for this is that the mp3 is not playable yet ...
if (error != nil) {
// Error initializing item - never called
} else {
// Item initialized OK -> Play
}
}
})
So, is there's some solid way of finding out whether the file is playable or not?
I know that raising the limit for playback start would help, it just doesn't seem to be right. And "wait until download is finished" is not an answer... :)
Thanks a lot!
Upvotes: 2
Views: 1418
Reputation: 6610
I figured it out. This function returns error, if file is not playable (for whatever reason). In my tests it successfully returned false for incomplete files with absolutely no audio data, and true if there was at least something to play (no matter if the file wasn't complete). In my opinion this is the same function that is being used by AVAudioPlayer/AVPlayer. It's in Obj-C and i haven't rewrote it in swift yet as I wasn't sure if it won't be leaky...
+ (BOOL) isFilePlayable:(NSURL*)url {
// Try opening audiofile -> if it's playable it will open, if not, it will return error
AudioFileID audioFileID;;
OSErr err;
err = AudioFileOpenURL((__bridge CFURLRef)url,
kAudioFileReadPermission,
0,
&audioFileID);
if (err != noErr) {
NSLog(@"Couldn't open audio file...");
return NO;
}
AudioFileClose(audioFileID);
return YES;
}
Upvotes: 1
Reputation: 36072
Knowing the bitrate of the mp3 file would let you estimate how much of the file you need to be able to safely begin playback, BUT who knows what AVAudioPlayer
will do if its file changes after it has opened it?
If you don't need to keep the mp3 once you've finished playing it, you could switch from AVAudioPlayer
, which can only play local files, to AVPlayer
which can also play files streamed from the internet:
var url = NSURL(string:"http://YOUR_URL")
player = AVPlayer(URL:url)
player.play()
It will play as soon as enough data is available for sustained playback.
If you want to keep the streamed bytes that AVPlayer
is playing, things get more complicated, but it can be achieved via AVAssetResourceLoader
as detailed in this blog post
UPDATE
If you want to download multiple files in the background, do it! You can still hand the data off to AVPlayer
via AVAssetResourceLoader
and AVPlayer
will decide when it has enough data for playback (is that what you mean by playable?)
Upvotes: 1