element-ios/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift

225 lines
7.7 KiB
Swift

//
// 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
protocol VoiceMessageAudioPlayerDelegate: AnyObject {
func audioPlayerDidStartLoading(_ 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)
}
enum VoiceMessageAudioPlayerError: Error {
case genericError
}
class VoiceMessageAudioPlayer: NSObject {
private var playerItem: AVPlayerItem?
private var audioPlayer: AVPlayer?
private var statusObserver: NSKeyValueObservation?
private var playbackBufferEmptyObserver: NSKeyValueObservation?
private var rateObserver: NSKeyValueObservation?
private var playToEndObsever: NSObjectProtocol?
private let delegateContainer = DelegateContainer()
private(set) var url: URL?
var isPlaying: Bool {
guard let audioPlayer = audioPlayer else {
return false
}
return (audioPlayer.rate > 0)
}
var duration: TimeInterval {
guard let item = self.audioPlayer?.currentItem else {
return 0
}
let duration = CMTimeGetSeconds(item.duration)
return duration.isNaN ? 0.0 : duration
}
var currentTime: TimeInterval {
guard let audioPlayer = self.audioPlayer else {
return 0.0
}
let currentTime = CMTimeGetSeconds(audioPlayer.currentTime())
return currentTime.isNaN ? 0.0 : currentTime
}
private(set) var isStopped = true
deinit {
removeObservers()
}
func loadContentFromURL(_ url: URL) {
if self.url == url {
return
}
self.url = url
removeObservers()
delegateContainer.notifyDelegatesWithBlock { delegate in
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidStartLoading(self)
}
playerItem = AVPlayerItem(url: url)
audioPlayer = AVPlayer(playerItem: playerItem)
addObservers()
}
func play() {
isStopped = false
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
} catch {
MXLog.error("Could not redirect audio playback to speakers.")
}
audioPlayer?.play()
}
func pause() {
audioPlayer?.pause()
}
func stop() {
isStopped = true
audioPlayer?.pause()
audioPlayer?.seek(to: .zero)
}
func seekToTime(_ time: TimeInterval) {
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() {
guard let audioPlayer = audioPlayer, let playerItem = playerItem else {
return
}
statusObserver = playerItem.observe(\.status, options: [.old, .new]) { [weak self] item, change in
guard let self = self else { return }
switch playerItem.status {
case .failed:
self.delegateContainer.notifyDelegatesWithBlock { delegate in
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayer(self, didFailWithError: playerItem.error ?? VoiceMessageAudioPlayerError.genericError)
}
case .readyToPlay:
self.delegateContainer.notifyDelegatesWithBlock { delegate in
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidFinishLoading(self)
}
default:
break
}
}
playbackBufferEmptyObserver = playerItem.observe(\.isPlaybackBufferEmpty, options: [.old, .new]) { [weak self] item, change in
guard let self = self else { return }
if playerItem.isPlaybackBufferEmpty {
self.delegateContainer.notifyDelegatesWithBlock { delegate in
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidStartLoading(self)
}
} else {
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 {
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.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.delegateContainer.notifyDelegatesWithBlock { delegate in
(delegate as? VoiceMessageAudioPlayerDelegate)?.audioPlayerDidFinishPlaying(self)
}
}
}
private func removeObservers() {
statusObserver?.invalidate()
playbackBufferEmptyObserver?.invalidate()
rateObserver?.invalidate()
NotificationCenter.default.removeObserver(playToEndObsever as Any)
}
}
extension VoiceMessageAudioPlayerDelegate {
func audioPlayerDidStartLoading(_ 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) { }
}