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

View File

@ -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?";

View File

@ -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
}

View File

@ -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

View File

@ -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 }

View File

@ -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)

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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)