Created
October 3, 2017 03:38
-
-
Save jakebromberg/7fb7993eef17fe4f31b448713ed888bd to your computer and use it in GitHub Desktop.
Takes out some of the pain from loading an AVPlayer object.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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