Skip to content

Instantly share code, notes, and snippets.

@jakebromberg
Created October 3, 2017 03:38
Show Gist options
  • Save jakebromberg/7fb7993eef17fe4f31b448713ed888bd to your computer and use it in GitHub Desktop.
Save jakebromberg/7fb7993eef17fe4f31b448713ed888bd to your computer and use it in GitHub Desktop.
Takes out some of the pain from loading an AVPlayer object.
import AVFoundation
/// The `PlayerLoader` class provides a callback mechanism for consumers of `AVPlayer` when loading a resource.
/// AVFoundation works by an inconvenient series of APIs that rely on KVO for asynchronous operations. This class
/// eliminates the boilerplate that KVO imposes and makes the callsite much more clean.
public final class PlayerLoader: NSObject {
public typealias Callback = (AVPlayer) -> ()
/// The callback will initialize to a non-nil value. The callback is set to nil once it's invoked, at which time the
/// KVO observation is removed. This is necessary to avoid double-removing the KVO observation.
private var callback: Callback?
private let player: AVPlayer
// This is declared `var` because we have to take its pointer value
private static var observationContext = NSObject()
private let observationKeypath = #keyPath(AVPlayer.currentItem.status)
init(player: AVPlayer, callback: @escaping Callback) {
self.callback = callback
self.player = player
super.init()
if self.player.currentItem?.status == AVPlayerItemStatus.readyToPlay {
self.callback?(self.player)
} else {
self.player.addObserver(
self,
forKeyPath: self.observationKeypath,
options: [NSKeyValueObservingOptions.new],
context: &PlayerLoader.observationContext
)
}
}
public convenience init(url: URL, callback: @escaping Callback) {
self.init(player: AVPlayer(url: url), callback: callback)
}
override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let context = context, context == &PlayerLoader.observationContext else {
return
}
guard let keyPath = keyPath, keyPath == self.observationKeypath else {
return
}
guard let currentItem = self.player.currentItem else {
return
}
guard !CMTimeGetSeconds(currentItem.duration).isNaN else {
return
}
guard currentItem.status == AVPlayerItemStatus.readyToPlay else {
return
}
self.player.removeObserver(self, forKeyPath: self.observationKeypath)
self.player.preroll(atRate: 1.0) { _ in
self.callback?(self.player)
self.callback = nil
}
}
deinit {
// This is really awkward but is one of the reasons why this class exists in the first place.
if self.callback != nil {
self.player.removeObserver(self, forKeyPath: self.observationKeypath)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment