mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-23 18:12:44 +02:00
#4094 - Added multiple observation on media services and a mediaServiceProvider that prevents simultaneous playback from multiple player instances.
This commit is contained in:
@@ -21,6 +21,7 @@ protocol VoiceMessageAudioPlayerDelegate: AnyObject {
|
||||
func audioPlayerDidFinishLoading(_ audioPlayer: VoiceMessageAudioPlayer)
|
||||
|
||||
func audioPlayerDidStartPlaying(_ audioPlayer: VoiceMessageAudioPlayer)
|
||||
func audioPlayerDidPausePlaying(_ audioPlayer: VoiceMessageAudioPlayer)
|
||||
func audioPlayerDidStopPlaying(_ audioPlayer: VoiceMessageAudioPlayer)
|
||||
func audioPlayerDidFinishPlaying(_ audioPlayer: VoiceMessageAudioPlayer)
|
||||
|
||||
@@ -41,7 +42,7 @@ class VoiceMessageAudioPlayer: NSObject {
|
||||
private var rateObserver: NSKeyValueObservation?
|
||||
private var playToEndObsever: NSObjectProtocol?
|
||||
|
||||
weak var delegate: VoiceMessageAudioPlayerDelegate?
|
||||
private let delegateContainer = DelegateContainer()
|
||||
|
||||
private(set) var url: URL?
|
||||
|
||||
@@ -88,8 +89,10 @@ class VoiceMessageAudioPlayer: NSObject {
|
||||
|
||||
removeObservers()
|
||||
|
||||
delegate?.audioPlayerDidStartLoading(self)
|
||||
|
||||
delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidStartLoading(self)
|
||||
}
|
||||
|
||||
playerItem = AVPlayerItem(url: url)
|
||||
audioPlayer = AVPlayer(playerItem: playerItem)
|
||||
|
||||
@@ -122,6 +125,14 @@ class VoiceMessageAudioPlayer: NSObject {
|
||||
audioPlayer?.seek(to: CMTime(seconds: time, preferredTimescale: 60000))
|
||||
}
|
||||
|
||||
func registerDelegate(_ delegate: VoiceMessageAudioPlayerDelegate) {
|
||||
delegateContainer.registerDelegate(delegate)
|
||||
}
|
||||
|
||||
func deregisterDelegate(_ delegate: VoiceMessageAudioPlayerDelegate) {
|
||||
delegateContainer.deregisterDelegate(delegate)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func addObservers() {
|
||||
@@ -134,9 +145,13 @@ class VoiceMessageAudioPlayer: NSObject {
|
||||
|
||||
switch playerItem.status {
|
||||
case .failed:
|
||||
self.delegate?.audioPlayer(self, didFailWithError: playerItem.error ?? VoiceMessageAudioPlayerError.genericError)
|
||||
self.delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayer(self, didFailWithError: playerItem.error ?? VoiceMessageAudioPlayerError.genericError)
|
||||
}
|
||||
case .readyToPlay:
|
||||
self.delegate?.audioPlayerDidFinishLoading(self)
|
||||
self.delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidFinishLoading(self)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -146,9 +161,13 @@ class VoiceMessageAudioPlayer: NSObject {
|
||||
guard let self = self else { return }
|
||||
|
||||
if playerItem.isPlaybackBufferEmpty {
|
||||
self.delegate?.audioPlayerDidStartLoading(self)
|
||||
self.delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidStartLoading(self)
|
||||
}
|
||||
} else {
|
||||
self.delegate?.audioPlayerDidFinishLoading(self)
|
||||
self.delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidFinishLoading(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,16 +175,28 @@ class VoiceMessageAudioPlayer: NSObject {
|
||||
guard let self = self else { return }
|
||||
|
||||
if audioPlayer.rate == 0.0 {
|
||||
self.delegate?.audioPlayerDidStopPlaying(self)
|
||||
if self.isStopped {
|
||||
self.delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidStopPlaying(self)
|
||||
}
|
||||
} else {
|
||||
self.delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidPausePlaying(self)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.delegate?.audioPlayerDidStartPlaying(self)
|
||||
self.delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidStartPlaying(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playToEndObsever = NotificationCenter.default.addObserver(forName: Notification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem, queue: nil) { [weak self] notification in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.delegate?.audioPlayerDidFinishPlaying(self)
|
||||
self.delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidFinishPlaying(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,11 +209,17 @@ class VoiceMessageAudioPlayer: NSObject {
|
||||
}
|
||||
|
||||
extension VoiceMessageAudioPlayerDelegate {
|
||||
func audioPlayerDidStartLoading(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
|
||||
}
|
||||
func audioPlayerDidStartLoading(_ audioPlayer: VoiceMessageAudioPlayer) { }
|
||||
|
||||
func audioPlayerDidFinishLoading(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
|
||||
}
|
||||
func audioPlayerDidFinishLoading(_ audioPlayer: VoiceMessageAudioPlayer) { }
|
||||
|
||||
func audioPlayerDidStartPlaying(_ audioPlayer: VoiceMessageAudioPlayer) { }
|
||||
|
||||
func audioPlayerDidPausePlaying(_ audioPlayer: VoiceMessageAudioPlayer) { }
|
||||
|
||||
func audioPlayerDidStopPlaying(_ audioPlayer: VoiceMessageAudioPlayer) { }
|
||||
|
||||
func audioPlayerDidFinishPlaying(_ audioPlayer: VoiceMessageAudioPlayer) { }
|
||||
|
||||
func audioPlayer(_ audioPlayer: VoiceMessageAudioPlayer, didFailWithError: Error) { }
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ class VoiceMessageAudioRecorder: NSObject, AVAudioRecorderDelegate {
|
||||
}
|
||||
|
||||
private var audioRecorder: AVAudioRecorder?
|
||||
private let delegateContainer = DelegateContainer()
|
||||
|
||||
var url: URL? {
|
||||
return audioRecorder?.url
|
||||
@@ -47,8 +48,6 @@ class VoiceMessageAudioRecorder: NSObject, AVAudioRecorderDelegate {
|
||||
return audioRecorder?.isRecording ?? false
|
||||
}
|
||||
|
||||
weak var delegate: VoiceMessageAudioRecorderDelegate?
|
||||
|
||||
func recordWithOuputURL(_ url: URL) {
|
||||
|
||||
let settings = [AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
|
||||
@@ -62,9 +61,13 @@ class VoiceMessageAudioRecorder: NSObject, AVAudioRecorderDelegate {
|
||||
audioRecorder?.delegate = self
|
||||
audioRecorder?.isMeteringEnabled = true
|
||||
audioRecorder?.record()
|
||||
delegate?.audioRecorderDidStartRecording(self)
|
||||
delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioRecorderDelegate)?.audioRecorderDidStartRecording(self)
|
||||
}
|
||||
} catch {
|
||||
delegate?.audioRecorder(self, didFailWithError: VoiceMessageAudioRecorderError.genericError)
|
||||
delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioRecorderDelegate)?.audioRecorder(self, didFailWithError: VoiceMessageAudioRecorderError.genericError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,18 +95,32 @@ class VoiceMessageAudioRecorder: NSObject, AVAudioRecorderDelegate {
|
||||
return self.normalizedPowerLevelFromDecibels(audioRecorder.averagePower(forChannel: channelNumber))
|
||||
}
|
||||
|
||||
func registerDelegate(_ delegate: VoiceMessageAudioPlayerDelegate) {
|
||||
delegateContainer.registerDelegate(delegate)
|
||||
}
|
||||
|
||||
func deregisterDelegate(_ delegate: VoiceMessageAudioPlayerDelegate) {
|
||||
delegateContainer.deregisterDelegate(delegate)
|
||||
}
|
||||
|
||||
// MARK: - AVAudioRecorderDelegate
|
||||
|
||||
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully success: Bool) {
|
||||
if success {
|
||||
delegate?.audioRecorderDidFinishRecording(self)
|
||||
delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioRecorderDelegate)?.audioRecorderDidFinishRecording(self)
|
||||
}
|
||||
} else {
|
||||
delegate?.audioRecorder(self, didFailWithError: VoiceMessageAudioRecorderError.genericError)
|
||||
delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioRecorderDelegate)?.audioRecorder(self, didFailWithError: VoiceMessageAudioRecorderError.genericError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: Error?) {
|
||||
delegate?.audioRecorder(self, didFailWithError: VoiceMessageAudioRecorderError.genericError)
|
||||
delegateContainer.notifyDelegatesWithBlock { delegate in
|
||||
(delegate as? VoiceMessageAudioRecorderDelegate)?.audioRecorder(self, didFailWithError: VoiceMessageAudioRecorderError.genericError)
|
||||
}
|
||||
}
|
||||
|
||||
private func normalizedPowerLevelFromDecibels(_ decibels: Float) -> Float {
|
||||
@@ -114,3 +131,11 @@ class VoiceMessageAudioRecorder: NSObject, AVAudioRecorderDelegate {
|
||||
extension String: LocalizedError {
|
||||
public var errorDescription: String? { return self }
|
||||
}
|
||||
|
||||
extension VoiceMessageAudioRecorderDelegate {
|
||||
func audioRecorderDidStartRecording(_ audioRecorder: VoiceMessageAudioRecorder) { }
|
||||
|
||||
func audioRecorderDidFinishRecording(_ audioRecorder: VoiceMessageAudioRecorder) { }
|
||||
|
||||
func audioRecorder(_ audioRecorder: VoiceMessageAudioRecorder, didFailWithError: Error) { }
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import DSWaveformImage
|
||||
public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate, VoiceMessageAudioRecorderDelegate, VoiceMessageAudioPlayerDelegate {
|
||||
|
||||
private let themeService: ThemeService
|
||||
private let mediaServiceProvider: VoiceMessageMediaServiceProvider
|
||||
|
||||
private let _voiceMessageToolbarView: VoiceMessageToolbarView
|
||||
private let timeFormatter: DateFormatter
|
||||
private var displayLink: CADisplayLink!
|
||||
@@ -44,9 +46,11 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
return _voiceMessageToolbarView
|
||||
}
|
||||
|
||||
@objc public init(themeService: ThemeService) {
|
||||
_voiceMessageToolbarView = VoiceMessageToolbarView.loadFromNib()
|
||||
@objc public init(themeService: ThemeService, mediaServiceProvider: VoiceMessageMediaServiceProvider) {
|
||||
self.themeService = themeService
|
||||
self.mediaServiceProvider = mediaServiceProvider
|
||||
|
||||
_voiceMessageToolbarView = VoiceMessageToolbarView.loadFromNib()
|
||||
self.timeFormatter = DateFormatter()
|
||||
|
||||
super.init()
|
||||
@@ -76,8 +80,8 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||
let temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(ProcessInfo().globallyUniqueString).appendingPathExtension("m4a")
|
||||
|
||||
audioRecorder = VoiceMessageAudioRecorder()
|
||||
audioRecorder?.delegate = self
|
||||
audioRecorder = mediaServiceProvider.audioRecorder()
|
||||
audioRecorder?.registerDelegate(self)
|
||||
audioRecorder?.recordWithOuputURL(temporaryFileURL)
|
||||
}
|
||||
|
||||
@@ -94,8 +98,8 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
return
|
||||
}
|
||||
|
||||
audioPlayer = VoiceMessageAudioPlayer()
|
||||
audioPlayer?.delegate = self
|
||||
audioPlayer = mediaServiceProvider.audioPlayer()
|
||||
audioPlayer?.registerDelegate(self)
|
||||
audioPlayer?.loadContentFromURL(url)
|
||||
audioSamples = []
|
||||
|
||||
@@ -161,6 +165,10 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func audioPlayerDidPausePlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func audioPlayerDidStopPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
updateUI()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@objc public class VoiceMessageMediaServiceProvider: NSObject, VoiceMessageAudioPlayerDelegate, VoiceMessageAudioRecorderDelegate {
|
||||
|
||||
private let audioPlayers: NSHashTable<VoiceMessageAudioPlayer>
|
||||
private let audioRecorders: NSHashTable<VoiceMessageAudioRecorder>
|
||||
|
||||
@objc public static let sharedProvider = VoiceMessageMediaServiceProvider()
|
||||
|
||||
private override 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() {
|
||||
stopAllServicesExcept(nil)
|
||||
}
|
||||
|
||||
// MARK: - VoiceMessageAudioPlayerDelegate
|
||||
|
||||
func audioPlayerDidStartPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
stopAllServicesExcept(audioPlayer)
|
||||
}
|
||||
|
||||
// MARK: - VoiceMessageAudioRecorderDelegate
|
||||
|
||||
func audioRecorderDidStartRecording(_ audioRecorder: VoiceMessageAudioRecorder) {
|
||||
stopAllServicesExcept(audioRecorder)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func stopAllServicesExcept(_ service: AnyObject?) {
|
||||
for audioPlayer in audioPlayers.allObjects {
|
||||
if audioPlayer === service {
|
||||
continue
|
||||
}
|
||||
|
||||
audioPlayer.stop()
|
||||
}
|
||||
|
||||
for audioRecoder in audioRecorders.allObjects {
|
||||
if audioRecoder === service {
|
||||
continue
|
||||
}
|
||||
|
||||
audioRecoder.stopRecording()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ enum VoiceMessagePlaybackControllerState {
|
||||
}
|
||||
|
||||
class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMessagePlaybackViewDelegate {
|
||||
|
||||
private let audioPlayer: VoiceMessageAudioPlayer
|
||||
private let timeFormatter: DateFormatter
|
||||
private var displayLink: CADisplayLink!
|
||||
@@ -39,14 +40,14 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
|
||||
|
||||
let playbackView: VoiceMessagePlaybackView
|
||||
|
||||
init() {
|
||||
init(mediaServiceProvider: VoiceMessageMediaServiceProvider) {
|
||||
playbackView = VoiceMessagePlaybackView.loadFromNib()
|
||||
audioPlayer = VoiceMessageAudioPlayer()
|
||||
audioPlayer = mediaServiceProvider.audioPlayer()
|
||||
|
||||
timeFormatter = DateFormatter()
|
||||
timeFormatter.dateFormat = "m:ss"
|
||||
|
||||
audioPlayer.delegate = self
|
||||
audioPlayer.registerDelegate(self)
|
||||
playbackView.delegate = self
|
||||
|
||||
displayLink = CADisplayLink(target: WeakObjectWrapper(self), selector: #selector(handleDisplayLinkTick))
|
||||
@@ -94,10 +95,14 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
|
||||
state = .playing
|
||||
}
|
||||
|
||||
func audioPlayerDidStopPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
func audioPlayerDidPausePlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
state = .paused
|
||||
}
|
||||
|
||||
func audioPlayerDidStopPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
state = .stopped
|
||||
}
|
||||
|
||||
func audioPlayer(_ audioPlayer: VoiceMessageAudioPlayer, didFailWithError error: Error) {
|
||||
state = .error
|
||||
MXLog.error("Failed playing voice message with error: \(error)")
|
||||
|
||||
Reference in New Issue
Block a user