Video Chat Improvements

This commit is contained in:
Ilya Laktyushin 2021-05-20 15:51:14 +04:00
parent e0f9f4614d
commit a434c891a2
4 changed files with 271 additions and 59 deletions

View File

@ -147,7 +147,23 @@ final class GroupVideoNode: ASDisplayNode {
}
var aspectRatio: CGFloat {
return self.videoView.getAspect()
let orientation = self.videoView.getOrientation()
var aspect = self.videoView.getAspect()
if aspect <= 0.01 {
aspect = 3.0 / 4.0
}
let rotatedAspect: CGFloat
switch orientation {
case .rotation0:
rotatedAspect = 1.0 / aspect
case .rotation90:
rotatedAspect = aspect
case .rotation180:
rotatedAspect = 1.0 / aspect
case .rotation270:
rotatedAspect = aspect
}
return rotatedAspect
}
var keepBackdropSize = false
@ -247,7 +263,7 @@ final class GroupVideoNode: ASDisplayNode {
}
if let backdropEffectView = self.backdropEffectView {
let maxSide = max(bounds.width, bounds.height)
let maxSide = max(bounds.width, bounds.height) * 2.0
let squareBounds = CGRect(x: (bounds.width - maxSide) / 2.0, y: (bounds.width - maxSide) / 2.0, width: maxSide, height: maxSide)
transition.animateView {
backdropEffectView.frame = squareBounds

View File

@ -701,8 +701,9 @@ public final class VoiceChatController: ViewController {
fileprivate let actionButton: VoiceChatActionButton
private let leftBorderNode: ASDisplayNode
private let rightBorderNode: ASDisplayNode
private let mainStageNode: VoiceChatMainStageNode
private let mainStageContainerNode: ASDisplayNode
private let mainStageBackgroundNode: ASDisplayNode
private let mainStageNode: VoiceChatMainStageNode
private let transitionContainerNode: ASDisplayNode
private var isScheduling = false
@ -807,7 +808,7 @@ public final class VoiceChatController: ViewController {
private var endpointToPeerId: [String: PeerId] = [:]
private var peerIdToEndpoint: [PeerId: String] = [:]
private var currentDominantSpeaker: PeerId?
private var currentDominantSpeaker: (PeerId, Double)?
private var currentForcedSpeaker: PeerId?
private var effectiveSpeaker: (PeerId, String?)?
@ -969,13 +970,18 @@ public final class VoiceChatController: ViewController {
self.rightBorderNode.isUserInteractionEnabled = false
self.rightBorderNode.clipsToBounds = false
self.mainStageNode = VoiceChatMainStageNode(context: self.context, call: self.call)
self.mainStageContainerNode = ASDisplayNode()
self.mainStageContainerNode.clipsToBounds = true
self.mainStageContainerNode.isUserInteractionEnabled = false
self.mainStageContainerNode.isHidden = true
self.mainStageBackgroundNode = ASDisplayNode()
// self.mainStageBackgroundNode.backgroundColor = .black
self.mainStageBackgroundNode.alpha = 0.0
self.mainStageBackgroundNode.isUserInteractionEnabled = false
self.mainStageNode = VoiceChatMainStageNode(context: self.context, call: self.call)
self.transitionContainerNode = ASDisplayNode()
self.transitionContainerNode.clipsToBounds = true
self.transitionContainerNode.isUserInteractionEnabled = false
@ -1032,13 +1038,13 @@ public final class VoiceChatController: ViewController {
}, switchToPeer: { [weak self] peerId, videoEndpointId, expand in
if let strongSelf = self {
if expand, let videoEndpointId = videoEndpointId {
strongSelf.currentDominantSpeaker = peerId
strongSelf.currentDominantSpeaker = (peerId, CACurrentMediaTime())
strongSelf.effectiveSpeaker = (peerId, videoEndpointId)
strongSelf.updateDisplayMode(.fullscreen(controlsHidden: false))
} else {
strongSelf.currentForcedSpeaker = nil
if peerId != strongSelf.currentDominantSpeaker {
strongSelf.currentDominantSpeaker = peerId
if peerId != strongSelf.currentDominantSpeaker?.0 {
strongSelf.currentDominantSpeaker = (peerId, CACurrentMediaTime())
}
strongSelf.updateMainVideo(waitForFullSize: false, updateMembers: true, force: true)
}
@ -1657,6 +1663,7 @@ public final class VoiceChatController: ViewController {
self.contentContainer.addSubnode(self.fullscreenListNode)
self.addSubnode(self.transitionContainerNode)
self.mainStageContainerNode.addSubnode(self.mainStageBackgroundNode)
self.mainStageContainerNode.addSubnode(self.mainStageNode)
self.updateDecorationsColors()
@ -1801,7 +1808,7 @@ public final class VoiceChatController: ViewController {
if maxLevelWithVideo == nil {
if let peerId = strongSelf.currentDominantSpeaker {
maxLevelWithVideo = (peerId, 0.0)
maxLevelWithVideo = (peerId.0, 0.0)
} else if strongSelf.peerIdToEndpoint.count > 0 {
for entry in strongSelf.currentEntries {
if case let .peer(peerEntry) = entry {
@ -1816,7 +1823,10 @@ public final class VoiceChatController: ViewController {
if case .fullscreen = strongSelf.displayMode {
if let (peerId, _) = maxLevelWithVideo {
strongSelf.currentDominantSpeaker = peerId
if let peer = strongSelf.currentDominantSpeaker, CACurrentMediaTime() - peer.1 < 5.0 {
} else {
strongSelf.currentDominantSpeaker = (peerId, CACurrentMediaTime())
}
strongSelf.updateMainVideo(waitForFullSize: false)
}
}
@ -1962,7 +1972,7 @@ public final class VoiceChatController: ViewController {
self.mainStageNode.togglePin = { [weak self] in
if let strongSelf = self {
if let peerId = strongSelf.currentForcedSpeaker {
strongSelf.currentDominantSpeaker = peerId
strongSelf.currentDominantSpeaker = (peerId, CACurrentMediaTime())
strongSelf.currentForcedSpeaker = nil
} else {
strongSelf.currentForcedSpeaker = strongSelf.effectiveSpeaker?.0
@ -3137,6 +3147,7 @@ public final class VoiceChatController: ViewController {
let videoBottomEdgeY = self.isLandscape ? layout.size.height : layout.size.height - layout.intrinsicInsets.bottom - 84.0
let videoFrame = CGRect(x: 0.0, y: videoTopEdgeY, width: isLandscape ? layout.size.width - layout.safeInsets.right - 84.0 : layout.size.width, height: videoBottomEdgeY - videoTopEdgeY)
transition.updateFrame(node: self.mainStageContainerNode, frame: videoFrame)
transition.updateFrame(node: self.mainStageBackgroundNode, frame: CGRect(origin: CGPoint(), size: videoFrame.size))
if !self.mainStageNode.animating {
transition.updateFrame(node: self.mainStageNode, frame: CGRect(origin: CGPoint(), size: videoFrame.size))
}
@ -4331,26 +4342,12 @@ public final class VoiceChatController: ViewController {
if !validSources.contains(videoEndpointId) {
self.videoNodes[videoEndpointId] = nil
self.readyVideoDisposables.set(nil, forKey: videoEndpointId)
// loop: for j in 0 ..< self.currentFullscreenEntries.count {
// let fullscreenEntry = self.currentFullscreenEntries[j]
// switch fullscreenEntry {
// case let .peer(peerEntry):
// if peerEntry.effectiveVideoEndpointId == videoEndpointId {
// let presentationData = self.presentationData.withUpdated(theme: self.darkTheme)
// self.fullscreenListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: j, previousIndex: j, item: fullscreenEntry.item(context: self.context, presentationData: presentationData, interaction: self.itemInteraction!), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
// break loop
// }
// default:
// break
// }
// }
}
}
}
private func updateMainVideo(waitForFullSize: Bool, updateMembers: Bool = true, force: Bool = false, completion: (() -> Void)? = nil) {
let effectiveMainSpeaker = self.currentForcedSpeaker ?? self.currentDominantSpeaker
let effectiveMainSpeaker = self.currentForcedSpeaker ?? self.currentDominantSpeaker?.0
guard effectiveMainSpeaker != self.effectiveSpeaker?.0 || force else {
return
}
@ -4473,42 +4470,46 @@ public final class VoiceChatController: ViewController {
if self.isScheduled && translation < 0.0 {
return
}
if case let .modal(isExpanded, previousIsFilled) = self.effectiveDisplayMode {
var topInset: CGFloat = 0.0
if let (currentTopInset, currentPanOffset) = self.panGestureArguments {
topInset = currentTopInset
if case let .known(value) = contentOffset, value <= 0.5 {
switch self.effectiveDisplayMode {
case let .modal(isExpanded, previousIsFilled):
var topInset: CGFloat = 0.0
if let (currentTopInset, currentPanOffset) = self.panGestureArguments {
topInset = currentTopInset
if case let .known(value) = contentOffset, value <= 0.5 {
} else {
translation = currentPanOffset
if self.isExpanded {
recognizer.setTranslation(CGPoint(), in: self.contentContainer.view)
}
}
self.panGestureArguments = (currentTopInset, translation)
}
let currentOffset = topInset + translation
var isFilled = previousIsFilled
if currentOffset < 20.0 {
isFilled = true
} else if currentOffset > 40.0 {
isFilled = false
}
if isFilled != previousIsFilled {
self.displayMode = .modal(isExpanded: isExpanded, isFilled: isFilled)
self.updateDecorationsColors()
}
if self.isExpanded {
} else {
translation = currentPanOffset
if self.isExpanded {
recognizer.setTranslation(CGPoint(), in: self.contentContainer.view)
if currentOffset > 0.0 {
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
}
}
case let .fullscreen(controlsHidden):
break
self.panGestureArguments = (currentTopInset, translation)
}
let currentOffset = topInset + translation
var isFilled = previousIsFilled
if currentOffset < 20.0 {
isFilled = true
} else if currentOffset > 40.0 {
isFilled = false
}
if isFilled != previousIsFilled {
self.displayMode = .modal(isExpanded: isExpanded, isFilled: isFilled)
self.updateDecorationsColors()
}
if self.isExpanded {
} else {
if currentOffset > 0.0 {
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
}
}
}
if let (layout, navigationHeight) = self.validLayout {
@ -5051,6 +5052,9 @@ public final class VoiceChatController: ViewController {
let effectiveSpeakerPeerId = self.effectiveSpeaker?.0
if let effectiveSpeakerPeerId = effectiveSpeakerPeerId, let otherItemNode = verticalItemNodes[effectiveSpeakerPeerId] {
self.mainStageNode.animateTransitionIn(from: otherItemNode, transition: transition)
self.mainStageBackgroundNode.alpha = 1.0
self.mainStageBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
self.fullscreenListNode.forEachItemNode { itemNode in
@ -5125,6 +5129,8 @@ public final class VoiceChatController: ViewController {
self?.mainStageContainerNode.isHidden = true
})
self.mainStageBackgroundNode.alpha = 0.0
self.mainStageBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)

View File

@ -253,12 +253,15 @@ final class VoiceChatMainStageNode: ASDisplayNode {
transition.updateFrame(view: snapshotView, frame: infoFrame)
}
sourceNode.alpha = 0.0
self.animatingIn = true
let startLocalFrame = sourceNode.view.convert(sourceNode.bounds, to: self.supernode?.view)
self.update(size: startLocalFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: .immediate)
self.frame = startLocalFrame
self.update(size: targetFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: transition)
transition.updateFrame(node: self, frame: targetFrame, completion: { [weak self] _ in
sourceNode.alpha = 1.0
self?.animatingIn = false
})
}
@ -300,12 +303,15 @@ final class VoiceChatMainStageNode: ASDisplayNode {
transition.updateFrame(view: snapshotView, frame: CGRect(origin: CGPoint(), size: targetFrame.size))
}
targetNode.alpha = 0.0
self.update(size: targetFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: transition)
transition.updateFrame(node: self, frame: targetFrame, completion: { [weak self] _ in
if let strongSelf = self {
completion()
infoView?.removeFromSuperview()
targetNode.alpha = 1.0
strongSelf.animatingOut = false
strongSelf.frame = initialFrame
strongSelf.update(size: initialFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate)

View File

@ -0,0 +1,184 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramPresentationData
import AppBundle
import ContextUI
import TelegramStringFormatting
final class VoiceChatShareScreenContextItem: ContextMenuCustomItem {
fileprivate let text: String
fileprivate let icon: (PresentationTheme) -> UIImage?
fileprivate let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void
init(text: String, icon: @escaping (PresentationTheme) -> UIImage?, action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) {
self.text = text
self.icon = icon
self.action = action
}
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
return VoiceChatShareScreenContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
}
}
private let textFont = Font.regular(17.0)
private final class VoiceChatShareScreenContextItemNode: ASDisplayNode, ContextMenuCustomNode {
private let item: VoiceChatShareScreenContextItem
private let presentationData: PresentationData
private let getController: () -> ContextControllerProtocol?
private let actionSelected: (ContextMenuActionResult) -> Void
private let backgroundNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let textNode: ImmediateTextNode
private let iconNode: VoiceChatRecordingIconNode
private let buttonNode: HighlightTrackingButtonNode
private var timer: SwiftSignalKit.Timer?
private var pointerInteraction: PointerInteraction?
init(presentationData: PresentationData, item: VoiceChatShareScreenContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
self.item = item
self.presentationData = presentationData
self.getController = getController
self.actionSelected = actionSelected
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
let subtextFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isAccessibilityElement = false
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isAccessibilityElement = false
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
self.highlightedBackgroundNode.alpha = 0.0
self.textNode = ImmediateTextNode()
self.textNode.isAccessibilityElement = false
self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = false
self.textNode.attributedText = NSAttributedString(string: item.text, font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
self.textNode.maximumNumberOfLines = 1
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.isAccessibilityElement = true
self.buttonNode.accessibilityLabel = presentationData.strings.VoiceChat_StopRecording
self.iconNode = VoiceChatRecordingIconNode(hasBackground: true)
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.highlightedBackgroundNode)
self.addSubnode(self.textNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.buttonNode)
self.buttonNode.highligthedChanged = { [weak self] highligted in
guard let strongSelf = self else {
return
}
if highligted {
strongSelf.highlightedBackgroundNode.alpha = 1.0
} else {
strongSelf.highlightedBackgroundNode.alpha = 0.0
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
deinit {
self.timer?.invalidate()
}
override func didLoad() {
super.didLoad()
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: { [weak self] in
if let strongSelf = self {
strongSelf.highlightedBackgroundNode.alpha = 0.75
}
}, willExit: { [weak self] in
if let strongSelf = self {
strongSelf.highlightedBackgroundNode.alpha = 0.0
}
})
}
private var validLayout: CGSize?
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let sideInset: CGFloat = 16.0
let iconSideInset: CGFloat = 12.0
let verticalInset: CGFloat = 12.0
let iconSide = 16.0 + (1.0 + UIScreenPixel) * 2.0
let iconSize: CGSize = CGSize(width: iconSide, height: iconSide)
let standardIconWidth: CGFloat = 32.0
var rightTextInset: CGFloat = sideInset
if !iconSize.width.isZero {
rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset
}
let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude))
let verticalSpacing: CGFloat = 2.0
let combinedTextHeight = textSize.height + verticalSpacing
return (CGSize(width: textSize.width + sideInset + rightTextInset, height: verticalInset * 2.0 + combinedTextHeight), { size, transition in
let hadLayout = self.validLayout != nil
self.validLayout = size
let verticalOrigin = floor((size.height - combinedTextHeight) / 2.0)
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize)
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
if !iconSize.width.isZero {
transition.updateFrameAdditive(node: self.iconNode, frame: CGRect(origin: CGPoint(x: size.width - standardIconWidth - iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
}
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
})
}
func updateTheme(presentationData: PresentationData) {
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
let subtextFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
}
@objc private func buttonPressed() {
self.performAction()
}
func performAction() {
guard let controller = self.getController() else {
return
}
self.item.action(controller, { [weak self] result in
self?.actionSelected(result)
})
}
func setIsHighlighted(_ value: Bool) {
if value {
self.highlightedBackgroundNode.alpha = 1.0
} else {
self.highlightedBackgroundNode.alpha = 0.0
}
}
}