mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-06-20 18:24:43 +00:00
Voice Chat Fixes
This commit is contained in:
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
Binary file not shown.
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user