AVPlayerItem.Status and AVPlayerItem classes provide
information about current state of player:
check if there is any error
current playback time
duration of video
state of buffer
check is a player ready to play
Get information about player
var player: AVPlayer? = AVPlayer(url: urlToMovie)
var currentPlaybackTime: TimeInterval {
get {
guard let item = player?.currentItem else {
return 0
}
return CMTimeGetSeconds(item.currentTime())
}
}
var duration: TimeInterval {
get {
guard let item = player?.currentItem else {
return 0
}
return CMTimeGetSeconds(item.duration)
}
}
var isError: Bool {
get {
guard let error = player?.currentItem?.error as NSError? else {
return false
}
return true // 500 ... 599 ~= error.code for server error
}
}
var isBufferEmpty: Bool {
get {
guard let isBufferEmpty = player?.currentItem?.isPlaybackBufferEmpty else {
return true
}
return isBufferEmpty
}
}
var isBufferFull: Bool {
guard let isBufferFull = player?.currentItem?.isPlaybackBufferFull else {
return false
}
return isBufferFull
}
var isPlaybackLikelyToKeepUp: Bool {
get {
guard let isPlaybackLikelyToKeepUp = player?.currentItem?.isPlaybackLikelyToKeepUp else {
return true
}
return isPlaybackLikelyToKeepUp
}
}
You can observe these values using KVO.
Observe player events with KVO
private static var context = 1
private func subscribeToPlayer() {
guard let player = player else { return }
player.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem.status), options: [.new, .initial], context: &AVPlayerHelper.context)
player.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: [.new], context: &AVPlayerHelper.context)
player.addObserver(self, forKeyPath: #keyPath(AVPlayer.actionAtItemEnd), options: [.new], context: &AVPlayerHelper.context)
player.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem.isPlaybackLikelyToKeepUp),
options: [.new], context: &AVPlayerHelper.context)
// ...
}
override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath else { return }
guard context == &AVPlayerHelper.context else { return }
switch keyPath {
case #keyPath(AVPlayer.currentItem.isPlaybackLikelyToKeepUp):
guard let isPlaybackLikelyToKeepUp = player?.currentItem?.isPlaybackLikelyToKeepUp else { return }
delegate?.handleVideoLoadingChanged(playerHelper: self, isBufferEmpty: isBufferEmpty)
case #keyPath(AVPlayer.currentItem.status):
if let newStatus = change.map({ $0[.newKey] as? NSNumber }), let parsedNewStatus = newStatus.map({ AVPlayerItem.Status(rawValue: $0.intValue) ?? .unknown}) {
currentItemStatus = parsedNewStatus
switch currentItemStatus {
case .failed:
if let error = player?.currentItem?.error as NSError? {
if error.code == 500 {
delegate?.handleServerError(playerHelper: self)
} else {
delegate?.handleInternetConnectionError(playerHelper: self)
}
}
default:
break
}
} else {
currentItemStatus = .unknown
}
delegate?.handleStatusChanged(playerHelper: self, oldStatus: oldStatus, newStatus: currentItemStatus)
default: break
}
}
current playback time
AVPlayer object allows to add the observer to observe a current
play time. This is useful for changing the value of the slider that indicates the current playback time.
Don't forget to remove the observer when it's not needed.
observe a current play time
class AVPlayerHelper: NSObject {
private(set) var player: AVPlayer?
private(set) var currentTime: CMTime?
var delegate: AVPlayerHelperProtocol?
private var timeObserverToken: Any?
// ...
private func addPeriodicTimeObserver() {
guard timeObserverToken == nil else { return }
let timeScale = CMTimeScale(NSEC_PER_SEC)
let time = CMTime(seconds: 0.03, preferredTimescale: timeScale)
timeObserverToken = player?.addPeriodicTimeObserver(forInterval: time,
queue: .main,
using: { [weak self] time in
guard let helper = self else { return }
// for example, the delegate can change the slider
// to the appropriate value
helper.delegate?.handleTimeChanged(playerHelper: helper,
timeInSeconds: CMTimeGetSeconds(time))
})
}
func open(urlResolved: URL) {
close()
player = AVPlayer(url: urlResolved)
addPeriodicTimeObserver()
// ...
}
// don't forget to remove a time observer
func close() {
player?.pause()
if let timeObserverToken = timeObserverToken {
player?.removeTimeObserver(timeObserverToken)
self.timeObserverToken = nil
}
// ...
}
}
Use the current item object of player to get duration of video.
var duration: TimeInterval {
get {
guard let item = player?.currentItem else {
return 0
}
return CMTimeGetSeconds(item.duration)
}
}