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