mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-24 10:32:46 +02:00
#4655 - Allowing voice message playback to be controlled from the lock screen or the control center.
This commit is contained in:
committed by
Stefan Ceriu
parent
60d316b18f
commit
bd255d370c
@@ -45,6 +45,7 @@ class VoiceMessageAudioPlayer: NSObject {
|
||||
private let delegateContainer = DelegateContainer()
|
||||
|
||||
private(set) var url: URL?
|
||||
private(set) var displayName: String?
|
||||
|
||||
var isPlaying: Bool {
|
||||
guard let audioPlayer = audioPlayer else {
|
||||
@@ -68,12 +69,13 @@ class VoiceMessageAudioPlayer: NSObject {
|
||||
removeObservers()
|
||||
}
|
||||
|
||||
func loadContentFromURL(_ url: URL) {
|
||||
func loadContentFromURL(_ url: URL, displayName: String? = nil) {
|
||||
if self.url == url {
|
||||
return
|
||||
}
|
||||
|
||||
self.url = url
|
||||
self.displayName = displayName
|
||||
|
||||
removeObservers()
|
||||
|
||||
|
||||
@@ -15,20 +15,44 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MediaPlayer
|
||||
|
||||
@objc public class VoiceMessageMediaServiceProvider: NSObject, VoiceMessageAudioPlayerDelegate, VoiceMessageAudioRecorderDelegate {
|
||||
|
||||
private enum Constants {
|
||||
static let roomAvatarImageSize: CGFloat = 100.0
|
||||
static let roomAvatarFontSize: CGFloat = 40.0
|
||||
}
|
||||
|
||||
private let audioPlayers: NSMapTable<NSString, VoiceMessageAudioPlayer>
|
||||
private let audioRecorders: NSHashTable<VoiceMessageAudioRecorder>
|
||||
|
||||
// Retain currently playing audio player so it doesn't stop playing on timeline cell reusage
|
||||
private var displayLink: CADisplayLink!
|
||||
|
||||
// Retain currently playing audio player so it doesn't stop playing on timeline cell reuse
|
||||
private var currentlyPlayingAudioPlayer: VoiceMessageAudioPlayer?
|
||||
|
||||
@objc public static let sharedProvider = VoiceMessageMediaServiceProvider()
|
||||
|
||||
private var roomAvatar: UIImage?
|
||||
@objc public var currentRoomSummary: MXRoomSummary? {
|
||||
didSet {
|
||||
roomAvatar = AvatarGenerator.generateAvatar(forMatrixItem: currentRoomSummary?.roomId,
|
||||
withDisplayName: currentRoomSummary?.displayname,
|
||||
size: Constants.roomAvatarImageSize,
|
||||
andFontSize: Constants.roomAvatarFontSize)
|
||||
}
|
||||
}
|
||||
|
||||
private override init() {
|
||||
audioPlayers = NSMapTable<NSString, VoiceMessageAudioPlayer>(valueOptions: .weakMemory)
|
||||
audioRecorders = NSHashTable<VoiceMessageAudioRecorder>(options: .weakMemory)
|
||||
|
||||
super.init()
|
||||
|
||||
displayLink = CADisplayLink(target: WeakTarget(self, selector: #selector(handleDisplayLinkTick)), selector: WeakTarget.triggerSelector)
|
||||
displayLink.isPaused = true
|
||||
displayLink.add(to: .current, forMode: .common)
|
||||
}
|
||||
|
||||
@objc func audioPlayerForIdentifier(_ identifier: String) -> VoiceMessageAudioPlayer {
|
||||
@@ -57,12 +81,21 @@ import Foundation
|
||||
|
||||
func audioPlayerDidStartPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
currentlyPlayingAudioPlayer = audioPlayer
|
||||
setUpRemoteCommandCenter()
|
||||
stopAllServicesExcept(audioPlayer)
|
||||
}
|
||||
|
||||
func audioPlayerDidStopPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
if currentlyPlayingAudioPlayer == audioPlayer {
|
||||
currentlyPlayingAudioPlayer = nil
|
||||
tearDownRemoteCommandCenter()
|
||||
}
|
||||
}
|
||||
|
||||
func audioPlayerDidFinishPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
if currentlyPlayingAudioPlayer == audioPlayer {
|
||||
currentlyPlayingAudioPlayer = nil
|
||||
tearDownRemoteCommandCenter()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,4 +129,90 @@ import Foundation
|
||||
audioPlayer.unloadContent()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func handleDisplayLinkTick() {
|
||||
updateNowPlayingInfoCenter()
|
||||
}
|
||||
|
||||
private func setUpRemoteCommandCenter() {
|
||||
displayLink.isPaused = false
|
||||
|
||||
UIApplication.shared.beginReceivingRemoteControlEvents()
|
||||
|
||||
let commandCenter = MPRemoteCommandCenter.shared()
|
||||
|
||||
commandCenter.playCommand.isEnabled = true
|
||||
commandCenter.playCommand.removeTarget(nil)
|
||||
commandCenter.playCommand.addTarget { [weak self] event in
|
||||
guard let audioPlayer = self?.currentlyPlayingAudioPlayer else {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
audioPlayer.play()
|
||||
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
|
||||
commandCenter.pauseCommand.isEnabled = true
|
||||
commandCenter.pauseCommand.removeTarget(nil)
|
||||
commandCenter.pauseCommand.addTarget { [weak self] event in
|
||||
guard let audioPlayer = self?.currentlyPlayingAudioPlayer else {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
audioPlayer.pause()
|
||||
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
|
||||
commandCenter.skipForwardCommand.isEnabled = true
|
||||
commandCenter.skipForwardCommand.removeTarget(nil)
|
||||
commandCenter.skipForwardCommand.addTarget { [weak self] event in
|
||||
guard let audioPlayer = self?.currentlyPlayingAudioPlayer, let skipEvent = event as? MPSkipIntervalCommandEvent else {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
audioPlayer.seekToTime(audioPlayer.currentTime + skipEvent.interval)
|
||||
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
|
||||
commandCenter.skipBackwardCommand.isEnabled = true
|
||||
commandCenter.skipBackwardCommand.removeTarget(nil)
|
||||
commandCenter.skipBackwardCommand.addTarget { [weak self] event in
|
||||
guard let audioPlayer = self?.currentlyPlayingAudioPlayer, let skipEvent = event as? MPSkipIntervalCommandEvent else {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
audioPlayer.seekToTime(audioPlayer.currentTime - skipEvent.interval)
|
||||
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
}
|
||||
|
||||
private func tearDownRemoteCommandCenter() {
|
||||
displayLink.isPaused = true
|
||||
|
||||
UIApplication.shared.endReceivingRemoteControlEvents()
|
||||
|
||||
let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
|
||||
nowPlayingInfoCenter.nowPlayingInfo = nil
|
||||
}
|
||||
|
||||
private func updateNowPlayingInfoCenter() {
|
||||
guard let audioPlayer = currentlyPlayingAudioPlayer else {
|
||||
return
|
||||
}
|
||||
|
||||
let artwork = MPMediaItemArtwork(boundsSize: .init(width: Constants.roomAvatarImageSize, height: Constants.roomAvatarImageSize)) { [weak self] size in
|
||||
return self?.roomAvatar ?? UIImage()
|
||||
}
|
||||
|
||||
let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
|
||||
nowPlayingInfoCenter.nowPlayingInfo = [MPMediaItemPropertyTitle: audioPlayer.displayName ?? "Voice message",
|
||||
MPMediaItemPropertyArtist: currentRoomSummary?.displayname as Any,
|
||||
MPMediaItemPropertyArtwork: artwork,
|
||||
MPMediaItemPropertyPlaybackDuration: audioPlayer.duration as Any,
|
||||
MPNowPlayingInfoPropertyElapsedPlaybackTime: audioPlayer.currentTime as Any]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,7 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
|
||||
|
||||
let playbackView: VoiceMessagePlaybackView
|
||||
|
||||
init(mediaServiceProvider: VoiceMessageMediaServiceProvider,
|
||||
cacheManager: VoiceMessageAttachmentCacheManager) {
|
||||
init(mediaServiceProvider: VoiceMessageMediaServiceProvider, cacheManager: VoiceMessageAttachmentCacheManager) {
|
||||
self.mediaServiceProvider = mediaServiceProvider
|
||||
self.cacheManager = cacheManager
|
||||
|
||||
@@ -93,7 +92,7 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
|
||||
audioPlayer.play()
|
||||
}
|
||||
} else if let url = urlToLoad {
|
||||
audioPlayer.loadContentFromURL(url)
|
||||
audioPlayer.loadContentFromURL(url, displayName: attachment?.originalFileName)
|
||||
audioPlayer.play()
|
||||
}
|
||||
}
|
||||
@@ -153,6 +152,7 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
|
||||
details.progress = (audioPlayer.duration > 0.0 ? audioPlayer.currentTime / audioPlayer.duration : 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
details.loading = self.loading
|
||||
|
||||
playbackView.configureWithDetails(details)
|
||||
|
||||
Reference in New Issue
Block a user