restructure video
This commit is contained in:
parent
c2f1cd200d
commit
0f0bc68f1e
@ -21,7 +21,8 @@
|
|||||||
|
|
||||||
public var addSizeConstraintsForAspectRatio = true
|
public var addSizeConstraintsForAspectRatio = true
|
||||||
public var shouldNotifyDelegateOnUpdate = true
|
public var shouldNotifyDelegateOnUpdate = true
|
||||||
|
public var shouldNotifyDelegateOnDefaultSizeChange = false
|
||||||
|
|
||||||
// Allows for a view to hardcode which height to use if there is none in the json.
|
// Allows for a view to hardcode which height to use if there is none in the json.
|
||||||
var imageWidth: CGFloat?
|
var imageWidth: CGFloat?
|
||||||
var imageHeight: CGFloat?
|
var imageHeight: CGFloat?
|
||||||
@ -228,7 +229,7 @@
|
|||||||
|
|
||||||
let widthWillChange = !MVMCoreGetterUtility.cgfequal(widthConstraint?.constant ?? 0, width ?? 0)
|
let widthWillChange = !MVMCoreGetterUtility.cgfequal(widthConstraint?.constant ?? 0, width ?? 0)
|
||||||
let heightWillChange = !MVMCoreGetterUtility.cgfequal(heightConstraint?.constant ?? 0, height ?? 0)
|
let heightWillChange = !MVMCoreGetterUtility.cgfequal(heightConstraint?.constant ?? 0, height ?? 0)
|
||||||
let sizeWillChange = (width == nil || height == nil) && !(size?.equalTo(imageView.image?.size ?? CGSize.zero) ?? false)
|
let sizeWillChange = shouldNotifyDelegateOnDefaultSizeChange && (width == nil || height == nil) && !(size?.equalTo(imageView.image?.size ?? CGSize.zero) ?? false)
|
||||||
let heightChangeFromSpinner = (heightConstraint?.isActive ?? false) ? false : ((height ?? size?.height) ?? 0) < loadingSpinnerHeightConstraint?.constant ?? CGFloat.leastNormalMagnitude
|
let heightChangeFromSpinner = (heightConstraint?.isActive ?? false) ? false : ((height ?? size?.height) ?? 0) < loadingSpinnerHeightConstraint?.constant ?? CGFloat.leastNormalMagnitude
|
||||||
return widthWillChange || heightWillChange || sizeWillChange || heightChangeFromSpinner
|
return widthWillChange || heightWillChange || sizeWillChange || heightChangeFromSpinner
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import AVKit
|
|||||||
|
|
||||||
open class Video: View {
|
open class Video: View {
|
||||||
public let videoViewController = AVPlayerViewController()
|
public let videoViewController = AVPlayerViewController()
|
||||||
|
public var delegateObject: MVMCoreUIDelegateObject?
|
||||||
|
|
||||||
/// Used to track the state and respond..
|
/// Used to track the state and respond..
|
||||||
private var stateKVOToken: NSKeyValueObservation?
|
private var stateKVOToken: NSKeyValueObservation?
|
||||||
@ -21,9 +22,22 @@ open class Video: View {
|
|||||||
videoViewController.videoGravity = .resizeAspectFill
|
videoViewController.videoGravity = .resizeAspectFill
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if the video is visible in the molecule delegate
|
||||||
|
open func isVisibleInDelegate() -> Bool {
|
||||||
|
guard let containingView = (delegateObject?.moleculeDelegate as? UIViewController)?.view else { return true }
|
||||||
|
return isVisible(in: containingView)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the video is visible in the passed in view
|
||||||
|
open func isVisible(in view: UIView) -> Bool {
|
||||||
|
return MVMCoreUIUtility.isView(self, visibleIn: view)
|
||||||
|
}
|
||||||
|
|
||||||
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||||
|
self.delegateObject = delegateObject
|
||||||
super.set(with: model, delegateObject, additionalData)
|
super.set(with: model, delegateObject, additionalData)
|
||||||
guard let model = model as? VideoModel else { return }
|
guard let model = model as? VideoModel else { return }
|
||||||
|
|
||||||
if let controller = delegateObject?.moleculeDelegate as? UIViewController {
|
if let controller = delegateObject?.moleculeDelegate as? UIViewController {
|
||||||
controller.addChild(videoViewController)
|
controller.addChild(videoViewController)
|
||||||
videoViewController.didMove(toParent: controller)
|
videoViewController.didMove(toParent: controller)
|
||||||
@ -31,26 +45,19 @@ open class Video: View {
|
|||||||
videoViewController.showsPlaybackControls = model.showControls
|
videoViewController.showsPlaybackControls = model.showControls
|
||||||
videoViewController.player = model.videoDataManager.player
|
videoViewController.player = model.videoDataManager.player
|
||||||
addStateObserver()
|
addStateObserver()
|
||||||
|
model.addVisibilityHalting(for: self, delegateObject: delegateObject)
|
||||||
|
|
||||||
switch (model.videoDataManager.videoState) {
|
switch (model.videoDataManager.videoState) {
|
||||||
case .none:
|
case .none:
|
||||||
// Begin loading the video
|
// Begin loading the video
|
||||||
model.videoDataManager.loadVideo()
|
model.videoDataManager.loadVideo()
|
||||||
case .loaded:
|
case .loaded:
|
||||||
// Video loaded
|
guard isVisibleInDelegate() else { return }
|
||||||
if model.alwaysReset {
|
// Video loaded, unhalt it if necessary.
|
||||||
// Always start video at the beginning.
|
model.halted = false
|
||||||
model.videoDataManager.player?.seek(to: .zero)
|
|
||||||
}
|
|
||||||
if model.autoPlay {
|
|
||||||
model.videoDataManager.player?.play()
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle pause behavior
|
|
||||||
model.addVisibleBehavior(for: self, delegateObject: delegateObject)
|
|
||||||
model.addScrollBehavior(for: self, delegateObject: delegateObject)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Listens and responds to video loading state changes.
|
/// Listens and responds to video loading state changes.
|
||||||
@ -73,10 +80,10 @@ open class Video: View {
|
|||||||
MVMCoreDispatchUtility.performSyncBlock(onMainThread: {
|
MVMCoreDispatchUtility.performSyncBlock(onMainThread: {
|
||||||
// Play the video
|
// Play the video
|
||||||
self.videoViewController.player = item.player
|
self.videoViewController.player = item.player
|
||||||
if model.autoPlay {
|
if !model.halted && model.autoPlay && self.isVisibleInDelegate() {
|
||||||
item.player?.play()
|
item.player?.play()
|
||||||
|
UIAccessibility.post(notification: .screenChanged, argument: self)
|
||||||
}
|
}
|
||||||
UIAccessibility.post(notification: .screenChanged, argument: self)
|
|
||||||
})
|
})
|
||||||
case .failed:
|
case .failed:
|
||||||
if let errorObject = item.loadFailedError {
|
if let errorObject = item.loadFailedError {
|
||||||
|
|||||||
@ -15,15 +15,36 @@ open class VideoModel: MoleculeModelProtocol {
|
|||||||
public var showControls = false
|
public var showControls = false
|
||||||
public var autoPlay = true
|
public var autoPlay = true
|
||||||
public var alwaysReset = false
|
public var alwaysReset = false
|
||||||
|
weak public var view: Video?
|
||||||
|
|
||||||
/// When the video is halted because it is no longer visible
|
/// When the video is halted because it is no longer visible
|
||||||
private var halted = false
|
public var halted: Bool = false {
|
||||||
|
didSet {
|
||||||
|
guard halted != oldValue,
|
||||||
|
videoDataManager.videoState == .loaded else { return }
|
||||||
|
if halted {
|
||||||
|
videoDataManager.player?.pause()
|
||||||
|
printThat(string: "halted")
|
||||||
|
} else {
|
||||||
|
printThat(string: "unhalted")
|
||||||
|
if alwaysReset {
|
||||||
|
// Always start video at the beginning.
|
||||||
|
videoDataManager.player?.seek(to: .zero)
|
||||||
|
}
|
||||||
|
if autoPlay {
|
||||||
|
videoDataManager.player?.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Keeps a reference to the video data.
|
/// Keeps a reference to the video data.
|
||||||
public var videoDataManager: VideoDataManager
|
public var videoDataManager: VideoDataManager
|
||||||
|
|
||||||
private weak var visibleBehavior: PageVisibilityClosureBehavior?
|
private weak var visibleBehavior: PageVisibilityClosureBehavior?
|
||||||
private weak var scrollBehavior: PageScrolledClosureBehavior?
|
private weak var scrollBehavior: PageScrolledClosureBehavior?
|
||||||
|
private var activeListener: Any?
|
||||||
|
private var resignActiveListener: Any?
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case moleculeName
|
case moleculeName
|
||||||
@ -62,38 +83,48 @@ open class VideoModel: MoleculeModelProtocol {
|
|||||||
try container.encode(alwaysReset, forKey: .alwaysReset)
|
try container.encode(alwaysReset, forKey: .alwaysReset)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func haltVideo() {
|
func printThat(string: String) {
|
||||||
guard !halted else { return }
|
var newString = "sss" + string + " model:\(Unmanaged.passUnretained(self).toOpaque())"
|
||||||
halted = true
|
if var view: UIView = self.view {
|
||||||
print("halted \(video)")
|
newString = newString + " view:\(Unmanaged.passUnretained(view).toOpaque())"
|
||||||
videoDataManager.player?.pause()
|
while (view as? BGVideoImageMolecule) == nil {
|
||||||
|
if let superView = view.superview {
|
||||||
|
view = superView
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let title = (((view as? BGVideoImageMolecule)?.view as? FooterView)?.view as? TwoButtonView)?.primaryButton.title(for: .normal) {
|
||||||
|
newString = newString + " \(title)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print(newString)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func unhaltVideo() {
|
open func addVisibilityHalting(for view: Video, delegateObject: MVMCoreUIDelegateObject?) {
|
||||||
guard halted else { return }
|
printThat(string: "before adding")
|
||||||
|
// reset
|
||||||
halted = false
|
halted = false
|
||||||
print("unhalted \(video)")
|
(view.model as? VideoModel)?.view = nil
|
||||||
guard videoDataManager.videoState == .loaded else { return }
|
self.view = view
|
||||||
if alwaysReset {
|
printThat(string: "after adding")
|
||||||
// Always start video at the beginning.
|
|
||||||
videoDataManager.player?.seek(to: .zero)
|
addVisibleBehavior(for: view, delegateObject: delegateObject)
|
||||||
}
|
addScrollBehavior(for: view, delegateObject: delegateObject)
|
||||||
if autoPlay {
|
addActiveListener(for: view, delegateObject: delegateObject)
|
||||||
videoDataManager.player?.play()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a behavior to pause the video on page hidden behavior and unpause if necessary on page shown.
|
/// Adds a behavior to pause the video on page hidden behavior and unpause if necessary on page shown.
|
||||||
open func addVisibleBehavior(for view: Video, delegateObject: MVMCoreUIDelegateObject?) {
|
open func addVisibleBehavior(for view: Video, delegateObject: MVMCoreUIDelegateObject?) {
|
||||||
|
|
||||||
let onShow = { [weak self] in
|
let onShow = { [weak self] in
|
||||||
guard let self = self,
|
guard let self = self,
|
||||||
let containingView = (delegateObject?.moleculeDelegate as? UIViewController)?.view,
|
let view = self.view,
|
||||||
MVMCoreUIUtility.isView(view, visibleIn: containingView) else { return }
|
view.isVisibleInDelegate() else { return }
|
||||||
self.unhaltVideo()
|
self.halted = false
|
||||||
}
|
}
|
||||||
let onHide: () -> Void = { [weak self] in
|
let onHide: () -> Void = { [weak self] in
|
||||||
guard let self = self else { return }
|
self?.halted = true
|
||||||
self.haltVideo()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
guard visibleBehavior == nil else {
|
guard visibleBehavior == nil else {
|
||||||
@ -115,12 +146,8 @@ open class VideoModel: MoleculeModelProtocol {
|
|||||||
// If visible to not visible, pause video.
|
// If visible to not visible, pause video.
|
||||||
// If not visible to visible, unpause if needed, add visible behavior
|
// If not visible to visible, unpause if needed, add visible behavior
|
||||||
guard let self = self,
|
guard let self = self,
|
||||||
let containingView = (delegateObject?.moleculeDelegate as? UIViewController)?.view else { return }
|
let view = self.view else { return }
|
||||||
if !MVMCoreUIUtility.isView(view, visibleIn: containingView) {
|
self.halted = !view.isVisible(in: scrollView)
|
||||||
self.haltVideo()
|
|
||||||
} else {
|
|
||||||
self.unhaltVideo()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
guard scrollBehavior == nil else {
|
guard scrollBehavior == nil else {
|
||||||
@ -133,4 +160,33 @@ open class VideoModel: MoleculeModelProtocol {
|
|||||||
delegate.add(behavior: scrollBehavior)
|
delegate.add(behavior: scrollBehavior)
|
||||||
self.scrollBehavior = scrollBehavior
|
self.scrollBehavior = scrollBehavior
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func addActiveListener(for view: Video, delegateObject: MVMCoreUIDelegateObject?) {
|
||||||
|
removeActiveListener()
|
||||||
|
|
||||||
|
guard let containingView = (delegateObject?.moleculeDelegate as? UIViewController)?.view else { return }
|
||||||
|
resignActiveListener = NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: OperationQueue.main) { [weak self] (notification) in
|
||||||
|
self?.halted = true
|
||||||
|
}
|
||||||
|
activeListener = NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: OperationQueue.main) { [weak self] (notification) in
|
||||||
|
if MVMCoreUIUtility.isView(view, visibleIn: containingView) {
|
||||||
|
self?.halted = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func removeActiveListener() {
|
||||||
|
if let observer = activeListener {
|
||||||
|
NotificationCenter.default.removeObserver(observer)
|
||||||
|
activeListener = nil
|
||||||
|
}
|
||||||
|
if let observer = resignActiveListener {
|
||||||
|
NotificationCenter.default.removeObserver(observer)
|
||||||
|
resignActiveListener = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
removeActiveListener()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user