Skip to content

Instantly share code, notes, and snippets.

@MaximKotliar
Last active November 28, 2022 18:33
Show Gist options
  • Save MaximKotliar/c8b628ff9c7644b596711152594e1024 to your computer and use it in GitHub Desktop.
Save MaximKotliar/c8b628ff9c7644b596711152594e1024 to your computer and use it in GitHub Desktop.
A easier way to handle AVPlayer's events
// AVPlayerDelegate.swift
//
// Created by Maxim Kotliar on 7/30/18.
//
import AVFoundation
@objc protocol AVPlayerDelegate: class {
@objc optional func playerDidStartPlayback()
@objc optional func playerDidPausePlayback()
@objc optional func playerDidChangeCurrentItem(to item: AVPlayerItem?)
@objc optional func playerItemTimeJumped(item: AVPlayerItem)
@objc optional func playerItemPlaybackStalled(item: AVPlayerItem)
@objc optional func playerItemDidPlayToEndTime(item: AVPlayerItem)
@objc optional func playerItemPlayToEndTimeFailed(item: AVPlayerItem)
}
private let notificationNames: [Notification.Name] = [.AVPlayerItemTimeJumped,
.AVPlayerItemPlaybackStalled,
.AVPlayerItemDidPlayToEndTime,
.AVPlayerItemFailedToPlayToEndTime]
extension AVPlayer {
private struct AssociatedKey {
static var delegate: UInt8 = 0
static var currentItem: UInt8 = 0
static var rate: UInt8 = 0
}
var delegate: AVPlayerDelegate? {
get {
return objc_getAssociatedObject(self, &AssociatedKey.delegate) as? AVPlayerDelegate
}
set {
objc_setAssociatedObject(self, &AssociatedKey.delegate, newValue, .OBJC_ASSOCIATION_ASSIGN)
setupObservers()
}
}
private func setupObservers() {
let notificationCenter = NotificationCenter.default
notificationNames.forEach {
guard delegate != nil else {
notificationCenter.removeObserver(self, name: $0, object: nil)
return
}
notificationCenter.addObserver(self,
selector: #selector(handleNotification(_:)),
name: $0,
object: nil)
}
switch delegate {
case .some:
currentItemObserver = observe(\.currentItem, options: [.new]) { [weak self] _, change in
guard let item = change.newValue else { return }
self?.delegate?.playerDidChangeCurrentItem?(to: item)
}
rateObserver = observe(\.rate, options: [.old, .new]) { [weak self] _, change in
guard let oldRate = change.oldValue else { return }
guard let newRate = change.newValue else { return }
if oldRate != 0 && newRate == 0 {
self?.delegate?.playerDidPausePlayback?()
} else if oldRate == 0 && newRate != 0 {
self?.delegate?.playerDidStartPlayback?()
}
}
case .none:
currentItemObserver = nil
rateObserver = nil
}
}
private var currentItemObserver: NSKeyValueObservation? {
get {
return objc_getAssociatedObject(self, &AssociatedKey.currentItem) as? NSKeyValueObservation
}
set {
currentItemObserver.flatMap { $0.invalidate() }
objc_setAssociatedObject(self, &AssociatedKey.currentItem, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
private var rateObserver: NSKeyValueObservation? {
get {
return objc_getAssociatedObject(self, &AssociatedKey.rate) as? NSKeyValueObservation
}
set {
rateObserver.flatMap { $0.invalidate() }
objc_setAssociatedObject(self, &AssociatedKey.rate, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
@objc private func handleNotification(_ notification: Notification) {
guard let item = notification.object as? AVPlayerItem else { return }
guard item == currentItem else { return }
switch notification.name {
case .AVPlayerItemTimeJumped:
delegate?.playerItemTimeJumped?(item: item)
case .AVPlayerItemPlaybackStalled:
delegate?.playerItemPlaybackStalled?(item: item)
case .AVPlayerItemDidPlayToEndTime:
delegate?.playerItemDidPlayToEndTime?(item: item)
case .AVPlayerItemFailedToPlayToEndTime:
delegate?.playerItemPlayToEndTimeFailed?(item: item)
default:
return
}
}
func cleanupObservers() {
notificationNames.forEach { name in
NotificationCenter.default.removeObserver(self, name: name, object: nil)
}
currentItemObserver = nil
rateObserver = nil
}
}
@divyeshmakwana96
Copy link

.AVPlayerItemTimeJumped has now changed to AVPlayerItem.timeJumpedNotification FYI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment