Skip to content

Commit

Permalink
#4094 - Added multiple observation on media services and a mediaServi…
Browse files Browse the repository at this point in the history
…ceProvider that prevents simultaneous playback from multiple player instances.
  • Loading branch information
stefanceriu committed Jun 23, 2021
1 parent fc7d311 commit 667d590
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 34 deletions.
2 changes: 1 addition & 1 deletion Riot/Modules/Room/RoomViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ - (void)finalizeInit
// Show / hide actions button in document preview according BuildSettings
self.allowActionsInDocumentPreview = BuildSettings.messageDetailsAllowShare;

_voiceMessageController = [[VoiceMessageController alloc] initWithThemeService:ThemeService.shared];
_voiceMessageController = [[VoiceMessageController alloc] initWithThemeService:ThemeService.shared mediaServiceProvider:VoiceMessageMediaServiceProvider.sharedProvider];
self.voiceMessageController.delegate = self;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class VoiceMessageBubbleCell: SizableBaseBubbleCell, BubbleCellReactionsDisplaya
return
}

playbackController = VoiceMessagePlaybackController()
playbackController = VoiceMessagePlaybackController(mediaServiceProvider: VoiceMessageMediaServiceProvider.sharedProvider)
bubbleCellContentView?.addSubview(playbackController.playbackView)

contentView.vc_addSubViewMatchingParent(playbackController.playbackView)
Expand Down
60 changes: 44 additions & 16 deletions Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,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?

Expand Down Expand Up @@ -88,8 +88,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)

Expand Down Expand Up @@ -122,6 +124,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() {
Expand All @@ -134,9 +144,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
}
Expand All @@ -146,26 +160,36 @@ 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)
}
}
}

rateObserver = audioPlayer.observe(\.rate, options: [.old, .new]) { [weak self] player, change in
guard let self = self else { return }

if audioPlayer.rate == 0.0 {
self.delegate?.audioPlayerDidStopPlaying(self)
self.delegateContainer.notifyDelegatesWithBlock { delegate in
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidStopPlaying(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)
}
}
}

Expand All @@ -178,11 +202,15 @@ 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 audioPlayerDidStopPlaying(_ audioPlayer: VoiceMessageAudioPlayer) { }

func audioPlayerDidFinishPlaying(_ audioPlayer: VoiceMessageAudioPlayer) { }

func audioPlayer(_ audioPlayer: VoiceMessageAudioPlayer, didFailWithError: Error) { }
}
39 changes: 32 additions & 7 deletions Riot/Modules/Room/VoiceMessages/VoiceMessageAudioRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class VoiceMessageAudioRecorder: NSObject, AVAudioRecorderDelegate {
}

private var audioRecorder: AVAudioRecorder?
private let delegateContainer = DelegateContainer()

var url: URL? {
return audioRecorder?.url
Expand All @@ -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),
Expand All @@ -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)
}
}
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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) { }
}
16 changes: 10 additions & 6 deletions Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand All @@ -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()
Expand Down Expand Up @@ -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)
}

Expand All @@ -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 = []

Expand Down
Original file line number Diff line number Diff line change
@@ -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 pauseAllServices() {
pauseAllServicesExcept(nil)
}

// MARK: - VoiceMessageAudioPlayerDelegate

func audioPlayerDidStartPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
pauseAllServicesExcept(audioPlayer)
}

// MARK: - VoiceMessageAudioRecorderDelegate

func audioRecorderDidStartRecording(_ audioRecorder: VoiceMessageAudioRecorder) {
pauseAllServicesExcept(audioRecorder)
}

// MARK: - Private

private func pauseAllServicesExcept(_ service: AnyObject?) {
for audioPlayer in audioPlayers.allObjects {
if audioPlayer === service {
break
}

audioPlayer.pause()
}

for audioRecoder in audioRecorders.allObjects {
if audioRecoder === service {
break
}

audioRecoder.stopRecording()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ enum VoiceMessagePlaybackControllerState {
}

class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMessagePlaybackViewDelegate {

private let audioPlayer: VoiceMessageAudioPlayer
private let timeFormatter: DateFormatter
private var displayLink: CADisplayLink!
Expand All @@ -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))
Expand Down
Loading

0 comments on commit 667d590

Please sign in to comment.