import AVFoundation import AVKit import Foundation @objc protocol RCTPlayerObserverHandlerObjc { func handleDidFailToFinishPlaying(notification:NSNotification!) func handlePlaybackStalled(notification:NSNotification!) func handlePlayerItemDidReachEnd(notification:NSNotification!) // unused // func handleAVPlayerAccess(notification:NSNotification!) } protocol RCTPlayerObserverHandler: RCTPlayerObserverHandlerObjc { func handleTimeUpdate(time:CMTime) func handleReadyForDisplay(changeObject: Any, change:NSKeyValueObservedChange) func handleTimeMetadataChange(playerItem:AVPlayerItem, change:NSKeyValueObservedChange<[AVMetadataItem]?>) func handlePlayerItemStatusChange(playerItem:AVPlayerItem, change:NSKeyValueObservedChange) func handlePlaybackBufferKeyEmpty(playerItem:AVPlayerItem, change:NSKeyValueObservedChange) func handlePlaybackLikelyToKeepUp(playerItem:AVPlayerItem, change:NSKeyValueObservedChange) func handlePlaybackRateChange(player: AVPlayer, change: NSKeyValueObservedChange) func handleExternalPlaybackActiveChange(player: AVPlayer, change: NSKeyValueObservedChange) func handleViewControllerOverlayViewFrameChange(overlayView:UIView, change:NSKeyValueObservedChange) } class RCTPlayerObserver: NSObject { var _handlers: RCTPlayerObserverHandler! var player:AVPlayer? { willSet { removePlayerObservers() removePlayerTimeObserver() } didSet { if player != nil { addPlayerObservers() addPlayerTimeObserver() } } } var playerItem:AVPlayerItem? { willSet { removePlayerItemObservers() } didSet { if playerItem != nil { addPlayerItemObservers() } } } var playerViewController:AVPlayerViewController? { willSet { removePlayerViewControllerObservers() } didSet { if playerViewController != nil { addPlayerViewControllerObservers() } } } var playerLayer:AVPlayerLayer? { willSet { removePlayerLayerObserver() } didSet { if playerLayer == nil { addPlayerLayerObserver() } } } private var _progressUpdateInterval:TimeInterval = 250 private var _timeObserver:Any? private var _playerRateChangeObserver:NSKeyValueObservation? private var _playerExpernalPlaybackActiveObserver:NSKeyValueObservation? private var _playerItemStatusObserver:NSKeyValueObservation? private var _playerPlaybackBufferEmptyObserver:NSKeyValueObservation? private var _playerPlaybackLikelyToKeepUpObserver:NSKeyValueObservation? private var _playerTimedMetadataObserver:NSKeyValueObservation? private var _playerViewControllerReadyForDisplayObserver:NSKeyValueObservation? private var _playerLayerReadyForDisplayObserver:NSKeyValueObservation? private var _playerViewControllerOverlayFrameObserver:NSKeyValueObservation? deinit { NotificationCenter.default.removeObserver(_handlers) } func addPlayerObservers() { guard let player = player else { return } _playerRateChangeObserver = player.observe(\.rate, changeHandler: _handlers.handlePlaybackRateChange) _playerExpernalPlaybackActiveObserver = player.observe(\.isExternalPlaybackActive, changeHandler: _handlers.handleExternalPlaybackActiveChange) } func removePlayerObservers() { _playerRateChangeObserver?.invalidate() _playerExpernalPlaybackActiveObserver?.invalidate() } func addPlayerItemObservers() { guard let playerItem = playerItem else { return } _playerItemStatusObserver = playerItem.observe(\.status, options: [.new, .old], changeHandler: _handlers.handlePlayerItemStatusChange) _playerPlaybackBufferEmptyObserver = playerItem.observe(\.isPlaybackBufferEmpty, options: [.new, .old], changeHandler: _handlers.handlePlaybackBufferKeyEmpty) _playerPlaybackLikelyToKeepUpObserver = playerItem.observe(\.isPlaybackLikelyToKeepUp, options: [.new, .old], changeHandler: _handlers.handlePlaybackLikelyToKeepUp) _playerTimedMetadataObserver = playerItem.observe(\.timedMetadata, options: [.new], changeHandler: _handlers.handleTimeMetadataChange) } func removePlayerItemObservers() { _playerItemStatusObserver?.invalidate() _playerPlaybackBufferEmptyObserver?.invalidate() _playerPlaybackLikelyToKeepUpObserver?.invalidate() _playerTimedMetadataObserver?.invalidate() } func addPlayerViewControllerObservers() { guard let playerViewController = playerViewController else { return } _playerViewControllerReadyForDisplayObserver = playerViewController.observe(\.isReadyForDisplay, options: [.new], changeHandler: _handlers.handleReadyForDisplay) _playerViewControllerOverlayFrameObserver = playerViewController.contentOverlayView?.observe(\.frame, options: [.new, .old], changeHandler: _handlers.handleViewControllerOverlayViewFrameChange) } func removePlayerViewControllerObservers() { _playerViewControllerReadyForDisplayObserver?.invalidate() _playerViewControllerOverlayFrameObserver?.invalidate() } func addPlayerLayerObserver() { _playerLayerReadyForDisplayObserver = playerLayer?.observe(\.isReadyForDisplay, options: [.new], changeHandler: _handlers.handleReadyForDisplay) } func removePlayerLayerObserver() { _playerLayerReadyForDisplayObserver?.invalidate() } func addPlayerTimeObserver() { removePlayerTimeObserver() let progressUpdateIntervalMS:Float64 = _progressUpdateInterval / 1000 // @see endScrubbing in AVPlayerDemoPlaybackViewController.m // of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html _timeObserver = player?.addPeriodicTimeObserver( forInterval: CMTimeMakeWithSeconds(progressUpdateIntervalMS, preferredTimescale: Int32(NSEC_PER_SEC)), queue:nil, using:_handlers.handleTimeUpdate ) } /* Cancels the previously registered time observer. */ func removePlayerTimeObserver() { if _timeObserver != nil { player?.removeTimeObserver(_timeObserver) _timeObserver = nil } } func addTimeObserverIfNotSet() { if (_timeObserver == nil) { addPlayerTimeObserver() } } func replaceTimeObserverIfSet(_ newUpdateInterval:Float64? = nil) { if let newUpdateInterval = newUpdateInterval { _progressUpdateInterval = newUpdateInterval } if (_timeObserver != nil) { addPlayerTimeObserver() } } func attachPlayerEventListeners() { NotificationCenter.default.removeObserver(_handlers, name:NSNotification.Name.AVPlayerItemDidPlayToEndTime, object:player?.currentItem) NotificationCenter.default.addObserver(_handlers, selector:#selector(RCTPlayerObserverHandler.handlePlayerItemDidReachEnd(notification:)), name:NSNotification.Name.AVPlayerItemDidPlayToEndTime, object:player?.currentItem) NotificationCenter.default.removeObserver(_handlers, name:NSNotification.Name.AVPlayerItemPlaybackStalled, object:nil) NotificationCenter.default.addObserver(_handlers, selector:#selector(RCTPlayerObserverHandler.handlePlaybackStalled(notification:)), name:NSNotification.Name.AVPlayerItemPlaybackStalled, object:nil) NotificationCenter.default.removeObserver(_handlers, name: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime, object:nil) NotificationCenter.default.addObserver(_handlers, selector:#selector(RCTPlayerObserverHandler.handleDidFailToFinishPlaying(notification:)), name: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime, object:nil) } func clearPlayer() { player = nil playerItem = nil NotificationCenter.default.removeObserver(_handlers) } }