animal_chin
animal_chin

Reputation: 6610

Check if mp3 is playable in swift

I'm trying to download & play remote mp3s.

Basically:

  1. User clicks the mp3,
  2. Download starts on background
  3. and then, when it's possible, the playback starts.

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

Answers (2)

animal_chin
animal_chin

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

Gordon Childs
Gordon Childs

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

Related Questions