2022-07-08 14:30:49 +02:00

122 lines
4.7 KiB
Swift

import Foundation
import AVFoundation
import SwiftSignalKit
import UniversalMediaPlayer
import AccountContext
import AVKit
public class ExternalVideoPlayer: NSObject, AVRoutePickerViewDelegate {
private let context: AccountContext
let content: NativeVideoContent
let player: AVPlayer?
private var didPlayToEndTimeObserver: NSObjectProtocol?
private var timeObserver: Any?
private var statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .buffering(initial: true, whilePlaying: false, progress: 0.0, display: true), soundEnabled: true)
private let _status = ValuePromise<MediaPlayerStatus>()
var status: Signal<MediaPlayerStatus, NoError> {
return self._status.get()
}
private var seekId: Int = 0
private weak var routePickerView: UIView?
public var isActiveUpdated: (Bool) -> Void = { _ in }
public init(context: AccountContext, content: NativeVideoContent) {
self.context = context
self.content = content
if let path = context.account.postbox.mediaBox.completedResourcePath(content.fileReference.media.resource, pathExtension: "mp4") {
let player = AVPlayer(url: URL(fileURLWithPath: path))
self.player = player
} else {
self.player = nil
}
super.init()
self.startObservingForAirPlayStatusChanges()
self.isActiveUpdated(self.player?.isExternalPlaybackActive ?? false)
if let player = self.player {
self.didPlayToEndTimeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: nil, using: { [weak self] notification in
if let strongSelf = self {
strongSelf.player?.seek(to: CMTime(seconds: 0.0, preferredTimescale: 30))
strongSelf.play()
}
})
self.timeObserver = player.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 10), queue: DispatchQueue.main) { [weak self] time in
guard let strongSelf = self else {
return
}
strongSelf.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: strongSelf.statusValue.duration, dimensions: CGSize(), timestamp: CMTimeGetSeconds(time), baseRate: 1.0, seekId: strongSelf.seekId, status: strongSelf.statusValue.status, soundEnabled: true)
strongSelf._status.set(strongSelf.statusValue)
}
}
self._status.set(self.statusValue)
}
deinit {
if let timeObserver = self.timeObserver {
self.player?.removeTimeObserver(timeObserver)
}
if let didPlayToEndTimeObserver = self.didPlayToEndTimeObserver {
NotificationCenter.default.removeObserver(didPlayToEndTimeObserver)
}
self.stopObservingForAirPlayStatusChanges()
}
public func play() {
self.player?.play()
}
public func openRouteSelection() {
if #available(iOS 11.0, *) {
let routePickerView = AVRoutePickerView()
routePickerView.delegate = self
if #available(iOS 13.0, *) {
routePickerView.prioritizesVideoDevices = true
}
self.context.sharedContext.mainWindow?.viewController?.view.addSubview(routePickerView)
if let routePickerButton = routePickerView.subviews.first(where: { $0 is UIButton }) as? UIButton {
routePickerButton.sendActions(for: .touchUpInside)
}
}
}
@available(iOS 11.0, *)
public func routePickerViewDidEndPresentingRoutes(_ routePickerView: AVRoutePickerView) {
routePickerView.removeFromSuperview()
self.play()
}
private var observerContextAirplay = 1
func startObservingForAirPlayStatusChanges()
{
self.player?.addObserver(self, forKeyPath: #keyPath(AVPlayer.isExternalPlaybackActive), options: .new, context: &observerContextAirplay)
}
func stopObservingForAirPlayStatusChanges()
{
self.player?.removeObserver(self, forKeyPath: #keyPath(AVPlayer.isExternalPlaybackActive))
}
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &observerContextAirplay {
self.isActiveUpdated(self.player?.isExternalPlaybackActive ?? false)
}
else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}