mirror of https://github.com/jitsi/jitsi-meet
parent
181580871f
commit
f8163de765
@ -1,26 +0,0 @@ |
||||
// |
||||
// JitsiViewController.swift |
||||
// PiPApp |
||||
// |
||||
// Created by Daniel Ornelas on 3/5/18. |
||||
// Copyright © 2018 Atlassian Inc. All rights reserved. |
||||
// |
||||
|
||||
import JitsiMeet |
||||
import UIKit |
||||
|
||||
final class JitsiViewController: UIViewController { |
||||
|
||||
override func viewDidLoad() { |
||||
super.viewDidLoad() |
||||
|
||||
guard let jitsiView = self.view as? JitsiMeetView else { return } |
||||
|
||||
jitsiView.welcomePageEnabled = true |
||||
jitsiView.load(nil) |
||||
|
||||
// TODO: delete me, this is only testing access to swift object in SDK |
||||
let jitsiManager = JitsiManager() |
||||
jitsiManager.testMe() |
||||
} |
||||
} |
@ -1,17 +0,0 @@ |
||||
// |
||||
// JitsiManager.swift |
||||
// JitsiMeet |
||||
// |
||||
// Created by Daniel Ornelas on 3/5/18. |
||||
// Copyright © 2018 Jitsi. All rights reserved. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
@objc(JitsiManager) |
||||
public class JitsiManager: NSObject { |
||||
|
||||
public func testMe() { |
||||
print("hi there") |
||||
} |
||||
} |
@ -0,0 +1,104 @@ |
||||
// Copyright © 2018 Jitsi. All rights reserved. |
||||
|
||||
final class DragGestureController { |
||||
|
||||
var insets: UIEdgeInsets = UIEdgeInsets.zero |
||||
|
||||
private var frameBeforeDragging: CGRect = CGRect.zero |
||||
private weak var view: UIView? |
||||
private lazy var panGesture: UIPanGestureRecognizer = { |
||||
return UIPanGestureRecognizer(target: self, action: #selector(handlePan(gesture:))) |
||||
}() |
||||
|
||||
func startDragListener(inView view: UIView) { |
||||
self.view = view |
||||
view.addGestureRecognizer(panGesture) |
||||
panGesture.isEnabled = true |
||||
} |
||||
|
||||
func stopDragListener() { |
||||
panGesture.isEnabled = false |
||||
view?.removeGestureRecognizer(panGesture) |
||||
view = nil |
||||
} |
||||
|
||||
@objc private func handlePan(gesture: UIPanGestureRecognizer) { |
||||
guard let view = self.view else { return } |
||||
|
||||
let translation = gesture.translation(in: view.superview) |
||||
let velocity = gesture.velocity(in: view.superview) |
||||
var frame = frameBeforeDragging |
||||
|
||||
switch gesture.state { |
||||
case .began: |
||||
frameBeforeDragging = view.frame |
||||
|
||||
case .changed: |
||||
frame.origin.x = floor(frame.origin.x + translation.x) |
||||
frame.origin.y = floor(frame.origin.y + translation.y) |
||||
view.frame = frame |
||||
|
||||
case .ended: |
||||
let currentPos = view.frame.origin |
||||
let finalPos = calculateFinalPosition() |
||||
|
||||
let distance = CGPoint(x: currentPos.x - finalPos.x, |
||||
y: currentPos.y - finalPos.y) |
||||
let distanceMagnitude = magnitude(vector: distance) |
||||
let velocityMagnitude = magnitude(vector: velocity) |
||||
let animationDuration = 0.5 |
||||
let initialSpringVelocity = velocityMagnitude / distanceMagnitude / CGFloat(animationDuration) |
||||
|
||||
frame.origin = CGPoint(x: finalPos.x, y: finalPos.y) |
||||
|
||||
UIView.animate(withDuration: animationDuration, |
||||
delay: 0, |
||||
usingSpringWithDamping: 0.9, |
||||
initialSpringVelocity: initialSpringVelocity, |
||||
options: .curveLinear, |
||||
animations: { |
||||
view.frame = frame |
||||
}, completion: nil) |
||||
|
||||
default: |
||||
break |
||||
} |
||||
} |
||||
|
||||
private func calculateFinalPosition() -> CGPoint { |
||||
guard |
||||
let view = self.view, |
||||
let bounds = view.superview?.frame |
||||
else { return CGPoint.zero } |
||||
|
||||
let currentSize = view.frame.size |
||||
let adjustedBounds = UIEdgeInsetsInsetRect(bounds, insets) |
||||
let threshold: CGFloat = 20.0 |
||||
let velocity = panGesture.velocity(in: view.superview) |
||||
let location = panGesture.location(in: view.superview) |
||||
|
||||
let goLeft: Bool |
||||
if fabs(velocity.x) > threshold { |
||||
goLeft = velocity.x < -threshold |
||||
} else { |
||||
goLeft = location.x < bounds.midX |
||||
} |
||||
|
||||
let goUp: Bool |
||||
if fabs(velocity.y) > threshold { |
||||
goUp = velocity.y < -threshold |
||||
} else { |
||||
goUp = location.y < bounds.midY |
||||
} |
||||
|
||||
let finalPosX: CGFloat = goLeft ? adjustedBounds.origin.x : bounds.size.width - insets.right - currentSize.width |
||||
let finalPosY: CGFloat = goUp ? adjustedBounds.origin.y : bounds.size.height - insets.bottom - currentSize.height |
||||
|
||||
return CGPoint(x: finalPosX, y: finalPosY) |
||||
} |
||||
|
||||
private func magnitude(vector: CGPoint) -> CGFloat { |
||||
return sqrt(pow(vector.x, 2) + pow(vector.y, 2)) |
||||
} |
||||
} |
||||
|
@ -0,0 +1,146 @@ |
||||
// Copyright © 2018 Jitsi. All rights reserved. |
||||
|
||||
import Foundation |
||||
|
||||
/// Creates and present a JitsiMeetView inside of an external window that can be dragged |
||||
/// when minimized (if PiP mode is enabled) |
||||
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() |
||||
|
||||
fileprivate lazy var meetViewController: JitsiMeetViewController = { return self.makeMeetViewController() }() |
||||
fileprivate lazy var meetWindow: JitsiMeetWindow = { return self.makeMeetWindow() }() |
||||
fileprivate var meetingInPiP: Bool = false |
||||
|
||||
/// Presents and loads a jitsi meet view |
||||
/// |
||||
/// - Parameter url: The url of the presentation |
||||
public func load(withUrl url: URL?) { |
||||
meetWindow.show() |
||||
meetViewController.jitsiMeetView.load(url) |
||||
} |
||||
|
||||
/// Presents and loads a jitsi meet view with configuration |
||||
/// |
||||
/// - Parameter urlObject: A dictionary of keys to be used for configuration |
||||
public func load(withUrlObject urlObject: [AnyHashable : Any]?) { |
||||
meetWindow.show() |
||||
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) |
||||
} |
||||
|
||||
// MARK: - helpers |
||||
|
||||
fileprivate func cleanUp() { |
||||
// TODO: more clean up work on this |
||||
|
||||
dragController.stopDragListener() |
||||
meetWindow.isHidden = true |
||||
} |
||||
|
||||
private func makeMeetViewController() -> JitsiMeetViewController { |
||||
let vc = JitsiMeetViewController() |
||||
vc.jitsiMeetView.delegate = self |
||||
vc.jitsiMeetView.welcomePageEnabled = self.welcomeScreenEnabled |
||||
vc.jitsiMeetView.pictureInPictureEnabled = self.allowPiP |
||||
return vc |
||||
} |
||||
|
||||
private func makeMeetWindow() -> JitsiMeetWindow { |
||||
let window = JitsiMeetWindow(frame: UIScreen.main.bounds) |
||||
window.backgroundColor = .clear |
||||
window.windowLevel = UIWindowLevelStatusBar + 100 |
||||
window.rootViewController = self.meetViewController |
||||
return window |
||||
} |
||||
} |
||||
|
||||
extension JitsiMeetManager: JitsiMeetViewDelegate { |
||||
|
||||
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) |
||||
} |
||||
} |
||||
|
||||
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) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
// Copyright © 2018 Jitsi. All rights reserved. |
||||
|
||||
|
||||
/// Wrapper ViewController of a JitsiMeetView |
||||
/// |
||||
/// TODO: should consider refactor and move out several logic of the JitsiMeetView to |
||||
/// this class |
||||
open class JitsiMeetViewController: UIViewController { |
||||
|
||||
private(set) var jitsiMeetView: JitsiMeetView = JitsiMeetView() |
||||
|
||||
override open func loadView() { |
||||
super.loadView() |
||||
self.view = jitsiMeetView |
||||
} |
||||
} |
@ -0,0 +1,45 @@ |
||||
// 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) |
||||
} |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue