Voice Chat Fixes

This commit is contained in:
Ilya Laktyushin
2021-03-12 20:44:43 +04:00
parent 0ec1dd0484
commit 34f4daf07d
19 changed files with 1320 additions and 1243 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6261,3 +6261,5 @@ Sorry for the inconvenience.";
"Invitation.JoinVoiceChat" = "Join Voice Chat";
"VoiceChat.CancelSpeakRequest" = "Cancel Request to Speak";
"VoiceChat.RemovePeerConfirmationChannel" = "Are you sure you want to remove %@ from the channel?";
@@ -16,6 +16,7 @@ public enum DeleteChatPeerAction {
case clearCache
case clearCacheSuggestion
case removeFromGroup
case removeFromChannel
}
private let avatarFont = avatarPlaceholderFont(size: 26.0)
@@ -142,6 +143,8 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
}
case .removeFromGroup:
text = strings.VoiceChat_RemovePeerConfirmation(peer.displayTitle(strings: strings, displayOrder: nameOrder))
case .removeFromChannel:
text = strings.VoiceChat_RemovePeerConfirmationChannel(peer.displayTitle(strings: strings, displayOrder: nameOrder))
default:
break
}
@@ -137,6 +137,8 @@ open class ManagedAnimationNode: ASDisplayNode {
public var trackStack: [ManagedAnimationItem] = []
public var didTryAdvancingState = false
public var isPlaying = false
public init(size: CGSize) {
self.intrinsicSize = size
@@ -549,6 +549,7 @@ public final class VoiceChatController: ViewController {
let peer = peerEntry.peer
var text: VoiceChatParticipantItem.ParticipantText
var expandedText: VoiceChatParticipantItem.ParticipantText?
let icon: VoiceChatParticipantItem.Icon
switch peerEntry.state {
case .listening:
@@ -586,10 +587,14 @@ public final class VoiceChatController: ViewController {
text = .text(presentationData.strings.VoiceChat_StatusWantsToSpeak, .accent)
icon = .wantsToSpeak
}
if let about = peerEntry.about, !about.isEmpty {
expandedText = .text(about, .generic)
}
let revealOptions: [VoiceChatParticipantItem.RevealOption] = []
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, ssrc: peerEntry.ssrc, presence: peerEntry.presence, text: text, icon: icon, enabled: true, selectable: !peerEntry.isMyPeer || peerEntry.canManageCall || peerEntry.raisedHand, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, getVideo: {
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, ssrc: peerEntry.ssrc, presence: peerEntry.presence, text: text, expandedText: expandedText, icon: icon, enabled: true, selectable: !peerEntry.isMyPeer || peerEntry.canManageCall || peerEntry.raisedHand, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, getVideo: {
if let ssrc = peerEntry.ssrc {
return interaction.getPeerVideo(ssrc)
} else {
@@ -2054,7 +2059,7 @@ public final class VoiceChatController: ViewController {
return formatSendTitle(self.presentationData.strings.VoiceChat_InviteLink_InviteListeners(Int32(count)))
})]
}
let shareController = ShareController(context: self.context, subject: .url(inviteLinks.listenerLink), segmentedValues: segmentedValues, forcedTheme: self.darkTheme, forcedActionTitle: self.presentationData.strings.VoiceChat_InviteLink_CopyListenerLink)
let shareController = ShareController(context: self.context, subject: .url(inviteLinks.listenerLink), segmentedValues: segmentedValues, forcedTheme: self.darkTheme, forcedActionTitle: self.presentationData.strings.VoiceChat_CopyInviteLink)
shareController.actionCompleted = { [weak self] in
if let strongSelf = self {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
@@ -108,6 +108,7 @@ public final class VoiceChatJoinScreen: ViewController {
let activeCall = CachedChannelData.ActiveCall(id: call.info.id, accessHash: call.info.accessHash, title: call.info.title)
if availablePeers.count > 0 && defaultJoinAsPeerId == nil {
strongSelf.dismiss()
strongSelf.join(activeCall)
} else {
strongSelf.controllerNode.setPeer(call: activeCall, peer: peer, title: call.info.title, memberCount: call.info.participantCount)
@@ -66,6 +66,7 @@ final class VoiceChatParticipantItem: ListViewItem {
let ssrc: UInt32?
let presence: PeerPresence?
let text: ParticipantText
let expandedText: ParticipantText?
let icon: Icon
let enabled: Bool
public let selectable: Bool
@@ -77,7 +78,7 @@ final class VoiceChatParticipantItem: ListViewItem {
let action: ((ASDisplayNode) -> Void)?
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, ssrc: UInt32?, presence: PeerPresence?, text: ParticipantText, icon: Icon, enabled: Bool, selectable: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, getVideo: @escaping () -> GroupVideoNode?, revealOptions: [RevealOption], revealed: Bool?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, action: ((ASDisplayNode) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, ssrc: UInt32?, presence: PeerPresence?, text: ParticipantText, expandedText: ParticipantText?, icon: Icon, enabled: Bool, selectable: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, getVideo: @escaping () -> GroupVideoNode?, revealOptions: [RevealOption], revealed: Bool?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, action: ((ASDisplayNode) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
self.presentationData = presentationData
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
@@ -86,6 +87,7 @@ final class VoiceChatParticipantItem: ListViewItem {
self.ssrc = ssrc
self.presence = presence
self.text = text
self.expandedText = expandedText
self.icon = icon
self.enabled = enabled
self.selectable = selectable
@@ -160,6 +162,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
fileprivate let avatarNode: AvatarNode
private let titleNode: TextNode
private let statusNode: TextNode
private let expandedStatusNode: TextNode
private var credibilityIconNode: ASImageNode?
private let actionContainerNode: ASDisplayNode
@@ -215,6 +218,12 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
self.statusNode.contentMode = .left
self.statusNode.contentsScale = UIScreen.main.scale
self.expandedStatusNode = TextNode()
self.expandedStatusNode.isUserInteractionEnabled = false
self.expandedStatusNode.contentMode = .left
self.expandedStatusNode.contentsScale = UIScreen.main.scale
self.expandedStatusNode.alpha = 0.0
self.actionContainerNode = ASDisplayNode()
self.actionButtonNode = HighlightableButtonNode()
@@ -234,6 +243,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
self.offsetContainerNode.addSubnode(self.avatarNode)
self.offsetContainerNode.addSubnode(self.titleNode)
self.offsetContainerNode.addSubnode(self.statusNode)
self.offsetContainerNode.addSubnode(self.expandedStatusNode)
self.offsetContainerNode.addSubnode(self.actionContainerNode)
self.actionContainerNode.addSubnode(self.actionButtonNode)
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
@@ -278,6 +288,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: strongSelf.extractedBackgroundImageNode, frame: rect)
}
transition.updateAlpha(node: strongSelf.statusNode, alpha: isExtracted ? 0.0 : 1.0)
transition.updateAlpha(node: strongSelf.expandedStatusNode, alpha: isExtracted ? 1.0 : 0.0)
transition.updateAlpha(node: strongSelf.actionContainerNode, alpha: isExtracted ? 0.0 : 1.0)
transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? 12.0 : 0.0, y: 0.0))
@@ -305,6 +318,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
func asyncLayout() -> (_ item: VoiceChatParticipantItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
let makeExpandedStatusLayout = TextNode.asyncLayout(self.expandedStatusNode)
var currentDisabledOverlayNode = self.disabledOverlayNode
let currentItem = self.layoutParams?.0
@@ -415,6 +429,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - rightInset - 30.0 - titleIconsWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (expandedStatusLayout, expandedStatusApply) = makeExpandedStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 4, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let insets = UIEdgeInsets()
@@ -480,7 +495,11 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
strongSelf.wavesColor = wavesColor
let nonExtractedRect = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width - 16.0, height: layout.contentSize.height))
let extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: 16.0 + params.leftInset, dy: 0.0)
var extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: 16.0 + params.leftInset, dy: 0.0)
let extractedHeight = extractedRect.height + expandedStatusLayout.size.height - statusLayout.size.height
extractedRect.size.height = extractedHeight
strongSelf.extractedRect = extractedRect
strongSelf.nonExtractedRect = nonExtractedRect
@@ -554,6 +573,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
let _ = titleApply()
let _ = statusApply()
let _ = expandedStatusApply()
if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 0)
@@ -570,6 +590,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset + verticalOffset), size: titleLayout.size))
transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: statusLayout.size))
transition.updateFrame(node: strongSelf.expandedStatusNode, frame: CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: expandedStatusLayout.size))
if let currentCredibilityIconImage = currentCredibilityIconImage {
let iconNode: ASImageNode
@@ -74,9 +74,9 @@ class VoiceChatRecordingIconNode: ASDisplayNode {
private func setupAnimation() {
let animation = CAKeyframeAnimation(keyPath: "opacity")
animation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.0 as NSNumber]
animation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.55 as NSNumber]
animation.keyTimes = [0.0 as NSNumber, 0.4546 as NSNumber, 0.9091 as NSNumber, 1 as NSNumber]
animation.duration = 0.5
animation.duration = 0.7
animation.autoreverses = true
animation.repeatCount = Float.infinity
self.dotNode.layer.add(animation, forKey: "recording")
@@ -431,7 +431,7 @@ func voiceChatTitleEditController(sharedContext: SharedAccountContext, account:
let previousValue = value ?? ""
let newValue = contentNode.value
apply(previousValue != newValue ? newValue : nil)
apply(previousValue != newValue || value == nil ? newValue : nil)
}
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
File diff suppressed because it is too large Load Diff
@@ -413,13 +413,25 @@ public final class MediaManagerImpl: NSObject, MediaManager {
}
self.mediaPlaybackStateDisposable.set(throttledSignal.start(next: { accountStateAndType in
if let (account, stateOrLoading, type) = accountStateAndType, type == .music, case let .state(state) = stateOrLoading, state.status.duration >= 60.0 * 20.0, case .playing = state.status.status {
if let item = state.item as? MessageMediaPlaylistItem {
var storedState: MediaPlaybackStoredState?
if state.status.timestamp > 5.0 && state.status.timestamp < state.status.duration - 5.0 {
storedState = MediaPlaybackStoredState(timestamp: state.status.timestamp, playbackRate: state.status.baseRate > 1.0 ? .x2 : .x1)
let minimumStoreDuration: Double?
if let (account, stateOrLoading, type) = accountStateAndType {
switch type {
case .music:
minimumStoreDuration = 15.0 * 60.0
case .voice:
minimumStoreDuration = 5.0 * 60.0
case .file:
minimumStoreDuration = nil
}
if let minimumStoreDuration = minimumStoreDuration, case let .state(state) = stateOrLoading, state.status.duration >= minimumStoreDuration, case .playing = state.status.status {
if let item = state.item as? MessageMediaPlaylistItem {
var storedState: MediaPlaybackStoredState?
if state.status.timestamp > 5.0 && state.status.timestamp < state.status.duration - 5.0 {
storedState = MediaPlaybackStoredState(timestamp: state.status.timestamp, playbackRate: state.status.baseRate > 1.0 ? .x2 : .x1)
}
let _ = updateMediaPlaybackStoredStateInteractively(postbox: account.postbox, messageId: item.message.id, state: storedState).start()
}
let _ = updateMediaPlaybackStoredStateInteractively(postbox: account.postbox, messageId: item.message.id, state: storedState).start()
}
}
}))
@@ -492,7 +504,7 @@ public final class MediaManagerImpl: NSObject, MediaManager {
case .voice:
strongSelf.musicMediaPlayer?.control(.playback(.pause))
strongSelf.voiceMediaPlayer?.stop()
if let (account, playlist, settings, _) = inputData {
if let (account, playlist, settings, storedState) = inputData {
let voiceMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, account: account, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: .reversed, initialLooping: .none, initialPlaybackRate: settings.voicePlaybackRate, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: true, type: type)
strongSelf.voiceMediaPlayer = voiceMediaPlayer
voiceMediaPlayer.playedToEnd = { [weak voiceMediaPlayer] in
@@ -507,6 +519,11 @@ public final class MediaManagerImpl: NSObject, MediaManager {
strongSelf.voiceMediaPlayer = nil
}
}
var control = control
if let timestamp = storedState?.timestamp {
control = .seek(timestamp)
}
voiceMediaPlayer.control(control)
} else {
strongSelf.voiceMediaPlayer = nil
@@ -111,7 +111,30 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
if self.theme != presentationData.theme || self.icon != icon || self.isActive != isActive {
self.theme = presentationData.theme
self.icon = icon
let isActiveUpdated = self.isActive != isActive
self.isActive = isActive
if isActiveUpdated {
if let snapshotView = self.backgroundNode.view.snapshotContentTree() {
snapshotView.frame = self.backgroundNode.view.frame
self.view.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
}
if let snapshotView = self.textNode.view.snapshotContentTree() {
snapshotView.frame = self.textNode.view.frame
self.view.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
}
}
self.backgroundNode.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(isActive ? presentationData.theme.list.itemAccentColor.cgColor : presentationData.theme.list.itemDisabledTextColor.cgColor)