diff --git a/Demo.xcodeproj/project.pbxproj b/Demo.xcodeproj/project.pbxproj index 95f1e50..f4190ed 100644 --- a/Demo.xcodeproj/project.pbxproj +++ b/Demo.xcodeproj/project.pbxproj @@ -119,7 +119,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0900; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = Teambition; TargetAttributes = { 4A6BC7871C76F8C500DACDA5 = { @@ -222,12 +222,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -254,7 +256,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -275,12 +277,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -301,7 +305,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/Demo/ViewController.swift b/Demo/ViewController.swift index 0baa562..1b2ac02 100644 --- a/Demo/ViewController.swift +++ b/Demo/ViewController.swift @@ -37,7 +37,7 @@ class ViewController: UIViewController { // mp4 - let mp4 = URL(string: "https://tcs.teambition.net/storage/1013eff73697f68b5e981613debcdcf9673b?download=49DCA8D7-ADD7-487D-83E2-C414D6F9AE23.mp4&Signature=eyJhbGciOiJIUzI1NiJ9.eyJyZXNvdXJjZSI6Ii9zdG9yYWdlLzEwMTNlZmY3MzY5N2Y2OGI1ZTk4MTYxM2RlYmNkY2Y5NjczYiIsImV4cCI6MTUyMTY3NjgwMH0.lqC5zkN-URLa7QxvMpRu78O-4HMvicacIRTVClfnYKI")! + let mp4 = URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8")! let mp4Item = FilePreviewItem(previewItemURL: mp4, previewItemTitle: "49DCA8D7-ADD7-487D-83E2-C414D6F9AE23.mp4", fileExtension: "mp4", fileKey: "1013eff73697f68b5e981613debcdcf9673b") // .key diff --git a/FilePreviewController.xcodeproj/project.pbxproj b/FilePreviewController.xcodeproj/project.pbxproj index 6efc277..41dc0a6 100644 --- a/FilePreviewController.xcodeproj/project.pbxproj +++ b/FilePreviewController.xcodeproj/project.pbxproj @@ -237,7 +237,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -290,7 +290,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/FilePreviewController.xcodeproj/xcshareddata/xcschemes/FilePreviewController.xcscheme b/FilePreviewController.xcodeproj/xcshareddata/xcschemes/FilePreviewController.xcscheme index 2843797..fc0b8a2 100644 --- a/FilePreviewController.xcodeproj/xcshareddata/xcschemes/FilePreviewController.xcscheme +++ b/FilePreviewController.xcodeproj/xcshareddata/xcschemes/FilePreviewController.xcscheme @@ -1,6 +1,6 @@ (lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): @@ -140,7 +141,7 @@ open class FilePreviewController: QLPreviewController { progressView.tintColor = UIColor.blue return progressView }() - fileprivate var shouldDisplayToolbar: Bool { + var shouldDisplayToolbar: Bool { get { return items?.count > 0 } @@ -279,11 +280,7 @@ open class FilePreviewController: QLPreviewController { customNavigationBar = customHeaderView } } - - open override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - } - + deinit { if let navigationBar = navigationBar { navigationBar.removeObserver(self, forKeyPath: "center") @@ -328,13 +325,13 @@ open class FilePreviewController: QLPreviewController { self.view.layoutIfNeeded() self.navigationBar?.superview?.layoutIfNeeded() self.originalToolbar?.isHidden = true - self.navigationBar?.superview?.bringSubview(toFront: self.customNavigationBar!) }, completion: { (_) in self.originalToolbar?.isHidden = true DispatchQueue.main.async { self.navigationBar?.superview?.bringSubview(toFront: self.customNavigationBar!) } }) + } setNeedsStatusBarAppearanceUpdate() } @@ -350,6 +347,8 @@ open class FilePreviewController: QLPreviewController { presentingViewController?.dismissFilePreviewController() } + func willDismiss() {} + func getNavigationBar(fromView view: UIView) -> UINavigationBar? { for v in view.subviews { if v is UINavigationBar { diff --git a/FilePreviewController/SingleFilePreviewController.swift b/FilePreviewController/SingleFilePreviewController.swift index 19fcbba..ebb891e 100644 --- a/FilePreviewController/SingleFilePreviewController.swift +++ b/FilePreviewController/SingleFilePreviewController.swift @@ -8,44 +8,234 @@ import Foundation import QuickLook +import AVKit + +extension QLPreviewController { + func hideErrorMessage() { + let errorView = view.subviews.first(where: { String(describing: $0.classForCoder) == "QLErrorView" }) + errorView?.subviews.compactMap { $0 as? UILabel }.forEach { $0.text = nil } + } +} + +extension FilePreviewItem { + var isVideo: Bool { + if #available(iOS 10, *) { + let supportExtensions = [ + "mp4", "mov", "qt", "avi", "3gp", "wmv", "mkv", "rmvb", "rm", "xvid", "mpg" + ] + if let fileExtension = fileExtension { + return supportExtensions.contains(fileExtension.lowercased()) + } + return false + } else { + return false + } + } +} open class SingleFilePreviewController: FilePreviewController { - var singleItemDataSource: SingleItemDataSource! - + private var avPlayerController: TBAVPlayerController? + + var previewItem: FilePreviewItem? + public init(previewItem: FilePreviewItem?) { + self.previewItem = previewItem super.init(nibName: nil, bundle: nil) - singleItemDataSource = SingleItemDataSource(previewItem: previewItem) - originalDataSource = singleItemDataSource - dataSource = self } required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } - + + public func updateItem(_ previewItem: FilePreviewItem?) { + guard let previewItem = previewItem, let url = previewItem.previewItemURL else { + return + } + self.previewItem = previewItem + navigationItem.title = previewItem.previewItemTitle + let asset = AVAsset(url: url) + let isPlayable = asset.isPlayable + if previewItem.isVideo, isPlayable { + setupAVPlayerViewController(with: url) + } else { + if !isPlayable { + controllerDelegate?.previewController(self as FilePreviewController, failedToLoadRemotePreviewItem: previewItem, error: NSError(domain: "Video can't play", code: 0, userInfo: nil)) + } + originalDataSource = SingleItemDataSource(previewItem: previewItem) + dataSource = self + reloadData() + } + } + + private func setupAVPlayerViewController(with url: URL) { + guard self.avPlayerController == nil else { + return + } + hideNavigationBarAndToolbar() + let avPlayerController = TBAVPlayerController(url: url) + view.addSubview(avPlayerController.view) + avPlayerController.view.frame = view.frame + addChildViewController(avPlayerController) + self.avPlayerController = avPlayerController + self.avPlayerController?.touchHandle = { [weak self] avPlayerController in + guard let strongSelf = self, let navigationController = strongSelf.navigationController else { + return + } + if !navigationController.isNavigationBarHidden || !avPlayerController.isPlaybackControlsHidden { + strongSelf.updateNavigationBarAndToolbar() + avPlayerController.showsPlaybackControls = navigationController.isNavigationBarHidden + } + } + avPlayerController.play() + } + + private func updateNavigationBarAndToolbar() { + if let navigationController = navigationController { + navigationController.setNavigationBarHidden(!navigationController.isNavigationBarHidden, animated: true) + if shouldDisplayToolbar { + navigationController.setToolbarHidden(!navigationController.isNavigationBarHidden, animated: true) + } + } + } + + private func hideNavigationBarAndToolbar() { + if let navigationController = navigationController { + navigationController.setNavigationBarHidden(true, animated: false) + navigationController.setToolbarHidden(true, animated: false) + } + } + open override func viewDidLoad() { super.viewDidLoad() + updateItem(previewItem) + } + + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if previewItem == nil { + hideErrorMessage() + } + if let avPlayerController = avPlayerController, avPlayerController.isPlaying { + hideNavigationBarAndToolbar() + } } - public func updateItem(_ previewItem: FilePreviewItem?) { - singleItemDataSource.previewItem = previewItem - reloadData() + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + if avPlayerController != nil { + avPlayerController?.play() + } + } + + open override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear (animated) + if avPlayerController != nil { + avPlayerController?.pause() + } + } + + private func releaseAVPlayerViewController() { + if let avPlayerController = avPlayerController { + avPlayerController.removeFromParentViewController() + avPlayerController.view.removeFromSuperview() + self.avPlayerController = nil + } + } + + override func willDismiss() { + super.willDismiss() + releaseAVPlayerViewController() + } +} + +public extension SingleFilePreviewController { + @objc override func showMoreActivity() { + guard let previewItem = previewItem else { + return + } + if let delegate = controllerDelegate { + delegate.previewController(self, showMoreItems: previewItem) + } + } + + @objc override func showShareActivity() { + guard let previewItem = previewItem else { + return + } + if let delegate = controllerDelegate { + delegate.previewController(self, willShareItem: previewItem) + } else { + showDefaultShareActivity() + } } } class SingleItemDataSource: NSObject, QLPreviewControllerDataSource { - var previewItem: QLPreviewItem? + let previewItem: QLPreviewItem - init(previewItem: QLPreviewItem?) { - super.init() + init(previewItem: QLPreviewItem) { self.previewItem = previewItem + super.init() } func numberOfPreviewItems(in controller: QLPreviewController) -> Int { - return (previewItem != nil) ? 1 : 0 + return 1 } func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { - return previewItem! + return previewItem + } +} + +class TBAVPlayerController: AVPlayerViewController { + var isPlaying = false + + var touchHandle: ((TBAVPlayerController) -> Void)? + + var isPlaybackControlsHidden: Bool { + var playbackControlsIsHidden = false + if #available(iOS 11.0, *) { + if let playerViewControllerContentView = view.subviews.filter({ String(describing: $0.classForCoder) == "AVPlayerViewControllerContentView" }).first, + let playbackControlsView = playerViewControllerContentView.subviews.filter({ String(describing: $0.classForCoder) == "AVPlaybackControlsView" }).first { + playbackControlsIsHidden = playbackControlsView.subviews.reduce(true, { $0 && $1.isHidden }) + } + } else { + if let playbackControlsView = view.subviews.first?.subviews.first(where: { $0.subviews.contains(where: { String(describing: $0.classForCoder) == "AVAlphaUpdatingView" }) }) { + playbackControlsIsHidden = playbackControlsView.subviews.reduce(true, { $0 && $1.isHidden }) + } + } + return playbackControlsIsHidden + } + + init(url: URL) { + super.init(nibName: nil, bundle: nil) + let asset = AVAsset(url: url) + let item = AVPlayerItem(asset: asset) + player = AVPlayer(playerItem: item) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + touchHandle?(self) + } + + deinit { + pause() + player?.currentItem?.cancelPendingSeeks() + player?.currentItem?.asset.cancelLoading() + } + + func play() { + isPlaying = true + player?.play() + } + + func pause() { + isPlaying = false + player?.pause() } }