diff --git a/Example/EVPlayer.xcodeproj/project.pbxproj b/Example/EVPlayer.xcodeproj/project.pbxproj index 063c722..63a95c1 100644 --- a/Example/EVPlayer.xcodeproj/project.pbxproj +++ b/Example/EVPlayer.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 054C66B129C48865007810C6 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C66B029C48865007810C6 /* DetailViewController.swift */; }; 4023CDD121E26305A4A2D428 /* Pods_EVPlayer_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E69C5B750995D242EB97C5D /* Pods_EVPlayer_Tests.framework */; }; 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; @@ -28,6 +29,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 054C66B029C48865007810C6 /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 0E69C5B750995D242EB97C5D /* Pods_EVPlayer_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_EVPlayer_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1072533C1591EE4D8F6CDBF1 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 1C435D38827410900FDC7ABA /* Pods-EVPlayer_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EVPlayer_Tests.release.xcconfig"; path = "Target Support Files/Pods-EVPlayer_Tests/Pods-EVPlayer_Tests.release.xcconfig"; sourceTree = ""; }; @@ -104,6 +106,7 @@ children = ( 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 607FACD71AFB9204008FA782 /* ViewController.swift */, + 054C66B029C48865007810C6 /* DetailViewController.swift */, 607FACD91AFB9204008FA782 /* Main.storyboard */, 607FACDC1AFB9204008FA782 /* Images.xcassets */, 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, @@ -332,6 +335,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 054C66B129C48865007810C6 /* DetailViewController.swift in Sources */, 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, ); diff --git a/Example/EVPlayer/Base.lproj/Main.storyboard b/Example/EVPlayer/Base.lproj/Main.storyboard index abe8912..7a070b9 100644 --- a/Example/EVPlayer/Base.lproj/Main.storyboard +++ b/Example/EVPlayer/Base.lproj/Main.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -20,11 +18,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/EVPlayer/DetailViewController.swift b/Example/EVPlayer/DetailViewController.swift new file mode 100644 index 0000000..e387637 --- /dev/null +++ b/Example/EVPlayer/DetailViewController.swift @@ -0,0 +1,19 @@ +// +// DetailViewController.swift +// EVPlayer_Example +// +// Created by Emirhan Saygiver on 17.03.2023. +// Copyright © 2023 CocoaPods. All rights reserved. +// + +import UIKit + +class DetailViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + +} diff --git a/Example/EVPlayer/ViewController.swift b/Example/EVPlayer/ViewController.swift index db36eff..70e599c 100644 --- a/Example/EVPlayer/ViewController.swift +++ b/Example/EVPlayer/ViewController.swift @@ -7,11 +7,12 @@ // import UIKit -import AVKit import EVPlayer class ViewController: UIViewController { + @IBOutlet private weak var goButton: UIButton! + var evPlayer: EVPlayer! let media = EVMedia(mediaURL: URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4"), @@ -23,6 +24,11 @@ class ViewController: UIViewController { configureEVPlayer() } +// override func viewDidAppear(_ animated: Bool) { +// super.viewDidAppear(animated) +// evPlayer.changeStateForNavigationChanges(to: .play) +// } + private func configureEVPlayer() { evPlayer = EVPlayer(frame: CGRect(x: 0, y: 0, width: 350, height: 200)) @@ -34,20 +40,26 @@ class ViewController: UIViewController { initialState: .quickPlay) evPlayer.load(with: config) } + + @IBAction private func goTapped() { + let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController + evPlayer.changeStateForNavigationChanges(to: .pause) + navigationController?.pushViewController(vc, animated: true) + } } extension ViewController: EVPlayerDelegate { - - func stateDidChanged(player: AVPlayer?, to state: EVVideoState) { + + func evPlayer(stateDidChangedTo state: EVVideoState) { print("stateDidChanged", state) } - func playTimeDidChanged(player: AVPlayer?, currentTime: Double, totalTime: Double, loadedRange: String) { -// print("DOWNLOADED -> %", loadedRange) + func evPlayer(timeChangedTo currentTime: Double, totalTime: Double, loadedRange: Double) { + print("loadedRange ->", loadedRange) } - func fullScreenTransactionUpdate(to state: EVFullScreenState) { - print("Current FullScreen State:", state) + func evPlayer(fullScreenTransactionUpdateTo state: EVFullScreenState) { + print("stateDidChanged", state) } } diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj index e60cc6b..f7d77e1 100644 --- a/Example/Pods/Pods.xcodeproj/project.pbxproj +++ b/Example/Pods/Pods.xcodeproj/project.pbxproj @@ -26,7 +26,7 @@ 054C669229C45506007810C6 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C668D29C45506007810C6 /* Constants.swift */; }; 054C669329C45506007810C6 /* EVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C668F29C45506007810C6 /* EVConfiguration.swift */; }; 054C669429C45506007810C6 /* EVPlayerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C669129C45506007810C6 /* EVPlayerCache.swift */; }; - 054C669729C4551D007810C6 /* EVTapGestureOrganizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C669629C4551D007810C6 /* EVTapGestureOrganizer.swift */; }; + 054C669729C4551D007810C6 /* EVSeekDuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C669629C4551D007810C6 /* EVSeekDuration.swift */; }; 054C669B29C45525007810C6 /* EVViewDefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C669929C45525007810C6 /* EVViewDefaultLogger.swift */; }; 054C669C29C45525007810C6 /* EVViewLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C669A29C45525007810C6 /* EVViewLogger.swift */; }; 054C66A029C4552F007810C6 /* EVFullScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C669E29C4552F007810C6 /* EVFullScreenView.swift */; }; @@ -36,6 +36,7 @@ 054C66A929C45614007810C6 /* EVVideoState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C66A829C45614007810C6 /* EVVideoState.swift */; }; 054C66AB29C4564D007810C6 /* EVPlayerAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 054C66AA29C4564D007810C6 /* EVPlayerAssets.xcassets */; }; 054C66AE29C46EA1007810C6 /* EVFullScreenState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C66AD29C46EA1007810C6 /* EVFullScreenState.swift */; }; + 054C66B329C48F6B007810C6 /* NavigationAdapterImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C66B229C48F6B007810C6 /* NavigationAdapterImpl.swift */; }; 0916AFBB13A3C31638DFBD6BD5E206D6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */; }; 42ABA48C939EDAA756FBF170826352D0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */; }; 46F140EB1FB669B50FD1996F00A6C714 /* EVPlayer-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = EA599ABA994E7E06FC8B7867249E74D2 /* EVPlayer-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -85,7 +86,7 @@ 054C668D29C45506007810C6 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 054C668F29C45506007810C6 /* EVConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EVConfiguration.swift; sourceTree = ""; }; 054C669129C45506007810C6 /* EVPlayerCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EVPlayerCache.swift; sourceTree = ""; }; - 054C669629C4551D007810C6 /* EVTapGestureOrganizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EVTapGestureOrganizer.swift; sourceTree = ""; }; + 054C669629C4551D007810C6 /* EVSeekDuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EVSeekDuration.swift; sourceTree = ""; }; 054C669929C45525007810C6 /* EVViewDefaultLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EVViewDefaultLogger.swift; sourceTree = ""; }; 054C669A29C45525007810C6 /* EVViewLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EVViewLogger.swift; sourceTree = ""; }; 054C669E29C4552F007810C6 /* EVFullScreenView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EVFullScreenView.swift; sourceTree = ""; }; @@ -95,6 +96,7 @@ 054C66A829C45614007810C6 /* EVVideoState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EVVideoState.swift; sourceTree = ""; }; 054C66AA29C4564D007810C6 /* EVPlayerAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = EVPlayerAssets.xcassets; sourceTree = ""; }; 054C66AD29C46EA1007810C6 /* EVFullScreenState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EVFullScreenState.swift; sourceTree = ""; }; + 054C66B229C48F6B007810C6 /* NavigationAdapterImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationAdapterImpl.swift; sourceTree = ""; }; 07065CBB3097971958BA756C1A613427 /* EVPlayer-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "EVPlayer-dummy.m"; sourceTree = ""; }; 0AA2A7C26321450F893C547B4DB2C1EE /* Pods-EVPlayer_Tests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-EVPlayer_Tests-umbrella.h"; sourceTree = ""; }; 0B86A6A56D62604DF11594801228C7B1 /* Pods-EVPlayer_Tests */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-EVPlayer_Tests"; path = Pods_EVPlayer_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -176,9 +178,9 @@ 054C667E29C454DC007810C6 /* Interfaces */, 054C669029C45506007810C6 /* Cache */, 054C668E29C45506007810C6 /* Configuration */, + 054C669529C4551D007810C6 /* SeekDuration */, 054C668C29C45506007810C6 /* Constants */, 054C668929C454EB007810C6 /* Media */, - 054C669529C4551D007810C6 /* GestureOrganizer */, 054C669829C45525007810C6 /* Logger */, 054C66A229C45541007810C6 /* Extensions */, ); @@ -209,6 +211,7 @@ 054C668029C454DC007810C6 /* WorkerImpl.swift */, 054C668229C454DC007810C6 /* ObserverImpl.swift */, 054C668329C454DC007810C6 /* PlayerImpl.swift */, + 054C66B229C48F6B007810C6 /* NavigationAdapterImpl.swift */, ); path = Interfaces; sourceTree = ""; @@ -245,12 +248,12 @@ path = Cache; sourceTree = ""; }; - 054C669529C4551D007810C6 /* GestureOrganizer */ = { + 054C669529C4551D007810C6 /* SeekDuration */ = { isa = PBXGroup; children = ( - 054C669629C4551D007810C6 /* EVTapGestureOrganizer.swift */, + 054C669629C4551D007810C6 /* EVSeekDuration.swift */, ); - path = GestureOrganizer; + path = SeekDuration; sourceTree = ""; }; 054C669829C45525007810C6 /* Logger */ = { @@ -369,9 +372,9 @@ CF1408CF629C7361332E53B88F7BD30C = { isa = PBXGroup; children = ( - 054C66AF29C48725007810C6 /* Resources */, 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, 07F6DC8AE14A6C3250F9A7D6460DD39A /* Development Pods */, + 054C66AF29C48725007810C6 /* Resources */, D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */, 030C8965371EDC81E9D4AFCEC36DA715 /* Products */, E754BAEB4F697AE7682B1A83F3E23552 /* Targets Support Files */, @@ -568,6 +571,7 @@ 054C667B29C454D0007810C6 /* EVCoverView.swift in Sources */, 054C667C29C454D0007810C6 /* EVOverlayView.swift in Sources */, F8EF824FF54FE2DECCDC0C3C1B9E00BD /* EVPlayer-dummy.m in Sources */, + 054C66B329C48F6B007810C6 /* NavigationAdapterImpl.swift in Sources */, 054C66A129C4552F007810C6 /* EVPlayerController.swift in Sources */, 054C667829C454D0007810C6 /* EVThumbnailView.swift in Sources */, 054C667729C454D0007810C6 /* EVPlayerPropertiesView.swift in Sources */, @@ -578,7 +582,7 @@ 054C66A029C4552F007810C6 /* EVFullScreenView.swift in Sources */, 054C669429C45506007810C6 /* EVPlayerCache.swift in Sources */, 054C669B29C45525007810C6 /* EVViewDefaultLogger.swift in Sources */, - 054C669729C4551D007810C6 /* EVTapGestureOrganizer.swift in Sources */, + 054C669729C4551D007810C6 /* EVSeekDuration.swift in Sources */, 054C667929C454D0007810C6 /* EVEmptyView.swift in Sources */, 054C668829C454DC007810C6 /* PlayerImpl.swift in Sources */, 054C668729C454DC007810C6 /* ObserverImpl.swift in Sources */, diff --git a/Source/Configuration/EVConfiguration.swift b/Source/Configuration/EVConfiguration.swift index 901088a..bb3ce69 100644 --- a/Source/Configuration/EVConfiguration.swift +++ b/Source/Configuration/EVConfiguration.swift @@ -8,55 +8,6 @@ import Foundation import AVKit -public enum EVSeekDuration { - case k5 - case k10 - case k15 - case k30 - case k45 - case k60 - case k75 - case k90 - - var value: Double { - switch self { - case .k5: return 5 - case .k10: return 10 - case .k15: return 15 - case .k30: return 30 - case .k45: return 45 - case .k60: return 60 - case .k75: return 75 - case .k90: return 90 - } - } - var forwardImage: UIImage? { - switch self { - case .k5: return Constants.Icons.forwardImage5 - case .k10: return Constants.Icons.forwardImage10 - case .k15: return Constants.Icons.forwardImage15 - case .k30: return Constants.Icons.forwardImage30 - case .k45: return Constants.Icons.forwardImage45 - case .k60: return Constants.Icons.forwardImage60 - case .k75: return Constants.Icons.forwardImage75 - case .k90: return Constants.Icons.forwardImage90 - } - } - - var rewindImage: UIImage? { - switch self { - case .k5: return Constants.Icons.rewindImage5 - case .k10: return Constants.Icons.rewindImage10 - case .k15: return Constants.Icons.rewindImage15 - case .k30: return Constants.Icons.rewindImage30 - case .k45: return Constants.Icons.rewindImage45 - case .k60: return Constants.Icons.rewindImage60 - case .k75: return Constants.Icons.rewindImage75 - case .k90: return Constants.Icons.rewindImage90 - } - } -} - public protocol EVConfigurationInterface { var media: EVMedia? { get } var initialState: EVVideoState? { get } @@ -73,7 +24,7 @@ public protocol EVConfigurationInterface { var fullScreenPresentationStyle: UIModalPresentationStyle { get } var isSeekAnimationsEnabled: Bool { get } var isTransactionAnimated: Bool { get } - var playerCacher: EVPlayerCacheability { get } + var assetCacher: EVPlayerCacheability { get } } public struct EVConfiguration: EVConfigurationInterface { @@ -84,10 +35,7 @@ public struct EVConfiguration: EVConfigurationInterface { public var volume: Float? /// Cache url assets and improves reuse - public var playerCacher: EVPlayerCacheability - - /// Has single and double tap events - public var gestureOrganizer: EVTapGestureOrganizerImpl + public var assetCacher: EVPlayerCacheability // MARK: - Initializer @@ -96,16 +44,14 @@ public struct EVConfiguration: EVConfigurationInterface { seekTime: CMTime? = nil, isMuted: Bool? = nil, volume: Float? = nil, - playerCacher: EVPlayerCacheability = EVPlayerCache.shared, - gestureOrganizer: EVTapGestureOrganizerImpl = EVTapGestureOrganizer()) { + playerCacher: EVPlayerCacheability = EVPlayerCache.shared) { self.initialState = initialState self.media = media self.seekTime = seekTime self.isMuted = isMuted self.volume = volume - self.playerCacher = playerCacher - self.gestureOrganizer = gestureOrganizer + self.assetCacher = playerCacher } /// Seek forward player value, the default is 10 sec diff --git a/Source/EVPlayer.swift b/Source/EVPlayer.swift index c62d8df..9c158aa 100644 --- a/Source/EVPlayer.swift +++ b/Source/EVPlayer.swift @@ -9,22 +9,16 @@ import UIKit import AVKit public protocol EVPlayerDelegate: AnyObject { - func stateDidChanged(player: AVPlayer?, to state: EVVideoState) - func playTimeDidChanged(player: AVPlayer?, currentTime: Double, totalTime: Double, loadedRange: String) - func fullScreenTransactionUpdate(to state: EVFullScreenState) -} - -extension EVPlayerDelegate { - func stateDidChanged(player: AVPlayer?, to state: EVVideoState) { } - func playTimeDidChanged(player: AVPlayer?, currentTime: Double, totalTime: Double, loadedRange: String) { } - func fullScreenTransactionUpdate(to state: EVFullScreenState) { } + func evPlayer(stateDidChangedTo state: EVVideoState) + func evPlayer(timeChangedTo currentTime: Double, totalTime: Double, loadedRange: Double) + func evPlayer(fullScreenTransactionUpdateTo state: EVFullScreenState) } open class EVPlayer: UIView { // MARK: - UI Properties - let videoStreamView = UIView() + let videoLayer = UIView() let thumbnailView = EVThumbnailView() let coverView = EVCoverView() let propertiesStackView = EVPlayerPropertiesView() @@ -46,16 +40,14 @@ open class EVPlayer: UIView { var timeObserver: Any? var progressBarHighlightedObserver: NSKeyValueObservation? - // Delegate public weak var delegate: EVPlayerDelegate? - // Setup Configuration var configuration: EVConfiguration? // State lazy var videoState: EVVideoState = .empty { didSet { - delegate?.stateDidChanged(player: player, to: videoState) + delegate?.evPlayer(stateDidChangedTo: videoState) } } @@ -88,7 +80,7 @@ open class EVPlayer: UIView { progressBarHighlightedObserver?.invalidate() progressBarHighlightedObserver = nil NotificationCenter.default.removeObserver(self) - videoStreamView.layer.sublayers?.forEach { $0.removeFromSuperlayer() } + videoLayer.layer.sublayers?.forEach { $0.removeFromSuperlayer() } if let timeObserverToken = timeObserver { player?.removeTimeObserver(timeObserverToken) timeObserver = nil @@ -126,6 +118,9 @@ extension EVPlayer: EVObserverProtocol { } /// state updater extension EVPlayer: EVStateProtocol { } +// Implementation of EVNavigationAdapter +/// Public state updater for parent +extension EVPlayer: EVNavigationAdapter { } // MARK: - Delegates @@ -133,7 +128,7 @@ extension EVPlayer: EVStateProtocol { } extension EVPlayer: EVCoverViewDelegate { - func play() { + public func play() { updateState(to: videoState == .pause ? .play : .pause) } @@ -186,7 +181,7 @@ extension EVPlayer: EVCoverViewDelegate { return } - delegate?.fullScreenTransactionUpdate(to: .willEnter) + delegate?.evPlayer(fullScreenTransactionUpdateTo: .willEnter) var fsConfig = config fsConfig.initialState = videoState @@ -200,7 +195,8 @@ extension EVPlayer: EVCoverViewDelegate { EVPlayerController.show(withConfiguration: fsConfig, presentCallback: { [weak self] in guard let strongSelf = self else { return } - strongSelf.delegate?.fullScreenTransactionUpdate(to: .didEnter) + strongSelf.delegate?.evPlayer(fullScreenTransactionUpdateTo: .didEnter) + }, willDismissCallback: { [weak self] config in guard let strongSelf = self else { return } @@ -208,12 +204,12 @@ extension EVPlayer: EVCoverViewDelegate { strongSelf.applyConfiguration(config) strongSelf.seek(to: config.seekTime, continueFrom: config.initialState) - strongSelf.delegate?.fullScreenTransactionUpdate(to: .willDismiss) + strongSelf.delegate?.evPlayer(fullScreenTransactionUpdateTo: .willDismiss) }, didDismissCallback: { [weak self] in guard let strongSelf = self else { return } - strongSelf.delegate?.fullScreenTransactionUpdate(to: .didDismiss) + strongSelf.delegate?.evPlayer(fullScreenTransactionUpdateTo: .didDismiss) }) } } @@ -240,10 +236,10 @@ extension EVPlayer { private func handleDoubleTap(sender: UITapGestureRecognizer) { if sender.state == .ended { - if (sender.location(ofTouch: 0, in: videoStreamView).x + 75) < videoStreamView.bounds.midX { // Rewind + if (sender.location(ofTouch: 0, in: videoLayer).x + 75) < videoLayer.bounds.midX { // Rewind coverView.rewindEvent() - } else if sender.location(ofTouch: 0, in: videoStreamView).x > (videoStreamView.bounds.midX + 75) { // Forward + } else if sender.location(ofTouch: 0, in: videoLayer).x > (videoLayer.bounds.midX + 75) { // Forward coverView.forwardEvent() } else { diff --git a/Source/GestureOrganizer/EVTapGestureOrganizer.swift b/Source/GestureOrganizer/EVTapGestureOrganizer.swift deleted file mode 100644 index 4a9302e..0000000 --- a/Source/GestureOrganizer/EVTapGestureOrganizer.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// EVTapGestureOrganizer.swift -// EVPlayer -// -// Created by Emirhan Saygiver on 17.03.2023. -// - -import UIKit - -public protocol EVTapGestureOrganizerImpl { - typealias EVGestureEvent = (UITapGestureRecognizer) -> Void - var singleTapGR: UITapGestureRecognizer { get } - var doubleTapGR: UITapGestureRecognizer { get } - var singleTapEvent: EVGestureEvent? { get set } - var doubleTapEvent: EVGestureEvent? { get set } -} - -public final class EVTapGestureOrganizer: EVTapGestureOrganizerImpl { - - // Gestures - public lazy var singleTapGR = UITapGestureRecognizer(target: self, action: #selector(handleSingleTap)) - public lazy var doubleTapGR = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap)) - - public var singleTapEvent: ((UITapGestureRecognizer) -> Void)? - public var doubleTapEvent: ((UITapGestureRecognizer) -> Void)? - - public init() { - singleTapGR.numberOfTapsRequired = 1 - doubleTapGR.numberOfTapsRequired = 2 - singleTapGR.require(toFail: doubleTapGR) - } - - @objc - private func handleSingleTap(sender: UITapGestureRecognizer) { - singleTapEvent?(sender) - } - - @objc - private func handleDoubleTap(sender: UITapGestureRecognizer) { - doubleTapEvent?(sender) - } -} diff --git a/Source/Interfaces/NavigationAdapterImpl.swift b/Source/Interfaces/NavigationAdapterImpl.swift new file mode 100644 index 0000000..cf351ec --- /dev/null +++ b/Source/Interfaces/NavigationAdapterImpl.swift @@ -0,0 +1,27 @@ +// +// NavigationChangeAdapter.swift +// EVPlayer +// +// Created by Emirhan Saygiver on 17.03.2023. +// + +import Foundation + +/** Example Usage: + XViewController owns EVPlayer property and want to change navigation, + you can use it like changeStateForNavigationChanges(to: pause) before transaction. + And also, back to your XViewController, in viewWillApper you may call changeStateForNavigationChanges(to: play) + it will continue from last pause time. +*/ + +/// Use this protocol for changing EVPlayer state, when trying to change top UIViewController on view hierarchy. +public protocol EVNavigationAdapter { + func changeStateForNavigationChanges(to state: EVVideoState) +} + +extension EVNavigationAdapter where Self: EVPlayer { + + public func changeStateForNavigationChanges(to state: EVVideoState) { + updateState(to: state) + } +} diff --git a/Source/Interfaces/ObserverImpl.swift b/Source/Interfaces/ObserverImpl.swift index e34d293..a3f095a 100644 --- a/Source/Interfaces/ObserverImpl.swift +++ b/Source/Interfaces/ObserverImpl.swift @@ -8,23 +8,23 @@ import AVKit protocol EVObserverProtocol { - func addObservers() - func addTimeObserver() - func addProgressBarValueChangedObserver() + func setObservers() + func setTimeObserver() + func setProgressBarValueChangedObserver() func addAVPlayerItemDidPlayToEndTimeNotification() func addWillResignActiveNotification() } extension EVObserverProtocol where Self: EVPlayer { - func addObservers() { - addTimeObserver() - addProgressBarValueChangedObserver() + func setObservers() { + setTimeObserver() + setProgressBarValueChangedObserver() addAVPlayerItemDidPlayToEndTimeNotification() addWillResignActiveNotification() } - func addTimeObserver() { + func setTimeObserver() { guard let player = player else { return } let timeInterval = CMTime(seconds: 1, preferredTimescale: 1) @@ -45,7 +45,7 @@ extension EVObserverProtocol where Self: EVPlayer { }) } - func addProgressBarValueChangedObserver() { + func setProgressBarValueChangedObserver() { progressBarHighlightedObserver = propertiesStackView.videoSlider.observe(\EVSliderView.isTracking, options: [.old, .new]) { [weak self] (_, change) in guard let strongSelf = self else { return } diff --git a/Source/Interfaces/PlayerImpl.swift b/Source/Interfaces/PlayerImpl.swift index 41761f1..557bbc8 100644 --- a/Source/Interfaces/PlayerImpl.swift +++ b/Source/Interfaces/PlayerImpl.swift @@ -29,7 +29,6 @@ extension EVPlayerProtocol where Self: EVPlayer { } player = AVPlayer(playerItem: playerItem) player?.automaticallyWaitsToMinimizeStalling = true - player?.isMuted = true } func setPlayerLayer() { @@ -38,7 +37,7 @@ extension EVPlayerProtocol where Self: EVPlayer { } playerLayer = AVPlayerLayer(player: player) playerLayer?.videoGravity = configuration?.videoGravity ?? .resize - videoStreamView.layer.addSublayer(playerLayer!) + videoLayer.layer.addSublayer(playerLayer!) } func seek(to time: CMTime?, continueFrom state: EVVideoState? = nil) { diff --git a/Source/Interfaces/StateImpl.swift b/Source/Interfaces/StateImpl.swift index c2f1485..0c8f011 100644 --- a/Source/Interfaces/StateImpl.swift +++ b/Source/Interfaces/StateImpl.swift @@ -38,6 +38,7 @@ extension EVStateProtocol where Self: EVPlayer { player?.pause() case .thumbnail: + updateState(to: .pause) bringSubview(toFront: thumbnailView) thumbnailView.isHidden = false propertiesStackView.isHidden = true diff --git a/Source/Interfaces/UIImpl.swift b/Source/Interfaces/UIImpl.swift index 6e33ea6..11398c0 100644 --- a/Source/Interfaces/UIImpl.swift +++ b/Source/Interfaces/UIImpl.swift @@ -41,10 +41,10 @@ extension EVUIProtocol where Self: EVPlayer { func setupUI() { // Video stream layer - addSubview(videoStreamView) - videoStreamView.backgroundColor = .black - videoStreamView.isUserInteractionEnabled = true - videoStreamView.cuiPinToSuperview() + addSubview(videoLayer) + videoLayer.backgroundColor = .black + videoLayer.isUserInteractionEnabled = true + videoLayer.cuiPinToSuperview() // thumbnailView addSubview(thumbnailView) @@ -95,13 +95,14 @@ extension EVUIProtocol where Self: EVPlayer { } let progressValue = (progressTime.seconds / duration.seconds) * 100 - let progressValueStrWithOneCharAfterComma = String(format: "%.1f", progressValue) - delegate?.playTimeDidChanged(player: player, - currentTime: progressTime.seconds, - totalTime: duration.seconds, - loadedRange: progressValueStrWithOneCharAfterComma) propertiesStackView.updateDuration(current: progressTime <= duration ? progressTime : duration, total: duration) + + if let progressValueWithOneCharAfterComma = Double(String(format: "%.1f", progressValue)) { + delegate?.evPlayer(timeChangedTo: progressTime.seconds, + totalTime: duration.seconds, + loadedRange: progressValueWithOneCharAfterComma) + } } func applyConfiguration(_ configuration: EVConfiguration?) { @@ -126,15 +127,15 @@ extension EVUIProtocol where Self: EVPlayer { } let overlayView = EVOverlayView(frame: CGRect(x: 0, y: 0, - width: videoStreamView.frame.width / 3.5, - height: videoStreamView.frame.height), + width: videoLayer.frame.width / 3.5, + height: videoLayer.frame.height), type: type) - videoStreamView.addSubview(overlayView) + videoLayer.addSubview(overlayView) overlayView.cuiPinTopToSuperView() overlayView.cuiPinBottomToSuperView() - overlayView.widthAnchor.cuiSet(to: videoStreamView.frame.width / 3.5) + overlayView.widthAnchor.cuiSet(to: videoLayer.frame.width / 3.5) switch type { case .forward: @@ -175,10 +176,10 @@ extension EVUIProtocol where Self: EVPlayer { func setGestureRecognizers() { singleTapGR.numberOfTapsRequired = 1 - videoStreamView.addGestureRecognizer(singleTapGR) + videoLayer.addGestureRecognizer(singleTapGR) doubleTapGR.numberOfTapsRequired = 2 - videoStreamView.addGestureRecognizer(doubleTapGR) + videoLayer.addGestureRecognizer(doubleTapGR) singleTapGR.require(toFail: doubleTapGR) } diff --git a/Source/Interfaces/WorkerImpl.swift b/Source/Interfaces/WorkerImpl.swift index c788221..f461fdb 100644 --- a/Source/Interfaces/WorkerImpl.swift +++ b/Source/Interfaces/WorkerImpl.swift @@ -32,15 +32,15 @@ extension EVWorkerProtocol where Self: EVPlayer { } public func createPlayer(with url: URL) { - if let avAssetFromCache = configuration?.playerCacher.asset(for: url) { + if let avAssetFromCache = configuration?.assetCacher.asset(for: url) { setPlayerItem(with: avAssetFromCache) setPlayer() setPlayerLayer() - addObservers() + setObservers() } else { let avAsset = AVAsset(url: url) - configuration?.playerCacher.add(asset: avAsset, for: url) + configuration?.assetCacher.add(asset: avAsset, for: url) createPlayer(with: url) } } diff --git a/Source/Logger/EVViewDefaultLogger.swift b/Source/Logger/EVViewDefaultLogger.swift index 91790eb..cbabca0 100644 --- a/Source/Logger/EVViewDefaultLogger.swift +++ b/Source/Logger/EVViewDefaultLogger.swift @@ -7,7 +7,7 @@ import Foundation -public struct EVViewDefaultLogger: EVViewLogger { +struct EVViewDefaultLogger: EVViewLogger { static var logger: EVViewLogger = EVViewDefaultLogger() diff --git a/Source/SeekDuration/EVSeekDuration.swift b/Source/SeekDuration/EVSeekDuration.swift new file mode 100644 index 0000000..575e747 --- /dev/null +++ b/Source/SeekDuration/EVSeekDuration.swift @@ -0,0 +1,59 @@ +// +// EVSeekDuration.swift +// EVPlayer +// +// Created by Emirhan Saygiver on 17.03.2023. +// + +import UIKit + +public enum EVSeekDuration { + case k5 + case k10 + case k15 + case k30 + case k45 + case k60 + case k75 + case k90 + + var value: Double { + switch self { + case .k5: return 5 + case .k10: return 10 + case .k15: return 15 + case .k30: return 30 + case .k45: return 45 + case .k60: return 60 + case .k75: return 75 + case .k90: return 90 + } + } + + var forwardImage: UIImage? { + switch self { + case .k5: return Constants.Icons.forwardImage5 + case .k10: return Constants.Icons.forwardImage10 + case .k15: return Constants.Icons.forwardImage15 + case .k30: return Constants.Icons.forwardImage30 + case .k45: return Constants.Icons.forwardImage45 + case .k60: return Constants.Icons.forwardImage60 + case .k75: return Constants.Icons.forwardImage75 + case .k90: return Constants.Icons.forwardImage90 + } + } + + var rewindImage: UIImage? { + switch self { + case .k5: return Constants.Icons.rewindImage5 + case .k10: return Constants.Icons.rewindImage10 + case .k15: return Constants.Icons.rewindImage15 + case .k30: return Constants.Icons.rewindImage30 + case .k45: return Constants.Icons.rewindImage45 + case .k60: return Constants.Icons.rewindImage60 + case .k75: return Constants.Icons.rewindImage75 + case .k90: return Constants.Icons.rewindImage90 + } + } + +} diff --git a/Source/State/EVVideoState.swift b/Source/State/EVVideoState.swift index 4f8722d..767efa6 100644 --- a/Source/State/EVVideoState.swift +++ b/Source/State/EVVideoState.swift @@ -8,11 +8,24 @@ import Foundation public enum EVVideoState { + + /// Thumbnail view will shown at first, + /// Loading starts + /// After player is ready thumbnail view will hidden case quickPlay + case play + case pause + + /// Thumbnail view will shown at top with play button case thumbnail + + /// Update cover layer, shows restart button case ended + case restart + + /// Empty view will top if url is empty case empty } diff --git a/Source/Views/EVSliderView.swift b/Source/Views/EVSliderView.swift index 138e711..498b825 100644 --- a/Source/Views/EVSliderView.swift +++ b/Source/Views/EVSliderView.swift @@ -45,8 +45,6 @@ final class EVSliderView: UISlider { value = 0 thumbTintColor = .white minimumTrackTintColor = .orange - addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged) - addTarget(self, action: #selector(touchUpInside(_:)), for: .touchUpInside) setThumbImage(makeSliderImage(size: CGSize(width: 14, height: 14)), for: .normal) setThumbImage(makeSliderImage(size: CGSize(width: 18, height: 18)), for: .highlighted) @@ -55,6 +53,8 @@ final class EVSliderView: UISlider { value = 1.0 minimumTrackTintColor = .white thumbTintColor = .clear + addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged) + addTarget(self, action: #selector(touchUpInside(_:)), for: .touchUpInside) setThumbImage(makeSliderImage(size: CGSize(width: 4, height: 4)), for: .normal) setThumbImage(makeSliderImage(size: CGSize(width: 4, height: 4)), for: .highlighted) self.transform = CGAffineTransform(scaleX: 1.0, y: 1.2) diff --git a/Source/Views/EVThumbnailView.swift b/Source/Views/EVThumbnailView.swift index ba358f0..fbdeb7c 100644 --- a/Source/Views/EVThumbnailView.swift +++ b/Source/Views/EVThumbnailView.swift @@ -17,7 +17,7 @@ public class EVThumbnailView: EVBaseView { private let imageView: UIImageView = { let imageView = UIImageView() - imageView.contentMode = .scaleAspectFit + imageView.contentMode = .scaleToFill return imageView }() @@ -42,6 +42,7 @@ public class EVThumbnailView: EVBaseView { weak var delegate: EVThumbnailViewDelegate? override func setup() { + backgroundColor = .black addSubview(imageView) addSubview(alphaView) addSubview(centeredButton)