``` player = AVPlayer() player?.automaticallyWaitsToMinimizeStalling = false playerLayer = AVPlayerLayer(player: player) playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspect // add KVO observers and NotificationCenter observers player?.replaceCurrentItem(with: playerItem) playerStatusObserver = player?.observe(\.currentItem?.status, options: [.new, .old]) { [weak self] (player, change) in switch (player.status) { case .readyToPlay: DispatchQueue.main.async { // play video } case .failed, .unknown: print("Video Failed to Play") @unknown default: break } } playerRateObserver = player?.observe(\.rate, options: [.new, .old], changeHandler: { [weak self](player, change) in if player.rate == 1 { DispatchQueue.main.async { // if player isn't playing play it } } else { DispatchQueue.main.async { // is player is playing pause it } } }) playerTimeControlStatusObserver = player?.observe(\.timeControlStatus, options: [.new, .old]) { [weak self](player, change) in switch (player.timeControlStatus) { case .playing: DispatchQueue.main.async { [weak self] in // if player isn't playing pay it } case .paused: print("timeControlStatus is paused") // *** SOMETIMES PRINTS after .AVPlayerItemPlaybackStalled runs*** case .waitingToPlayAtSpecifiedRate: print("timeControlStatus- .waitingToPlayAtSpecifiedRate") if let reason = player.reasonForWaitingToPlay { switch reason { case .evaluatingBufferingRate: print("timeControlStatus- .evaluatingBufferingRate") // never prints case .toMinimizeStalls: print("timeControlStatus- .toMinimizeStalls") // never prints case .noItemToPlay: print("timeControlStatus- .noItemToPlay") // never prints default: print("Unknown \(reason)") } } @unknown default: break } } playbackLikelyToKeepUpObserver = player?.currentItem?.observe(\.isPlaybackLikelyToKeepUp, options: [.old, .new]) { (playerItem, change) in print("isPlaybackLikelyToKeepUp") // never prints } playbackBufferEmptyObserver = player?.currentItem?.observe(\.isPlaybackBufferEmpty, options: [.old, .new]) { (playerItem, change) in print("isPlaybackBufferEmpty") // never prints } playbackBufferFullObserver = player?.currentItem?.observe(\.isPlaybackBufferFull, options: [.old, .new]) { (playerItem, change) in print("isPlaybackBufferFull") // never prints } NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem) NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemFailedToPlayToEndTime(_:)), name: .AVPlayerItemFailedToPlayToEndTime, object: playerItem) NotificationCenter.default.addObserver(self, selector: #selector(playerItemNewError(_:)), name: .AVPlayerItemNewErrorLogEntry, object: playerItem) NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemPlaybackStalled(_:)), name: NSNotification.Name.AVPlayerItemPlaybackStalled, object: playerItem) @objc func playerItemDidReachEnd(_ notification: Notification) { // show replay button } @objc func playerItemFailedToPlayToEndTime(_ notification: Notification) { print("playerItemFailedToPlayToEndTime") // never prints if let error = notification.userInfo?["AVPlayerItemFailedToPlayToEndTime"] as? Error { print(error.localizedDescription) // never prints } } @objc func playerItemNewError(_ notification: Notification) { print("playerItemNewErrorLogEntry") // never prints } @objc func playerItemPlaybackStalled(_ notification: Notification) { print("playerItemPlaybackStalled") // *** PRINTS *** } ```