#4545 - Switch back to using multiple audio player instances, allow pausing when starting a new player.

This commit is contained in:
Stefan Ceriu
2021-07-12 15:30:48 +03:00
parent 4224609839
commit 6f9cfaa0b5
3 changed files with 87 additions and 72 deletions
@@ -44,6 +44,9 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
private let _voiceMessageToolbarView: VoiceMessageToolbarView
private var displayLink: CADisplayLink!
private var audioRecorder: VoiceMessageAudioRecorder?
private var audioPlayer: VoiceMessageAudioPlayer?
private var waveformAnalyser: WaveformAnalyzer?
private var audioSamples: [Float] = []
@@ -53,7 +56,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
@objc public weak var delegate: VoiceMessageControllerDelegate?
@objc public var isRecordingAudio: Bool {
return mediaServiceProvider.audioRecorder.isRecording || isInLockedMode
return audioRecorder?.isRecording ?? false || isInLockedMode
}
@objc public var voiceMessageToolbarView: UIView {
@@ -101,8 +104,9 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(ProcessInfo().globallyUniqueString).appendingPathExtension("m4a")
mediaServiceProvider.audioRecorder.registerDelegate(self)
mediaServiceProvider.audioRecorder.recordWithOuputURL(temporaryFileURL)
audioRecorder = mediaServiceProvider.audioRecorder()
audioRecorder?.registerDelegate(self)
audioRecorder?.recordWithOuputURL(temporaryFileURL)
}
func voiceMessageToolbarViewDidRequestRecordingFinish(_ toolbarView: VoiceMessageToolbarView) {
@@ -111,8 +115,8 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
func voiceMessageToolbarViewDidRequestRecordingCancel(_ toolbarView: VoiceMessageToolbarView) {
isInLockedMode = false
mediaServiceProvider.audioRecorder.stopRecording()
deleteRecordingAtURL(mediaServiceProvider.audioRecorder.url)
audioRecorder?.stopRecording()
deleteRecordingAtURL(audioRecorder?.url)
UINotificationFeedbackGenerator().notificationOccurred(.error)
updateUI()
}
@@ -123,21 +127,21 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
}
func voiceMessageToolbarViewDidRequestPlaybackToggle(_ toolbarView: VoiceMessageToolbarView) {
if mediaServiceProvider.audioPlayer.isPlaying {
mediaServiceProvider.audioPlayer.pause()
if audioPlayer?.isPlaying ?? false {
audioPlayer?.pause()
} else {
mediaServiceProvider.audioPlayer.play()
audioPlayer?.play()
}
}
func voiceMessageToolbarViewDidRequestSend(_ toolbarView: VoiceMessageToolbarView) {
guard let url = mediaServiceProvider.audioRecorder.url else {
guard let url = audioRecorder?.url else {
MXLog.error("Invalid audio recording URL")
return
}
mediaServiceProvider.audioPlayer.stop()
mediaServiceProvider.audioRecorder.stopRecording()
audioPlayer?.stop()
audioRecorder?.stopRecording()
sendRecordingAtURL(url)
@@ -191,23 +195,25 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
// MARK: - Private
private func finishRecording() {
let recordDuration = mediaServiceProvider.audioRecorder.currentTime
mediaServiceProvider.audioRecorder.stopRecording()
let recordDuration = audioRecorder?.currentTime
audioRecorder?.stopRecording()
guard let url = mediaServiceProvider.audioRecorder.url else {
guard let url = audioRecorder?.url else {
MXLog.error("Invalid audio recording URL")
return
}
guard isInLockedMode else {
if recordDuration >= Constants.minimumRecordingDuration {
if recordDuration ?? 0 >= Constants.minimumRecordingDuration {
sendRecordingAtURL(url)
}
return
}
mediaServiceProvider.audioPlayer.registerDelegate(self)
mediaServiceProvider.audioPlayer.loadContentFromURL(url)
audioPlayer = mediaServiceProvider.audioPlayer()
audioPlayer?.registerDelegate(self)
audioPlayer?.loadContentFromURL(url)
audioSamples = []
updateUI()
@@ -255,7 +261,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
private func updateUI() {
let shouldUpdateFromAudioPlayer = isInLockedMode && !mediaServiceProvider.audioRecorder.isRecording
let shouldUpdateFromAudioPlayer = isInLockedMode && !(audioRecorder?.isRecording ?? false)
if shouldUpdateFromAudioPlayer {
updateUIFromAudioPlayer()
@@ -265,7 +271,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
}
private func updateUIFromAudioRecorder() {
let isRecording = mediaServiceProvider.audioRecorder.isRecording
let isRecording = audioRecorder?.isRecording ?? false
displayLink.isPaused = !isRecording
@@ -275,11 +281,11 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
padSamplesArrayToSize(requiredNumberOfSamples)
}
let sample = mediaServiceProvider.audioRecorder.averagePowerForChannelNumber(0)
let sample = audioRecorder?.averagePowerForChannelNumber(0) ?? 0.0
audioSamples.insert(sample, at: 0)
audioSamples.removeLast()
let currentTime = mediaServiceProvider.audioRecorder.currentTime
let currentTime = audioRecorder?.currentTime ?? 0.0
if currentTime >= Constants.maximumAudioRecordingDuration {
finishRecording()
@@ -311,12 +317,16 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
}
private func updateUIFromAudioPlayer() {
guard let url = mediaServiceProvider.audioPlayer.url else {
guard let audioPlayer = audioPlayer else {
return
}
guard let url = audioPlayer.url else {
MXLog.error("Invalid audio player url.")
return
}
displayLink.isPaused = !mediaServiceProvider.audioPlayer.isPlaying
displayLink.isPaused = !audioPlayer.isPlaying
let requiredNumberOfSamples = _voiceMessageToolbarView.getRequiredNumberOfSamples()
if audioSamples.count != requiredNumberOfSamples && requiredNumberOfSamples > 0 {
@@ -337,11 +347,11 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
}
var details = VoiceMessageToolbarViewDetails()
details.state = (mediaServiceProvider.audioRecorder.isRecording ? (isInLockedMode ? .lockedModeRecord : .record) : (isInLockedMode ? .lockedModePlayback : .idle))
details.elapsedTime = VoiceMessageController.timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: (mediaServiceProvider.audioPlayer.isPlaying ? mediaServiceProvider.audioPlayer.currentTime : mediaServiceProvider.audioPlayer.duration)))
details.state = (audioRecorder?.isRecording ?? false ? (isInLockedMode ? .lockedModeRecord : .record) : (isInLockedMode ? .lockedModePlayback : .idle))
details.elapsedTime = VoiceMessageController.timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: (audioPlayer.isPlaying ? audioPlayer.currentTime : audioPlayer.duration)))
details.audioSamples = audioSamples
details.isPlaying = mediaServiceProvider.audioPlayer.isPlaying
details.progress = (mediaServiceProvider.audioPlayer.duration > 0.0 ? mediaServiceProvider.audioPlayer.currentTime / mediaServiceProvider.audioPlayer.duration : 0.0)
details.isPlaying = audioPlayer.isPlaying
details.progress = (audioPlayer.duration > 0.0 ? audioPlayer.currentTime / audioPlayer.duration : 0.0)
_voiceMessageToolbarView.configureWithDetails(details)
}
@@ -18,32 +18,63 @@ import Foundation
@objc public class VoiceMessageMediaServiceProvider: NSObject, VoiceMessageAudioPlayerDelegate, VoiceMessageAudioRecorderDelegate {
let audioPlayer = VoiceMessageAudioPlayer()
var mediaIdentifier: String?
let audioRecorder = VoiceMessageAudioRecorder()
private let audioPlayers: NSHashTable<VoiceMessageAudioPlayer>
private let audioRecorders: NSHashTable<VoiceMessageAudioRecorder>
@objc public static let sharedProvider = VoiceMessageMediaServiceProvider()
private override init() {
super.init()
audioPlayers = NSHashTable<VoiceMessageAudioPlayer>(options: .weakMemory)
audioRecorders = NSHashTable<VoiceMessageAudioRecorder>(options: .weakMemory)
}
@objc func audioPlayer() -> VoiceMessageAudioPlayer {
let audioPlayer = VoiceMessageAudioPlayer()
audioPlayer.registerDelegate(self)
audioPlayers.add(audioPlayer)
return audioPlayer
}
@objc func audioRecorder() -> VoiceMessageAudioRecorder {
let audioRecorder = VoiceMessageAudioRecorder()
audioRecorder.registerDelegate(self)
audioRecorders.add(audioRecorder)
return audioRecorder
}
@objc func stopAllServices() {
audioPlayer.stop()
audioRecorder.stopRecording()
stopAllServicesExcept(nil)
}
// MARK: - VoiceMessageAudioPlayerDelegate
func audioPlayerDidStartPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
audioRecorder.stopRecording()
stopAllServicesExcept(audioPlayer)
}
// MARK: - VoiceMessageAudioRecorderDelegate
func audioRecorderDidStartRecording(_ audioRecorder: VoiceMessageAudioRecorder) {
audioPlayer.stop()
stopAllServicesExcept(audioRecorder)
}
// MARK: - Private
private func stopAllServicesExcept(_ service: AnyObject?) {
for audioPlayer in audioPlayers.allObjects {
if audioPlayer === service {
continue
}
audioPlayer.pause()
}
for audioRecoder in audioRecorders.allObjects {
if audioRecoder === service {
continue
}
audioRecoder.stopRecording()
}
}
}
@@ -37,7 +37,8 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
}()
private let cacheManager: VoiceMessageAttachmentCacheManager
private let mediaServiceProvider: VoiceMessageMediaServiceProvider
private let audioPlayer: VoiceMessageAudioPlayer
private var displayLink: CADisplayLink!
private var samples: [Float] = []
private var duration: TimeInterval = 0
@@ -46,9 +47,6 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
private var state: VoiceMessagePlaybackControllerState = .stopped {
didSet {
if state == .stopped || state == .error {
mediaServiceProvider.audioPlayer.deregisterDelegate(self)
}
updateUI()
displayLink.isPaused = (state != .playing)
}
@@ -59,10 +57,11 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
init(mediaServiceProvider: VoiceMessageMediaServiceProvider,
cacheManager: VoiceMessageAttachmentCacheManager) {
self.cacheManager = cacheManager
self.mediaServiceProvider = mediaServiceProvider
playbackView = VoiceMessagePlaybackView.loadFromNib()
audioPlayer = mediaServiceProvider.audioPlayer()
audioPlayer.registerDelegate(self)
playbackView.delegate = self
displayLink = CADisplayLink(target: WeakTarget(self, selector: #selector(handleDisplayLinkTick)), selector: WeakTarget.triggerSelector)
@@ -83,29 +82,16 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
// MARK: - VoiceMessagePlaybackViewDelegate
func voiceMessagePlaybackViewDidRequestPlaybackToggle() {
if mediaServiceProvider.mediaIdentifier == attachment?.eventId {
if mediaServiceProvider.audioPlayer.isPlaying {
mediaServiceProvider.audioPlayer.pause()
} else {
mediaServiceProvider.audioPlayer.registerDelegate(self)
mediaServiceProvider.audioPlayer.play()
}
if audioPlayer.isPlaying {
audioPlayer.pause()
} else {
if let url = urlToLoad {
mediaServiceProvider.mediaIdentifier = attachment?.eventId
mediaServiceProvider.audioPlayer.registerDelegate(self)
mediaServiceProvider.audioPlayer.loadContentFromURL(url)
mediaServiceProvider.audioPlayer.play()
}
audioPlayer.play()
}
}
// MARK: - VoiceMessageAudioPlayerDelegate
func audioPlayerDidFinishLoading(_ audioPlayer: VoiceMessageAudioPlayer) {
if audioPlayer.url != self.urlToLoad {
state = .stopped
}
updateUI()
}
@@ -149,8 +135,8 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
details.currentTime = VoiceMessagePlaybackController.timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: self.duration))
details.progress = 0.0
default:
details.currentTime = VoiceMessagePlaybackController.timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: mediaServiceProvider.audioPlayer.currentTime))
details.progress = (mediaServiceProvider.audioPlayer.duration > 0.0 ? mediaServiceProvider.audioPlayer.currentTime / mediaServiceProvider.audioPlayer.duration : 0.0)
details.currentTime = VoiceMessagePlaybackController.timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: audioPlayer.currentTime))
details.progress = (audioPlayer.duration > 0.0 ? audioPlayer.currentTime / audioPlayer.duration : 0.0)
}
details.loading = self.loading
@@ -162,7 +148,6 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
return
}
mediaServiceProvider.audioPlayer.deregisterDelegate(self)
self.state = .stopped
self.loading = true
self.samples = []
@@ -178,21 +163,10 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
}
self.loading = false
self.urlToLoad = result.1
self.audioPlayer.loadContentFromURL(result.1)
self.duration = result.2
self.samples = result.3
if self.mediaServiceProvider.mediaIdentifier == self.attachment?.eventId {
self.mediaServiceProvider.audioPlayer.registerDelegate(self)
if self.mediaServiceProvider.audioPlayer.isPlaying {
self.state = .playing
} else if self.mediaServiceProvider.audioPlayer.currentTime > 0 {
self.state = .paused
} else {
self.state = .stopped
}
}
self.updateUI()
case .failure:
self.state = .error