Video Chat Improvements

This commit is contained in:
Ilya Laktyushin
2021-05-22 13:48:04 +04:00
parent d20661d5a9
commit 4efe0468c5
7 changed files with 77 additions and 35 deletions

View File

@@ -623,11 +623,13 @@ public final class AvatarNode: ASDisplayNode {
} }
} }
public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont, letters: [String], peerId: PeerId) { public func drawPeerAvatarLetters(context: CGContext, size: CGSize, round: Bool = true, font: UIFont, letters: [String], peerId: PeerId) {
if round {
context.beginPath() context.beginPath()
context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height:
size.height)) size.height))
context.clip() context.clip()
}
let colorIndex: Int let colorIndex: Int
if peerId.namespace == .max { if peerId.namespace == .max {

View File

@@ -87,9 +87,9 @@ public func peerAvatarImageData(account: Account, peerReference: PeerReference?,
} }
} }
public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize, font: UIFont = avatarPlaceholderFont(size: 13.0), fullSize: Bool = false) -> Signal<UIImage?, NoError> { public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false) -> Signal<UIImage?, NoError> {
let iconSignal: Signal<UIImage?, NoError> let iconSignal: Signal<UIImage?, NoError>
if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) { if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, round: round, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) {
if fullSize, let fullSizeSignal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.last, displayDimensions: size, emptyColor: nil, synchronousLoad: true) { if fullSize, let fullSizeSignal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.last, displayDimensions: size, emptyColor: nil, synchronousLoad: true) {
iconSignal = combineLatest(.single(nil) |> then(signal), .single(nil) |> then(fullSizeSignal)) iconSignal = combineLatest(.single(nil) |> then(signal), .single(nil) |> then(fullSizeSignal))
|> mapToSignal { thumbnailImage, fullSizeImage -> Signal<UIImage?, NoError> in |> mapToSignal { thumbnailImage, fullSizeImage -> Signal<UIImage?, NoError> in
@@ -113,10 +113,13 @@ public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize,
if displayLetters.count == 2 && displayLetters[0].isSingleEmoji && displayLetters[1].isSingleEmoji { if displayLetters.count == 2 && displayLetters[0].isSingleEmoji && displayLetters[1].isSingleEmoji {
displayLetters = [displayLetters[0]] displayLetters = [displayLetters[0]]
} }
if !drawLetters {
displayLetters = []
}
iconSignal = Signal { subscriber in iconSignal = Signal { subscriber in
let image = generateImage(size, rotatedContext: { size, context in let image = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), font: font, letters: displayLetters, peerId: peerId) drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), round: round, font: font, letters: displayLetters, peerId: peerId)
})?.withRenderingMode(.alwaysOriginal) })?.withRenderingMode(.alwaysOriginal)
subscriber.putNext(image) subscriber.putNext(image)

View File

@@ -132,6 +132,14 @@ private final class CallVideoNode: ASDisplayNode {
self.isReadyTimer?.invalidate() self.isReadyTimer?.invalidate()
} }
override func didLoad() {
super.didLoad()
if #available(iOS 13.0, *) {
self.layer.cornerCurve = .continuous
}
}
func animateRadialMask(from fromRect: CGRect, to toRect: CGRect) { func animateRadialMask(from fromRect: CGRect, to toRect: CGRect) {
let maskLayer = CAShapeLayer() let maskLayer = CAShapeLayer()
maskLayer.frame = fromRect maskLayer.frame = fromRect

View File

@@ -222,9 +222,6 @@ private class CallControllerToastItemNode: ASDisplayNode {
self.clipNode = ASDisplayNode() self.clipNode = ASDisplayNode()
self.clipNode.clipsToBounds = true self.clipNode.clipsToBounds = true
self.clipNode.layer.cornerRadius = 14.0 self.clipNode.layer.cornerRadius = 14.0
if #available(iOS 13.0, *) {
self.clipNode.layer.cornerCurve = .continuous
}
self.effectView = UIVisualEffectView() self.effectView = UIVisualEffectView()
self.effectView.effect = UIBlurEffect(style: .light) self.effectView.effect = UIBlurEffect(style: .light)
@@ -248,6 +245,14 @@ private class CallControllerToastItemNode: ASDisplayNode {
self.clipNode.addSubnode(self.textNode) self.clipNode.addSubnode(self.textNode)
} }
override func didLoad() {
super.didLoad()
if #available(iOS 13.0, *) {
self.clipNode.layer.cornerCurve = .continuous
}
}
func update(width: CGFloat, content: Content, transition: ContainedViewLayoutTransition) -> CGFloat { func update(width: CGFloat, content: Content, transition: ContainedViewLayoutTransition) -> CGFloat {
let inset: CGFloat = 30.0 let inset: CGFloat = 30.0
let isNarrowScreen = width <= 320.0 let isNarrowScreen = width <= 320.0

View File

@@ -688,7 +688,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
strongSelf.audioLevelView = audioLevelView strongSelf.audioLevelView = audioLevelView
strongSelf.offsetContainerNode.view.insertSubview(audioLevelView, at: 0) strongSelf.offsetContainerNode.view.insertSubview(audioLevelView, at: 0)
if let item = strongSelf.item, strongSelf.videoNode != nil || item.active { if let item = strongSelf.item, strongSelf.videoNode != nil && !item.active {
audioLevelView.alpha = 0.0 audioLevelView.alpha = 0.0
} }
} }

View File

@@ -48,12 +48,12 @@ final class VoiceChatMainStageNode: ASDisplayNode {
private let audioLevelDisposable = MetaDisposable() private let audioLevelDisposable = MetaDisposable()
private let speakingPeerDisposable = MetaDisposable() private let speakingPeerDisposable = MetaDisposable()
private let speakingAudioLevelDisposable = MetaDisposable() private let speakingAudioLevelDisposable = MetaDisposable()
private var avatarNode: ASImageNode private var backdropAvatarNode: ImageNode
private var backdropEffectView: UIVisualEffectView?
private var avatarNode: ImageNode
private let titleNode: ImmediateTextNode private let titleNode: ImmediateTextNode
private let microphoneNode: VoiceChatMicrophoneNode private let microphoneNode: VoiceChatMicrophoneNode
private let avatarDisposable = MetaDisposable()
private let speakingContainerNode: ASDisplayNode private let speakingContainerNode: ASDisplayNode
private var speakingEffectView: UIVisualEffectView? private var speakingEffectView: UIVisualEffectView?
private let speakingAvatarNode: AvatarNode private let speakingAvatarNode: AvatarNode
@@ -129,7 +129,12 @@ final class VoiceChatMainStageNode: ASDisplayNode {
self.pinButtonTitleNode.attributedText = NSAttributedString(string: "Unpin", font: Font.regular(17.0), textColor: .white) self.pinButtonTitleNode.attributedText = NSAttributedString(string: "Unpin", font: Font.regular(17.0), textColor: .white)
self.pinButtonNode = HighlightableButtonNode() self.pinButtonNode = HighlightableButtonNode()
self.avatarNode = ASImageNode() self.backdropAvatarNode = ImageNode()
self.backdropAvatarNode.contentMode = .scaleAspectFill
self.backdropAvatarNode.displaysAsynchronously = false
self.backdropAvatarNode.isHidden = true
self.avatarNode = ImageNode()
self.avatarNode.displaysAsynchronously = false self.avatarNode.displaysAsynchronously = false
self.avatarNode.isHidden = true self.avatarNode.isHidden = true
@@ -152,14 +157,12 @@ final class VoiceChatMainStageNode: ASDisplayNode {
self.clipsToBounds = true self.clipsToBounds = true
self.cornerRadius = backgroundCornerRadius self.cornerRadius = backgroundCornerRadius
if #available(iOS 13.0, *) {
self.layer.cornerCurve = .continuous
}
self.addSubnode(self.backgroundNode) self.addSubnode(self.backgroundNode)
self.addSubnode(self.topFadeNode) self.addSubnode(self.topFadeNode)
self.addSubnode(self.bottomFadeNode) self.addSubnode(self.bottomFadeNode)
self.addSubnode(self.bottomFillNode) self.addSubnode(self.bottomFillNode)
self.addSubnode(self.backdropAvatarNode)
self.addSubnode(self.avatarNode) self.addSubnode(self.avatarNode)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.microphoneNode) self.addSubnode(self.microphoneNode)
@@ -215,7 +218,6 @@ final class VoiceChatMainStageNode: ASDisplayNode {
} }
deinit { deinit {
self.avatarDisposable.dispose()
self.videoReadyDisposable.dispose() self.videoReadyDisposable.dispose()
self.audioLevelDisposable.dispose() self.audioLevelDisposable.dispose()
self.speakingPeerDisposable.dispose() self.speakingPeerDisposable.dispose()
@@ -226,10 +228,25 @@ final class VoiceChatMainStageNode: ASDisplayNode {
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
if #available(iOS 13.0, *) {
self.layer.cornerCurve = .continuous
}
let speakingEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) let speakingEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
self.speakingContainerNode.view.insertSubview(speakingEffectView, at: 0) self.speakingContainerNode.view.insertSubview(speakingEffectView, at: 0)
self.speakingEffectView = speakingEffectView self.speakingEffectView = speakingEffectView
let effect: UIVisualEffect
if #available(iOS 13.0, *) {
effect = UIBlurEffect(style: .systemMaterialDark)
} else {
effect = UIBlurEffect(style: .dark)
}
let backdropEffectView = UIVisualEffectView(effect: effect)
backdropEffectView.isHidden = true
self.view.insertSubview(backdropEffectView, aboveSubview: self.backdropAvatarNode.view)
self.backdropEffectView = backdropEffectView
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap))) self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap)))
self.speakingContainerNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.speakingTap))) self.speakingContainerNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.speakingTap)))
@@ -476,12 +493,8 @@ final class VoiceChatMainStageNode: ASDisplayNode {
if !arePeersEqual(previousPeerEntry?.peer, peerEntry.peer) { if !arePeersEqual(previousPeerEntry?.peer, peerEntry.peer) {
let peer = peerEntry.peer let peer = peerEntry.peer
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.avatarDisposable.set((peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true) self.backdropAvatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), round: false, font: avatarPlaceholderFont(size: 78.0), drawLetters: false))
|> deliverOnMainQueue).start(next: { [weak self] image in self.avatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true))
if let strongSelf = self {
strongSelf.avatarNode.image = image
}
}))
self.titleNode.attributedText = NSAttributedString(string: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.semibold(15.0), textColor: .white) self.titleNode.attributedText = NSAttributedString(string: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.semibold(15.0), textColor: .white)
if let (size, sideInset, bottomInset, isLandscape) = self.validLayout { if let (size, sideInset, bottomInset, isLandscape) = self.validLayout {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate) self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate)
@@ -581,6 +594,13 @@ final class VoiceChatMainStageNode: ASDisplayNode {
self.microphoneNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: true, color: .white), animated: true) self.microphoneNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: true, color: .white), animated: true)
} }
private func setAvatarHidden(_ hidden: Bool) {
self.backdropAvatarNode.isHidden = hidden
self.backdropEffectView?.isHidden = hidden
self.avatarNode.isHidden = hidden
self.audioLevelView?.isHidden = hidden
}
func update(peer: (peer: PeerId, endpointId: String?)?, waitForFullSize: Bool, completion: (() -> Void)? = nil) { func update(peer: (peer: PeerId, endpointId: String?)?, waitForFullSize: Bool, completion: (() -> Void)? = nil) {
let previousPeer = self.currentPeer let previousPeer = self.currentPeer
if previousPeer?.0 == peer?.0 && previousPeer?.1 == peer?.1 { if previousPeer?.0 == peer?.0 && previousPeer?.1 == peer?.1 {
@@ -594,8 +614,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
if let (_, endpointId) = peer { if let (_, endpointId) = peer {
if endpointId != previousPeer?.1 { if endpointId != previousPeer?.1 {
if let endpointId = endpointId { if let endpointId = endpointId {
self.avatarNode.isHidden = true self.setAvatarHidden(true)
self.audioLevelView?.isHidden = true
self.call.makeIncomingVideoView(endpointId: endpointId, completion: { [weak self] videoView in self.call.makeIncomingVideoView(endpointId: endpointId, completion: { [weak self] videoView in
Queue.mainQueue().async { Queue.mainQueue().async {
@@ -643,15 +662,14 @@ final class VoiceChatMainStageNode: ASDisplayNode {
} }
}) })
} else { } else {
self.avatarNode.isHidden = false self.setAvatarHidden(false)
self.audioLevelView?.isHidden = false
if let currentVideoNode = self.currentVideoNode { if let currentVideoNode = self.currentVideoNode {
currentVideoNode.removeFromSupernode() currentVideoNode.removeFromSupernode()
self.currentVideoNode = nil self.currentVideoNode = nil
} }
} }
} else { } else {
self.audioLevelView?.isHidden = self.currentPeer?.1 != nil self.setAvatarHidden(endpointId != nil)
completion?() completion?()
} }
} else { } else {
@@ -705,6 +723,10 @@ final class VoiceChatMainStageNode: ASDisplayNode {
} }
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
transition.updateFrame(node: self.backdropAvatarNode, frame: CGRect(origin: CGPoint(), size: size))
if let backdropEffectView = self.backdropEffectView {
transition.updateFrame(view: backdropEffectView, frame: CGRect(origin: CGPoint(), size: size))
}
let avatarSize = CGSize(width: 180.0, height: 180.0) let avatarSize = CGSize(width: 180.0, height: 180.0)
let avatarFrame = CGRect(origin: CGPoint(x: (size.width - avatarSize.width) / 2.0, y: (size.height - avatarSize.height) / 2.0), size: avatarSize) let avatarFrame = CGRect(origin: CGPoint(x: (size.width - avatarSize.width) / 2.0, y: (size.height - avatarSize.height) / 2.0), size: avatarSize)

View File

@@ -129,9 +129,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
self.contentNode = ASDisplayNode() self.contentNode = ASDisplayNode()
self.contentNode.clipsToBounds = true self.contentNode.clipsToBounds = true
self.contentNode.cornerRadius = backgroundCornerRadius self.contentNode.cornerRadius = backgroundCornerRadius
if #available(iOS 13.0, *) {
self.contentNode.layer.cornerCurve = .continuous
}
self.backgroundNode = ASDisplayNode() self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = panelBackgroundColor self.backgroundNode.backgroundColor = panelBackgroundColor
@@ -203,6 +201,10 @@ final class VoiceChatTileItemNode: ASDisplayNode {
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
if #available(iOS 13.0, *) {
self.contentNode.layer.cornerCurve = .continuous
}
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap))) self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap)))
} }