#4094 - Added multiple observation on media services and a mediaServiceProvider that prevents simultaneous playback from multiple player instances.

This commit is contained in:
Stefan Ceriu
2021-06-23 17:52:08 +03:00
parent 6d62b17e5b
commit 242fb1d808
8 changed files with 237 additions and 35 deletions
@@ -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)")