// // VideoGalleryContentViewController.swift // Tusker // // Created by Shadowfacts on 3/19/24. // Copyright © 2024 Shadowfacts. All rights reserved. // import UIKit import GalleryVC import AVFoundation class VideoGalleryContentViewController: UIViewController, GalleryContentViewController { private let url: URL let caption: String? private let item: AVPlayerItem let player: AVPlayer @available(iOS, obsoleted: 16.0, message: "Use AVPlayer.defaultRate") @Box private var playbackSpeed: Float = 1 private var presentationSizeObservation: NSKeyValueObservation? private var statusObservation: NSKeyValueObservation? private var rateObservation: NSKeyValueObservation? private var isFirstAppearance = true private var hideControlsWorkItem: DispatchWorkItem? init(url: URL, caption: String?) { self.url = url self.caption = caption let asset = AVAsset(url: url) self.item = AVPlayerItem(asset: asset) self.player = AVPlayer(playerItem: item) super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() container?.setGalleryContentLoading(true) let playerView = PlayerView(item: item, player: player) playerView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(playerView) NSLayoutConstraint.activate([ playerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), playerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), playerView.topAnchor.constraint(equalTo: view.topAnchor), playerView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) presentationSizeObservation = item.observe(\.presentationSize, changeHandler: { [unowned self] item, _ in MainActor.runUnsafely { self.preferredContentSize = item.presentationSize self.container?.galleryContentChanged() } }) statusObservation = item.observe(\.status, changeHandler: { [unowned self] item, _ in MainActor.runUnsafely { if item.status == .readyToPlay { self.container?.setGalleryContentLoading(false) statusObservation = nil } } }) rateObservation = player.observe(\.rate, options: .old, changeHandler: { [unowned self] player, info in hideControlsWorkItem?.cancel() if player.rate > 0 && info.oldValue == 0 { hideControlsWorkItem = DispatchWorkItem { [weak self] in guard let self, let container = self.container, container.galleryControlsVisible else { return } container.setGalleryControlsVisible(false, animated: true) } DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5), execute: hideControlsWorkItem!) } }) preferredContentSize = item.presentationSize } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if isFirstAppearance { isFirstAppearance = false player.play() } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) player.pause() } // MARK: GalleryContentViewController var container: (any GalleryVC.GalleryContentViewControllerContainer)? var contentSize: CGSize { item.presentationSize } var activityItemsForSharing: [Any] { [VideoActivityItemSource(asset: item.asset, url: url)] } private lazy var overlayVC = VideoOverlayViewController(player: player, playbackSpeed: _playbackSpeed) var contentOverlayAccessoryViewController: UIViewController? { overlayVC } private(set) lazy var bottomControlsAccessoryViewController: UIViewController? = VideoControlsViewController(player: player, playbackSpeed: _playbackSpeed) func setControlsVisible(_ visible: Bool, animated: Bool) { overlayVC.setVisible(visible) hideControlsWorkItem?.cancel() } } private class PlayerView: UIView { override class var layerClass: AnyClass { AVPlayerLayer.self } private var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer } private let player: AVPlayer private var presentationSizeObservation: NSKeyValueObservation? override var intrinsicContentSize: CGSize { player.currentItem?.presentationSize ?? CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric) } init(item: AVPlayerItem, player: AVPlayer) { self.player = player super.init(frame: .zero) playerLayer.player = player playerLayer.videoGravity = .resizeAspect presentationSizeObservation = item.observe(\.presentationSize, changeHandler: { [unowned self] _, _ in MainActor.runUnsafely { self.invalidateIntrinsicContentSize() } }) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }