Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve audio messages performance #5792

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 32 additions & 22 deletions Signal/ConversationView/CellViews/AudioWaveformProgressView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ class AudioWaveformProgressView: UIView {

var thumbColor: UIColor = Theme.primaryTextColor {
didSet {
thumbView.backgroundColor = thumbColor
thumbLayer.backgroundColor = thumbColor.cgColor
}
}

var value: CGFloat = 0 {
didSet {
guard value != oldValue else { return }
redrawSamples()
updateUIProgress()
}
}

Expand Down Expand Up @@ -65,23 +65,27 @@ class AudioWaveformProgressView: UIView {
}
}

private let thumbView = UIView()
private let thumbLayer = CALayer()
private let playedShapeLayer = CAShapeLayer()
private let unplayedShapeLayer = CAShapeLayer()
private let playedShapeMask = CALayer()
private let loadingAnimation: AnimationView

init(mediaCache: CVMediaCache) {
self.loadingAnimation = mediaCache.buildLottieAnimationView(name: "waveformLoading")

super.init(frame: .zero)

unplayedShapeLayer.fillColor = unplayedColor.cgColor
layer.addSublayer(unplayedShapeLayer)

playedShapeLayer.fillColor = playedColor.cgColor
layer.addSublayer(playedShapeLayer)

unplayedShapeLayer.fillColor = unplayedColor.cgColor
layer.addSublayer(unplayedShapeLayer)
playedShapeMask.backgroundColor = UIColor.black.cgColor
playedShapeLayer.mask = playedShapeMask

addSubview(thumbView)
layer.addSublayer(thumbLayer)

loadingAnimation.contentMode = .scaleAspectFit
loadingAnimation.loopMode = .loop
Expand All @@ -103,7 +107,7 @@ class AudioWaveformProgressView: UIView {
func resetContents(showLoadingAnimation: Bool) {
playedShapeLayer.path = nil
unplayedShapeLayer.path = nil
thumbView.isHidden = true
thumbLayer.isHidden = true
loadingAnimation.frame = bounds

if showLoadingAnimation {
Expand All @@ -119,7 +123,7 @@ class AudioWaveformProgressView: UIView {

loadingAnimation.stop()
loadingAnimation.isHidden = true
thumbView.isHidden = false
thumbLayer.isHidden = false

guard width > 0 else {
return
Expand All @@ -129,9 +133,6 @@ class AudioWaveformProgressView: UIView {
let minSampleSpacing: CGFloat = 2
let minSampleHeight: CGFloat = 2

let playedBezierPath = UIBezierPath()
let unplayedBezierPath = UIBezierPath()

// Calculate the number of lines we want to render based on the view width.
let targetSamplesCount = Int((width + minSampleSpacing) / (sampleWidth + minSampleSpacing))

Expand All @@ -156,24 +157,23 @@ class AudioWaveformProgressView: UIView {

playedShapeLayer.frame = bounds
unplayedShapeLayer.frame = bounds
var playedShapeBounds = bounds
playedShapeBounds.width = 0
playedShapeMask.frame = playedShapeBounds

let progress = self.value
let thumbXPos = width * progress

thumbView.frame.size = CGSize(width: sampleWidth, height: height)
thumbView.layer.cornerRadius = sampleWidth / 2
thumbView.frame.origin.x = thumbXPos
let path = UIBezierPath()

defer {
playedShapeLayer.path = playedBezierPath.cgPath
unplayedShapeLayer.path = unplayedBezierPath.cgPath
unplayedShapeLayer.path = path.cgPath
playedShapeLayer.path = path.cgPath
}

let playedLines = Int(CGFloat(amplitudes.count) * progress)
thumbLayer.frame.size = CGSize(width: sampleWidth, height: height)
thumbLayer.cornerRadius = sampleWidth / 2

for (x, sample) in amplitudes.enumerated() {
let path: UIBezierPath = ((x > playedLines) || (progress == 0)) ? unplayedBezierPath : playedBezierPath
// reset all changes in this file and check how playedShapeLayer draw itself

for (x, sample) in amplitudes.enumerated() {
// The sample represents the magnitude of sound at this point
// from 0 (silence) to 1 (loudest possible value). Calculate the
// height of the sample view so that the loudest value is the
Expand All @@ -194,6 +194,16 @@ class AudioWaveformProgressView: UIView {

path.append(UIBezierPath(roundedRect: sampleFrame, cornerRadius: sampleWidth / 2))
}

updateUIProgress()
}

private func updateUIProgress() {
let progress = self.value
let thumbXPos = width * progress

thumbLayer.frame.x = thumbXPos
playedShapeMask.frame.width = thumbLayer.frame.maxX
}
}

Expand Down
28 changes: 13 additions & 15 deletions SignalUI/AV/AudioPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class AudioPlayer: NSObject {

private var audioPlayer: AVAudioPlayer?

private var audioPlayerPoller: Timer?
private var displayLink: CADisplayLink?

private let audioActivity: AudioActivity

Expand Down Expand Up @@ -93,16 +93,14 @@ public class AudioPlayer: NSObject {

audioPlayer?.play()

audioPlayerPoller?.invalidate()
let audioPlayerPoller = Timer.weakTimer(
withTimeInterval: 0.05,
target: self,
selector: #selector(audioPlayerUpdated(timer:)),
userInfo: nil,
repeats: true
)
RunLoop.main.add(audioPlayerPoller, forMode: .common)
self.audioPlayerPoller = audioPlayerPoller
displayLink = CADisplayLink(target: self, selector: #selector(audioPlayerUpdated))
if #available(iOSApplicationExtension 15.0, *) {
displayLink?.preferredFrameRateRange = .init(minimum: 8, maximum: 8)
} else {
displayLink?.preferredFramesPerSecond = 8
}

displayLink?.add(to: .main, forMode: .common)

// Prevent device from sleeping while playing audio.
DeviceSleepManager.shared.addBlock(blockObject: self)
Expand All @@ -120,7 +118,7 @@ public class AudioPlayer: NSObject {

audioPlayer.pause()

audioPlayerPoller?.invalidate()
displayLink?.invalidate()

delegate?.setAudioProgress(audioPlayer.currentTime, duration: audioPlayer.duration, playbackRate: playbackRate)

Expand Down Expand Up @@ -208,7 +206,7 @@ public class AudioPlayer: NSObject {
delegate?.audioPlaybackState = .stopped

audioPlayer?.pause()
audioPlayerPoller?.invalidate()
displayLink?.invalidate()

delegate?.setAudioProgress(0, duration: 0, playbackRate: playbackRate)

Expand Down Expand Up @@ -327,10 +325,10 @@ public class AudioPlayer: NSObject {
// MARK: Events

@objc
private func audioPlayerUpdated(timer: Timer) {
private func audioPlayerUpdated() {
AssertIsOnMainThread()

owsAssertDebug(audioPlayerPoller != nil)
owsAssertDebug(displayLink != nil)
guard let audioPlayer else {
owsFailDebug("audioPlayer == nil")
return
Expand Down