Improve audio playback rate controls

This commit is contained in:
Ilya Laktyushin 2023-02-10 07:48:41 +04:00
parent c53d7a1401
commit 88dd028371
14 changed files with 146 additions and 71 deletions

View File

@ -8910,3 +8910,11 @@ Sorry for the inconvenience.";
"Appearance.VoiceOver.Theme" = "%@ Theme";
"ChatList.EmptyChatListWithArchive" = "All of your chats are archived.";
"VoiceOver.Media.PlaybackRate05X" = "0.5X";
"VoiceOver.Media.PlaybackRate125X" = "1.25X";
"VoiceOver.Media.PlaybackRate15X" = "1.5X";
"VoiceOver.Media.PlaybackRate175X" = "1.75X";
"VoiceOver.Media.PlaybackRate2X" = "2X";
"Conversation.AudioRateTooltip15X" = "Audio will play at 1.5X speed.";

View File

@ -2713,7 +2713,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause))
}
}
mediaAccessoryPanel.setRate = { [weak self] rate in
mediaAccessoryPanel.setRate = { [weak self] rate, fromMenu in
guard let strongSelf = self else {
return
}
@ -2742,21 +2742,28 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
})
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let slowdown: Bool?
let text: String?
let rate: CGFloat?
if baseRate == .x1 {
slowdown = true
text = presentationData.strings.Conversation_AudioRateTooltipNormal
rate = 1.0
} else if baseRate == .x1_5 {
text = presentationData.strings.Conversation_AudioRateTooltip15X
rate = 1.5
} else if baseRate == .x2 {
slowdown = false
text = presentationData.strings.Conversation_AudioRateTooltipSpeedUp
rate = 2.0
} else {
slowdown = nil
text = nil
rate = nil
}
if let slowdown = slowdown {
if let rate, let text, !fromMenu {
controller.present(
UndoOverlayController(
presentationData: presentationData,
content: .audioRate(
slowdown: slowdown,
text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
rate: rate,
text: text
),
elevatedLayout: false,
animateInAsReplacement: hasTooltip,

View File

@ -171,7 +171,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
public var tapAction: (() -> Void)?
public var close: (() -> Void)?
public var setRate: ((AudioPlaybackRate) -> Void)?
public var setRate: ((AudioPlaybackRate, Bool) -> Void)?
public var togglePlayPause: (() -> Void)?
public var playPrevious: (() -> Void)?
public var playNext: (() -> Void)?
@ -184,21 +184,27 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
guard self.playbackBaseRate != oldValue, let playbackBaseRate = self.playbackBaseRate else {
return
}
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
switch playbackBaseRate {
case .x0_5:
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRate05X
case .x1:
self.rateButton.setContent(.image(optionsRateImage(rate: "1X", color: self.theme.rootController.navigationBar.controlColor)))
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateNormal
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
case .x1_25:
self.rateButton.setContent(.image(optionsRateImage(rate: "1.25X", color: self.theme.rootController.navigationBar.accentTextColor)))
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRate125X
case .x1_5:
self.rateButton.setContent(.image(optionsRateImage(rate: "1.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRate15X
case .x1_75:
self.rateButton.setContent(.image(optionsRateImage(rate: "1.75X", color: self.theme.rootController.navigationBar.accentTextColor)))
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRate175X
case .x2:
self.rateButton.setContent(.image(optionsRateImage(rate: "2X", color: self.theme.rootController.navigationBar.accentTextColor)))
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateFast
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRate2X
default:
break
}
@ -379,8 +385,12 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
case .x1:
self.rateButton.setContent(.image(optionsRateImage(rate: "1X", color: self.theme.rootController.navigationBar.controlColor)))
case .x1_25:
self.rateButton.setContent(.image(optionsRateImage(rate: "1.25X", color: self.theme.rootController.navigationBar.controlColor)))
case .x1_5:
self.rateButton.setContent(.image(optionsRateImage(rate: "1.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
case .x1_75:
self.rateButton.setContent(.image(optionsRateImage(rate: "1.75X", color: self.theme.rootController.navigationBar.controlColor)))
case .x2:
self.rateButton.setContent(.image(optionsRateImage(rate: "2X", color: self.theme.rootController.navigationBar.accentTextColor)))
default:
@ -493,6 +503,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 44.0 - rightInset, y: 0.0), size: CGSize(width: 44.0, height: minHeight)))
let rateButtonSize = CGSize(width: 30.0, height: minHeight)
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 33.0 - closeButtonSize.width - rateButtonSize.width - rightInset, y: -4.0), size: rateButtonSize))
transition.updateFrame(node: self.playPauseIconNode, frame: CGRect(origin: CGPoint(x: 6.0, y: 4.0 + UIScreenPixel), size: CGSize(width: 28.0, height: 28.0)))
transition.updateFrame(node: self.actionButton, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 40.0, height: 37.0)))
transition.updateFrame(node: self.scrubbingNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 37.0 - 2.0), size: CGSize(width: size.width, height: 2.0)))
@ -520,14 +531,16 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
} else {
nextRate = .x2
}
self.setRate?(nextRate)
self.setRate?(nextRate, false)
}
private func speedList(strings: PresentationStrings) -> [(String, String, AudioPlaybackRate)] {
let speedList: [(String, String, AudioPlaybackRate)] = [
("0.5x", "0.5x", .x0_5),
(strings.PlaybackSpeed_Normal, "1x", .x1),
("1.25x", "1.25x", .x1_25),
("1.5x", "1.5x", .x1_5),
("1.75x", "1.75x", .x1_75),
("2x", "2x", .x2)
]
return speedList
@ -547,7 +560,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
}, action: { [weak self] _, f in
f(.default)
self?.setRate?(rate)
self?.setRate?(rate, true)
})))
}
@ -626,7 +639,7 @@ private final class PlayPauseIconNode: ManagedAnimationNode {
}
private func optionsRateImage(rate: String, color: UIColor = .white) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 16.0), rotatedContext: { size, context in
return generateImage(CGSize(width: 36.0, height: 16.0), rotatedContext: { size, context in
UIGraphicsPushContext(context)
context.clear(CGRect(origin: CGPoint(), size: size))
@ -640,7 +653,11 @@ private func optionsRateImage(rate: String, color: UIColor = .white) -> UIImage?
var offset = CGPoint(x: 1.0, y: 0.0)
var width: CGFloat
if rate.count >= 3 {
if rate.count >= 5 {
string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
offset.x += -0.5
width = 34.0
} else if rate.count >= 3 {
if rate == "0.5X" {
string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
offset.x += -0.5

View File

@ -11,7 +11,7 @@ public final class MediaNavigationAccessoryPanel: ASDisplayNode {
public let containerNode: MediaNavigationAccessoryContainerNode
public var close: (() -> Void)?
public var setRate: ((AudioPlaybackRate) -> Void)?
public var setRate: ((AudioPlaybackRate, Bool) -> Void)?
public var togglePlayPause: (() -> Void)?
public var tapAction: (() -> Void)?
public var playPrevious: (() -> Void)?
@ -32,8 +32,8 @@ public final class MediaNavigationAccessoryPanel: ASDisplayNode {
close()
}
}
self.containerNode.headerNode.setRate = { [weak self] rate in
self?.setRate?(rate)
self.containerNode.headerNode.setRate = { [weak self] rate, fromMenu in
self?.setRate?(rate, fromMenu)
}
self.containerNode.headerNode.togglePlayPause = { [weak self] in
if let strongSelf = self, let togglePlayPause = strongSelf.togglePlayPause {

View File

@ -669,7 +669,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause))
}
}
mediaAccessoryPanel.setRate = { [weak self] rate in
mediaAccessoryPanel.setRate = { [weak self] rate, fromMenu in
guard let strongSelf = self else {
return
}
@ -687,41 +687,48 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
}
strongSelf.context.sharedContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: type)
// var hasTooltip = false
// strongSelf.forEachController({ controller in
// if let controller = controller as? UndoOverlayController {
// hasTooltip = true
// controller.dismissWithCommitAction()
// }
// return true
// })
//
// let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
// let slowdown: Bool?
// if baseRate == .x1 {
// slowdown = true
// } else if baseRate == .x2 {
// slowdown = false
// } else {
// slowdown = nil
// }
// if let slowdown = slowdown {
// strongSelf.present(
// UndoOverlayController(
// presentationData: presentationData,
// content: .audioRate(
// slowdown: slowdown,
// text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
// ),
// elevatedLayout: false,
// animateInAsReplacement: hasTooltip,
// action: { action in
// return true
// }
// ),
// in: .current
// )
// }
var hasTooltip = false
strongSelf.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
hasTooltip = true
controller.dismissWithCommitAction()
}
return true
})
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let text: String?
let rate: CGFloat?
if baseRate == .x1 {
text = presentationData.strings.Conversation_AudioRateTooltipNormal
rate = 1.0
} else if baseRate == .x1_5 {
text = presentationData.strings.Conversation_AudioRateTooltip15X
rate = 1.5
} else if baseRate == .x2 {
text = presentationData.strings.Conversation_AudioRateTooltipSpeedUp
rate = 2.0
} else {
text = nil
rate = nil
}
if let rate, let text, !fromMenu {
strongSelf.present(
UndoOverlayController(
presentationData: presentationData,
content: .audioRate(
rate: rate,
text: text
),
elevatedLayout: false,
animateInAsReplacement: hasTooltip,
action: { action in
return true
}
),
in: .current
)
}
})
}
mediaAccessoryPanel.togglePlayPause = { [weak self] in

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -36,7 +36,7 @@ private func generateCollapseIcon(theme: PresentationTheme) -> UIImage? {
}
private func optionsRateImage(rate: String, color: UIColor = .white) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 16.0), rotatedContext: { size, context in
return generateImage(CGSize(width: 36.0, height: 16.0), rotatedContext: { size, context in
UIGraphicsPushContext(context)
context.clear(CGRect(origin: CGPoint(), size: size))
@ -50,7 +50,11 @@ private func optionsRateImage(rate: String, color: UIColor = .white) -> UIImage?
var offset = CGPoint(x: 1.0, y: 0.0)
var width: CGFloat
if rate.count >= 3 {
if rate.count >= 5 {
string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
offset.x += -0.5
width = 33.0
} else if rate.count >= 3 {
if rate == "0.5X" {
string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
offset.x += -0.5
@ -418,8 +422,14 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
let baseRate: AudioPlaybackRate
if value.status.baseRate.isEqual(to: 2.0) {
baseRate = .x2
} else if value.status.baseRate.isEqual(to: 1.75) {
baseRate = .x1_75
} else if value.status.baseRate.isEqual(to: 1.5) {
baseRate = .x1_25
} else if value.status.baseRate.isEqual(to: 1.25) {
baseRate = .x1_5
} else if value.status.baseRate.isEqual(to: 0.5) {
baseRate = .x0_5
} else {
baseRate = .x1
}
@ -784,8 +794,14 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
switch baseRate {
case .x2:
self.rateButton.setImage(optionsRateImage(rate: "2X", color: self.presentationData.theme.list.itemAccentColor), for: [])
case .x1_75:
self.rateButton.setImage(optionsRateImage(rate: "1.75X", color: self.presentationData.theme.list.itemAccentColor), for: [])
case .x1_5:
self.rateButton.setImage(optionsRateImage(rate: "1.5X", color: self.presentationData.theme.list.itemAccentColor), for: [])
case .x1_25:
self.rateButton.setImage(optionsRateImage(rate: "1.25X", color: self.presentationData.theme.list.itemAccentColor), for: [])
case .x0_5:
self.rateButton.setImage(optionsRateImage(rate: "0.5X", color: self.presentationData.theme.list.itemAccentColor), for: [])
default:
self.rateButton.setImage(optionsRateImage(rate: "1X", color: self.presentationData.theme.list.itemSecondaryTextColor), for: [])
}

View File

@ -262,7 +262,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause))
}
}
mediaAccessoryPanel.setRate = { [weak self] rate in
mediaAccessoryPanel.setRate = { [weak self] rate, fromMenu in
guard let strongSelf = self else {
return
}
@ -291,21 +291,28 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
})
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let slowdown: Bool?
let text: String?
let rate: CGFloat?
if baseRate == .x1 {
slowdown = true
text = presentationData.strings.Conversation_AudioRateTooltipNormal
rate = 1.0
} else if baseRate == .x1_5 {
text = presentationData.strings.Conversation_AudioRateTooltip15X
rate = 1.5
} else if baseRate == .x2 {
slowdown = false
text = presentationData.strings.Conversation_AudioRateTooltipSpeedUp
rate = 2.0
} else {
slowdown = nil
text = nil
rate = nil
}
if let slowdown = slowdown {
if let rate, let text, !fromMenu {
controller.present(
UndoOverlayController(
presentationData: presentationData,
content: .audioRate(
slowdown: slowdown,
text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp
rate: rate,
text: text
),
elevatedLayout: false,
animateInAsReplacement: hasTooltip,

View File

@ -18,7 +18,9 @@ public enum MusicPlaybackSettingsLooping: Int32 {
public enum AudioPlaybackRate: Int32 {
case x0_5 = 500
case x1 = 1000
case x1_25 = 1250
case x1_5 = 1500
case x1_75 = 1750
case x2 = 2000
case x4 = 4000
case x8 = 8000

View File

@ -26,7 +26,7 @@ public enum UndoOverlayContent {
case linkCopied(text: String)
case banned(text: String)
case importedMessage(text: String)
case audioRate(slowdown: Bool, text: String)
case audioRate(rate: CGFloat, text: String)
case forward(savedMessages: Bool, text: String)
case autoDelete(isOn: Bool, title: String?, text: String)
case gigagroupConversion(text: String)

View File

@ -540,11 +540,21 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
displayUndo = false
}
self.originalRemainingSeconds = duration
case let .audioRate(slowdown, text):
case let .audioRate(rate, text):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: slowdown ? "anim_voicespeedstop" : "anim_voicespeed", colors: [:], scale: 0.066)
let animationName: String
if rate == 1.5 {
animationName = "anim_voice1_5x"
} else if rate == 2.0 {
animationName = "anim_voice2x"
} else {
animationName = "anim_voice1x"
}
self.animationNode = AnimationNode(animation: animationName, colors: [:], scale: 0.066)
self.animatedStickerNode = nil
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)