Video improvements

This commit is contained in:
Ali
2020-07-03 21:25:36 +04:00
parent c8c1c96f16
commit b542dc3fd7
44 changed files with 1097 additions and 613 deletions

View File

@@ -22,27 +22,65 @@ enum CallControllerButtonsMode: Equatable {
}
case active(speakerMode: CallControllerButtonsSpeakerMode, videoState: VideoState)
case incoming
case incoming(speakerMode: CallControllerButtonsSpeakerMode, videoState: VideoState)
case outgoingRinging(speakerMode: CallControllerButtonsSpeakerMode, videoState: VideoState)
}
private enum ButtonDescription: Equatable {
enum Key: Hashable {
case accept
case end
case enableCamera
case switchCamera
case soundOutput
case mute
}
enum SoundOutput {
case speaker
case bluetooth
}
enum EndType {
case outgoing
case decline
case end
}
case accept
case end(EndType)
case enableCamera(Bool)
case switchCamera
case soundOutput(SoundOutput)
case mute(Bool)
var key: Key {
switch self {
case .accept:
return .accept
case .end:
return .end
case .enableCamera:
return .enableCamera
case .switchCamera:
return .switchCamera
case .soundOutput:
return .soundOutput
case .mute:
return .mute
}
}
}
final class CallControllerButtonsNode: ASDisplayNode {
private let acceptButton: CallControllerButtonNode
private let declineButton: CallControllerButtonNode
private let muteButton: CallControllerButtonNode
private let endButton: CallControllerButtonNode
private let speakerButton: CallControllerButtonNode
private let swichCameraButton: CallControllerButtonNode
private var buttonNodes: [ButtonDescription.Key: CallControllerButtonItemNode] = [:]
private var mode: CallControllerButtonsMode?
private var validLayout: CGFloat?
var isMuted = false {
didSet {
self.muteButton.isSelected = self.isMuted
}
}
var isMuted = false
var isCameraPaused = false
var accept: (() -> Void)?
var mute: (() -> Void)?
@@ -52,57 +90,30 @@ final class CallControllerButtonsNode: ASDisplayNode {
var rotateCamera: (() -> Void)?
init(strings: PresentationStrings) {
self.acceptButton = CallControllerButtonNode(type: .accept, label: strings.Call_Accept)
self.acceptButton.alpha = 0.0
self.declineButton = CallControllerButtonNode(type: .end, label: strings.Call_Decline)
self.declineButton.alpha = 0.0
self.muteButton = CallControllerButtonNode(type: .mute, label: nil)
self.muteButton.alpha = 0.0
self.endButton = CallControllerButtonNode(type: .end, label: nil)
self.endButton.alpha = 0.0
self.speakerButton = CallControllerButtonNode(type: .speaker, label: nil)
self.speakerButton.alpha = 0.0
self.swichCameraButton = CallControllerButtonNode(type: .switchCamera, label: nil)
self.swichCameraButton.alpha = 0.0
super.init()
self.addSubnode(self.acceptButton)
self.addSubnode(self.declineButton)
self.addSubnode(self.muteButton)
self.addSubnode(self.endButton)
self.addSubnode(self.speakerButton)
self.addSubnode(self.swichCameraButton)
self.acceptButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
self.declineButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
self.muteButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
self.endButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
self.speakerButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
self.swichCameraButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
}
func updateLayout(constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) {
let previousLayout = self.validLayout
func updateLayout(strings: PresentationStrings, constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = constrainedWidth
if let mode = self.mode, previousLayout != self.validLayout {
self.updateButtonsLayout(mode: mode, width: constrainedWidth, animated: false)
if let mode = self.mode {
self.updateButtonsLayout(strings: strings, mode: mode, width: constrainedWidth, animated: transition.isAnimated)
}
}
func updateMode(_ mode: CallControllerButtonsMode) {
func updateMode(strings: PresentationStrings, mode: CallControllerButtonsMode) {
if self.mode != mode {
let previousMode = self.mode
self.mode = mode
if let validLayout = self.validLayout {
self.updateButtonsLayout(mode: mode, width: validLayout, animated: previousMode != nil)
self.updateButtonsLayout(strings: strings, mode: mode, width: validLayout, animated: previousMode != nil)
}
}
}
private func updateButtonsLayout(mode: CallControllerButtonsMode, width: CGFloat, animated: Bool) {
private var appliedMode: CallControllerButtonsMode?
private func updateButtonsLayout(strings: PresentationStrings, mode: CallControllerButtonsMode, width: CGFloat, animated: Bool) {
let transition: ContainedViewLayoutTransition
if animated {
transition = .animated(duration: 0.3, curve: .spring)
@@ -110,147 +121,273 @@ final class CallControllerButtonsNode: ASDisplayNode {
transition = .immediate
}
let threeButtonSpacing: CGFloat = 28.0
let twoButtonSpacing: CGFloat = 105.0
let buttonSize = CGSize(width: 75.0, height: 75.0)
let threeButtonsWidth = 3.0 * buttonSize.width + 2.0 * threeButtonSpacing
let twoButtonsWidth = 2.0 * buttonSize.width + 1.0 * twoButtonSpacing
let previousMode = self.appliedMode
self.appliedMode = mode
var origin = CGPoint(x: floor((width - threeButtonsWidth) / 2.0), y: 0.0)
var animatePositionsWithDelay = false
if let previousMode = previousMode {
switch previousMode {
case .incoming, .outgoingRinging:
if case .active = mode {
animatePositionsWithDelay = true
}
default:
break
}
}
for button in [self.muteButton, self.endButton, self.speakerButton] {
transition.updateFrame(node: button, frame: CGRect(origin: origin, size: buttonSize))
if button === self.speakerButton {
transition.updateFrame(node: self.swichCameraButton, frame: CGRect(origin: origin, size: buttonSize))
let minSmallButtonSideInset: CGFloat = 34.0
let maxSmallButtonSpacing: CGFloat = 34.0
let smallButtonSize: CGFloat = 60.0
let topBottomSpacing: CGFloat = 84.0
let maxLargeButtonSpacing: CGFloat = 115.0
let largeButtonSize: CGFloat = 72.0
let minLargeButtonSideInset: CGFloat = minSmallButtonSideInset - 6.0
struct PlacedButton {
let button: ButtonDescription
let frame: CGRect
}
var buttons: [PlacedButton] = []
switch mode {
case .incoming(let speakerMode, let videoState), .outgoingRinging(let speakerMode, let videoState):
var topButtons: [ButtonDescription] = []
var bottomButtons: [ButtonDescription] = []
let soundOutput: ButtonDescription.SoundOutput
switch speakerMode {
case .none, .builtin, .speaker:
soundOutput = .speaker
case .headphones:
soundOutput = .bluetooth
case .bluetooth:
soundOutput = .bluetooth
}
origin.x += buttonSize.width + threeButtonSpacing
switch videoState {
case .active, .available:
topButtons.append(.enableCamera(!self.isCameraPaused))
topButtons.append(.mute(self.isMuted))
topButtons.append(.switchCamera)
case .notAvailable:
topButtons.append(.enableCamera(!self.isCameraPaused))
topButtons.append(.mute(self.isMuted))
topButtons.append(.soundOutput(soundOutput))
}
let topButtonsContentWidth = CGFloat(topButtons.count) * smallButtonSize
let topButtonsAvailableSpacingWidth = width - topButtonsContentWidth - minSmallButtonSideInset * 2.0
let topButtonsSpacing = min(maxSmallButtonSpacing, topButtonsAvailableSpacingWidth / CGFloat(topButtons.count - 1))
let topButtonsWidth = CGFloat(topButtons.count) * smallButtonSize + CGFloat(topButtons.count - 1) * topButtonsSpacing
var topButtonsLeftOffset = floor((width - topButtonsWidth) / 2.0)
for button in topButtons {
buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: topButtonsLeftOffset, y: 0.0), size: CGSize(width: smallButtonSize, height: smallButtonSize))))
topButtonsLeftOffset += smallButtonSize + topButtonsSpacing
}
if case .incoming = mode {
bottomButtons.append(.end(.decline))
bottomButtons.append(.accept)
} else {
bottomButtons.append(.end(.outgoing))
}
let bottomButtonsContentWidth = CGFloat(bottomButtons.count) * largeButtonSize
let bottomButtonsAvailableSpacingWidth = width - bottomButtonsContentWidth - minLargeButtonSideInset * 2.0
let bottomButtonsSpacing = min(maxLargeButtonSpacing, bottomButtonsAvailableSpacingWidth / CGFloat(bottomButtons.count - 1))
let bottomButtonsWidth = CGFloat(bottomButtons.count) * largeButtonSize + CGFloat(bottomButtons.count - 1) * bottomButtonsSpacing
var bottomButtonsLeftOffset = floor((width - bottomButtonsWidth) / 2.0)
for button in bottomButtons {
buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: bottomButtonsLeftOffset, y: smallButtonSize + topBottomSpacing), size: CGSize(width: largeButtonSize, height: largeButtonSize))))
bottomButtonsLeftOffset += largeButtonSize + bottomButtonsSpacing
}
case let .active(speakerMode, videoState):
var topButtons: [ButtonDescription] = []
let soundOutput: ButtonDescription.SoundOutput
switch speakerMode {
case .none, .builtin, .speaker:
soundOutput = .speaker
case .headphones:
soundOutput = .bluetooth
case .bluetooth:
soundOutput = .bluetooth
}
switch videoState {
case .active, .available:
topButtons.append(.enableCamera(!self.isCameraPaused))
topButtons.append(.mute(isMuted))
topButtons.append(.switchCamera)
case .notAvailable:
topButtons.append(.enableCamera(!self.isCameraPaused))
topButtons.append(.mute(isMuted))
topButtons.append(.soundOutput(soundOutput))
}
topButtons.append(.end(.end))
let topButtonsContentWidth = CGFloat(topButtons.count) * smallButtonSize
let topButtonsAvailableSpacingWidth = width - topButtonsContentWidth - minSmallButtonSideInset * 2.0
let topButtonsSpacing = min(maxSmallButtonSpacing, topButtonsAvailableSpacingWidth / CGFloat(topButtons.count - 1))
let topButtonsWidth = CGFloat(topButtons.count) * smallButtonSize + CGFloat(topButtons.count - 1) * topButtonsSpacing
var topButtonsLeftOffset = floor((width - topButtonsWidth) / 2.0)
for button in topButtons {
buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: topButtonsLeftOffset, y: smallButtonSize + topBottomSpacing), size: CGSize(width: smallButtonSize, height: smallButtonSize))))
topButtonsLeftOffset += smallButtonSize + topButtonsSpacing
}
}
origin = CGPoint(x: floor((width - twoButtonsWidth) / 2.0), y: 0.0)
for button in [self.declineButton, self.acceptButton] {
transition.updateFrame(node: button, frame: CGRect(origin: origin, size: buttonSize))
origin.x += buttonSize.width + twoButtonSpacing
let delayIncrement = 0.015
var validKeys: [ButtonDescription.Key] = []
for button in buttons {
validKeys.append(button.button.key)
var buttonTransition = transition
var animateButtonIn = false
let buttonNode: CallControllerButtonItemNode
if let current = self.buttonNodes[button.button.key] {
buttonNode = current
} else {
buttonNode = CallControllerButtonItemNode()
self.buttonNodes[button.button.key] = buttonNode
self.addSubnode(buttonNode)
buttonNode.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
buttonTransition = .immediate
animateButtonIn = transition.isAnimated
}
let buttonContent: CallControllerButtonItemNode.Content
let buttonText: String
switch button.button {
case .accept:
buttonContent = CallControllerButtonItemNode.Content(
appearance: .color(.green),
image: .accept
)
buttonText = strings.Call_Accept
case let .end(type):
buttonContent = CallControllerButtonItemNode.Content(
appearance: .color(.red),
image: .end
)
switch type {
case .outgoing:
buttonText = ""
case .decline:
buttonText = strings.Call_Decline
case .end:
buttonText = strings.Call_End
}
case let .enableCamera(isEnabled):
buttonContent = CallControllerButtonItemNode.Content(
appearance: .blurred(isFilled: isEnabled),
image: .camera
)
buttonText = strings.Call_Camera
case .switchCamera:
buttonContent = CallControllerButtonItemNode.Content(
appearance: .blurred(isFilled: false),
image: .flipCamera
)
buttonText = strings.Call_Flip
case let .soundOutput(value):
let image: CallControllerButtonItemNode.Content.Image
switch value {
case .speaker:
image = .speaker
case .bluetooth:
image = .bluetooth
}
buttonContent = CallControllerButtonItemNode.Content(
appearance: .blurred(isFilled: false),
image: image
)
buttonText = strings.Call_Speaker
case let .mute(isMuted):
buttonContent = CallControllerButtonItemNode.Content(
appearance: .blurred(isFilled: isMuted),
image: .mute
)
buttonText = strings.Call_Mute
}
var buttonDelay = 0.0
if animatePositionsWithDelay {
switch button.button.key {
case .enableCamera:
buttonDelay = 0.0
case .mute:
buttonDelay = delayIncrement * 1.0
case .switchCamera:
buttonDelay = delayIncrement * 2.0
case .end:
buttonDelay = delayIncrement * 3.0
default:
break
}
}
buttonTransition.updateFrame(node: buttonNode, frame: button.frame, delay: buttonDelay)
buttonNode.update(size: button.frame.size, content: buttonContent, text: buttonText, transition: buttonTransition)
if animateButtonIn {
buttonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
switch mode {
case .incoming:
for button in [self.declineButton, self.acceptButton] {
button.alpha = 1.0
}
for button in [self.muteButton, self.endButton, self.speakerButton, self.swichCameraButton] {
button.alpha = 0.0
}
case let .active(speakerMode, videoState):
for button in [self.muteButton] {
if animated && button.alpha.isZero {
button.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
button.alpha = 1.0
}
switch videoState {
case .active, .available:
for button in [self.speakerButton] {
if animated && !button.alpha.isZero {
button.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
button.alpha = 0.0
}
for button in [self.swichCameraButton] {
if animated && button.alpha.isZero {
button.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
button.alpha = 1.0
}
case .notAvailable:
for button in [self.swichCameraButton] {
if animated && !button.alpha.isZero {
button.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
button.alpha = 0.0
}
for button in [self.speakerButton] {
if animated && button.alpha.isZero {
button.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
button.alpha = 1.0
}
}
var animatingAcceptButton = false
if self.endButton.alpha.isZero {
if animated {
if !self.acceptButton.alpha.isZero {
animatingAcceptButton = true
self.endButton.layer.animatePosition(from: self.acceptButton.position, to: self.endButton.position, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.acceptButton.animateRollTransition()
self.endButton.layer.animate(from: (CGFloat.pi * 5 / 4) as NSNumber, to: 0.0 as NSNumber, keyPath: "transform.rotation.z", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.3)
self.acceptButton.layer.animatePosition(from: self.acceptButton.position, to: self.endButton.position, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.acceptButton.alpha = 0.0
strongSelf.acceptButton.layer.removeAnimation(forKey: "position")
strongSelf.acceptButton.layer.removeAnimation(forKey: "transform.rotation.z")
}
var removedKeys: [ButtonDescription.Key] = []
for (key, button) in self.buttonNodes {
if !validKeys.contains(key) {
removedKeys.append(key)
if animated {
if case .accept = key {
if let endButton = self.buttonNodes[.end] {
transition.updateFrame(node: button, frame: endButton.frame)
if let content = button.currentContent {
button.update(size: endButton.frame.size, content: content, text: button.currentText, transition: transition)
}
transition.updateTransformScale(node: button, scale: 0.1)
transition.updateAlpha(node: button, alpha: 0.0, completion: { [weak button] _ in
button?.removeFromSupernode()
})
}
self.endButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
} else {
transition.updateAlpha(node: button, alpha: 0.0, completion: { [weak button] _ in
button?.removeFromSupernode()
})
}
self.endButton.alpha = 1.0
} else {
button.removeFromSupernode()
}
if !self.declineButton.alpha.isZero {
if animated {
self.declineButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
}
self.declineButton.alpha = 0.0
}
if self.acceptButton.alpha.isZero && !animatingAcceptButton {
self.acceptButton.alpha = 0.0
}
self.speakerButton.isSelected = speakerMode == .speaker
self.speakerButton.isHidden = speakerMode == .none
let speakerButtonType: CallControllerButtonType
switch speakerMode {
case .none, .builtin, .speaker:
speakerButtonType = .speaker
case .headphones:
speakerButtonType = .bluetooth
case .bluetooth:
speakerButtonType = .bluetooth
}
self.speakerButton.updateType(speakerButtonType)
}
}
for key in removedKeys {
self.buttonNodes.removeValue(forKey: key)
}
}
@objc func buttonPressed(_ button: CallControllerButtonNode) {
if button === self.muteButton {
self.mute?()
} else if button === self.endButton || button === self.declineButton {
self.end?()
} else if button === self.speakerButton {
self.speaker?()
} else if button === self.acceptButton {
self.accept?()
} else if button === self.swichCameraButton {
self.rotateCamera?()
@objc func buttonPressed(_ button: CallControllerButtonItemNode) {
for (key, listButton) in self.buttonNodes {
if button === listButton {
switch key {
case .accept:
self.accept?()
case .end:
self.end?()
case .enableCamera:
self.toggleVideo?()
case .switchCamera:
self.rotateCamera?()
case .soundOutput:
self.speaker?()
case .mute:
self.mute?()
}
break
}
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let buttons = [
self.acceptButton,
self.declineButton,
self.muteButton,
self.endButton,
self.speakerButton,
self.swichCameraButton
]
for button in buttons {
if button.isHidden || button.alpha.isZero {
continue
}
for (_, button) in self.buttonNodes {
if let result = button.view.hitTest(self.view.convert(point, to: button.view), with: event) {
return result
}