This commit is contained in:
Ali 2021-05-27 01:15:57 +04:00
commit 9e99110a55
19 changed files with 2464 additions and 2251 deletions

View file

@ -6475,3 +6475,5 @@ Sorry for the inconvenience.";
"VoiceChat.ParticipantIsSpeaking" = "%1$@ is speaking";
"WallpaperPreview.WallpaperColors" = "Colors";
"VoiceChat.UnmuteSuggestion" = "You are on mute. Tap here to speak.";

View file

@ -388,6 +388,7 @@ public protocol PresentationGroupCall: class {
var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { get }
var isSpeaking: Signal<Bool, NoError> { get }
var canBeRemoved: Signal<Bool, NoError> { get }
var state: Signal<PresentationGroupCallState, NoError> { get }
var stateVersion: Signal<Int, NoError> { get }

View file

@ -185,11 +185,10 @@ final class BlobView: UIView {
var level: CGFloat = 0 {
didSet {
return;
CATransaction.begin()
CATransaction.setDisableActions(true)
let lv = minScale + (maxScale - minScale) * level
shapeLayer.transform = CATransform3DMakeScale(lv, lv, 1)
let lv = self.minScale + (self.maxScale - self.minScale) * self.level
self.shapeLayer.transform = CATransform3DMakeScale(lv, lv, 1)
CATransaction.commit()
}
}
@ -257,7 +256,7 @@ final class BlobView: UIView {
layer.addSublayer(shapeLayer)
shapeLayer.transform = CATransform3DMakeScale(minScale, minScale, 1)
self.shapeLayer.transform = CATransform3DMakeScale(minScale, minScale, 1)
}
required init?(coder: NSCoder) {
@ -265,75 +264,59 @@ final class BlobView: UIView {
}
func setColor(_ color: UIColor, animated: Bool) {
let previousColor = shapeLayer.fillColor
shapeLayer.fillColor = color.cgColor
let previousColor = self.shapeLayer.fillColor
self.shapeLayer.fillColor = color.cgColor
if animated, let previousColor = previousColor {
shapeLayer.animate(from: previousColor, to: color.cgColor, keyPath: "fillColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
self.shapeLayer.animate(from: previousColor, to: color.cgColor, keyPath: "fillColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
}
}
func updateSpeedLevel(to newSpeedLevel: CGFloat) {
speedLevel = max(speedLevel, newSpeedLevel)
self.speedLevel = max(self.speedLevel, newSpeedLevel)
if abs(lastSpeedLevel - newSpeedLevel) > 0.5 {
animateToNewShape()
}
// if abs(lastSpeedLevel - newSpeedLevel) > 0.5 {
// animateToNewShape()
// }
}
func startAnimating() {
animateToNewShape()
self.animateToNewShape()
}
func stopAnimating() {
fromPoints = currentPoints
toPoints = nil
pop_removeAnimation(forKey: "blob")
self.shapeLayer.removeAnimation(forKey: "path")
}
private func animateToNewShape() {
guard !isCircle else { return }
if pop_animation(forKey: "blob") != nil {
fromPoints = currentPoints
toPoints = nil
pop_removeAnimation(forKey: "blob")
if self.shapeLayer.path == nil {
let points = generateNextBlob(for: self.bounds.size)
self.shapeLayer.path = UIBezierPath.smoothCurve(through: points, length: bounds.width, smoothness: smoothness).cgPath
}
if fromPoints == nil {
fromPoints = generateNextBlob(for: bounds.size)
}
if toPoints == nil {
toPoints = generateNextBlob(for: bounds.size)
}
let nextPoints = generateNextBlob(for: self.bounds.size)
let nextPath = UIBezierPath.smoothCurve(through: nextPoints, length: bounds.width, smoothness: smoothness).cgPath
let animation = POPBasicAnimation()
animation.property = POPAnimatableProperty.property(withName: "blob.transition", initializer: { property in
property?.readBlock = { blobView, values in
guard let blobView = blobView as? BlobView, let values = values else { return }
values.pointee = blobView.transition
}
property?.writeBlock = { blobView, values in
guard let blobView = blobView as? BlobView, let values = values else { return }
blobView.transition = values.pointee
}
}) as? POPAnimatableProperty
animation.completionBlock = { [weak self] animation, finished in
let animation = CABasicAnimation(keyPath: "path")
let previousPath = self.shapeLayer.path
self.shapeLayer.path = nextPath
animation.duration = CFTimeInterval(1 / (self.minSpeed + (self.maxSpeed - self.minSpeed) * self.speedLevel))
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
animation.fromValue = previousPath
animation.toValue = nextPath
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
animation.completion = { [weak self] finished in
if finished {
self?.fromPoints = self?.currentPoints
self?.toPoints = nil
self?.animateToNewShape()
}
}
animation.duration = CFTimeInterval(1 / (minSpeed + (maxSpeed - minSpeed) * speedLevel))
animation.timingFunction = CAMediaTimingFunction(name: .linear)
animation.fromValue = 0
animation.toValue = 1
//pop_add(animation, forKey: "blob")
lastSpeedLevel = speedLevel
speedLevel = 0
self.shapeLayer.add(animation, forKey: "path")
self.lastSpeedLevel = self.speedLevel
self.speedLevel = 0
}
// MARK: Helpers

View file

@ -653,7 +653,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
}
case let .extracted(extracted, keepInPlace):
let springDuration: Double = 0.42 * animationDurationFactor
let springDamping: CGFloat = 104.0
var springDamping: CGFloat = 104.0
if case let .extracted(source) = self.source, source.centerVertically {
springDamping = 124.0
}
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor)
self.actionsContainerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
@ -662,13 +665,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
let contentParentNode = extracted
let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view)
var actionsSpringDamping = springDamping
var actionsDuration = springDuration
var actionsOffset: CGFloat = 0.0
var contentDuration = springDuration
if case let .extracted(source) = self.source, source.centerVertically {
actionsOffset = -(originalProjectedContentViewFrame.1.height - originalProjectedContentViewFrame.0.height) * 0.57
actionsSpringDamping *= 1.2
actionsDuration *= 1.0
contentDuration *= 0.9
}
@ -684,7 +685,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
reactionContextNode.animateIn(from: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size))
}
self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y + actionsOffset)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: actionsDuration, initialVelocity: 0.0, damping: actionsSpringDamping, additive: true)
self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y + actionsOffset)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: actionsDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
let contentContainerOffset = CGPoint(x: localContentSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localContentSourceFrame.center.y - self.contentContainerNode.frame.center.y - contentParentNode.contentRect.minY)
self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentContainerOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: contentDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
contentParentNode.applyAbsoluteOffsetSpring?(-contentContainerOffset.y, springDuration, springDamping)

View file

@ -425,14 +425,32 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
additionalDisposable.set((combineLatest(queue: .mainQueue(),
membersState.get(),
context.account.postbox.peerView(id: peerId),
context.account.postbox.contactPeersView(accountPeerId: context.account.peerId, includePresences: true)
).start(next: { [weak self] state, contactsView in
).start(next: { [weak self] state, peerView, contactsView in
guard let strongSelf = self else {
return
}
var entries: [ChannelMembersSearchEntry] = []
if case .inviteToCall = mode, !filters.contains(where: { filter in
var canInviteByLink = false
if let peer = peerViewMainPeer(peerView) {
if !(peer.addressName?.isEmpty ?? true) {
canInviteByLink = true
} else if let peer = peer as? TelegramChannel {
if peer.flags.contains(.isCreator) || (peer.adminRights?.rights.contains(.canInviteUsers) == true) {
canInviteByLink = true
}
} else if let peer = peer as? TelegramGroup {
if case .creator = peer.role {
canInviteByLink = true
} else if case let .admin(rights, _) = peer.role, rights.rights.contains(.canInviteUsers) {
canInviteByLink = true
}
}
}
if case .inviteToCall = mode, canInviteByLink, !filters.contains(where: { filter in
if case .excludeNonMembers = filter {
return true
} else {

View file

@ -8,7 +8,6 @@ import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import LegacyComponents
import AnimatedCountLabelNode
private let blue = UIColor(rgb: 0x007fff)
@ -680,8 +679,8 @@ final class CurveView: UIView {
}
CATransaction.begin()
CATransaction.setDisableActions(true)
let lv = minOffset + (maxOffset - minOffset) * level
shapeLayer.transform = CATransform3DMakeTranslation(0.0, lv * 16.0, 0.0)
let lv = self.minOffset + (self.maxOffset - self.minOffset) * self.level
self.shapeLayer.transform = CATransform3DMakeTranslation(0.0, lv * 16.0, 0.0)
CATransaction.commit()
}
}
@ -695,39 +694,16 @@ final class CurveView: UIView {
return layer
}()
private var transition: CGFloat = 0 {
didSet {
guard let currentPoints = currentPoints else { return }
shapeLayer.path = UIBezierPath.smoothCurve(through: currentPoints, length: bounds.width, smoothness: smoothness, curve: true).cgPath
}
}
override var frame: CGRect {
didSet {
if self.frame.size != oldValue.size {
self.fromPoints = nil
self.toPoints = nil
self.shapeLayer.path = nil
self.animateToNewShape()
}
}
}
private var fromPoints: [CGPoint]?
private var toPoints: [CGPoint]?
private var currentPoints: [CGPoint]? {
guard let fromPoints = fromPoints, let toPoints = toPoints else { return nil }
return fromPoints.enumerated().map { offset, fromPoint in
let toPoint = toPoints[offset]
return CGPoint(
x: fromPoint.x + (toPoint.x - fromPoint.x) * transition,
y: fromPoint.y + (toPoint.y - fromPoint.y) * transition
)
}
}
init(
pointsCount: Int,
minRandomness: CGFloat,
@ -749,7 +725,7 @@ final class CurveView: UIView {
super.init(frame: .zero)
layer.addSublayer(shapeLayer)
self.layer.addSublayer(self.shapeLayer)
}
required init?(coder: NSCoder) {
@ -757,7 +733,7 @@ final class CurveView: UIView {
}
func setColor(_ color: UIColor) {
shapeLayer.fillColor = color.cgColor
self.shapeLayer.fillColor = color.cgColor
}
func updateSpeedLevel(to newSpeedLevel: CGFloat) {
@ -769,57 +745,40 @@ final class CurveView: UIView {
}
func startAnimating() {
animateToNewShape()
self.animateToNewShape()
}
func stopAnimating() {
fromPoints = currentPoints
toPoints = nil
pop_removeAnimation(forKey: "curve")
self.shapeLayer.removeAnimation(forKey: "path")
}
private func animateToNewShape() {
if pop_animation(forKey: "curve") != nil {
fromPoints = currentPoints
toPoints = nil
pop_removeAnimation(forKey: "curve")
if self.shapeLayer.path == nil {
let points = self.generateNextCurve(for: self.bounds.size)
self.shapeLayer.path = UIBezierPath.smoothCurve(through: points, length: bounds.width, smoothness: self.smoothness, curve: true).cgPath
}
if fromPoints == nil {
fromPoints = generateNextCurve(for: bounds.size)
}
if toPoints == nil {
toPoints = generateNextCurve(for: bounds.size)
}
let nextPoints = self.generateNextCurve(for: self.bounds.size)
let nextPath = UIBezierPath.smoothCurve(through: nextPoints, length: bounds.width, smoothness: self.smoothness, curve: true).cgPath
let animation = POPBasicAnimation()
animation.property = POPAnimatableProperty.property(withName: "curve.transition", initializer: { property in
property?.readBlock = { curveView, values in
guard let curveView = curveView as? CurveView, let values = values else { return }
values.pointee = curveView.transition
}
property?.writeBlock = { curveView, values in
guard let curveView = curveView as? CurveView, let values = values else { return }
curveView.transition = values.pointee
}
}) as? POPAnimatableProperty
animation.completionBlock = { [weak self] animation, finished in
let animation = CABasicAnimation(keyPath: "path")
let previousPath = self.shapeLayer.path
self.shapeLayer.path = nextPath
animation.duration = CFTimeInterval(1 / (self.minSpeed + (self.maxSpeed - self.minSpeed) * self.speedLevel))
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
animation.fromValue = previousPath
animation.toValue = nextPath
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
animation.completion = { [weak self] finished in
if finished {
self?.fromPoints = self?.currentPoints
self?.toPoints = nil
self?.animateToNewShape()
}
}
animation.duration = CFTimeInterval(1 / (minSpeed + (maxSpeed - minSpeed) * speedLevel))
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
animation.fromValue = 0
animation.toValue = 1
pop_add(animation, forKey: "curve")
self.shapeLayer.add(animation, forKey: "path")
lastSpeedLevel = speedLevel
speedLevel = 0
self.lastSpeedLevel = self.speedLevel
self.speedLevel = 0
}
private func generateNextCurve(for size: CGSize) -> [CGPoint] {
@ -871,8 +830,8 @@ final class CurveView: UIView {
CATransaction.begin()
CATransaction.setDisableActions(true)
shapeLayer.position = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0)
shapeLayer.bounds = self.bounds
self.shapeLayer.position = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0)
self.shapeLayer.bounds = self.bounds
CATransaction.commit()
}
}

View file

@ -625,6 +625,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private var isScheduled = false
private var isScheduledStarted = false
private let isSpeakingPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
public var isSpeaking: Signal<Bool, NoError> {
return self.isSpeakingPromise.get()
}
private var screencastFramesDisposable: Disposable?
private var screencastStateDisposable: Disposable?
@ -1608,6 +1613,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
var result: [(PeerId, UInt32, Float, Bool)] = []
var myLevel: Float = 0.0
var myLevelHasVoice: Bool = false
var orignalMyLevelHasVoice: Bool = false
var missingSsrcs = Set<UInt32>()
for (ssrcKey, level, hasVoice) in levels {
var peerId: PeerId?
@ -1622,6 +1628,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
if let peerId = peerId {
if case .local = ssrcKey {
orignalMyLevelHasVoice = hasVoice
if !strongSelf.isMutedValue.isEffectivelyMuted {
myLevel = level
myLevelHasVoice = hasVoice
@ -1638,6 +1645,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
let mappedLevel = myLevel * 1.5
strongSelf.myAudioLevelPipe.putNext(mappedLevel)
strongSelf.processMyAudioLevel(level: mappedLevel, hasVoice: myLevelHasVoice)
strongSelf.isSpeakingPromise.set(orignalMyLevelHasVoice)
if !missingSsrcs.isEmpty {
strongSelf.participantsContext?.ensureHaveParticipants(ssrcs: missingSsrcs)
@ -2919,22 +2927,22 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
public func makeIncomingVideoView(endpointId: String, requestClone: Bool, completion: @escaping (PresentationCallVideoView?, PresentationCallVideoView?) -> Void) {
self.genericCallContext?.makeIncomingVideoView(endpointId: endpointId, requestClone: requestClone, completion: { view, _ in
if let view = view {
let setOnFirstFrameReceived = view.setOnFirstFrameReceived
let setOnOrientationUpdated = view.setOnOrientationUpdated
let setOnIsMirroredUpdated = view.setOnIsMirroredUpdated
let updateIsEnabled = view.updateIsEnabled
completion(PresentationCallVideoView(
holder: view,
view: view.view,
self.genericCallContext?.makeIncomingVideoView(endpointId: endpointId, requestClone: requestClone, completion: { mainView, cloneView in
if let mainView = mainView {
let setOnFirstFrameReceived = mainView.setOnFirstFrameReceived
let setOnOrientationUpdated = mainView.setOnOrientationUpdated
let setOnIsMirroredUpdated = mainView.setOnIsMirroredUpdated
let updateIsEnabled = mainView.updateIsEnabled
let mainVideoView = PresentationCallVideoView(
holder: mainView,
view: mainView.view,
setOnFirstFrameReceived: { f in
setOnFirstFrameReceived(f)
},
getOrientation: { [weak view] in
if let view = view {
getOrientation: { [weak mainView] in
if let mainView = mainView {
let mappedValue: PresentationCallVideoView.Orientation
switch view.getOrientation() {
switch mainView.getOrientation() {
case .rotation0:
mappedValue = .rotation0
case .rotation90:
@ -2949,9 +2957,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
return .rotation0
}
},
getAspect: { [weak view] in
if let view = view {
return view.getAspect()
getAspect: { [weak mainView] in
if let mainView = mainView {
return mainView.getAspect()
} else {
return 0.0
}
@ -2980,7 +2988,73 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
updateIsEnabled: { value in
updateIsEnabled(value)
}
), nil)
)
var cloneVideoView: PresentationCallVideoView?
if let cloneView = cloneView {
let setOnFirstFrameReceived = cloneView.setOnFirstFrameReceived
let setOnOrientationUpdated = cloneView.setOnOrientationUpdated
let setOnIsMirroredUpdated = cloneView.setOnIsMirroredUpdated
let updateIsEnabled = cloneView.updateIsEnabled
cloneVideoView = PresentationCallVideoView(
holder: cloneView,
view: cloneView.view,
setOnFirstFrameReceived: { f in
setOnFirstFrameReceived(f)
},
getOrientation: { [weak cloneView] in
if let cloneView = cloneView {
let mappedValue: PresentationCallVideoView.Orientation
switch cloneView.getOrientation() {
case .rotation0:
mappedValue = .rotation0
case .rotation90:
mappedValue = .rotation90
case .rotation180:
mappedValue = .rotation180
case .rotation270:
mappedValue = .rotation270
}
return mappedValue
} else {
return .rotation0
}
},
getAspect: { [weak cloneView] in
if let cloneView = cloneView {
return cloneView.getAspect()
} else {
return 0.0
}
},
setOnOrientationUpdated: { f in
setOnOrientationUpdated { value, aspect in
let mappedValue: PresentationCallVideoView.Orientation
switch value {
case .rotation0:
mappedValue = .rotation0
case .rotation90:
mappedValue = .rotation90
case .rotation180:
mappedValue = .rotation180
case .rotation270:
mappedValue = .rotation270
}
f?(mappedValue, aspect)
}
},
setOnIsMirroredUpdated: { f in
setOnIsMirroredUpdated { value in
f?(value)
}
},
updateIsEnabled: { value in
updateIsEnabled(value)
}
)
}
completion(mainVideoView, cloneVideoView)
} else {
completion(nil, nil)
}

View file

@ -3,7 +3,6 @@ import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import LegacyComponents
import AnimationUI
import AppBundle
import ManagedAnimationNode
@ -1370,13 +1369,11 @@ final class BlobView: UIView {
var level: CGFloat = 0 {
didSet {
return;
CATransaction.begin()
CATransaction.setDisableActions(true)
let lv = minScale + (maxScale - minScale) * level
shapeLayer.transform = CATransform3DMakeScale(lv, lv, 1)
self.scaleUpdated?(level)
let lv = self.minScale + (self.maxScale - self.minScale) * self.level
self.shapeLayer.transform = CATransform3DMakeScale(lv, lv, 1)
self.scaleUpdated?(self.level)
CATransaction.commit()
}
}
@ -1389,30 +1386,7 @@ final class BlobView: UIView {
layer.strokeColor = nil
return layer
}()
private var transition: CGFloat = 0 {
didSet {
guard let currentPoints = currentPoints else { return }
shapeLayer.path = UIBezierPath.smoothCurve(through: currentPoints, length: bounds.width, smoothness: smoothness).cgPath
}
}
private var fromPoints: [CGPoint]?
private var toPoints: [CGPoint]?
private var currentPoints: [CGPoint]? {
guard let fromPoints = fromPoints, let toPoints = toPoints else { return nil }
return fromPoints.enumerated().map { offset, fromPoint in
let toPoint = toPoints[offset]
return CGPoint(
x: fromPoint.x + (toPoint.x - fromPoint.x) * transition,
y: fromPoint.y + (toPoint.y - fromPoint.y) * transition
)
}
}
init(
pointsCount: Int,
minRandomness: CGFloat,
@ -1435,9 +1409,9 @@ final class BlobView: UIView {
super.init(frame: .zero)
layer.addSublayer(shapeLayer)
self.layer.addSublayer(self.shapeLayer)
shapeLayer.transform = CATransform3DMakeScale(minScale, minScale, 1)
self.shapeLayer.transform = CATransform3DMakeScale(minScale, minScale, 1)
}
required init?(coder: NSCoder) {
@ -1445,11 +1419,11 @@ final class BlobView: UIView {
}
func setColor(_ color: UIColor) {
shapeLayer.fillColor = color.cgColor
self.shapeLayer.fillColor = color.cgColor
}
func updateSpeedLevel(to newSpeedLevel: CGFloat) {
speedLevel = max(speedLevel, newSpeedLevel)
self.speedLevel = max(self.speedLevel, newSpeedLevel)
// if abs(lastSpeedLevel - newSpeedLevel) > 0.45 {
// animateToNewShape()
@ -1457,57 +1431,41 @@ final class BlobView: UIView {
}
func startAnimating() {
animateToNewShape()
self.animateToNewShape()
}
func stopAnimating() {
fromPoints = currentPoints
toPoints = nil
pop_removeAnimation(forKey: "blob")
self.shapeLayer.removeAnimation(forKey: "path")
}
private func animateToNewShape() {
if pop_animation(forKey: "blob") != nil {
fromPoints = currentPoints
toPoints = nil
pop_removeAnimation(forKey: "blob")
if self.shapeLayer.path == nil {
let points = generateNextBlob(for: self.bounds.size)
self.shapeLayer.path = UIBezierPath.smoothCurve(through: points, length: bounds.width, smoothness: smoothness).cgPath
}
if fromPoints == nil {
fromPoints = generateNextBlob(for: bounds.size)
}
if toPoints == nil {
toPoints = generateNextBlob(for: bounds.size)
}
let nextPoints = generateNextBlob(for: self.bounds.size)
let nextPath = UIBezierPath.smoothCurve(through: nextPoints, length: bounds.width, smoothness: smoothness).cgPath
let animation = POPBasicAnimation()
animation.property = POPAnimatableProperty.property(withName: "blob.transition", initializer: { property in
property?.readBlock = { blobView, values in
guard let blobView = blobView as? BlobView, let values = values else { return }
values.pointee = blobView.transition
}
property?.writeBlock = { blobView, values in
guard let blobView = blobView as? BlobView, let values = values else { return }
blobView.transition = values.pointee
}
}) as? POPAnimatableProperty
animation.completionBlock = { [weak self] animation, finished in
let animation = CABasicAnimation(keyPath: "path")
let previousPath = self.shapeLayer.path
self.shapeLayer.path = nextPath
animation.duration = CFTimeInterval(1 / (minSpeed + (maxSpeed - minSpeed) * speedLevel))
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
animation.fromValue = previousPath
animation.toValue = nextPath
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
animation.completion = { [weak self] finished in
if finished {
self?.fromPoints = self?.currentPoints
self?.toPoints = nil
self?.animateToNewShape()
}
}
animation.duration = CFTimeInterval(1 / (minSpeed + (maxSpeed - minSpeed) * speedLevel))
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
animation.fromValue = 0
animation.toValue = 1
//pop_add(animation, forKey: "blob")
self.shapeLayer.add(animation, forKey: "path")
lastSpeedLevel = speedLevel
speedLevel = 0
self.lastSpeedLevel = self.speedLevel
self.speedLevel = 0
}
// MARK: Helpers

View file

@ -55,7 +55,7 @@ class VoiceChatActionItem: ListViewItem {
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = VoiceChatActionItemNode()
let (layout, apply) = node.asyncLayout()(self, params, false, nextItem == nil)
let (layout, apply) = node.asyncLayout()(self, params, previousItem == nil || previousItem is VoiceChatTilesGridItem, nextItem == nil)
node.contentSize = layout.contentSize
node.insets = layout.insets
@ -74,7 +74,7 @@ class VoiceChatActionItem: ListViewItem {
let makeLayout = nodeValue.asyncLayout()
async {
let (layout, apply) = makeLayout(self, params, false, nextItem == nil)
let (layout, apply) = makeLayout(self, params, previousItem == nil || previousItem is VoiceChatTilesGridItem, nextItem == nil)
Queue.mainQueue().async {
completion(layout, { _ in
apply()
@ -95,6 +95,7 @@ class VoiceChatActionItem: ListViewItem {
class VoiceChatActionItemNode: ListViewItemNode {
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let highlightContainerNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let iconNode: ASImageNode
@ -121,13 +122,17 @@ class VoiceChatActionItemNode: ListViewItemNode {
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
self.highlightContainerNode = ASDisplayNode()
self.highlightContainerNode.clipsToBounds = true
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
self.highlightContainerNode.addSubnode(self.highlightedBackgroundNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.activateArea)
@ -138,11 +143,19 @@ class VoiceChatActionItemNode: ListViewItemNode {
}
}
func asyncLayout() -> (_ item: VoiceChatActionItem, _ params: ListViewItemLayoutParams, _ firstWithHeader: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) {
override func didLoad() {
super.didLoad()
if #available(iOS 13.0, *) {
self.highlightContainerNode.layer.cornerCurve = .continuous
}
}
func asyncLayout() -> (_ item: VoiceChatActionItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let currentItem = self.item
return { item, params, firstWithHeader, last in
return { item, params, first, last in
var updatedTheme: PresentationTheme?
var updatedContent = false
@ -165,7 +178,7 @@ class VoiceChatActionItemNode: ListViewItemNode {
let contentHeight: CGFloat = 12.0 * 2.0 + titleLayout.size.height
let contentSize = CGSize(width: params.width, height: contentHeight)
let insets = UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0)
let insets = UIEdgeInsets()
let separatorHeight = UIScreenPixel
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -214,6 +227,10 @@ class VoiceChatActionItemNode: ListViewItemNode {
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: titleOffset + 1.0, y: floor((contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
strongSelf.highlightContainerNode.frame = CGRect(origin: CGPoint(x: params.leftInset, y: -UIScreenPixel), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel + 11.0))
strongSelf.highlightContainerNode.cornerRadius = first ? 11.0 : 0.0
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel))
}
})
@ -224,8 +241,8 @@ class VoiceChatActionItemNode: ListViewItemNode {
super.setHighlighted(highlighted, at: point, animated: animated)
if highlighted {
self.highlightedBackgroundNode.alpha = 1.0
if self.highlightedBackgroundNode.supernode == nil {
self.highlightContainerNode.alpha = 1.0
if self.highlightContainerNode.supernode == nil {
var anchorNode: ASDisplayNode?
if self.bottomStripeNode.supernode != nil {
anchorNode = self.bottomStripeNode
@ -233,24 +250,24 @@ class VoiceChatActionItemNode: ListViewItemNode {
anchorNode = self.topStripeNode
}
if let anchorNode = anchorNode {
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
self.insertSubnode(self.highlightContainerNode, aboveSubnode: anchorNode)
} else {
self.addSubnode(self.highlightedBackgroundNode)
self.addSubnode(self.highlightContainerNode)
}
}
} else {
if self.highlightedBackgroundNode.supernode != nil {
if self.highlightContainerNode.supernode != nil {
if animated {
self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
self.highlightContainerNode.layer.animateAlpha(from: self.highlightContainerNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
if let strongSelf = self {
if completed {
strongSelf.highlightedBackgroundNode.removeFromSupernode()
strongSelf.highlightContainerNode.removeFromSupernode()
}
}
})
self.highlightedBackgroundNode.alpha = 0.0
self.highlightContainerNode.alpha = 0.0
} else {
self.highlightedBackgroundNode.removeFromSupernode()
self.highlightContainerNode.removeFromSupernode()
}
}
}

View file

@ -116,6 +116,7 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
private let contentBackgroundNode: ASDisplayNode
private let titleNode: ASTextNode
private let previewContainerNode: ASDisplayNode
private let shimmerNode: ShimmerEffectForegroundNode
private let cameraButton: SolidRoundedButtonNode
private let screenButton: SolidRoundedButtonNode
private var broadcastPickerView: UIView?
@ -135,6 +136,8 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
private let hapticFeedback = HapticFeedback()
private let readyDisposable = MetaDisposable()
var shareCamera: ((Bool) -> Void)?
var switchCamera: (() -> Void)?
var dismiss: (() -> Void)?
@ -200,7 +203,10 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
self.previewContainerNode = ASDisplayNode()
self.previewContainerNode.clipsToBounds = true
self.previewContainerNode.cornerRadius = 11.0
self.previewContainerNode.backgroundColor = .black
self.previewContainerNode.backgroundColor = UIColor(rgb: 0x2b2b2f)
self.shimmerNode = ShimmerEffectForegroundNode(size: 200.0)
self.previewContainerNode.addSubnode(self.shimmerNode)
self.microphoneButton = HighlightTrackingButtonNode()
self.microphoneButton.isSelected = true
@ -293,9 +299,19 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
}
}
}
self.readyDisposable.set(self.cameraNode.ready.start(next: { [weak self] ready in
if let strongSelf = self {
Queue.mainQueue().after(0.07) {
strongSelf.shimmerNode.alpha = 0.0
strongSelf.shimmerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
}
}))
}
deinit {
self.readyDisposable.dispose()
self.applicationStateDisposable?.dispose()
}
@ -407,6 +423,13 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.containerLayout = (layout, navigationBarHeight)
let isLandscape: Bool
if layout.size.width > layout.size.height, case .compact = layout.metrics.widthClass {
isLandscape = true
} else {
isLandscape = false
}
var insets = layout.insets(options: [.statusBar, .input])
let cleanInsets = layout.insets(options: [.statusBar])
insets.top = max(10.0, insets.top)
@ -419,9 +442,14 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
let titleHeight: CGFloat = 54.0
var contentHeight = titleHeight + bottomInset + 52.0 + 17.0
let innerContentHeight: CGFloat = layout.size.height - contentHeight - 160.0
contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + innerContentHeight + buttonOffset
var width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
if isLandscape {
contentHeight = layout.size.height
width = layout.size.width
} else {
contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + innerContentHeight + buttonOffset
}
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
let previewInset: CGFloat = 16.0
let sideInset = floor((layout.size.width - width) / 2.0)
@ -432,6 +460,7 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
if backgroundFrame.minY < contentFrame.minY {
backgroundFrame.origin.y = contentFrame.minY
}
transition.updateAlpha(node: self.titleNode, alpha: isLandscape ? 0.0 : 1.0)
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
@ -442,11 +471,23 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 18.0), size: titleSize)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
let previewSize = CGSize(width: contentFrame.width - previewInset * 2.0, height: contentHeight - 243.0 - bottomInset)
transition.updateFrame(node: self.previewContainerNode, frame: CGRect(origin: CGPoint(x: previewInset, y: 56.0), size: previewSize))
let previewSize: CGSize
let previewFrame: CGRect
if isLandscape {
let previewHeight = contentHeight - layout.intrinsicInsets.bottom - 52.0 - 10.0
previewSize = CGSize(width: min(contentFrame.width - layout.safeInsets.left - layout.safeInsets.right, previewHeight * 1.7778), height: previewHeight)
previewFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((contentFrame.width - previewSize.width) / 2.0), y: 0.0), size: previewSize)
} else {
previewSize = CGSize(width: contentFrame.width - previewInset * 2.0, height: contentHeight - 243.0 - bottomInset)
previewFrame = CGRect(origin: CGPoint(x: previewInset, y: 56.0), size: previewSize)
}
transition.updateFrame(node: self.previewContainerNode, frame: previewFrame)
transition.updateFrame(node: self.shimmerNode, frame: CGRect(origin: CGPoint(), size: previewFrame.size))
self.shimmerNode.update(foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.07))
self.shimmerNode.updateAbsoluteRect(previewFrame, within: layout.size)
self.cameraNode.frame = CGRect(origin: CGPoint(), size: previewSize)
self.cameraNode.updateLayout(size: previewSize, layoutMode: .fillVertical, transition: .immediate)
self.cameraNode.updateLayout(size: previewSize, layoutMode: isLandscape ? .fillHorizontal : .fillVertical, transition: .immediate)
let microphoneFrame = CGRect(x: 16.0, y: previewSize.height - 48.0 - 16.0, width: 48.0, height: 48.0)
transition.updateFrame(node: self.microphoneButton, frame: microphoneFrame)
@ -459,20 +500,46 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
transition.updateFrame(view: self.switchCameraEffectView, frame: CGRect(origin: CGPoint(), size: switchCameraFrame.size))
transition.updateFrame(node: self.switchCameraIconNode, frame: CGRect(origin: CGPoint(), size: switchCameraFrame.size))
let buttonInset: CGFloat = 16.0
let cameraButtonHeight = self.cameraButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.cameraButton, frame: CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - insets.bottom - 16.0 - buttonOffset, width: contentFrame.width, height: cameraButtonHeight))
let screenButtonHeight = self.screenButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.screenButton, frame: CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - 8.0 - screenButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: screenButtonHeight))
if let broadcastPickerView = self.broadcastPickerView {
broadcastPickerView.frame = CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - 8.0 - screenButtonHeight - insets.bottom - 16.0, width: contentFrame.width + 1000.0, height: screenButtonHeight)
if isLandscape {
var buttonsCount: Int = 2
if let _ = self.broadcastPickerView {
buttonsCount += 1
} else {
self.screenButton.isHidden = true
}
let buttonInset: CGFloat = 6.0
let buttonWidth = floorToScreenPixels((contentFrame.width - layout.safeInsets.left - layout.safeInsets.right - CGFloat(buttonsCount + 1) * buttonInset) / CGFloat(buttonsCount))
let cameraButtonHeight = self.cameraButton.updateLayout(width: buttonWidth, transition: transition)
let screenButtonHeight = self.screenButton.updateLayout(width: buttonWidth, transition: transition)
let cancelButtonHeight = self.cancelButton.updateLayout(width: buttonWidth, transition: transition)
transition.updateFrame(node: self.cancelButton, frame: CGRect(x: layout.safeInsets.left + buttonInset, y: previewFrame.maxY + 10.0, width: buttonWidth, height: cancelButtonHeight))
if let broadcastPickerView = self.broadcastPickerView {
transition.updateFrame(node: self.screenButton, frame: CGRect(x: layout.safeInsets.left + buttonInset + buttonWidth + buttonInset, y: previewFrame.maxY + 10.0, width: buttonWidth, height: screenButtonHeight))
broadcastPickerView.frame = CGRect(x: layout.safeInsets.left + buttonInset + buttonWidth + buttonInset, y: previewFrame.maxY + 10.0, width: buttonWidth, height: screenButtonHeight)
transition.updateFrame(node: self.cameraButton, frame: CGRect(x: layout.safeInsets.left + buttonInset + buttonWidth + buttonInset + buttonWidth + buttonInset, y: previewFrame.maxY + 10.0, width: buttonWidth, height: cameraButtonHeight))
} else {
transition.updateFrame(node: self.cameraButton, frame: CGRect(x: layout.safeInsets.left + buttonInset + buttonWidth + buttonInset, y: previewFrame.maxY + 10.0, width: buttonWidth, height: cameraButtonHeight))
}
} else {
self.screenButton.isHidden = true
let buttonInset: CGFloat = 16.0
let cameraButtonHeight = self.cameraButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.cameraButton, frame: CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - insets.bottom - 16.0 - buttonOffset, width: contentFrame.width, height: cameraButtonHeight))
let screenButtonHeight = self.screenButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.screenButton, frame: CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - 8.0 - screenButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: screenButtonHeight))
if let broadcastPickerView = self.broadcastPickerView {
broadcastPickerView.frame = CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - 8.0 - screenButtonHeight - insets.bottom - 16.0, width: contentFrame.width + 1000.0, height: screenButtonHeight)
} else {
self.screenButton.isHidden = true
}
let cancelButtonHeight = self.cancelButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.cancelButton, frame: CGRect(x: buttonInset, y: contentHeight - cancelButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: cancelButtonHeight))
}
let cancelButtonHeight = self.cancelButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.cancelButton, frame: CGRect(x: buttonInset, y: contentHeight - cancelButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: cancelButtonHeight))
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
}

View file

@ -96,8 +96,8 @@ struct VoiceChatPeerEntry: Identifiable {
var peer: Peer
var about: String?
var isMyPeer: Bool
var videoEndpointId: String?
var presentationEndpointId: String?
var _videoEndpointId: String?
var _presentationEndpointId: String?
var state: State
var muteState: GroupCallParticipantsContext.Participant.MuteState?
var canManageCall: Bool
@ -107,6 +107,22 @@ struct VoiceChatPeerEntry: Identifiable {
var active: Bool
var isLandscape: Bool
var videoEndpointId: String? {
if let muteState = self.muteState, !muteState.canUnmute || muteState.mutedByYou {
return nil
} else {
return self._videoEndpointId
}
}
var presentationEndpointId: String? {
if let muteState = self.muteState, !muteState.canUnmute || muteState.mutedByYou {
return nil
} else {
return self._presentationEndpointId
}
}
var effectiveVideoEndpointId: String? {
return self.presentationEndpointId ?? self.videoEndpointId
}
@ -129,8 +145,8 @@ struct VoiceChatPeerEntry: Identifiable {
self.peer = peer
self.about = about
self.isMyPeer = isMyPeer
self.videoEndpointId = videoEndpointId
self.presentationEndpointId = presentationEndpointId
self._videoEndpointId = videoEndpointId
self._presentationEndpointId = presentationEndpointId
self.state = state
self.muteState = muteState
self.canManageCall = canManageCall
@ -155,10 +171,10 @@ struct VoiceChatPeerEntry: Identifiable {
if lhs.isMyPeer != rhs.isMyPeer {
return false
}
if lhs.videoEndpointId != rhs.videoEndpointId {
if lhs._videoEndpointId != rhs._videoEndpointId {
return false
}
if lhs.presentationEndpointId != rhs.presentationEndpointId {
if lhs._presentationEndpointId != rhs._presentationEndpointId {
return false
}
if lhs.state != rhs.state {
@ -763,6 +779,7 @@ public final class VoiceChatController: ViewController {
private var callStateDisposable: Disposable?
private var pushingToTalk = false
private var temporaryPushingToTalk = false
private let hapticFeedback = HapticFeedback()
private var callState: PresentationGroupCallState?
@ -782,6 +799,7 @@ public final class VoiceChatController: ViewController {
private var audioLevelsDisposable: Disposable?
private var myAudioLevelDisposable: Disposable?
private var isSpeakingDisposable: Disposable?
private var memberStatesDisposable: Disposable?
private var actionButtonColorDisposable: Disposable?
@ -827,7 +845,10 @@ public final class VoiceChatController: ViewController {
private var connectedOnce = false
private var ignoreConnecting = false
private var ignoreConnectingTimer: SwiftSignalKit.Timer?
private var displayUnmuteTooltipTimer: SwiftSignalKit.Timer?
private var lastUnmuteTooltipDisplayTimestamp: Double?
private var displayMode: DisplayMode = .modal(isExpanded: false, isFilled: false) {
didSet {
if case let .modal(isExpanded, _) = self.displayMode {
@ -1858,7 +1879,7 @@ public final class VoiceChatController: ViewController {
if let (currentPeerId, _, timestamp) = strongSelf.currentDominantSpeaker {
if CACurrentMediaTime() - timestamp > 2.5 && peerId != currentPeerId {
strongSelf.currentDominantSpeaker = (peerId, nil, CACurrentMediaTime())
strongSelf.updateMainVideo(waitForFullSize: false)
strongSelf.updateMainVideo(waitForFullSize: true)
}
}
}
@ -1879,6 +1900,40 @@ public final class VoiceChatController: ViewController {
strongSelf.actionButton.updateLevel(CGFloat(effectiveLevel))
})
self.isSpeakingDisposable = (self.call.isSpeaking
|> deliverOnMainQueue).start(next: { [weak self] isSpeaking in
guard let strongSelf = self else {
return
}
if let state = strongSelf.callState, state.muteState == nil || strongSelf.pushingToTalk {
strongSelf.displayUnmuteTooltipTimer?.invalidate()
} else {
if isSpeaking {
var shouldDisplayTooltip = false
if let previousTimstamp = strongSelf.lastUnmuteTooltipDisplayTimestamp, CACurrentMediaTime() < previousTimstamp + 60.0 {
shouldDisplayTooltip = true
} else if strongSelf.lastUnmuteTooltipDisplayTimestamp == nil {
shouldDisplayTooltip = true
}
if shouldDisplayTooltip {
let timer = SwiftSignalKit.Timer(timeout: 2.0, repeat: false, completion: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.lastUnmuteTooltipDisplayTimestamp = CACurrentMediaTime()
strongSelf.displayUnmuteTooltip()
strongSelf.displayUnmuteTooltipTimer?.invalidate()
strongSelf.displayUnmuteTooltipTimer = nil
}, queue: Queue.mainQueue())
timer.start()
strongSelf.displayUnmuteTooltipTimer = timer
}
} else {
strongSelf.displayUnmuteTooltipTimer?.invalidate()
}
}
})
self.leaveButton.addTarget(self, action: #selector(self.leavePressed), forControlEvents: .touchUpInside)
self.actionButton.addTarget(self, action: #selector(self.actionPressed), forControlEvents: .touchUpInside)
self.audioButton.addTarget(self, action: #selector(self.audioPressed), forControlEvents: .touchUpInside)
@ -2063,6 +2118,7 @@ public final class VoiceChatController: ViewController {
self.memberStatesDisposable?.dispose()
self.audioLevelsDisposable?.dispose()
self.myAudioLevelDisposable?.dispose()
self.isSpeakingDisposable?.dispose()
self.inviteDisposable.dispose()
self.memberEventsDisposable.dispose()
self.reconnectedAsEventsDisposable.dispose()
@ -2827,9 +2883,11 @@ public final class VoiceChatController: ViewController {
}
private var actionButtonPressTimer: SwiftSignalKit.Timer?
private var actionButtonPressedTimestamp: Double?
private func startActionButtonPressTimer() {
self.actionButtonPressTimer?.invalidate()
let pressTimer = SwiftSignalKit.Timer(timeout: 0.185, repeat: false, completion: { [weak self] in
self?.actionButtonPressedTimestamp = CACurrentMediaTime()
self?.actionButtonPressTimerFired()
self?.actionButtonPressTimer = nil
}, queue: Queue.mainQueue())
@ -2919,20 +2977,35 @@ public final class VoiceChatController: ViewController {
case .began:
self.actionButton.pressing = true
self.hapticFeedback.impact(.light)
self.actionButtonPressedTimestamp = nil
self.startActionButtonPressTimer()
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
}
case .ended, .cancelled:
self.pushingToTalk = false
self.actionButton.pressing = false
if self.actionButtonPressTimer != nil {
self.pushingToTalk = false
self.actionButton.pressing = false
self.stopActionButtonPressTimer()
self.call.toggleIsMuted()
} else {
self.hapticFeedback.impact(.light)
self.call.setIsMuted(action: .muted(isPushToTalkActive: false))
if self.pushingToTalk, let timestamp = self.actionButtonPressedTimestamp, CACurrentMediaTime() < timestamp + 0.5 {
self.pushingToTalk = false
self.temporaryPushingToTalk = true
self.call.setIsMuted(action: .unmuted)
Queue.mainQueue().after(0.1) {
self.temporaryPushingToTalk = false
self.actionButton.pressing = false
}
} else {
self.pushingToTalk = false
self.actionButton.pressing = false
self.call.setIsMuted(action: .muted(isPushToTalkActive: false))
}
}
if let callState = self.callState {
@ -3533,7 +3606,7 @@ public final class VoiceChatController: ViewController {
}
}
let videoButtonSize: CGSize
let audioButtonSize: CGSize
var buttonsTitleAlpha: CGFloat
var effectiveDisplayMode = self.displayMode
if let (layout, _) = self.validLayout, layout.size.width > layout.size.height, case .compact = layout.metrics.widthClass {
@ -3543,34 +3616,37 @@ public final class VoiceChatController: ViewController {
}
}
let hasCameraButton = self.cameraButton.isUserInteractionEnabled
let hasVideo = self.call.hasVideo
switch effectiveDisplayMode {
case .modal:
videoButtonSize = smallButtonSize
audioButtonSize = hasCameraButton ? smallButtonSize : sideButtonSize
buttonsTitleAlpha = 1.0
case .fullscreen:
videoButtonSize = sideButtonSize
audioButtonSize = sideButtonSize
buttonsTitleAlpha = 0.0
}
let hasVideo = self.call.hasVideo
self.cameraButton.update(size: hasVideo ? sideButtonSize : videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: hasVideo ? activeButtonAppearance : normalButtonAppearance, image: hasVideo ? .cameraOn : .cameraOff), text: self.presentationData.strings.VoiceChat_Video, transition: transition)
self.cameraButton.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: hasVideo ? activeButtonAppearance : normalButtonAppearance, image: hasVideo ? .cameraOn : .cameraOff), text: self.presentationData.strings.VoiceChat_Video, transition: transition)
self.switchCameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: normalButtonAppearance, image: .flipCamera), text: "", transition: transition)
self.switchCameraButton.update(size: audioButtonSize, content: CallControllerButtonItemNode.Content(appearance: normalButtonAppearance, image: .flipCamera), text: "", transition: transition)
transition.updateAlpha(node: self.switchCameraButton, alpha: hasVideo ? 1.0 : 0.0)
transition.updateTransformScale(node: self.switchCameraButton, scale: hasVideo ? 1.0 : 0.0)
transition.updateTransformScale(node: self.cameraButton, scale: hasCameraButton ? 1.0 : 0.0)
transition.updateAlpha(node: self.audioButton, alpha: hasVideo ? 0.0 : 1.0)
transition.updateTransformScale(node: self.audioButton, scale: hasVideo ? 0.0 : 1.0)
self.audioButton.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage, isEnabled: isSoundEnabled), text: soundTitle, transition: transition)
self.audioButton.update(size: audioButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage, isEnabled: isSoundEnabled), text: soundTitle, transition: transition)
self.audioButton.isUserInteractionEnabled = isSoundEnabled
self.leaveButton.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0xff3b30, 0.3)), image: .cancel), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
transition.updateAlpha(node: self.cameraButton.textNode, alpha: hasVideo ? buttonsTitleAlpha : 0.0)
transition.updateAlpha(node: self.cameraButton.textNode, alpha: hasCameraButton ? buttonsTitleAlpha : 0.0)
transition.updateAlpha(node: self.switchCameraButton.textNode, alpha: buttonsTitleAlpha)
transition.updateAlpha(node: self.audioButton.textNode, alpha: buttonsTitleAlpha)
transition.updateAlpha(node: self.audioButton.textNode, alpha: hasCameraButton ? 0.0 : buttonsTitleAlpha)
transition.updateAlpha(node: self.leaveButton.textNode, alpha: buttonsTitleAlpha)
}
@ -3796,7 +3872,7 @@ public final class VoiceChatController: ViewController {
}
if connected {
if let muteState = callState.muteState, !self.pushingToTalk {
if let muteState = callState.muteState, !self.pushingToTalk && !self.temporaryPushingToTalk {
if muteState.canUnmute {
actionButtonState = .active(state: .muted)
@ -3844,9 +3920,6 @@ public final class VoiceChatController: ViewController {
self.actionButton.update(size: centralButtonSize, buttonSize: CGSize(width: 112.0, height: 112.0), state: actionButtonState, title: actionButtonTitle, subtitle: actionButtonSubtitle, dark: self.isFullscreen, small: smallButtons, animated: true)
var hasCameraButton = self.callState?.isVideoEnabled ?? false
#if DEBUG
hasCameraButton = true
#endif
switch actionButtonState {
case let .active(state):
@ -3967,6 +4040,8 @@ public final class VoiceChatController: ViewController {
} : nil)
}
self.cameraButton.isUserInteractionEnabled = hasCameraButton
var buttonsTransition: ContainedViewLayoutTransition = .immediate
if !isFirstTime {
if case .animated(_, .spring) = transition {
@ -3977,22 +4052,21 @@ public final class VoiceChatController: ViewController {
}
self.updateButtons(transition: buttonsTransition)
self.cameraButton.isUserInteractionEnabled = hasCameraButton
if self.audioButton.supernode === self.bottomPanelNode {
transition.updateAlpha(node: self.cameraButton, alpha: hasCameraButton ? 1.0 : 0.0)
transition.updateFrameAsPositionAndBounds(node: self.switchCameraButton, frame: firstButtonFrame)
if !self.animatingButtonsSwap || transition.isAnimated {
if self.call.hasVideo {
transition.updateFrameAsPositionAndBounds(node: self.cameraButton, frame: secondButtonFrame, completion: { [weak self] _ in
if hasCameraButton {
transition.updateFrameAsPositionAndBounds(node: self.audioButton, frame: firstButtonFrame, completion: { [weak self] _ in
self?.animatingButtonsSwap = false
})
} else {
transition.updateFrameAsPositionAndBounds(node: self.cameraButton, frame: firstButtonFrame, completion: { [weak self] _ in
transition.updateFrameAsPositionAndBounds(node: self.audioButton, frame: secondButtonFrame, completion: { [weak self] _ in
self?.animatingButtonsSwap = false
})
}
transition.updateFrameAsPositionAndBounds(node: self.audioButton, frame: secondButtonFrame)
transition.updateFrameAsPositionAndBounds(node: self.cameraButton, frame: secondButtonFrame)
}
transition.updateFrameAsPositionAndBounds(node: self.leaveButton, frame: forthButtonFrame)
}
@ -4421,6 +4495,9 @@ public final class VoiceChatController: ViewController {
return
}
}
} else if self.effectiveSpeaker != nil, !fullscreenEntries.isEmpty {
self.updateMainVideo(waitForFullSize: true, entries: fullscreenEntries, force: true)
return
}
self.updateRequestedVideoChannels()
@ -4518,45 +4595,50 @@ public final class VoiceChatController: ViewController {
if !self.requestedVideoSources.contains(channel.endpointId) {
self.requestedVideoSources.insert(channel.endpointId)
self.call.makeIncomingVideoView(endpointId: channel.endpointId, requestClone: true, completion: { [weak self] videoView, backdropVideoView in
guard let strongSelf = self, let videoView = videoView else {
return
}
let videoNode = GroupVideoNode(videoView: videoView, backdropVideoView: backdropVideoView)
strongSelf.readyVideoDisposables.set((videoNode.ready
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue
).start(next: { [weak self, weak videoNode] _ in
if let strongSelf = self, let videoNode = videoNode {
Queue.mainQueue().after(0.1) {
strongSelf.readyVideoNodes.insert(channel.endpointId)
if videoNode.aspectRatio <= 0.77 {
strongSelf.wideVideoNodes.insert(channel.endpointId)
}
strongSelf.updateMembers()
Queue.mainQueue().async {
print("create main video \(channel.endpointId)")
print("create blur video \(channel.endpointId)")
guard let strongSelf = self, let videoView = videoView else {
return
}
let videoNode = GroupVideoNode(videoView: videoView, backdropVideoView: backdropVideoView)
strongSelf.readyVideoDisposables.set((videoNode.ready
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue
).start(next: { [weak self, weak videoNode] _ in
if let strongSelf = self, let videoNode = videoNode {
print("video ready \(channel.endpointId)")
Queue.mainQueue().after(0.1) {
strongSelf.readyVideoNodes.insert(channel.endpointId)
if videoNode.aspectRatio <= 0.77 {
strongSelf.wideVideoNodes.insert(channel.endpointId)
}
strongSelf.updateMembers()
if let interaction = strongSelf.itemInteraction {
loop: for i in 0 ..< strongSelf.currentFullscreenEntries.count {
let entry = strongSelf.currentFullscreenEntries[i]
switch entry {
case let .peer(peerEntry, _):
if peerEntry.effectiveVideoEndpointId == channel.endpointId {
let presentationData = strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme)
strongSelf.fullscreenListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.fullscreenItem(context: strongSelf.context, presentationData: presentationData, interaction: interaction), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
break loop
if let interaction = strongSelf.itemInteraction {
loop: for i in 0 ..< strongSelf.currentFullscreenEntries.count {
let entry = strongSelf.currentFullscreenEntries[i]
switch entry {
case let .peer(peerEntry, _):
if peerEntry.effectiveVideoEndpointId == channel.endpointId {
let presentationData = strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme)
strongSelf.fullscreenListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.fullscreenItem(context: strongSelf.context, presentationData: presentationData, interaction: interaction), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
break loop
}
default:
break
}
default:
break
}
}
}
}
}
}), forKey: channel.endpointId)
strongSelf.videoNodes[channel.endpointId] = videoNode
}), forKey: channel.endpointId)
strongSelf.videoNodes[channel.endpointId] = videoNode
if let _ = strongSelf.validLayout {
strongSelf.updateMembers()
if let _ = strongSelf.validLayout {
strongSelf.updateMembers()
}
}
})
}
@ -4739,7 +4821,7 @@ public final class VoiceChatController: ViewController {
}
}
case .fullscreen:
if fabs(translation) > 32.0 {
if abs(translation) > 32.0 {
if self.fullscreenListNode.layer.animationKeys()?.contains("opacity") == true {
self.fullscreenListNode.layer.removeAllAnimations()
}
@ -5262,6 +5344,13 @@ public final class VoiceChatController: ViewController {
}))
}
private func displayUnmuteTooltip() {
let location = self.actionButton.view.convert(self.actionButton.bounds, to: self.view).center
let point = CGRect(origin: CGPoint(x: location.x - 5.0, y: location.y - 5.0 - 68.0), size: CGSize(width: 10.0, height: 10.0))
self.controller?.present(TooltipScreen(text: self.presentationData.strings.VoiceChat_UnmuteSuggestion, style: .gradient(UIColor(rgb: 0x1d446c), UIColor(rgb: 0x193e63)), icon: nil, location: .point(point, .bottom), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}), in: .window(.root))
}
private func displayToggleVideoSourceTooltip(screencast: Bool) {
// guard let videoContainerNode = self.mainStageVideoContainerNode else {
@ -5344,7 +5433,7 @@ public final class VoiceChatController: ViewController {
self.mainStageContainerNode.isHidden = false
self.mainStageContainerNode.isUserInteractionEnabled = isFullscreen
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
let transition: ContainedViewLayoutTransition = .animated(duration: 0.55, curve: .spring)
if case .modal = previousDisplayMode, case .fullscreen = self.displayMode {
self.fullscreenListNode.isHidden = false
@ -5407,7 +5496,7 @@ public final class VoiceChatController: ViewController {
self.transitionMaskTopFillLayer.opacity = 1.0
}
self.transitionMaskBottomFillLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in
Queue.mainQueue().after(0.2) {
Queue.mainQueue().after(0.3) {
self?.transitionMaskTopFillLayer.opacity = 0.0
self?.transitionMaskBottomFillLayer.removeAllAnimations()
}
@ -5510,6 +5599,9 @@ public final class VoiceChatController: ViewController {
})
self.transitionMaskTopFillLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
if !transitionOffset.isZero {
self.transitionMaskBottomFillLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)

View file

@ -773,7 +773,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
nodeToAnimateIn = animationNode
}
var color = color
if color.rgb == 0x979797 {
if hasVideo || color.rgb == 0x979797 {
color = UIColor(rgb: 0xffffff)
}
animationNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: true, color: color), animated: true)
@ -884,8 +884,13 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
strongSelf.videoContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -9.0), duration: 0.2, additive: true)
strongSelf.audioLevelView?.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2)
}
transition.updateAlpha(node: videoNode, alpha: 0.0)
transition.updateAlpha(node: strongSelf.videoFadeNode, alpha: 0.0)
if videoNodeUpdated {
videoNode.alpha = 0.0
strongSelf.videoFadeNode.alpha = 0.0
} else {
transition.updateAlpha(node: videoNode, alpha: 0.0)
transition.updateAlpha(node: strongSelf.videoFadeNode, alpha: 0.0)
}
transition.updateAlpha(node: strongSelf.avatarNode, alpha: 1.0)
if let audioLevelView = strongSelf.audioLevelView {
transition.updateAlpha(layer: audioLevelView.layer, alpha: 1.0)

View file

@ -334,7 +334,11 @@ final class VoiceChatMainStageNode: ASDisplayNode {
self.appeared = false
let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
alphaTransition.updateAlpha(node: self.backgroundNode, alpha: 0.0)
if offset.isZero {
alphaTransition.updateAlpha(node: self.backgroundNode, alpha: 0.0)
} else {
self.backgroundNode.alpha = 0.0
}
alphaTransition.updateAlpha(node: self.topFadeNode, alpha: 0.0)
alphaTransition.updateAlpha(node: self.titleNode, alpha: 0.0)
alphaTransition.updateAlpha(node: self.microphoneNode, alpha: 0.0)
@ -430,7 +434,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
strongSelf.speakingContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
strongSelf.speakingContainerNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
let blobFrame = strongSelf.speakingAvatarNode.frame.insetBy(dx: -10.0, dy: -10.0)
let blobFrame = strongSelf.speakingAvatarNode.frame.insetBy(dx: -14.0, dy: -14.0)
strongSelf.speakingAudioLevelDisposable.set((getAudioLevel(peerId)
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else {
@ -613,9 +617,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
var delayTransition = false
if previousPeer?.0 == peer?.0 && self.appeared {
delayTransition = true
}
let appeared = self.appeared
}
if !delayTransition {
self.setAvatarHidden(true)
}
@ -660,6 +662,11 @@ final class VoiceChatMainStageNode: ASDisplayNode {
videoNode.alpha = 0.0
}
if waitForFullSize {
Queue.mainQueue().after(2.0) {
if let previousVideoNode = previousVideoNode {
previousVideoNode.removeFromSupernode()
}
}
strongSelf.videoReadyDisposable.set((videoNode.ready
|> filter { $0 }
|> take(1)
@ -823,7 +830,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
let speakingInset: CGFloat = 16.0
let speakingAvatarSize = CGSize(width: 30.0, height: 30.0)
let speakingTitleSize = self.speakingTitleNode.updateLayout(CGSize(width: 220.0, height: CGFloat.greatestFiniteMagnitude))
let speakingTitleSize = self.speakingTitleNode.updateLayout(CGSize(width: size.width - 100.0, height: CGFloat.greatestFiniteMagnitude))
let speakingContainerSize = CGSize(width: speakingTitleSize.width + speakingInset * 2.0 + speakingAvatarSize.width, height: 38.0)
self.speakingEffectView?.frame = CGRect(origin: CGPoint(), size: speakingContainerSize)
self.speakingAvatarNode.frame = CGRect(origin: CGPoint(x: 4.0, y: 4.0), size: speakingAvatarSize)

View file

@ -683,11 +683,11 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
let startContainerAvatarPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center
var animate = true
if containerNode.frame.width > containerNode.frame.height {
if startContainerAvatarPosition.y < -tileSize.height || startContainerAvatarPosition.y > containerNode.frame.height + tileSize.height {
if startContainerAvatarPosition.y < -tileSize.height * 2.0 || startContainerAvatarPosition.y > containerNode.frame.height + tileSize.height * 2.0 {
animate = false
}
} else {
if startContainerAvatarPosition.x < -tileSize.width || startContainerAvatarPosition.x > containerNode.frame.width + tileSize.width {
if startContainerAvatarPosition.x < -tileSize.width * 4.0 || startContainerAvatarPosition.x > containerNode.frame.width + tileSize.width * 4.0 {
animate = false
}
}

View file

@ -191,7 +191,9 @@ final class VoiceChatTileItemNode: ASDisplayNode {
gesture.cancel()
return
}
contextAction(strongSelf.contextSourceNode, gesture)
if item.videoReady {
contextAction(strongSelf.contextSourceNode, gesture)
}
}
self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in
guard let strongSelf = self, let _ = strongSelf.item else {
@ -275,6 +277,8 @@ final class VoiceChatTileItemNode: ASDisplayNode {
let previousItem = self.item
self.item = item
self.containerNode.isGestureEnabled = item.videoReady
if !item.videoReady {
let shimmerNode: VoiceChatTileShimmeringNode
if let current = self.shimmerNode {
@ -451,7 +455,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
if animate {
sourceNode.isHidden = true
Queue.mainQueue().after(0.6) {
Queue.mainQueue().after(0.7) {
sourceNode.isHidden = false
}
@ -642,7 +646,7 @@ class VoiceChatTileHighlightNode: ASDisplayNode {
}
}
private final class ShimmerEffectForegroundNode: ASDisplayNode {
final class ShimmerEffectForegroundNode: ASDisplayNode {
private var currentForegroundColor: UIColor?
private let imageNodeContainer: ASDisplayNode
private let imageNode: ASImageNode

View file

@ -156,6 +156,10 @@ public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int
presentationJsonDescription = data
}
}
if muteState?.canUnmute == false {
videoJsonDescription = nil
presentationJsonDescription = nil
}
parsedParticipants.append(GroupCallParticipantsContext.Participant(
peer: peer,
ssrc: ssrc,
@ -481,6 +485,10 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
presentationJsonDescription = data
}
}
if muteState?.canUnmute == false {
videoJsonDescription = nil
presentationJsonDescription = nil
}
parsedParticipants.append(GroupCallParticipantsContext.Participant(
peer: peer,
ssrc: ssrc,
@ -741,6 +749,10 @@ public func joinGroupCall(account: Account, peerId: PeerId, joinAs: PeerId?, cal
presentationJsonDescription = data
}
}
if muteState?.canUnmute == false {
videoJsonDescription = nil
presentationJsonDescription = nil
}
if !state.participants.contains(where: { $0.peer.id == peer.id }) {
state.participants.append(GroupCallParticipantsContext.Participant(
peer: peer,
@ -2150,7 +2162,10 @@ extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
presentationJsonDescription = data
}
}
if muteState?.canUnmute == false {
videoJsonDescription = nil
presentationJsonDescription = nil
}
self.init(
peerId: peerId,
ssrc: ssrc,
@ -2213,7 +2228,10 @@ extension GroupCallParticipantsContext.Update.StateUpdate {
presentationJsonDescription = data
}
}
if muteState?.canUnmute == false {
videoJsonDescription = nil
presentationJsonDescription = nil
}
participantUpdates.append(GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(
peerId: peerId,
ssrc: ssrc,

View file

@ -1299,22 +1299,28 @@ private:
completion(remoteRenderer, nil);
} else if ([VideoMetalView isSupported]) {
VideoMetalView *remoteRenderer = [[VideoMetalView alloc] initWithFrame:CGRectZero];
#if TARGET_OS_IPHONE
remoteRenderer.videoContentMode = UIViewContentModeScaleToFill;
#else
remoteRenderer.videoContentMode = UIViewContentModeScaleAspect;
#endif
VideoMetalView *cloneRenderer = nil;
if (requestClone) {
cloneRenderer = [[VideoMetalView alloc] initWithFrame:CGRectZero];
cloneRenderer.videoContentMode = UIViewContentModeScaleToFill;
}
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink = [remoteRenderer getSink];
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> cloneSink = [cloneRenderer getSink];
[queue dispatch:^{
__strong GroupCallThreadLocalContext *strongSelf = weakSelf;
if (strongSelf && strongSelf->_instance) {
strongSelf->_instance->addIncomingVideoOutput(endpointId.UTF8String, sink);
if (cloneSink) {
strongSelf->_instance->addIncomingVideoOutput(endpointId.UTF8String, cloneSink);
}
}
}];
completion(remoteRenderer, nil);
completion(remoteRenderer, cloneRenderer);
} else {
GLVideoView *remoteRenderer = [[GLVideoView alloc] initWithFrame:CGRectZero];
// [remoteRenderer setVideoContentMode:kCAGravityResizeAspectFill];