From be27b20ab03bd74ae0c1051a870fd8ad2f842cc3 Mon Sep 17 00:00:00 2001 From: Vitor Ferraz Varela Date: Fri, 5 Feb 2021 17:19:28 -0300 Subject: [PATCH] Revert "Merge pull request #423 from clappr/feature/fullscreen" This reverts commit 2772a078ca0902a3883148a0b22b262402ef5082. --- Clappr.xcodeproj/project.pbxproj | 12 + Example/Clappr/Base.lproj/Main.storyboard | 64 +- Example/Clappr/DashboardViewController.swift | 3 +- Example/Clappr/ViewController.swift | 52 +- Sources/Clappr/Classes/Base/Core.swift | 47 +- Sources/Clappr/Classes/Base/Options.swift | 13 +- Sources/Clappr/Classes/Enum/Event.swift | 2 + .../Clappr/Classes/Enum/InternalEvent.swift | 1 + .../FullScreen/FullScreenStateHandler.swift | 44 +- .../FullScreen/FullscreenController.swift | 20 + Sources/Clappr_iOS/Classes/Base/Player.swift | 3 + .../Core/MediaControl/FullscreenButton.swift | 2 +- .../Classes/Base/FullscreenTests.swift | 16 + .../Classes/Base/OptionsUnboxerTests.swift | 44 ++ .../Classes/Base/PlayerTests.swift | 10 + .../Classes/Plugin/Core/CoreTests.swift | 585 +++++++++++++----- .../MediaControl/FullscreenButtonTests.swift | 4 +- 17 files changed, 668 insertions(+), 254 deletions(-) create mode 100644 Sources/Clappr_iOS/Classes/Base/FullScreen/FullscreenController.swift create mode 100644 Tests/Clappr_Tests/Classes/Base/FullscreenTests.swift create mode 100644 Tests/Clappr_Tests/Classes/Base/OptionsUnboxerTests.swift diff --git a/Clappr.xcodeproj/project.pbxproj b/Clappr.xcodeproj/project.pbxproj index 1c6470c7d..600d63355 100644 --- a/Clappr.xcodeproj/project.pbxproj +++ b/Clappr.xcodeproj/project.pbxproj @@ -277,12 +277,14 @@ 9E1613FE2051D3DD006E95F2 /* Swifter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E1613FD2051D3DC006E95F2 /* Swifter.framework */; }; 9E1EB321208E3511005714D1 /* cover.png in Resources */ = {isa = PBXBuildFile; fileRef = 9E1EB320208E3511005714D1 /* cover.png */; }; 9E26CFF32016149600AE92B7 /* FullScreenStateHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E26CFF12016149600AE92B7 /* FullScreenStateHandler.swift */; }; + 9E26CFF42016149600AE92B7 /* FullscreenController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E26CFF22016149600AE92B7 /* FullscreenController.swift */; }; 9E2F9B191F9F8C2400A07786 /* FullscreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E692E121F9F8A54006510BE /* FullscreenUITests.swift */; }; 9E2F9B1A1F9F8C2600A07786 /* XCTWaiter+WaitFor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E692E141F9F8A54006510BE /* XCTWaiter+WaitFor.swift */; }; 9E2F9B1B1F9F8C2A00A07786 /* DashboardViewElements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E692E101F9F8A53006510BE /* DashboardViewElements.swift */; }; 9E2F9B1C1F9F8C2C00A07786 /* DashboardViewInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E692E111F9F8A54006510BE /* DashboardViewInteractor.swift */; }; 9E2F9B1D1F9F8C2F00A07786 /* PlayerViewElements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E692E0F1F9F8A53006510BE /* PlayerViewElements.swift */; }; 9E2F9B1E1F9F8C3100A07786 /* PlayerViewInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E692E131F9F8A54006510BE /* PlayerViewInteractor.swift */; }; + 9E3AE5341FB330C000DFBBEB /* OptionsUnboxerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E3AE5331FB330C000DFBBEB /* OptionsUnboxerTests.swift */; }; 9E5226791FBA207C008C4A15 /* NoOpPlaybackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5226781FBA207C008C4A15 /* NoOpPlaybackTests.swift */; }; 9E692E0A1F9F89B3006510BE /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B46D05A51DB7DBC50004B23F /* Nimble.framework */; }; 9E692E0B1F9F89B3006510BE /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B46D05A71DB7DBD60004B23F /* Quick.framework */; }; @@ -389,6 +391,7 @@ F7CB23871A0B33EF168195C9 /* Pods_Clappr_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C168F843D95B224A33184B83 /* Pods_Clappr_Example.framework */; }; FB0A223C2175492E00D2180F /* Loader+Plugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE6A3E32163EEAE0083C9CC /* Loader+Plugins.swift */; }; FB0A223F21754CBD00D2180F /* PlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB0A223D21754C1700D2180F /* PlayerTests.swift */; }; + FB3A8F1A209F8D7000CEFC3D /* FullscreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB61A3F2209F885800BDF267 /* FullscreenTests.swift */; }; FB46260121AC8D2B008D9A25 /* CoreFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB46260021AC8D2B008D9A25 /* CoreFactory.swift */; }; FBD47AAD21AD6733003265AF /* CoreFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB46260021AC8D2B008D9A25 /* CoreFactory.swift */; }; FBD47AAF21AD6856003265AF /* ContainerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD47AAE21AD6856003265AF /* ContainerFactory.swift */; }; @@ -652,6 +655,8 @@ 9E1613FD2051D3DC006E95F2 /* Swifter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Swifter.framework; path = Carthage/Build/iOS/Swifter.framework; sourceTree = ""; }; 9E1EB320208E3511005714D1 /* cover.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cover.png; sourceTree = ""; }; 9E26CFF12016149600AE92B7 /* FullScreenStateHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FullScreenStateHandler.swift; path = Sources/Clappr_iOS/Classes/Base/FullScreen/FullScreenStateHandler.swift; sourceTree = SOURCE_ROOT; }; + 9E26CFF22016149600AE92B7 /* FullscreenController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FullscreenController.swift; path = Sources/Clappr_iOS/Classes/Base/FullScreen/FullscreenController.swift; sourceTree = SOURCE_ROOT; }; + 9E3AE5331FB330C000DFBBEB /* OptionsUnboxerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsUnboxerTests.swift; sourceTree = ""; }; 9E5226781FBA207C008C4A15 /* NoOpPlaybackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoOpPlaybackTests.swift; sourceTree = ""; }; 9E692DFF1F9F898F006510BE /* Clappr_UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Clappr_UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9E692E0E1F9F8A53006510BE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -742,6 +747,7 @@ F16E2A9424FDA14000C0B220 /* sample.movpkg */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = sample.movpkg; sourceTree = ""; }; FB0A223D21754C1700D2180F /* PlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTests.swift; sourceTree = ""; }; FB46260021AC8D2B008D9A25 /* CoreFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreFactory.swift; sourceTree = ""; }; + FB61A3F2209F885800BDF267 /* FullscreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullscreenTests.swift; sourceTree = ""; }; FBD47AAE21AD6856003265AF /* ContainerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerFactory.swift; sourceTree = ""; }; FBE6A3E32163EEAE0083C9CC /* Loader+Plugins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Loader+Plugins.swift"; sourceTree = ""; }; FC6418DB2007DA6200E81B7C /* ClapprDateFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClapprDateFormatterTests.swift; sourceTree = ""; }; @@ -1310,6 +1316,7 @@ 9E26CFF02016148400AE92B7 /* FullScreen */ = { isa = PBXGroup; children = ( + 9E26CFF22016149600AE92B7 /* FullscreenController.swift */, 9E26CFF12016149600AE92B7 /* FullScreenStateHandler.swift */, ); path = FullScreen; @@ -1480,8 +1487,10 @@ C93466972305BC680062E8DE /* Factories */, 969703A51BEA8C7500B7F282 /* BaseObjectTests.swift */, 9697039F1BEA716100B7F282 /* EventHandlerTests.swift */, + FB61A3F2209F885800BDF267 /* FullscreenTests.swift */, 96A9CE371C97387000C21E80 /* LoaderTests.swift */, 329624A72051ECFF0062AE32 /* MediaOptionTests.swift */, + 9E3AE5331FB330C000DFBBEB /* OptionsUnboxerTests.swift */, EFEC758023C52A7D00FA6A52 /* PlaybackRendererTests.swift */, 96664E581BF0D58400095E6E /* PlaybackTests.swift */, 9603A06B1C05E6D100B55D8F /* PlayerTests.swift */, @@ -2555,6 +2564,7 @@ 96A9CE391C973A0600C21E80 /* LoaderTests.swift in Sources */, 568445B7251D39C7006568BF /* CoreLayerTests.swift in Sources */, FC6418E12007DC2F00E81B7C /* ClapprDateFormatterTests.swift in Sources */, + FB3A8F1A209F8D7000CEFC3D /* FullscreenTests.swift in Sources */, 48A29C08221DEF690004AD3B /* CorePluginTests.swift in Sources */, 23EDF91E224EAF9A00FB0D96 /* String+Ext.swift in Sources */, E7D0BD17218A389B00F5FDF2 /* SeekbarTests.swift in Sources */, @@ -2605,6 +2615,7 @@ C9EDF84F2162CF1600789E2F /* UINavigationControllerMock.swift in Sources */, 96A979881BFF964F00E5EA01 /* CoreTests.swift in Sources */, C9EDF85E2163E1E800789E2F /* MediaControlViewTests.swift in Sources */, + 9E3AE5341FB330C000DFBBEB /* OptionsUnboxerTests.swift in Sources */, 320A7A0F216D45A9006D2B24 /* PlayButtonTests.swift in Sources */, 969703A01BEA716100B7F282 /* EventHandlerTests.swift in Sources */, 9EED97082088E5B800D1C84A /* AVPlayerItemStub.swift in Sources */, @@ -2675,6 +2686,7 @@ 96D362C71D41339400CCB866 /* EventHandler.swift in Sources */, FBD47AAF21AD6856003265AF /* ContainerFactory.swift in Sources */, AB163517215ACA0C00635233 /* MediaControl.swift in Sources */, + 9E26CFF42016149600AE92B7 /* FullscreenController.swift in Sources */, 96D362E71D41339400CCB866 /* Plugin.swift in Sources */, 7B5EBDEC23297EFE00B2FBBC /* BottomDrawerPlugin.swift in Sources */, 96D362D81D41339400CCB866 /* PluginType.swift in Sources */, diff --git a/Example/Clappr/Base.lproj/Main.storyboard b/Example/Clappr/Base.lproj/Main.storyboard index 444c581fb..c2dddcf81 100644 --- a/Example/Clappr/Base.lproj/Main.storyboard +++ b/Example/Clappr/Base.lproj/Main.storyboard @@ -1,10 +1,9 @@ - + - - + @@ -36,7 +35,13 @@ - + + + + - + - - + + + + + - - - + + + + + + @@ -97,20 +112,10 @@ - - - - - - - - - - @@ -118,7 +123,7 @@ - - - - - - - - @@ -163,9 +158,4 @@ - - - - - diff --git a/Example/Clappr/DashboardViewController.swift b/Example/Clappr/DashboardViewController.swift index d86713808..674b66a27 100644 --- a/Example/Clappr/DashboardViewController.swift +++ b/Example/Clappr/DashboardViewController.swift @@ -19,7 +19,8 @@ class DashboardViewController: UIViewController { let options: Options = [ kSourceUrl: "http://clappr.io/highline.mp4", kPosterUrl: "http://clappr.io/poster.png", - kStartInFullscreen: switchFullscreen.isOn + kFullscreen: switchFullscreen.isOn, + kFullscreenByApp: switchFullscreenControledByApp.isOn ] viewController?.options = options } diff --git a/Example/Clappr/ViewController.swift b/Example/Clappr/ViewController.swift index 103af474d..0d298c406 100644 --- a/Example/Clappr/ViewController.swift +++ b/Example/Clappr/ViewController.swift @@ -5,11 +5,13 @@ class ViewController: UIViewController { @objc var fullscreenController = UIViewController() @IBOutlet weak var playerContainer: UIView! - @IBOutlet weak var fullscreenView: UIView! - @IBOutlet weak var playerView: UIView! @objc var player: Player! @objc var options: Options = [:] + @objc var fullscreenByApp: Bool { + return options[kFullscreenByApp] as? Bool ?? false + } + override func viewDidLoad() { super.viewDidLoad() createPlayer() @@ -54,42 +56,20 @@ class ViewController: UIViewController { } @objc func onRequestFullscreen() { - setOrientation(.landscapeRight) - setFullscreen(true) - setFullscreenConstraints() + guard fullscreenByApp else { return } + fullscreenController.modalPresentationStyle = .overFullScreen + present(fullscreenController, animated: false) { + self.player.setFullscreen(true) + } + player.presentFullscreenIn(fullscreenController) } @objc func onExitFullscreen() { - setOrientation(.portrait) - setFullscreen(false) - setPlayerContainerConstraints() - } - - private func setOrientation(_ orientation: UIInterfaceOrientation) { - UIDevice.current.setValue(orientation.rawValue, forKey: "orientation") - UIViewController.attemptRotationToDeviceOrientation() - } - - private func setFullscreen(_ fullscreen: Bool) { - player.setFullscreen(fullscreen) - fullscreenView.isHidden = !fullscreen - navigationController?.setNavigationBarHidden(fullscreen, animated: true) - } - - private func setFullscreenConstraints() { - fullscreenView.addSubviewMatchingConstraints(playerView) - NSLayoutConstraint.activate([ - fullscreenView.leftAnchor.constraint(equalTo: view.leftAnchor), - fullscreenView.topAnchor.constraint(equalTo: view.topAnchor), - fullscreenView.rightAnchor.constraint(equalTo: view.rightAnchor), - fullscreenView.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) - fullscreenView.layoutIfNeeded() - } - - private func setPlayerContainerConstraints() { - playerContainer.addSubviewMatchingConstraints(playerView) - playerContainer.layoutIfNeeded() + guard fullscreenByApp else { return } + fullscreenController.dismiss(animated: false) { + self.player.setFullscreen(false) + } + player.fitParentView() } override func viewWillDisappear(_ animated: Bool) { @@ -115,7 +95,7 @@ class ViewController: UIViewController { listenToPlayerEvents() - player.attachTo(playerView, controller: self) + player.attachTo(playerContainer, controller: self) } @IBAction func recreatePlayer(_ sender: Any) { diff --git a/Sources/Clappr/Classes/Base/Core.swift b/Sources/Clappr/Classes/Base/Core.swift index 6f4a27f88..674a3534d 100644 --- a/Sources/Clappr/Classes/Base/Core.swift +++ b/Sources/Clappr/Classes/Base/Core.swift @@ -21,10 +21,16 @@ open class Core: UIObject, UIGestureRecognizerDelegate { @objc open var overlayView = PassthroughView() #if os(iOS) - lazy var fullscreenHandler: FullscreenStateHandler? = FullscreenHandler(core: self) + @objc private (set) var fullscreenController: FullscreenController? = FullscreenController(nibName: nil, bundle: nil) + + lazy var fullscreenHandler: FullscreenStateHandler? = { + return self.optionsUnboxer.fullscreenControledByApp ? FullscreenByApp(core: self) : FullscreenByPlayer(core: self) as FullscreenStateHandler + }() private var orientationObserver: OrientationObserver? #endif + lazy var optionsUnboxer: OptionsUnboxer = OptionsUnboxer(options: self.options) + @objc open weak var activeContainer: Container? { willSet { @@ -112,6 +118,7 @@ open class Core: UIObject, UIGestureRecognizerDelegate { private func bindEventListeners() { #if os(iOS) listenTo(self, eventName: InternalEvent.userRequestEnterInFullscreen.rawValue) { [weak self] _ in self?.fullscreenHandler?.enterInFullscreen() } + listenTo(self, eventName: InternalEvent.userRequestExitFullscreen.rawValue) { [weak self] _ in self?.onUserRequestExitFullscreen() } orientationObserver = OrientationObserver(core: self) #endif } @@ -142,15 +149,24 @@ open class Core: UIObject, UIGestureRecognizerDelegate { private func addToContainer() { #if os(iOS) - renderCorePlugins() - renderMediaControlElements() - renderOverlayPlugins() - if options.bool(kStartInFullscreen, orElse: false) { - trigger(InternalEvent.userRequestEnterInFullscreen.rawValue) + if shouldEnterInFullScreen { + renderCorePlugins() + renderMediaControlElements() + fullscreenHandler?.enterInFullscreen() + } else { + renderInContainerView() + renderCorePlugins() + renderMediaControlElements() } + renderOverlayPlugins() #else + renderInContainerView() renderPlugins() #endif + } + + private func renderInContainerView() { + isFullscreen = false parentView?.addSubviewMatchingConstraints(view) } @@ -221,6 +237,14 @@ open class Core: UIObject, UIGestureRecognizerDelegate { plugin.safeRender() } + private var shouldEnterInFullScreen: Bool { + return optionsUnboxer.fullscreen && !optionsUnboxer.fullscreenControledByApp + } + + private var isFullscreenButtonDisable: Bool { optionsUnboxer.fullscreenDisabled } + private var isFullscreenControlledByPlayer: Bool { !optionsUnboxer.fullscreenControledByApp } + private var shouldDestroyPlayer: Bool { isFullscreenButtonDisable && isFullscreenControlledByPlayer } + open func addPlugin(_ plugin: Plugin) { let containsPluginWithPlaceholder = plugins.contains(where: { $0.hasPlaceholder }) @@ -235,6 +259,16 @@ open class Core: UIObject, UIGestureRecognizerDelegate { #endif } + private func onUserRequestExitFullscreen() { + #if os(iOS) + if shouldDestroyPlayer { + trigger(InternalEvent.requestDestroyPlayer.rawValue) + } else { + fullscreenHandler?.exitFullscreen() + } + #endif + } + @objc open func destroy() { Logger.logDebug("destroying", scope: "Core") @@ -255,6 +289,7 @@ open class Core: UIObject, UIGestureRecognizerDelegate { #if os(iOS) fullscreenHandler?.destroy() fullscreenHandler = nil + fullscreenController = nil orientationObserver = nil #endif view.removeFromSuperview() diff --git a/Sources/Clappr/Classes/Base/Options.swift b/Sources/Clappr/Classes/Base/Options.swift index 8917534c8..3cc40d9c0 100644 --- a/Sources/Clappr/Classes/Base/Options.swift +++ b/Sources/Clappr/Classes/Base/Options.swift @@ -2,8 +2,9 @@ public typealias Options = [String: Any] public let kPosterUrl = "posterUrl" public let kSourceUrl = "sourceUrl" -public let kStartInFullscreen = "startInFullscreen" -public let kDisableFullscreenButton = "disableFullscreenButton" +public let kFullscreen = "fullscreen" +public let kFullscreenDisabled = "fullscreenDisabled" +public let kFullscreenByApp = "fullscreenByApp" public let kStartAt = "startAt" public let kLiveStartTime = "liveStartTime" public let kPlaybackNotSupportedMessage = "playbackNotSupportedMessage" @@ -29,3 +30,11 @@ public let kMetaDataTitle = "mdTitle" public let kMetaDataDescription = "mdDescription" public let kMetaDataDate = "mdDate" public let kMetaDataArtwork = "mdArtwork" + +struct OptionsUnboxer { + let options: Options + + var fullscreenControledByApp: Bool { options.bool(kFullscreenByApp, orElse: false)} + var fullscreen: Bool { options.bool(kFullscreen, orElse: false) } + var fullscreenDisabled: Bool { options.bool(kFullscreenDisabled, orElse: false) } +} diff --git a/Sources/Clappr/Classes/Enum/Event.swift b/Sources/Clappr/Classes/Enum/Event.swift index 431a372e5..7724648a4 100644 --- a/Sources/Clappr/Classes/Enum/Event.swift +++ b/Sources/Clappr/Classes/Enum/Event.swift @@ -45,7 +45,9 @@ public enum Event: String, CaseIterable { case didChangeActiveContainer = "Clappr:didChangeActiveContainer" case willChangeActivePlayback = "Clappr:willChangeActivePlayback" case didChangeActivePlayback = "Clappr:didChangeActivePlayback" + case willEnterFullscreen = "Clappr:willEnterFullscreen" case didEnterFullscreen = "Clappr:didEnterFullscreen" + case willExitFullscreen = "Clappr:willExitFullscreen" case didExitFullscreen = "Clappr:didExitFullscreen" case didUpdateDuration = "Clappr:didUpdateDuration" case didShowModal = "Clappr:didShowModal" diff --git a/Sources/Clappr/Classes/Enum/InternalEvent.swift b/Sources/Clappr/Classes/Enum/InternalEvent.swift index 68f92a626..4461cd8b4 100644 --- a/Sources/Clappr/Classes/Enum/InternalEvent.swift +++ b/Sources/Clappr/Classes/Enum/InternalEvent.swift @@ -6,4 +6,5 @@ public enum InternalEvent: String { case didFinishScrubbing = "Clappr:didFinishScrubbing" case didQuickSeek = "Clappr:didQuickSeek" case didDragDrawer = "Clappr:didDragDrawer" + case requestDestroyPlayer = "Clappr:requestDestroyPlayer" } diff --git a/Sources/Clappr_iOS/Classes/Base/FullScreen/FullScreenStateHandler.swift b/Sources/Clappr_iOS/Classes/Base/FullScreen/FullScreenStateHandler.swift index d064d5f60..2fa12ff17 100644 --- a/Sources/Clappr_iOS/Classes/Base/FullScreen/FullScreenStateHandler.swift +++ b/Sources/Clappr_iOS/Classes/Base/FullScreen/FullScreenStateHandler.swift @@ -15,16 +15,58 @@ extension FullscreenStateHandler { func destroy() { } } -struct FullscreenHandler: FullscreenStateHandler { +struct FullscreenByApp: FullscreenStateHandler { var core: Core func set(fullscreen: Bool) { guard core.isFullscreen != fullscreen else { return } core.isFullscreen = fullscreen if fullscreen { + core.trigger(Event.willEnterFullscreen.rawValue) core.trigger(Event.didEnterFullscreen.rawValue) } else { + core.trigger(Event.willExitFullscreen.rawValue) core.trigger(Event.didExitFullscreen.rawValue) } } } + +struct FullscreenByPlayer: FullscreenStateHandler { + var core: Core + + func set(fullscreen: Bool) { + guard core.isFullscreen != fullscreen else { return } + fullscreen ? enterInFullscreen() : exitFullscreen() + } + + func enterInFullscreen() { + guard let fullscreenController = core.fullscreenController else { return } + guard !core.isFullscreen else { return } + core.trigger(Event.willEnterFullscreen.rawValue) + core.isFullscreen = true + fullscreenController.view.backgroundColor = UIColor.black + fullscreenController.modalPresentationStyle = .overFullScreen + core.parentController?.present(fullscreenController, animated: false) { + fullscreenController.view.addSubviewMatchingConstraints(self.core.view) + self.core.trigger(Event.didEnterFullscreen.rawValue) + } + } + + func exitFullscreen() { + guard core.isFullscreen else { return } + core.trigger(Event.willExitFullscreen.rawValue) + core.isFullscreen = false + handleExit() + core.trigger(Event.didExitFullscreen.rawValue) + } + + private func handleExit() { + core.parentView?.addSubviewMatchingConstraints(core.view) + core.fullscreenController?.dismiss(animated: false, completion: nil) + } + + func destroy() { + guard core.isFullscreen else { return } + handleExit() + } +} diff --git a/Sources/Clappr_iOS/Classes/Base/FullScreen/FullscreenController.swift b/Sources/Clappr_iOS/Classes/Base/FullScreen/FullscreenController.swift new file mode 100644 index 000000000..a066497b2 --- /dev/null +++ b/Sources/Clappr_iOS/Classes/Base/FullScreen/FullscreenController.swift @@ -0,0 +1,20 @@ +import UIKit + +class FullscreenController: UIViewController { + + override var shouldAutorotate: Bool { + return true + } + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return UIInterfaceOrientationMask.landscape + } + + override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { + return UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft ? UIInterfaceOrientation.landscapeLeft : UIInterfaceOrientation.landscapeRight + } + + override var prefersHomeIndicatorAutoHidden: Bool { + return true + } +} diff --git a/Sources/Clappr_iOS/Classes/Base/Player.swift b/Sources/Clappr_iOS/Classes/Base/Player.swift index 9b10fa27f..353388500 100644 --- a/Sources/Clappr_iOS/Classes/Base/Player.swift +++ b/Sources/Clappr_iOS/Classes/Base/Player.swift @@ -107,6 +107,9 @@ open class Player: BaseObject { } listenTo(core, eventName: InternalEvent.userRequestExitFullscreen.rawValue) { [weak self] userInfo in self?.trigger(.exitFullscreen, userInfo: userInfo) } + listenTo(core, eventName: InternalEvent.requestDestroyPlayer.rawValue) { [weak self] _ in + self?.destroy() + } } private func bindMediaControlEvents() { diff --git a/Sources/Clappr_iOS/Classes/Plugin/Core/MediaControl/FullscreenButton.swift b/Sources/Clappr_iOS/Classes/Plugin/Core/MediaControl/FullscreenButton.swift index 46685f11d..7bf35c4a0 100644 --- a/Sources/Clappr_iOS/Classes/Plugin/Core/MediaControl/FullscreenButton.swift +++ b/Sources/Clappr_iOS/Classes/Plugin/Core/MediaControl/FullscreenButton.swift @@ -12,7 +12,7 @@ open class FullscreenButton: MediaControl.Element { button.contentHorizontalAlignment = .fill button.addTarget(self, action: #selector(toggleFullscreenButton), for: .touchUpInside) - view.isHidden = core?.options[kDisableFullscreenButton] as? Bool ?? false + view.isHidden = core?.options[kFullscreenDisabled] as? Bool ?? false view.addSubview(button) } } diff --git a/Tests/Clappr_Tests/Classes/Base/FullscreenTests.swift b/Tests/Clappr_Tests/Classes/Base/FullscreenTests.swift new file mode 100644 index 000000000..99f751910 --- /dev/null +++ b/Tests/Clappr_Tests/Classes/Base/FullscreenTests.swift @@ -0,0 +1,16 @@ +import Quick +import Nimble + +@testable import Clappr + +class FullscreenTests: QuickSpec { + + override func spec() { + describe("#Fullscreen") { + it("prefersHomeIndicatorAutoHidden is set as true") { + let fullscreenController = FullscreenController() + expect(fullscreenController.prefersHomeIndicatorAutoHidden).to(beTrue()) + } + } + } +} diff --git a/Tests/Clappr_Tests/Classes/Base/OptionsUnboxerTests.swift b/Tests/Clappr_Tests/Classes/Base/OptionsUnboxerTests.swift new file mode 100644 index 000000000..be9553c5a --- /dev/null +++ b/Tests/Clappr_Tests/Classes/Base/OptionsUnboxerTests.swift @@ -0,0 +1,44 @@ +import Quick +import Nimble +@testable import Clappr + +class OptionsUnboxerTests: QuickSpec { + override func spec() { + + describe(".OptionsUnboxer") { + + var optionsUnboxer: OptionsUnboxer! + + context("when no options is passed") { + + beforeEach { + optionsUnboxer = OptionsUnboxer(options: [:]) + } + + it("returns `false` for `fullscreen`") { + expect(optionsUnboxer.fullscreen).to(beFalse()) + } + + it("returns `false` for `kFullscreenByApp`") { + expect(optionsUnboxer.fullscreenControledByApp).to(beFalse()) + } + } + + context("when options is passed") { + + beforeEach { + optionsUnboxer = OptionsUnboxer(options: [kFullscreen: true, kFullscreenByApp: true]) + } + + it("returns correct value for `fullscreen`") { + expect(optionsUnboxer.fullscreen).to(beTrue()) + } + + it("returns correct value for `kFullscreenByApp`") { + expect(optionsUnboxer.fullscreenControledByApp).to(beTrue()) + } + } + } + } +} + diff --git a/Tests/Clappr_Tests/Classes/Base/PlayerTests.swift b/Tests/Clappr_Tests/Classes/Base/PlayerTests.swift index ff2e8055e..86e99bc49 100644 --- a/Tests/Clappr_Tests/Classes/Base/PlayerTests.swift +++ b/Tests/Clappr_Tests/Classes/Base/PlayerTests.swift @@ -231,6 +231,16 @@ class PlayerTests: QuickSpec { expect(callbackWasCalled).to(beTrue()) } + + it("triggers a didDestroy event when requestDestroyPlayer was listened") { + player.on(.didDestroy) { _ in + callbackWasCalled = true + } + + player.core?.trigger(InternalEvent.requestDestroyPlayer.rawValue) + + expect(callbackWasCalled).to(beTrue()) + } } context("core dependency") { diff --git a/Tests/Clappr_Tests/Classes/Plugin/Core/CoreTests.swift b/Tests/Clappr_Tests/Classes/Plugin/Core/CoreTests.swift index 18f11e75b..7511dd9ab 100644 --- a/Tests/Clappr_Tests/Classes/Plugin/Core/CoreTests.swift +++ b/Tests/Clappr_Tests/Classes/Plugin/Core/CoreTests.swift @@ -4,18 +4,18 @@ import Nimble class CoreTests: QuickSpec { override func spec() { - + class StubPlayback: Playback { override class var name: String { return "stupPlayback" } } - + class FakeCorePlugin: UICorePlugin { override class var name: String { return "FakeCorePLugin" } - + override func bindEvents() { } } @@ -29,7 +29,7 @@ class CoreTests: QuickSpec { override func compose(inside rootView: UIView) { didCallCompose = true } - + override func attachUICorePlugin(_ plugin: UICorePlugin) { didCallAttachUICorePlugin = true } @@ -41,65 +41,65 @@ class CoreTests: QuickSpec { override func showUI() { didCallShowUI = true } - + override func hideUI() { didCallHideUI = true } } - + let options: Options = [kSourceUrl: "http//test.com"] var core: Core! var layerComposer: LayerComposer! - + beforeEach { layerComposer = LayerComposer() core = Core(options: options) Loader.shared.resetPlugins() Loader.shared.register(playbacks: [StubPlayback.self]) } - + describe(".Core") { - + describe("#init") { - + beforeEach { core = CoreFactory.create(with: options, layerComposer: layerComposer) core.render() } - + it("set frame Rect to zero") { expect(core.view.frame) == CGRect.zero } - + it("save options passed on parameter") { let options = ["SomeOption": true] let core = Core(options: options) - + expect(core.options["SomeOption"] as? Bool) == true } - + it("activeContainer is not nil") { expect(core.activeContainer).toNot(beNil()) } - + it("containers list is not empty") { expect(core.containers).toNot(beEmpty()) } - + it("stores plugin instances") { Loader.shared.register(plugins: [UICorePluginMock.self, CorePluginMock.self]) - + let core = CoreFactory.create(with: options, layerComposer: layerComposer) - + expect(core.plugins.count).to(equal(2)) expect(core.plugins.compactMap({ $0 as? UICorePluginMock })).toNot(beNil()) expect(core.plugins.compactMap({ $0 as? CorePluginMock })).toNot(beNil()) } - + it("has an overlayView as a PassthroughView") { expect(core.overlayView).to(beAnInstanceOf(PassthroughView.self)) } - + #if os(iOS) it("has only one drawerPlugin with placeholder") { Loader.shared.register(plugins: [ @@ -108,14 +108,14 @@ class CoreTests: QuickSpec { MockPlaceholderDrawerPluginTwo.self, CorePluginMock.self, ]) - + let core = CoreFactory.create(with: options, layerComposer: layerComposer) - + expect(core.plugins.count).to(equal(3)) expect(core.plugins.first).to(beAKindOf(MockPlaceholderDrawerPluginOne.self)) } - - + + it("add gesture recognizer") { expect(core.view.gestureRecognizers?.count).to(beGreaterThan(0)) } @@ -128,27 +128,27 @@ class CoreTests: QuickSpec { } #endif } - + describe("On view ready") { var parentView: UIView! var parentViewController: UIViewController! - + beforeEach { parentView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) parentViewController = UIViewController() } - + context("when a parentView is set") { it("triggers a core ready event") { let core = CoreFactory.create(with: [:], layerComposer: layerComposer) - + var didTriggerEvent = false core.listenTo(core, eventName: Event.didAttachView.rawValue) { _ in didTriggerEvent = true } - + core.attach(to: parentView, controller: parentViewController) - + expect(didTriggerEvent).to(beTrue()) } @@ -160,29 +160,29 @@ class CoreTests: QuickSpec { expect(layerComposerSpy.didCallCompose).to(beTrue()) } - + context("and there isn't an active container") { it("fails to attach") { let core = Core() - + var didAttachPlayer = false core.listenTo(core, eventName: Event.didAttachView.rawValue) { _ in didAttachPlayer = true } - + core.attach(to: parentView, controller: parentViewController) - + expect(didAttachPlayer).to(beFalse()) } } - + #if os(iOS) context("when attach is called") { it("should attach the MediaControl layer on LayerComposer") { let layerComposerSpy = LayerComposerSpy() let core = CoreFactory.create(with: [:], layerComposer: layerComposerSpy) let mediaControlMock = MediaControlMock(context: core) - + core.addPlugin(mediaControlMock) core.attach(to: .init(), controller: .init()) core.render() @@ -193,120 +193,346 @@ class CoreTests: QuickSpec { #endif } } - + describe("Core sharedData") { context("on a brand new instance") { it("starts empty") { core = CoreFactory.create(with: [:], layerComposer: layerComposer) - + expect(core.sharedData).to(beEmpty()) } } - + context("when stores a value on sharedData") { beforeEach { core = CoreFactory.create(with: [:], layerComposer: layerComposer) core.sharedData["testKey"] = "testValue" } - + it("retrieves stored value") { expect(core.sharedData["testKey"] as? String) == "testValue" } } } - + #if os(iOS) describe("Fullscreen") { var options: Options! - + beforeEach { - options = [kStartInFullscreen: false] + options = [kFullscreen: false] } - + context("when kFullscreen is false") { it("start as embed video") { let core = Core(options: options) core.parentView = UIView() - + core.render() - + + expect(core.parentView?.subviews.contains(core.view)).to(beTrue()) + expect(core.isFullscreen).to(beFalse()) + } + + it("start as embed video when `kFullscreenByApp: true`") { + options[kFullscreenByApp] = true + let core = Core(options: options) + core.parentView = UIView() + + core.render() + expect(core.parentView?.subviews.contains(core.view)).to(beTrue()) expect(core.isFullscreen).to(beFalse()) } + + it("start as fullscreen video when `kFullscreenByApp: false` and setFullscreen is called") { + options[kFullscreenByApp] = false + let player = Player(options: options) + + self.playerSetup(player: player) + + player.setFullscreen(true) + + expect(player.core!.parentView?.subviews.contains(core.view)).to(beFalse()) + expect(player.isFullscreen).to(beTrue()) + expect(player.core!.isFullscreen).to(beTrue()) + } } - + context("when kFullscreen is true") { - + + it("start as fullscreen video") { + let options: Options = [kFullscreen: true] + let core = Core(options: options) + core.parentView = UIView() + core.parentController = self.rootViewController() + var callbackWasCalled = false + core.on(Event.didEnterFullscreen.rawValue) { _ in + callbackWasCalled = true + } + + core.render() + + expect(callbackWasCalled).toEventually(beTrue(), timeout: 5) + expect(core.parentView?.subviews.contains(core.view)).to(beFalse()) + expect(core.fullscreenController?.view.subviews.contains(core.view)).to(beTrue()) + expect(core.isFullscreen).to(beTrue()) + } + it("start as embed video when `kFullscreenByApp: true`") { - let options: Options = [kStartInFullscreen: true] + let options: Options = [kFullscreen: true, kFullscreenByApp: true] let core = Core(options: options) core.parentView = UIView() - + core.render() - + + expect(core.parentView?.subviews.contains(core.view)).to(beTrue()) + expect(core.isFullscreen).to(beFalse()) + } + + it("start as fullscreen video when `kFullscreenByApp: false`") { + let options: Options = [kFullscreenByApp: true] + let core = Core(options: options) + core.parentView = UIView() + + core.render() + expect(core.parentView?.subviews.contains(core.view)).to(beTrue()) expect(core.isFullscreen).to(beFalse()) } - } - + + context("when fullscreen is controled by player") { + + beforeEach { + core = Core(options: [kFullscreenByApp: false]) + core.parentView = UIView() + core.render() + } + + context("and setFullscreen(true) is called") { + + beforeEach { + core.parentController = self.rootViewController() + core.setFullscreen(false) + } + + it("removes core from parentView") { + core.setFullscreen(true) + + expect(core.parentView?.subviews.contains(core.view)).toEventually(beFalse()) + } + + it("sets core as subview of fullscreenController") { + core.setFullscreen(true) + + expect(core.fullscreenController?.view.subviews.contains(core.view)).toEventually(beTrue()) + } + + it("set isFullscreen to true") { + core.setFullscreen(true) + + expect(core.isFullscreen).to(beTrue()) + } + + it("sets the backgroundColor of fullscreenController to black") { + core.setFullscreen(true) + + expect(core.fullscreenController?.view.backgroundColor).to(equal(.black)) + } + + it("sets the modalPresentationStyle of fullscreenController to .overFullscreen") { + core.setFullscreen(true) + + expect(core.fullscreenController?.modalPresentationStyle) + .to(equal(UIModalPresentationStyle.overFullScreen)) + } + + it("triggers InternalEvent.didEnterFullscreen") { + var didTriggerEvent = false + core.on(Event.didEnterFullscreen.rawValue) { _ in + didTriggerEvent = true + } + + core.setFullscreen(true) + + expect(didTriggerEvent).toEventually(beTrue()) + } + + it("triggers InternalEvent.willEnterFullscreen") { + var didTriggerEvent = false + core.on(Event.willEnterFullscreen.rawValue) { _ in + didTriggerEvent = true + } + + core.setFullscreen(true) + + expect(didTriggerEvent).to(beTrue()) + } + + it("only set core as subview of fullscreenController once") { + core.setFullscreen(true) + core.setFullscreen(true) + + expect(core.fullscreenController?.view.subviews.filter { $0 == core.view }.count).toEventually(equal(1)) + } + } + + context("and setFullscreen(false) is called") { + + beforeEach { + core.setFullscreen(true) + } + + it("set isFullscreen to false") { + core.setFullscreen(false) + + expect(core.isFullscreen).to(beFalse()) + } + + it("triggers InternalEvent.willExitFullscreen") { + var didTriggerEvent = false + core.on(Event.willExitFullscreen.rawValue) { _ in + didTriggerEvent = true + } + + core.setFullscreen(false) + + expect(didTriggerEvent).to(beTrue()) + } + + it("triggers InternalEvent.didExitFullscreen") { + var didTriggerEvent = false + core.on(Event.didExitFullscreen.rawValue) { _ in + didTriggerEvent = true + } + + core.setFullscreen(false) + + expect(didTriggerEvent).to(beTrue()) + } + + it("sets core as subview of core.parentView") { + core.setFullscreen(false) + + expect(core.parentView?.subviews).to(contain(core.view)) + } + + it("removes core as subview of fullscreenController") { + core.setFullscreen(false) + + expect(core.fullscreenController?.view.subviews).toNot(contain(core.view)) + } + + it("only set core as subview of parentView once") { + core.setFullscreen(false) + core.setFullscreen(false) + + expect(core.parentView?.subviews.filter { $0 == core.view }.count).to(equal(1)) + } + } + + describe("#shouldDestroy") { + describe("Event.userRequestExitFullscreen is triggered") { + context("isFullscreenByPlayer") { + context("when isFullscreenByPlayer and isFullscreenDisable") { + it("triggers shouldDestroyPlayer event") { + let options: Options = [ + kFullscreenByApp: false, + kFullscreenDisabled: true + ] + let core = Core(options: options) + var didTriggerEvent = false + + core.on(InternalEvent.requestDestroyPlayer.rawValue) { _ in + didTriggerEvent = true + } + core.trigger(InternalEvent.userRequestExitFullscreen.rawValue) + + expect(didTriggerEvent).to(beTrue()) + } + } + + context("when isFullscreenEnabled") { + it("doesn't trigger shouldDestroyPlayer event") { + let options: Options = [ + kFullscreenByApp: false, + kFullscreenDisabled: false + ] + let core = Core(options: options) + var didTriggerEvent = false + + core.on(InternalEvent.requestDestroyPlayer.rawValue) { _ in + didTriggerEvent = true + } + core.trigger(InternalEvent.userRequestExitFullscreen.rawValue) + + expect(didTriggerEvent).to(beFalse()) + } + } + } + } + } + } + describe("Forward events") { - + var player: Player! - + beforeEach { player = Player() self.playerSetup(player: player) } - - + + context("when core trigger InternalEvent.userRequestEnterInFullscreen") { it("triggers Event.requestFullscreen on player") { var didTriggerEvent = false player.on(.requestFullscreen) { _ in didTriggerEvent = true } - + player.core?.trigger(InternalEvent.userRequestEnterInFullscreen.rawValue) - + expect(didTriggerEvent).toEventually(beTrue()) } } - + context("when core trigger InternalEvent.userRequestExitFullscreen") { it("triggers Event.exitFullscreen on player") { var didTriggerEvent = false player.on(.exitFullscreen) { _ in didTriggerEvent = true } - + player.core?.trigger(InternalEvent.userRequestExitFullscreen.rawValue) - + expect(didTriggerEvent).toEventually(beTrue()) } } } - + context("when fullscreen is controled by app") { - + beforeEach { + core = Core(options: [kFullscreenByApp: false]) core.parentView = UIView() core.render() } - + context("and setFullscreen(true) is called") { - + beforeEach { core.parentController = self.rootViewController() core.setFullscreen(false) } - + it("set isFullscreen to true") { core.setFullscreen(true) - + expect(core.isFullscreen).to(beTrue()) } - + it("triggers InternalEvent.didEnterFullscreen") { var didTriggerEvent = false core.on(Event.didEnterFullscreen.rawValue) { _ in @@ -314,115 +540,136 @@ class CoreTests: QuickSpec { } core.setFullscreen(true) - + expect(didTriggerEvent).toEventually(beTrue()) } - + + it("triggers InternalEvent.willEnterFullscreen") { + var didTriggerEvent = false + core.on(Event.willEnterFullscreen.rawValue) { _ in + didTriggerEvent = true + } + + core.setFullscreen(true) + + expect(didTriggerEvent).to(beTrue()) + } } - + context("and setFullscreen(false) is called") { - + beforeEach { core.setFullscreen(true) } - + it("set isFullscreen to false") { core.setFullscreen(false) - + expect(core.isFullscreen).to(beFalse()) } - + + it("triggers InternalEvent.willExitFullscreen") { + var didTriggerEvent = false + core.on(Event.willExitFullscreen.rawValue) { _ in + didTriggerEvent = true + } + + core.setFullscreen(false) + + expect(didTriggerEvent).to(beTrue()) + } + it("triggers InternalEvent.didExitFullscreen") { var didTriggerEvent = false core.on(Event.didExitFullscreen.rawValue) { _ in didTriggerEvent = true } - + core.setFullscreen(false) - + expect(didTriggerEvent).to(beTrue()) } } } - + context("when no options of fullscreen was passed") { it("start as embed video") { let core = Core() core.parentView = UIView() core.render() - + expect(core.parentView?.subviews.contains(core.view)).to(beTrue()) expect(core.isFullscreen).to(beFalse()) } } - + context("when only kFullscreenByApp is true") { - + it("start as embed video") { - let options: Options = [:] + let options: Options = [kFullscreenByApp: true] let core = Core(options: options) core.parentView = UIView() - + core.render() - + expect(core.parentView?.subviews.contains(core.view)).to(beTrue()) expect(core.isFullscreen).to(beFalse()) } - + it("start as fullscreen video when its false") { - let player = Player(options: [:]) - + let player = Player(options: [kFullscreenByApp: false]) + self.playerSetup(player: player) - + player.setFullscreen(true) - + expect(player.core!.parentView?.subviews.contains(core.view)).to(beFalse()) expect(player.core!.isFullscreen).to(beTrue()) } } - + context("when only kFullscreenByApp is false") { - + it("start as fullscreen video") { - let player = Player(options: [:]) - + let player = Player(options: [kFullscreenByApp: false]) + self.playerSetup(player: player) - + player.setFullscreen(true) - + expect(player.core!.parentView?.subviews.contains(core.view)).to(beFalse()) expect(player.core!.isFullscreen).to(beTrue()) } } } #endif - + describe("#options") { it("updates the container options") { core.options = ["foo": "bar"] - + core.containers.forEach { container in expect(container.options["foo"] as? String).to(equal("bar")) } } - + it("triggers didUpdateOptions when setted") { var didUpdateOptionsTriggered = false core.on(Event.didUpdateOptions.rawValue) { _ in didUpdateOptionsTriggered = true } - + core.options = [:] - + expect(didUpdateOptionsTriggered).to(beTrue()) } } - + describe("#Destroy") { it("trigger willDestroy event") { var didCallWillDestroy = false - + core.on(Event.willDestroy.rawValue) { _ in didCallWillDestroy = true } @@ -430,32 +677,32 @@ class CoreTests: QuickSpec { expect(didCallWillDestroy).toEventually(beTrue()) } - + it("trigger didDestroy event") { var didCallDidDestroy = false - + core.on(Event.willDestroy.rawValue) { _ in didCallDidDestroy = true } core.destroy() - + expect(didCallDidDestroy).toEventually(beTrue()) } - + it("remove listeners") { var didTriggerEvent = false let eventName = "teste" - + core.listenTo(core, eventName: eventName) { _ in didTriggerEvent = true } - + core.destroy() core.trigger(eventName) - + expect(didTriggerEvent).toEventually(beFalse()) } - + it("remove all containers") { var countOfDestroyedContainers = 0 core.containers.forEach { container in @@ -464,124 +711,126 @@ class CoreTests: QuickSpec { } } let countOfContainers = core.containers.count - + core.destroy() - + expect(countOfContainers) == countOfDestroyedContainers } - + #if os(iOS) it("clears fullscreenController reference") { core.destroy() + + expect(core.fullscreenController).toEventually(beNil()) } - + it("clears fullscreenHandler reference") { core.destroy() - + expect(core.fullscreenHandler).toEventually(beNil()) } #endif - + it("protect the main thread when plugin crashes in render") { let expectation = QuickSpec.current.expectation(description: "doesn't crash") UICorePluginMock.crashOnDestroy = true let core = Core() let plugin = UICorePluginMock(context: core) core.addPlugin(plugin) - + core.destroy() - + expectation.fulfill() QuickSpec.current.waitForExpectations(timeout: 1) } } - + context("when changes a activePlayback") { - + it("trigger willChangeActivePlayback event") { let core = Core() let container = Container() var didCallEvent = false - + core.on(Event.willChangeActivePlayback.rawValue) { userInfo in didCallEvent = true } - + core.activeContainer = container core.activeContainer?.playback = AVFoundationPlayback(options: [:]) expect(didCallEvent).toEventually(beTrue()) } - + it("trigger willChangePlayback on container") { let core = Core() let container = Container() var didCallEvent = false - + container.on(Event.willChangePlayback.rawValue) { userInfo in didCallEvent = true } - + core.activeContainer = container core.activeContainer?.playback = AVFoundationPlayback(options: [:]) expect(didCallEvent).toEventually(beTrue()) } } - + context("when changes a activeContainer") { - + it("trigger willChangeActiveContainer event") { let core = Core() let container = Container() var didCallEvent = false - + core.on(Event.willChangeActiveContainer.rawValue) { userInfo in didCallEvent = true } core.activeContainer = container - + expect(didCallEvent).toEventually(beTrue()) } - + it("trigger didChangeActiveContainer event") { let core = Core() let container = Container() var didCallEvent = false - + core.on(Event.didChangeActiveContainer.rawValue) { userInfo in didCallEvent = true } core.activeContainer = container - + expect(didCallEvent).toEventually(beTrue()) } - + it("removes events for old container") { let core = Core() let container = Container() var didCallEvent = false - + core.activeContainer = container core.activeContainer?.playback = AVFoundationPlayback(options: [:]) container.on(Event.didChangeActivePlayback.rawValue) { userInfo in didCallEvent = true } - + core.activeContainer = container container.playback = AVFoundationPlayback(options: [:]) - + expect(didCallEvent).toEventually(beFalse()) } } - + describe("#render") { #if os(tvOS) context("core position") { it("is positioned in front of Container view") { Loader.shared.register(plugins: [FakeCorePlugin.self]) let core = CoreFactory.create(with: options, layerComposer: layerComposer) - + core.render() - + expect(core.view.subviews.count).to(equal(2)) expect(core.view.subviews.first?.accessibilityIdentifier).to(equal("Container")) expect(core.view.subviews[1].accessibilityIdentifier).to(beNil()) @@ -599,28 +848,28 @@ class CoreTests: QuickSpec { expect(plugin.view.superview).to(beNil()) } - + it("renders MediaControlElements after CorePlugins") { UICorePluginMock.crashOnRender = false let core = Core() let mediaControl = MediaControl(context: core) let element = MediaControlElementMock(context: core) let plugin = UICorePluginMock(context: core) - + var renderingOrder: [String] = [] element.on("render") { _ in renderingOrder.append("element") } - + plugin.on("render") { _ in renderingOrder.append("plugin") } - + core.addPlugin(mediaControl) core.addPlugin(element) core.addPlugin(plugin) core.render() - + expect(renderingOrder).toEventually(equal(["plugin", "element"])) } @@ -643,50 +892,50 @@ class CoreTests: QuickSpec { Loader.shared.register(plugins: [UICorePluginStub.self]) let layerComposer = LayerComposerSpy() let core = CoreFactory.create(with: options, layerComposer: layerComposer) - + core.render() - + expect(layerComposer.didCallAttachUICorePlugin).to(beTrue()) } - + it("calls render on Plugin") { Loader.shared.resetPlugins() Loader.shared.register(plugins: [UICorePluginStub.self]) let layerComposer = LayerComposer() let core = CoreFactory.create(with: options, layerComposer: layerComposer) let uiCorePlugin = core.plugins.first { $0.pluginName == "UICorePluginStub"} as? UICorePluginStub - + core.render() - + expect(uiCorePlugin?.didCallRender).to(beTrue()) } } } #endif - + it("protect the main thread when plugin crashes in render") { let expectation = QuickSpec.current.expectation(description: "doesn't crash") UICorePluginMock.crashOnRender = true let core = Core() let plugin = UICorePluginMock(context: core) core.addPlugin(plugin) - + core.render() - + expectation.fulfill() QuickSpec.current.waitForExpectations(timeout: 1) } } - + describe("rendering") { context("when plugin is overlay") { it("renders on the overlay view") { Loader.shared.register(plugins: [OverlayPluginMock.self]) let core = CoreFactory.create(with: [:], layerComposer: layerComposer) - + core.render() - + expect(core.overlayView.subviews.count).to(equal(1)) } } @@ -715,7 +964,7 @@ class CoreTests: QuickSpec { #endif } } - + private func playerSetup(player: Player) { #if os(iOS) player.attachTo(UIView(), controller: rootViewController()) @@ -740,35 +989,35 @@ class UICorePluginMock: UICorePlugin { static var crashOnRender = false static var didCallDestroy = false static var crashOnDestroy = false - + override class var name: String { return "UICorePluginMock" } - + override func render() { UICorePluginMock.didCallRender = true - + if UICorePluginMock.crashOnRender { codeThatCrashes() } - + trigger("render") } - + override func bindEvents() { } - + override func destroy() { UICorePluginMock.didCallDestroy = true - + if UICorePluginMock.crashOnDestroy { codeThatCrashes() } } - + static func reset() { UICorePluginMock.didCallRender = false } - + private func codeThatCrashes() { NSException(name:NSExceptionName(rawValue: "TestError"), reason:"Test Error", userInfo:nil).raise() } @@ -793,7 +1042,7 @@ class MockPlaceholderDrawerPluginOne: DrawerPlugin { override class var name: String { return "MockPlaceholderDrawerPluginOne" } - + override var placeholder: CGFloat { return 1 } @@ -803,7 +1052,7 @@ class MockPlaceholderDrawerPluginTwo: DrawerPlugin { override class var name: String { return "MockPlaceholderDrawerPluginTwo" } - + override var placeholder: CGFloat { return 1 } diff --git a/Tests/Clappr_Tests/Classes/Plugin/Core/MediaControl/FullscreenButtonTests.swift b/Tests/Clappr_Tests/Classes/Plugin/Core/MediaControl/FullscreenButtonTests.swift index e8aa9f24d..96d2ef042 100644 --- a/Tests/Clappr_Tests/Classes/Plugin/Core/MediaControl/FullscreenButtonTests.swift +++ b/Tests/Clappr_Tests/Classes/Plugin/Core/MediaControl/FullscreenButtonTests.swift @@ -69,7 +69,7 @@ class FullscreenButtonTests: QuickSpec { context("when a new instance of button is created") { context("and kFullscreenDisabled of core.options is true") { it("hides view button") { - core = Core(options: [kDisableFullscreenButton: true]) + core = Core(options: [kFullscreenDisabled: true]) fullscreenButton = FullscreenButton(context: core) fullscreenButton.button = UIButton() @@ -80,7 +80,7 @@ class FullscreenButtonTests: QuickSpec { context("and kFullscreenDisabled of core.options is false") { it("shows fullscreen button") { - core = Core(options: [kDisableFullscreenButton: false]) + core = Core(options: [kFullscreenDisabled: false]) fullscreenButton = FullscreenButton(context: core) fullscreenButton.button = UIButton()