Refactor PiP code into its own components

This commit is contained in:
Daniel Ornelas 2018-03-08 17:55:35 -06:00 committed by Lyubo Marinov
parent f8163de765
commit 0a5f60c637
6 changed files with 218 additions and 141 deletions

View File

@ -27,7 +27,7 @@
0BCA496C1EC4BBF900B793EE /* jitsi.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0BCA496B1EC4BBF900B793EE /* jitsi.ttf */; };
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BD906E81EC0C00300C8C18E /* JitsiMeet.h */; settings = {ATTRIBUTES = (Public, ); }; };
0F65EECE1D95DA94561BB47E /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */; };
C6A3425F204EF76800E062DD /* JitsiMeetWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425C204EF76800E062DD /* JitsiMeetWindow.swift */; };
C6A3425F204EF76800E062DD /* PiPWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425C204EF76800E062DD /* PiPWindow.swift */; };
C6A34260204EF76800E062DD /* JitsiMeetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425D204EF76800E062DD /* JitsiMeetManager.swift */; };
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
C6A3426D204F1C3300E062DD /* JitsiMeetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */; };
@ -60,7 +60,7 @@
0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
C6A3425C204EF76800E062DD /* JitsiMeetWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JitsiMeetWindow.swift; sourceTree = "<group>"; };
C6A3425C204EF76800E062DD /* PiPWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PiPWindow.swift; sourceTree = "<group>"; };
C6A3425D204EF76800E062DD /* JitsiMeetManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JitsiMeetManager.swift; sourceTree = "<group>"; };
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetViewController.swift; sourceTree = "<group>"; };
@ -155,13 +155,21 @@
name = Pods;
sourceTree = "<group>";
};
C6245F5A2052043F0040BE68 /* PiPWindow */ = {
isa = PBXGroup;
children = (
C6A3425E204EF76800E062DD /* DragGestureController.swift */,
C6A3425C204EF76800E062DD /* PiPWindow.swift */,
);
path = PiPWindow;
sourceTree = "<group>";
};
C6A3426B204F127900E062DD /* JitsiMeetManager */ = {
isa = PBXGroup;
children = (
C6A3425C204EF76800E062DD /* JitsiMeetWindow.swift */,
C6245F5A2052043F0040BE68 /* PiPWindow */,
C6A3425D204EF76800E062DD /* JitsiMeetManager.swift */,
C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */,
C6A3425E204EF76800E062DD /* DragGestureController.swift */,
);
path = JitsiMeetManager;
sourceTree = "<group>";
@ -336,7 +344,7 @@
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
C6A3426D204F1C3300E062DD /* JitsiMeetViewController.swift in Sources */,
C6A3425F204EF76800E062DD /* JitsiMeetWindow.swift in Sources */,
C6A3425F204EF76800E062DD /* PiPWindow.swift in Sources */,
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,

View File

@ -2,26 +2,19 @@
import Foundation
/// Creates and present a JitsiMeetView inside of an external window that can be dragged
/// when minimized (if PiP mode is enabled)
/// Creates and coordinates the presentation of JitsiMeetViewController inside of an external window
/// which can be resized and dragged with custom PiP mode
open class JitsiMeetManager: NSObject {
/// The Jitsi meet view delegate
public weak var delegate: JitsiMeetViewDelegate? = nil
/// Limits the boundries of meet view position on screen when minimized
public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25, left: 5, bottom: 5, right: 5)
/// Enables PiP mode for this jitsiMeet
public var allowPiP: Bool = true
/// The size ratio for jitsiMeetView when in PiP mode
public var pipSizeRatio: CGFloat = 0.333
/// Defines if welcome screen should be on
public var welcomeScreenEnabled: Bool = false
fileprivate let dragController: DragGestureController = DragGestureController()
public var welcomeScreenEnabled: Bool = false {
didSet {
meetViewController.jitsiMeetView.welcomePageEnabled = welcomeScreenEnabled
}
}
fileprivate lazy var meetViewController: JitsiMeetViewController = { return self.makeMeetViewController() }()
fileprivate lazy var meetWindow: JitsiMeetWindow = { return self.makeMeetWindow() }()
fileprivate var meetingInPiP: Bool = false
fileprivate lazy var meetWindow: PiPWindow = { return self.makeMeetWindow() }()
/// Presents and loads a jitsi meet view
///
@ -39,29 +32,8 @@ open class JitsiMeetManager: NSObject {
meetViewController.jitsiMeetView.loadURLObject(urlObject)
}
// MARK: - Manage PiP switching
// update size animation
fileprivate func updateMeetViewSize(isPiP: Bool) {
UIView.animate(withDuration: 0.25) {
self.meetViewController.view.frame = self.meetViewRect(isPiP: isPiP)
self.meetViewController.view.setNeedsLayout()
}
}
private func meetViewRect(isPiP: Bool) -> CGRect {
guard isPiP else {
return meetWindow.bounds
}
let bounds = meetWindow.bounds
// resize to suggested ratio and position to the bottom right
let adjustedBounds = UIEdgeInsetsInsetRect(bounds, dragBoundInsets)
let size = CGSize(width: bounds.size.width * pipSizeRatio,
height: bounds.size.height * pipSizeRatio)
let x: CGFloat = adjustedBounds.maxX - size.width
let y: CGFloat = adjustedBounds.maxY - size.height
return CGRect(x: x, y: y, width: size.width, height: size.height)
deinit {
cleanUp()
}
// MARK: - helpers
@ -69,20 +41,20 @@ open class JitsiMeetManager: NSObject {
fileprivate func cleanUp() {
// TODO: more clean up work on this
dragController.stopDragListener()
meetWindow.isHidden = true
meetWindow.stopDragGesture()
}
private func makeMeetViewController() -> JitsiMeetViewController {
let vc = JitsiMeetViewController()
vc.jitsiMeetView.delegate = self
vc.jitsiMeetView.welcomePageEnabled = self.welcomeScreenEnabled
vc.jitsiMeetView.pictureInPictureEnabled = self.allowPiP
vc.jitsiMeetView.pictureInPictureEnabled = true
vc.delegate = self
return vc
}
private func makeMeetWindow() -> JitsiMeetWindow {
let window = JitsiMeetWindow(frame: UIScreen.main.bounds)
private func makeMeetWindow() -> PiPWindow {
let window = PiPWindow(frame: UIScreen.main.bounds)
window.backgroundColor = .clear
window.windowLevel = UIWindowLevelStatusBar + 100
window.rootViewController = self.meetViewController
@ -90,57 +62,26 @@ open class JitsiMeetManager: NSObject {
}
}
extension JitsiMeetManager: JitsiMeetViewDelegate {
extension JitsiMeetManager: JitsiMeetViewControllerDelegate {
public func conferenceWillJoin(_ data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.delegate?.conferenceWillJoin!(data)
open func performPresentationUpdate(to: JitsiMeetPresentationUpdate) {
switch to {
case .enterPiP:
meetWindow.goToPiP()
case .traitChange:
// resize to full screen if rotation happens
if meetWindow.isInPiP {
meetWindow.goToFullScreen()
}
}
}
public func conferenceJoined(_ data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.delegate?.conferenceJoined!(data)
open func meetingStarted() {
// do something
}
open func meetingEnded(wasFailure: Bool) {
cleanUp()
}
}
public func conferenceWillLeave(_ data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.delegate?.conferenceWillLeave!(data)
}
}
public func conferenceLeft(_ data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.cleanUp()
self.delegate?.conferenceLeft!(data)
}
}
public func conferenceFailed(_ data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.cleanUp()
self.delegate?.conferenceFailed!(data)
}
}
public func loadConfigError(_ data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.delegate?.loadConfigError!(data)
}
}
public func enterPicture(inPicture data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.dragController.startDragListener(inView: self.meetViewController.view)
self.dragController.insets = self.dragBoundInsets
self.meetingInPiP = true
self.updateMeetViewSize(isPiP: true)
self.delegate?.enterPicture!(inPicture: data)
}
}
}

View File

@ -1,16 +1,84 @@
// Copyright © 2018 Jitsi. All rights reserved.
public enum JitsiMeetPresentationUpdate {
/// A system traitCollectionChange (usually screen rotation)
case traitChange
/// Meeting wants to enter PiP mode
case enterPiP
}
public protocol JitsiMeetViewControllerDelegate: class {
/// Notifies a change of the meeting presentation style.
///
/// - Parameter to: The presentation state that will be changed to
func performPresentationUpdate(to: JitsiMeetPresentationUpdate)
func meetingStarted()
func meetingEnded(wasFailure: Bool)
}
/// Wrapper ViewController of a JitsiMeetView
///
/// TODO: should consider refactor and move out several logic of the JitsiMeetView to
/// this class
/// Is suggested to override this class and implement some customization
/// on how to handle the JitsiMeetView delegate events
open class JitsiMeetViewController: UIViewController {
open weak var delegate: JitsiMeetViewControllerDelegate?
private(set) var jitsiMeetView: JitsiMeetView = JitsiMeetView()
override open func loadView() {
super.loadView()
self.view = jitsiMeetView
}
open override func viewDidLoad() {
super.viewDidLoad()
jitsiMeetView.delegate = self
}
open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
delegate?.performPresentationUpdate(to: .traitChange)
}
}
extension JitsiMeetViewController: JitsiMeetViewDelegate {
open func conferenceWillJoin(_ data: [AnyHashable : Any]!) {
// do something
}
open func conferenceJoined(_ data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.delegate?.meetingStarted()
}
}
open func conferenceWillLeave(_ data: [AnyHashable : Any]!) {
// do something
}
open func conferenceLeft(_ data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.delegate?.meetingEnded(wasFailure: true)
}
}
open func conferenceFailed(_ data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.delegate?.meetingEnded(wasFailure: true)
}
}
open func loadConfigError(_ data: [AnyHashable : Any]!) {
// do something
}
open func enterPicture(inPicture data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.delegate?.performPresentationUpdate(to: .enterPiP)
}
}
}

View File

@ -1,45 +0,0 @@
// Copyright © 2018 Jitsi. All rights reserved.
open class JitsiMeetWindow: UIWindow {
/// Help out to bubble up the gesture detection outside of the rootVC frame
open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard let vc = rootViewController else {
return super.point(inside: point, with: event)
}
return vc.view.frame.contains(point)
}
/// animate in the window
open func show() {
if self.isHidden || self.alpha < 1 {
self.isHidden = false
self.alpha = 0
UIView.animate(
withDuration: 0.1,
delay: 0,
options: .beginFromCurrentState,
animations: {
self.alpha = 1
},
completion: nil)
}
}
/// animate out the window
open func hide() {
if !self.isHidden || self.alpha > 0 {
UIView.animate(
withDuration: 0.1,
delay: 0,
options: .beginFromCurrentState,
animations: {
self.alpha = 0
self.isHidden = true
},
completion: nil)
}
}
}

View File

@ -0,0 +1,105 @@
// Copyright © 2018 Jitsi. All rights reserved.
/// A window that allows its root view controller to be presented
/// in full screen or in a custom Picture in Picture mode
open class PiPWindow: UIWindow {
/// Limits the boundries of root view position on screen when minimized
public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25, left: 5, bottom: 5, right: 5) {
didSet {
dragController.insets = dragBoundInsets
}
}
/// The size ratio for root view controller view when in PiP mode
public var pipSizeRatio: CGFloat = 0.333
/// The PiP state of this contents of the window
private(set) var isInPiP: Bool = false
private let dragController: DragGestureController = DragGestureController()
/// Help out to bubble up the gesture detection outside of the rootVC frame
open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard let vc = rootViewController else {
return super.point(inside: point, with: event)
}
return vc.view.frame.contains(point)
}
/// animate in the window
open func show() {
if self.isHidden || self.alpha < 1 {
self.isHidden = false
self.alpha = 0
UIView.animate(
withDuration: 0.1,
delay: 0,
options: .beginFromCurrentState,
animations: {
self.alpha = 1
},
completion: nil)
}
}
/// animate out the window
open func hide() {
if !self.isHidden || self.alpha > 0 {
UIView.animate(
withDuration: 0.1,
delay: 0,
options: .beginFromCurrentState,
animations: {
self.alpha = 0
self.isHidden = true
},
completion: nil)
}
}
/// Resize the root view to PiP mode
open func goToPiP() {
guard let view = rootViewController?.view else { return }
isInPiP = true
animateRootViewChange()
dragController.startDragListener(inView: view)
dragController.insets = dragBoundInsets
}
/// Resize the root view to full screen
open func goToFullScreen() {
isInPiP = false
animateRootViewChange()
dragController.stopDragListener()
}
/// Stop the dragging gesture of the root view
public func stopDragGesture() {
dragController.stopDragListener()
}
// MARK: - Manage presentation switching
private func animateRootViewChange() {
UIView.animate(withDuration: 0.25) {
self.rootViewController?.view.frame = self.changeRootViewRect()
self.rootViewController?.view.setNeedsLayout()
}
}
private func changeRootViewRect() -> CGRect {
guard isInPiP else {
return self.bounds
}
// resize to suggested ratio and position to the bottom right
let adjustedBounds = UIEdgeInsetsInsetRect(self.bounds, dragBoundInsets)
let size = CGSize(width: bounds.size.width * pipSizeRatio,
height: bounds.size.height * pipSizeRatio)
let x: CGFloat = adjustedBounds.maxX - size.width
let y: CGFloat = adjustedBounds.maxY - size.height
return CGRect(x: x, y: y, width: size.width, height: size.height)
}
}