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 shouldNotifyDelegateOnUpdate = true
public var shouldNotifyDelegateOnDefaultSizeChange = false
// Allows for a view to hardcode which height to use if there is none in the json.
var imageWidth: CGFloat?
var imageHeight: CGFloat?
@ -228,7 +229,7 @@
let widthWillChange = !MVMCoreGetterUtility.cgfequal(widthConstraint?.constant ?? 0, width ?? 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
return widthWillChange || heightWillChange || sizeWillChange || heightChangeFromSpinner
}

View File

@ -9,6 +9,7 @@ import AVKit
open class Video: View {
public let videoViewController = AVPlayerViewController()
public var delegateObject: MVMCoreUIDelegateObject?
/// Used to track the state and respond..
private var stateKVOToken: NSKeyValueObservation?
@ -21,9 +22,22 @@ open class Video: View {
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]?) {
self.delegateObject = delegateObject
super.set(with: model, delegateObject, additionalData)
guard let model = model as? VideoModel else { return }
if let controller = delegateObject?.moleculeDelegate as? UIViewController {
controller.addChild(videoViewController)
videoViewController.didMove(toParent: controller)
@ -31,26 +45,19 @@ open class Video: View {
videoViewController.showsPlaybackControls = model.showControls
videoViewController.player = model.videoDataManager.player
addStateObserver()
model.addVisibilityHalting(for: self, delegateObject: delegateObject)
switch (model.videoDataManager.videoState) {
case .none:
// Begin loading the video
model.videoDataManager.loadVideo()
case .loaded:
// Video loaded
if model.alwaysReset {
// Always start video at the beginning.
model.videoDataManager.player?.seek(to: .zero)
}
if model.autoPlay {
model.videoDataManager.player?.play()
}
guard isVisibleInDelegate() else { return }
// Video loaded, unhalt it if necessary.
model.halted = false
default:
break
}
// Handle pause behavior
model.addVisibleBehavior(for: self, delegateObject: delegateObject)
model.addScrollBehavior(for: self, delegateObject: delegateObject)
}
/// Listens and responds to video loading state changes.
@ -73,10 +80,10 @@ open class Video: View {
MVMCoreDispatchUtility.performSyncBlock(onMainThread: {
// Play the video
self.videoViewController.player = item.player
if model.autoPlay {
if !model.halted && model.autoPlay && self.isVisibleInDelegate() {
item.player?.play()
UIAccessibility.post(notification: .screenChanged, argument: self)
}
UIAccessibility.post(notification: .screenChanged, argument: self)
})
case .failed:
if let errorObject = item.loadFailedError {

View File

@ -15,15 +15,36 @@ open class VideoModel: MoleculeModelProtocol {
public var showControls = false
public var autoPlay = true
public var alwaysReset = false
weak public var view: Video?
/// 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.
public var videoDataManager: VideoDataManager
private weak var visibleBehavior: PageVisibilityClosureBehavior?
private weak var scrollBehavior: PageScrolledClosureBehavior?
private var activeListener: Any?
private var resignActiveListener: Any?
private enum CodingKeys: String, CodingKey {
case moleculeName
@ -62,38 +83,48 @@ open class VideoModel: MoleculeModelProtocol {
try container.encode(alwaysReset, forKey: .alwaysReset)
}
private func haltVideo() {
guard !halted else { return }
halted = true
print("halted \(video)")
videoDataManager.player?.pause()
func printThat(string: String) {
var newString = "sss" + string + " model:\(Unmanaged.passUnretained(self).toOpaque())"
if var view: UIView = self.view {
newString = newString + " view:\(Unmanaged.passUnretained(view).toOpaque())"
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() {
guard halted else { return }
open func addVisibilityHalting(for view: Video, delegateObject: MVMCoreUIDelegateObject?) {
printThat(string: "before adding")
// reset
halted = false
print("unhalted \(video)")
guard videoDataManager.videoState == .loaded else { return }
if alwaysReset {
// Always start video at the beginning.
videoDataManager.player?.seek(to: .zero)
}
if autoPlay {
videoDataManager.player?.play()
}
(view.model as? VideoModel)?.view = nil
self.view = view
printThat(string: "after adding")
addVisibleBehavior(for: view, delegateObject: delegateObject)
addScrollBehavior(for: view, delegateObject: delegateObject)
addActiveListener(for: view, delegateObject: delegateObject)
}
/// 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?) {
let onShow = { [weak self] in
guard let self = self,
let containingView = (delegateObject?.moleculeDelegate as? UIViewController)?.view,
MVMCoreUIUtility.isView(view, visibleIn: containingView) else { return }
self.unhaltVideo()
let view = self.view,
view.isVisibleInDelegate() else { return }
self.halted = false
}
let onHide: () -> Void = { [weak self] in
guard let self = self else { return }
self.haltVideo()
self?.halted = true
}
guard visibleBehavior == nil else {
@ -115,12 +146,8 @@ open class VideoModel: MoleculeModelProtocol {
// If visible to not visible, pause video.
// If not visible to visible, unpause if needed, add visible behavior
guard let self = self,
let containingView = (delegateObject?.moleculeDelegate as? UIViewController)?.view else { return }
if !MVMCoreUIUtility.isView(view, visibleIn: containingView) {
self.haltVideo()
} else {
self.unhaltVideo()
}
let view = self.view else { return }
self.halted = !view.isVisible(in: scrollView)
}
guard scrollBehavior == nil else {
@ -133,4 +160,33 @@ open class VideoModel: MoleculeModelProtocol {
delegate.add(behavior: 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()
}
}