Last active
January 16, 2025 11:37
-
-
Save MaximKotliar/c8b628ff9c7644b596711152594e1024 to your computer and use it in GitHub Desktop.
A easier way to handle AVPlayer's events
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
// 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 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
.AVPlayerItemTimeJumped
has now changed toAVPlayerItem.timeJumpedNotification
FYI.