restructure video

This commit is contained in:
Pfeil, Scott Robert 2021-02-12 14:19:33 -05:00
parent c2f1cd200d
commit 0f0bc68f1e
3 changed files with 109 additions and 45 deletions

View File

@ -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
} }

View File

@ -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 {

View File

@ -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()
}
} }