Skip to content

Commit

Permalink
Merge pull request #7257 from vector-im/nimau/PSF-1734_vb_control_center
Browse files Browse the repository at this point in the history
Fix the now playing info center while a voice broadcast is played
  • Loading branch information
nimau committed Jan 17, 2023
2 parents 5e86a2e + 648b409 commit a9ff128
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 10 deletions.
1 change: 1 addition & 0 deletions Riot/Assets/en.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2217,6 +2217,7 @@ Tap the + to start adding people.";
"voice_broadcast_stop_alert_agree_button" = "Yes, stop";
"voice_broadcast_voip_cannot_start_title" = "Can’t start a call";
"voice_broadcast_voip_cannot_start_description" = "You can’t start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.";
"voice_broadcast_playback_lock_screen_placeholder" = "Voice broadcast";

// MARK: - Version check

Expand Down
4 changes: 4 additions & 0 deletions Riot/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9219,6 +9219,10 @@ public class VectorL10n: NSObject {
public static var voiceBroadcastPlaybackLoadingError: String {
return VectorL10n.tr("Vector", "voice_broadcast_playback_loading_error")
}
/// Voice broadcast
public static var voiceBroadcastPlaybackLockScreenPlaceholder: String {
return VectorL10n.tr("Vector", "voice_broadcast_playback_lock_screen_placeholder")
}
/// Yes, stop
public static var voiceBroadcastStopAlertAgreeButton: String {
return VectorL10n.tr("Vector", "voice_broadcast_stop_alert_agree_button")
Expand Down
3 changes: 3 additions & 0 deletions Riot/Modules/Application/LegacyAppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,9 @@ - (void)applicationDidEnterBackground:(UIApplication *)application

// Pause Voice Broadcast recording if needed
[VoiceBroadcastRecorderProvider.shared pauseRecording];

// Pause Voice Broadcast playing if needed
[VoiceBroadcastPlaybackProvider.shared pausePlayingInProgressVoiceBroadcast];
}

- (void)applicationWillEnterForeground:(UIApplication *)application
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import MediaPlayer
private var roomAvatarLoader: MXMediaLoader?
private let audioPlayers: NSMapTable<NSString, VoiceMessageAudioPlayer>
private let audioRecorders: NSHashTable<VoiceMessageAudioRecorder>
private let nowPlayingInfoDelegates: NSMapTable<VoiceMessageAudioPlayer, VoiceMessageNowPlayingInfoDelegate>

private var displayLink: CADisplayLink!

Expand Down Expand Up @@ -93,6 +94,7 @@ import MediaPlayer
private override init() {
audioPlayers = NSMapTable<NSString, VoiceMessageAudioPlayer>(valueOptions: .weakMemory)
audioRecorders = NSHashTable<VoiceMessageAudioRecorder>(options: .weakMemory)
nowPlayingInfoDelegates = NSMapTable<VoiceMessageAudioPlayer, VoiceMessageNowPlayingInfoDelegate>(keyOptions: .weakMemory, valueOptions: .weakMemory)
activeAudioPlayers = Set<VoiceMessageAudioPlayer>()
super.init()

Expand Down Expand Up @@ -123,27 +125,54 @@ import MediaPlayer
pauseAllServicesExcept(nil)
}

func registerNowPlayingInfoDelegate(_ delegate: VoiceMessageNowPlayingInfoDelegate, forPlayer player: VoiceMessageAudioPlayer) {
nowPlayingInfoDelegates.setObject(delegate, forKey: player)
}

func deregisterNowPlayingInfoDelegate(forPlayer player: VoiceMessageAudioPlayer) {
nowPlayingInfoDelegates.removeObject(forKey: player)
}

// MARK: - VoiceMessageAudioPlayerDelegate

func audioPlayerDidStartPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
currentlyPlayingAudioPlayer = audioPlayer
activeAudioPlayers.insert(audioPlayer)
setUpRemoteCommandCenter()

let shouldSetupRemoteCommandCenter = nowPlayingInfoDelegates.object(forKey: audioPlayer)?.shouldSetupRemoteCommandCenter(audioPlayer: audioPlayer) ?? true
if shouldSetupRemoteCommandCenter {
setUpRemoteCommandCenter()
} else {
// clean up the remote command center
tearDownRemoteCommandCenter()
}
pauseAllServicesExcept(audioPlayer)
}

func audioPlayerDidStopPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
if currentlyPlayingAudioPlayer == audioPlayer {
currentlyPlayingAudioPlayer = nil
tearDownRemoteCommandCenter()
// If we have a NowPlayingInfoDelegate for this player
let nowPlayingInfoDelegate = nowPlayingInfoDelegates.object(forKey: audioPlayer)

// ask the delegate if we should disconnect from NowPlayingInfoCenter (if there's no delegate, we consider it safe to disconnect it)
if nowPlayingInfoDelegate?.shouldDisconnectFromNowPlayingInfoCenter(audioPlayer: audioPlayer) ?? true {
currentlyPlayingAudioPlayer = nil
tearDownRemoteCommandCenter()
}
}
activeAudioPlayers.remove(audioPlayer)
}

func audioPlayerDidFinishPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
if currentlyPlayingAudioPlayer == audioPlayer {
currentlyPlayingAudioPlayer = nil
tearDownRemoteCommandCenter()
// If we have a NowPlayingInfoDelegate for this player
let nowPlayingInfoDelegate = nowPlayingInfoDelegates.object(forKey: audioPlayer)

// ask the delegate if we should disconnect from NowPlayingInfoCenter (if there's no delegate, we consider it safe to disconnect it)
if nowPlayingInfoDelegate?.shouldDisconnectFromNowPlayingInfoCenter(audioPlayer: audioPlayer) ?? true {
currentlyPlayingAudioPlayer = nil
tearDownRemoteCommandCenter()
}
}
activeAudioPlayers.remove(audioPlayer)
}
Expand Down Expand Up @@ -249,16 +278,32 @@ import MediaPlayer

let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
nowPlayingInfoCenter.nowPlayingInfo = nil
nowPlayingInfoCenter.playbackState = .stopped

let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.isEnabled = false
commandCenter.playCommand.removeTarget(nil)
commandCenter.pauseCommand.isEnabled = false
commandCenter.pauseCommand.removeTarget(nil)
commandCenter.skipForwardCommand.isEnabled = false
commandCenter.skipForwardCommand.removeTarget(nil)
commandCenter.skipBackwardCommand.isEnabled = false
commandCenter.skipBackwardCommand.removeTarget(nil)
}

private func updateNowPlayingInfoCenter() {
guard let audioPlayer = currentlyPlayingAudioPlayer else {
return
}

let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
nowPlayingInfoCenter.nowPlayingInfo = [MPMediaItemPropertyTitle: VectorL10n.voiceMessageLockScreenPlaceholder,
MPMediaItemPropertyPlaybackDuration: audioPlayer.duration as Any,
MPNowPlayingInfoPropertyElapsedPlaybackTime: audioPlayer.currentTime as Any]
// Checks if we have a delegate for this player, or if we should update the NowPlayingInfoCenter ourselves
if let nowPlayingInfoDelegate = nowPlayingInfoDelegates.object(forKey: audioPlayer) {
nowPlayingInfoDelegate.updateNowPlayingInfoCenter(forPlayer: audioPlayer)
} else {
let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
nowPlayingInfoCenter.nowPlayingInfo = [MPMediaItemPropertyTitle: VectorL10n.voiceMessageLockScreenPlaceholder,
MPMediaItemPropertyPlaybackDuration: audioPlayer.duration as Any,
MPNowPlayingInfoPropertyElapsedPlaybackTime: audioPlayer.currentTime as Any]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// Copyright 2023 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 protocol VoiceMessageNowPlayingInfoDelegate {

func updateNowPlayingInfoCenter(forPlayer player: VoiceMessageAudioPlayer)

func shouldSetupRemoteCommandCenter(audioPlayer player: VoiceMessageAudioPlayer) -> Bool

func shouldDisconnectFromNowPlayingInfoCenter(audioPlayer: VoiceMessageAudioPlayer) -> Bool
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,15 @@ final class VoiceBroadcastPlaybackCoordinator: Coordinator, Presentable {
}

func endVoiceBroadcast() {}

func pausePlaying() {
viewModel.context.send(viewAction: .pause)
}

func pausePlayingInProgressVoiceBroadcast() {
// Pause the playback if we are playing a live voice broadcast (or waiting for more chunks)
if [.playing, .buffering].contains(viewModel.context.viewState.playbackState), viewModel.context.viewState.broadcastState != .stopped {
viewModel.context.send(viewAction: .pause)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ import Foundation
}
}

@objc public func pausePlayingInProgressVoiceBroadcast() {
coordinatorsForEventIdentifiers.forEach { _, coordinator in
coordinator.pausePlayingInProgressVoiceBroadcast()
}
}

private func handleEvent(event: MXEvent, direction: MXTimelineDirection, customObject: Any?) {
if direction == .backwards {
// ignore backwards events
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import Combine
import SwiftUI
import MediaPlayer

// TODO: VoiceBroadcastPlaybackViewModel must be revisited in order to not depend on MatrixSDK
// We need a VoiceBroadcastPlaybackServiceProtocol and VoiceBroadcastAggregatorProtocol
Expand Down Expand Up @@ -323,6 +324,7 @@ class VoiceBroadcastPlaybackViewModel: VoiceBroadcastPlaybackViewModelType, Voic
// Init and start the player on the first chunk
let audioPlayer = self.mediaServiceProvider.audioPlayerForIdentifier(result.eventIdentifier)
audioPlayer.registerDelegate(self)
self.mediaServiceProvider.registerNowPlayingInfoDelegate(self, forPlayer: audioPlayer)

audioPlayer.loadContentFromURL(result.url, displayName: chunk.attachment.originalFileName)
self.audioPlayer = audioPlayer
Expand Down Expand Up @@ -496,6 +498,7 @@ extension VoiceBroadcastPlaybackViewModel: VoiceMessageAudioPlayerDelegate {
state.playbackState = .stopped
state.playingState.isLive = false
audioPlayer.deregisterDelegate(self)
self.mediaServiceProvider.deregisterNowPlayingInfoDelegate(forPlayer: audioPlayer)
self.audioPlayer = nil
}

Expand All @@ -508,3 +511,48 @@ extension VoiceBroadcastPlaybackViewModel: VoiceMessageAudioPlayerDelegate {
stopIfVoiceBroadcastOver()
}
}

// MARK: - VoiceMessageNowPlayingInfoDelegate

extension VoiceBroadcastPlaybackViewModel: VoiceMessageNowPlayingInfoDelegate {

func shouldSetupRemoteCommandCenter(audioPlayer player: VoiceMessageAudioPlayer) -> Bool {
guard BuildSettings.allowBackgroundAudioMessagePlayback, audioPlayer != nil, audioPlayer === player else {
return false
}

// we should setup the remote command center only for ended voice broadcast because we won't get new chunk if the app is in background.
return state.broadcastState == .stopped
}

func shouldDisconnectFromNowPlayingInfoCenter(audioPlayer player: VoiceMessageAudioPlayer) -> Bool {
guard BuildSettings.allowBackgroundAudioMessagePlayback, audioPlayer != nil, audioPlayer === player else {
return true
}

// we should disconnect from the now playing info center if the playback is stopped or if the broadcast is in progress
return state.playbackState == .stopped || state.broadcastState != .stopped
}

func updateNowPlayingInfoCenter(forPlayer player: VoiceMessageAudioPlayer) {
guard audioPlayer != nil, audioPlayer === player else {
return
}

// Don't update the NowPlayingInfoCenter for live broadcasts
guard state.broadcastState == .stopped else {
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
return
}

let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
nowPlayingInfoCenter.nowPlayingInfo = [
// Title
MPMediaItemPropertyTitle: VectorL10n.voiceBroadcastPlaybackLockScreenPlaceholder,
// Duration
MPMediaItemPropertyPlaybackDuration: (state.playingState.duration / 1000.0) as Any,
// Elapsed time
MPNowPlayingInfoPropertyElapsedPlaybackTime: (state.bindings.progress / 1000.0) as Any,
]
}
}
1 change: 1 addition & 0 deletions changelog.d/pr-7257.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Voice Broadcast: The Now Playing Info Center now displays a voice broadcast instead of a voice message when a user is listening to a voice broadcast.

0 comments on commit a9ff128

Please sign in to comment.