AudioPlayer
AudioPlayer copied to clipboard
Preload next song in queue
It would be great if the next song in the queue was automatically buffered for gapless playback between tracks.
Hi @smkhalsa Someone already opened an issue for this feature. I'm going to let it open for now but unfortunately that's definitely not a priority to me and honestly it feels like a lot of complexity to add. I'll think about it :)
I agree with @delannoyk . It would have been easier if we used AVAudioItem instead of custom AudioItem. I will try it out and see if we can make an easy change. I won't consider performance in my solution though :)
@smkhalsa and @delannoyk .. What do you guys think of below implementation? I have done these changes in my project files only
Add below line in AudioItem.swift
public dynamic var data: AVAsset?
In AudioPlayer.swift, replace below line
player = AVPlayer(URL: URLInfo.URL)
with
if currentItem.data != nil {
player = AVPlayer(playerItem: AVPlayerItem(asset: currentItem.data!))
if let currentItemDuration = currentItemDuration where currentItemDuration > 0 {
updateNowPlayingInfoCenter()
delegate?.audioPlayer(self, didFindDuration: currentItemDuration, forItem: currentItem)
}
}
else {
player = AVPlayer(URL: URLInfo.URL)
}
let nextIndex = currentItemIndexInQueue! + 1
if (enqueuedItems?.count)! > nextIndex {
let temp = enqueuedItems![currentItemIndexInQueue!+1].item
if temp.data == nil {
preload(temp, index: nextIndex)
}
}
else if enqueuedItems![0].item.data == nil {
preload(enqueuedItems![0].item,index: 0)
}
Then add below function to the same file.. add anywhere inside player class. Haven't done rigorous testing, but this might work temporarily with minimal changes. As I mentioned above, haven't considered any performance or coding standards. I just wanted to think through of implementation :)
func preload(song: AudioItem, index: Int) {
let asset = AVURLAsset(URL: song.highestQualityURL.URL)
let keys = ["playable","tracks","duration"]
asset.loadValuesAsynchronouslyForKeys(keys) {
for key in keys {
let status = asset.statusOfValueForKey(key, error: nil)
if status == AVKeyValueStatus.Failed {
return
}
}
dispatch_async(dispatch_get_main_queue()) {
song.data = asset
self.enqueuedItems?[index].item = song
}
}
}
To be short the logic is, when you start playing an item, if there is another item in queue, then I will initialize the asset and store that along with AudioItem. When that item plays and if asset data exists for it, then I will use that instead of URL to send to player. For testing I have added high quality urls only so that's why you will see that in AVURLAsset creation.
Hi @han9over
Thank you for the leads in this! Did you get a chance to test it? I haven't checked yet but is there an opportunity to cancel a preloading-request? I'm wondering how the queue would be handled in case it changed suddenly.
In anyway, I'm currently working on some refactoring so I'll get a closer look a bit later.
Hey @delannoyk
I have tested it with my setup and it works well. Currently, i haven't looked into canceling preloading. I'm not preloading the whole queue, it just preloads the next song only. So, if the next song is removed, the preloaded data gets removed along with it.
I know there is lot of work to be done on this, my initiative was to provide some thoughts or ideas :)
by the way, not sure which city you are in, hope you are not near by or affected by those attacks? Be safe and take care.
Ok, I'll look take a closer look in the next few days then. Thanks for you leads.
Yep those events suck. I live about an hour drive away from Brussels so I'm safe, thanks!
good to hear.. thanks :)
Any updates on this? Thinking it might be time for me to move my project away from HysteriaPlayer, but gapless is important to me.