diff --git a/ImageViewer/Source/GalleryConfiguration.swift b/ImageViewer/Source/GalleryConfiguration.swift index f6ad0e13..a5f55a62 100644 --- a/ImageViewer/Source/GalleryConfiguration.swift +++ b/ImageViewer/Source/GalleryConfiguration.swift @@ -7,6 +7,7 @@ // import UIKit +import AVFoundation public typealias GalleryConfiguration = [GalleryConfigurationItem] @@ -153,11 +154,14 @@ public enum GalleryConfigurationItem { ///Allows the video player to automatically continue playing the next video case continuePlayVideoOnEnd(Bool) - ///Allows auto play video after gallery presented + ///Allows auto play video at progress after gallery presented case videoAutoPlay(Bool) - + ///Tint color of video controls case videoControlsColor(UIColor) + + /// Change how the video is displayed within a layer’s bounds rectangle + case videoLayerGravity(AVLayerVideoGravity) } public enum GalleryRotationMode { diff --git a/ImageViewer/Source/GalleryItem.swift b/ImageViewer/Source/GalleryItem.swift index 0a8cb0d2..c49c0ff6 100644 --- a/ImageViewer/Source/GalleryItem.swift +++ b/ImageViewer/Source/GalleryItem.swift @@ -7,14 +7,14 @@ // import UIKit +import AVFoundation public typealias ImageCompletion = (UIImage?) -> Void public typealias FetchImageBlock = (@escaping ImageCompletion) -> Void public typealias ItemViewControllerBlock = (_ index: Int, _ itemCount: Int, _ fetchImageBlock: FetchImageBlock, _ configuration: GalleryConfiguration, _ isInitialController: Bool) -> UIViewController public enum GalleryItem { - case image(fetchImageBlock: FetchImageBlock) - case video(fetchPreviewImageBlock: FetchImageBlock, videoURL: URL) + case video(fetchPreviewImageBlock: FetchImageBlock, videoURL: URL, player: AVPlayer?, videoProgress: Double) case custom(fetchImageBlock: FetchImageBlock, itemViewControllerBlock: ItemViewControllerBlock) } diff --git a/ImageViewer/Source/GalleryPagingDataSource.swift b/ImageViewer/Source/GalleryPagingDataSource.swift index 43ccbb22..7d1c5d37 100644 --- a/ImageViewer/Source/GalleryPagingDataSource.swift +++ b/ImageViewer/Source/GalleryPagingDataSource.swift @@ -85,9 +85,9 @@ final class GalleryPagingDataSource: NSObject, UIPageViewControllerDataSource { return imageController - case .video(let fetchImageBlock, let videoURL): + case .video(let fetchImageBlock, let videoURL, let player, let videoProgress): - let videoController = VideoViewController(index: itemIndex, itemCount: itemsDataSource.itemCount(), fetchImageBlock: fetchImageBlock, videoURL: videoURL, scrubber: scrubber, configuration: configuration, isInitialController: isInitial) + let videoController = VideoViewController(index: itemIndex, itemCount: itemsDataSource.itemCount(), fetchImageBlock: fetchImageBlock, videoURL: videoURL, player: player, videoProgress: videoProgress, scrubber: scrubber, configuration: configuration, isInitialController: isInitial) videoController.delegate = itemControllerDelegate videoController.displacedViewsDataSource = displacedViewsDataSource diff --git a/ImageViewer/Source/GalleryViewController.swift b/ImageViewer/Source/GalleryViewController.swift index 9101b0c8..716ea080 100644 --- a/ImageViewer/Source/GalleryViewController.swift +++ b/ImageViewer/Source/GalleryViewController.swift @@ -22,6 +22,8 @@ open class GalleryViewController: UIPageViewController, ItemControllerDelegate { fileprivate var thumbnailsButton: UIButton? = UIButton.thumbnailsButton() fileprivate var deleteButton: UIButton? = UIButton.deleteButton() fileprivate let scrubber = VideoScrubber() + + public var progressCallback: ((Double) -> Void)? fileprivate weak var initialItemController: ItemController? @@ -325,6 +327,14 @@ open class GalleryViewController: UIPageViewController, ItemControllerDelegate { layoutFooterView() layoutScrubber() } + + open override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if let videoViewController = viewControllers?.first as? VideoViewController { + progressCallback?(videoViewController.player.currentTime().seconds) + } + } private var defaultInsets: UIEdgeInsets { if #available(iOS 11.0, *) { @@ -420,7 +430,11 @@ open class GalleryViewController: UIPageViewController, ItemControllerDelegate { scrubber.bounds = CGRect(origin: CGPoint.zero, size: CGSize(width: self.view.bounds.width, height: 40)) scrubber.center = self.view.boundsCenter + scrubber.frame.origin.y = (footerView?.frame.origin.y ?? self.view.bounds.maxY) - scrubber.bounds.height + if #available(iOS 11.0, *) { + scrubber.frame.origin.y = scrubber.frame.origin.y - self.view.safeAreaInsets.bottom + } } @objc fileprivate func deleteItem() { @@ -675,11 +689,13 @@ open class GalleryViewController: UIPageViewController, ItemControllerDelegate { case (_ as ImageViewController, let item as UIImageView): guard let image = item.image else { return } let activityVC = UIActivityViewController(activityItems: [image], applicationActivities: nil) + activityVC.excludedActivityTypes = [.copyToPasteboard] self.present(activityVC, animated: true) case (_ as VideoViewController, let item as VideoView): guard let videoUrl = ((item.player?.currentItem?.asset) as? AVURLAsset)?.url else { return } let activityVC = UIActivityViewController(activityItems: [videoUrl], applicationActivities: nil) + activityVC.excludedActivityTypes = [.copyToPasteboard] self.present(activityVC, animated: true) default: return diff --git a/ImageViewer/Source/Thumbnails Controller/ThumbnailsViewController.swift b/ImageViewer/Source/Thumbnails Controller/ThumbnailsViewController.swift index 5555528f..12dd064b 100644 --- a/ImageViewer/Source/Thumbnails Controller/ThumbnailsViewController.swift +++ b/ImageViewer/Source/Thumbnails Controller/ThumbnailsViewController.swift @@ -116,7 +116,7 @@ class ThumbnailsViewController: UICollectionViewController, UICollectionViewDele } } - case .video(let fetchImageBlock, _): + case .video(let fetchImageBlock, _, _, _): fetchImageBlock() { image in diff --git a/ImageViewer/Source/VideoScrubber.swift b/ImageViewer/Source/VideoScrubber.swift index 409b303c..1bb26070 100644 --- a/ImageViewer/Source/VideoScrubber.swift +++ b/ImageViewer/Source/VideoScrubber.swift @@ -165,7 +165,6 @@ open class VideoScrubber: UIControl { } @objc func play() { - self.player?.play() } diff --git a/ImageViewer/Source/VideoView.swift b/ImageViewer/Source/VideoView.swift index 161121b0..1b3b42a4 100644 --- a/ImageViewer/Source/VideoView.swift +++ b/ImageViewer/Source/VideoView.swift @@ -11,29 +11,13 @@ import AVFoundation class VideoView: UIView { - let previewImageView = UIImageView() - var image: UIImage? { didSet { previewImageView.image = image } } + var image: UIImage? var player: AVPlayer? { - - willSet { - - if newValue == nil { - - player?.removeObserver(self, forKeyPath: "status") - player?.removeObserver(self, forKeyPath: "rate") - } - } - didSet { - if let player = self.player, let videoLayer = self.layer as? AVPlayerLayer { - videoLayer.player = player videoLayer.videoGravity = AVLayerVideoGravity.resizeAspect - - player.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil) - player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.new, context: nil) } } } @@ -41,45 +25,4 @@ class VideoView: UIView { override class var layerClass : AnyClass { return AVPlayerLayer.self } - - convenience init() { - self.init(frame: CGRect.zero) - } - - override init(frame: CGRect) { - super.init(frame: frame) - - self.addSubview(previewImageView) - - previewImageView.contentMode = .scaleAspectFill - previewImageView.autoresizingMask = [.flexibleWidth, .flexibleHeight] - previewImageView.clipsToBounds = true - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - deinit { - - player?.removeObserver(self, forKeyPath: "status") - player?.removeObserver(self, forKeyPath: "rate") - } - - override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - - if let status = self.player?.status, let rate = self.player?.rate { - - if status == .readyToPlay && rate != 0 { - - UIView.animate(withDuration: 0.3, animations: { [weak self] in - - if let strongSelf = self { - - strongSelf.previewImageView.alpha = 0 - } - }) - } - } - } } diff --git a/ImageViewer/Source/VideoViewController.swift b/ImageViewer/Source/VideoViewController.swift index 383a52c6..3e511a70 100644 --- a/ImageViewer/Source/VideoViewController.swift +++ b/ImageViewer/Source/VideoViewController.swift @@ -17,6 +17,7 @@ class VideoViewController: ItemBaseController { fileprivate let swipeToDismissFadeOutAccelerationFactor: CGFloat = 6 let videoURL: URL + let videoProgress: Double let player: AVPlayer unowned let scrubber: VideoScrubber @@ -27,20 +28,22 @@ class VideoViewController: ItemBaseController { private var autoPlayStarted: Bool = false private var autoPlayEnabled: Bool = false - init(index: Int, itemCount: Int, fetchImageBlock: @escaping FetchImageBlock, videoURL: URL, scrubber: VideoScrubber, configuration: GalleryConfiguration, isInitialController: Bool = false) { + private var videoLayerGravity: AVLayerVideoGravity? = nil + + init(index: Int, itemCount: Int, fetchImageBlock: @escaping FetchImageBlock, videoURL: URL, player: AVPlayer?, videoProgress: Double, scrubber: VideoScrubber, configuration: GalleryConfiguration, isInitialController: Bool = false) { self.videoURL = videoURL + self.videoProgress = player == nil ? videoProgress : 0 self.scrubber = scrubber - self.player = AVPlayer(url: self.videoURL) + self.player = player ?? AVPlayer(url: videoURL) ///Only those options relevant to the paging VideoViewController are explicitly handled here, the rest is handled by ItemViewControllers for item in configuration { - switch item { - case .videoAutoPlay(let enabled): autoPlayEnabled = enabled - + case .videoLayerGravity(let gravity): + videoLayerGravity = gravity default: break } } @@ -61,6 +64,10 @@ class VideoViewController: ItemBaseController { self.itemView.player = player self.itemView.contentMode = .scaleAspectFill + + if let gravity = self.videoLayerGravity { + (self.itemView.layer as? AVPlayerLayer)?.videoGravity = gravity + } } override func viewWillAppear(_ animated: Bool) { @@ -97,23 +104,29 @@ class VideoViewController: ItemBaseController { override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - - let isLandscape = itemView.bounds.width >= itemView.bounds.height - itemView.bounds.size = aspectFitSize(forContentOfSize: isLandscape ? fullHDScreenSizeLandscape : fullHDScreenSizePortrait, inBounds: self.scrollView.bounds.size) + + if itemView.bounds.width <= itemView.bounds.height + 10 && itemView.bounds.width >= itemView.bounds.height - 10 { + let minScale = min(scrollView.bounds.width, scrollView.bounds.height) + itemView.bounds.size = CGSize(width: minScale, height: minScale) + } else { + let isLandscape = itemView.bounds.width >= itemView.bounds.height + let ratio = itemView.bounds.width/itemView.bounds.height + let mainScreenRatio = isLandscape ? UIScreen.main.bounds.height/UIScreen.main.bounds.width : UIScreen.main.bounds.width/UIScreen.main.bounds.height + if (ratio > mainScreenRatio - 0.05 && ratio < mainScreenRatio + 0.05) { + itemView.bounds.size = aspectFitSize(forContentOfSize: isLandscape ? CGSize(width: UIScreen.main.bounds.height, height: UIScreen.main.bounds.width) : UIScreen.main.bounds.size, inBounds: self.scrollView.bounds.size) + } else { + itemView.bounds.size = aspectFitSize(forContentOfSize: isLandscape ? fullHDScreenSizeLandscape : fullHDScreenSizePortrait, inBounds: self.scrollView.bounds.size) + } + } itemView.center = scrollView.boundsCenter } @objc func playVideoInitially() { - self.player.play() - UIView.animate(withDuration: 0.25, animations: { [weak self] in - self?.embeddedPlayButton.alpha = 0 - }, completion: { [weak self] _ in - self?.embeddedPlayButton.isHidden = true }) } @@ -123,7 +136,6 @@ class VideoViewController: ItemBaseController { UIView.animate(withDuration: duration, animations: { [weak self] in self?.embeddedPlayButton.alpha = 0 - self?.itemView.previewImageView.alpha = 1 }) } @@ -137,7 +149,6 @@ class VideoViewController: ItemBaseController { } super.presentItem(alongsideAnimation: alongsideAnimation) { - circleButtonAnimation() completion() } @@ -150,14 +161,11 @@ class VideoViewController: ItemBaseController { } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - if keyPath == "rate" || keyPath == "status" { - fadeOutEmbeddedPlayButton() } else if keyPath == "contentOffset" { - handleSwipeToDismissTransition() } @@ -172,7 +180,6 @@ class VideoViewController: ItemBaseController { } func fadeOutEmbeddedPlayButton() { - if player.isPlaying() && embeddedPlayButton.alpha != 0 { UIView.animate(withDuration: 0.3, animations: { [weak self] in @@ -229,6 +236,9 @@ class VideoViewController: ItemBaseController { autoPlayStarted = true embeddedPlayButton.isHidden = true + if (videoProgress > 0) { + scrubber.player?.seek(to: CMTime(seconds: videoProgress, preferredTimescale: 1)) + } scrubber.play() } }