restructure video
This commit is contained in:
parent
c2f1cd200d
commit
0f0bc68f1e
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user