Refactor PiP code into its own components
This commit is contained in:
parent
f8163de765
commit
0a5f60c637
|
@ -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 */,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
public func conferenceJoined(_ data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.conferenceJoined!(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 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)
|
||||
}
|
||||
open func meetingStarted() {
|
||||
// do something
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
open func meetingEnded(wasFailure: Bool) {
|
||||
cleanUp()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue