Add Voice Broadcast left time countdown

This commit is contained in:
Philippe Loriaux
2022-11-23 08:43:22 +01:00
parent 8e30ef5a7a
commit 071f5181ce
12 changed files with 95 additions and 3 deletions
@@ -34,7 +34,13 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
private var chunkFile: AVAudioFile! = nil
private var chunkFrames: AVAudioFrameCount = 0
private var chunkFileNumber: Int = 0
private var currentElapsedTime: UInt = 0 // Time in seconds.
private var currentRemainingTime: UInt { // Time in seconds.
BuildSettings.voiceBroadcastMaxLength - currentElapsedTime
}
private var elapsedTimeTimer: Timer?
// MARK: Public
weak var serviceDelegate: VoiceBroadcastRecorderServiceDelegate?
@@ -67,12 +73,14 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
}
try audioEngine.start()
startTimer()
// Disable the sleep mode during the recording until we are able to handle it
UIApplication.shared.isIdleTimerDisabled = true
} catch {
MXLog.debug("[VoiceBroadcastRecorderService] startRecordingVoiceBroadcast error", context: error)
stopRecordingVoiceBroadcast()
invalidateTimer()
}
}
@@ -81,6 +89,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
audioEngine.stop()
audioEngine.inputNode.removeTap(onBus: audioNodeBus)
UIApplication.shared.isIdleTimerDisabled = false
invalidateTimer()
voiceBroadcastService?.stopVoiceBroadcast(success: { [weak self] _ in
MXLog.debug("[VoiceBroadcastRecorderService] Stopped")
@@ -110,6 +119,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
func pauseRecordingVoiceBroadcast() {
audioEngine.pause()
UIApplication.shared.isIdleTimerDisabled = false
invalidateTimer()
voiceBroadcastService?.pauseVoiceBroadcast(success: { [weak self] _ in
guard let self = self else { return }
@@ -126,6 +136,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
func resumeRecordingVoiceBroadcast() {
try? audioEngine.start()
startTimer()
voiceBroadcastService?.resumeVoiceBroadcast(success: { [weak self] _ in
guard let self = self else { return }
@@ -143,12 +154,14 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
private func resetValues() {
chunkFrames = 0
chunkFileNumber = 0
currentElapsedTime = 0
}
/// Release the service
private func tearDownVoiceBroadcastService() {
resetValues()
session.tearDownVoiceBroadcastService()
invalidateTimer()
do {
try AVAudioSession.sharedInstance().setActive(false)
@@ -157,6 +170,31 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
}
}
/// Start ElapsedTimeTimer.
private func startTimer() {
elapsedTimeTimer = Timer.scheduledTimer(timeInterval: 1.0,
target: self,
selector: #selector(updateCurrentElapsedTimeValue),
userInfo: nil,
repeats: true)
}
/// Invalidate ElapsedTimeTimer.
private func invalidateTimer() {
elapsedTimeTimer?.invalidate()
elapsedTimeTimer = nil
}
/// Update currentElapsedTime value.
@objc private func updateCurrentElapsedTimeValue() {
guard currentRemainingTime > 0 else {
stopRecordingVoiceBroadcast()
return
}
currentElapsedTime += 1
serviceDelegate?.voiceBroadcastRecorderService(self, didUpdateRemainingTime: self.currentRemainingTime)
}
/// Write audio buffer to chunk file.
private func writeBuffer(_ buffer: AVAudioPCMBuffer) {
let sampleRate = buffer.format.sampleRate
@@ -18,6 +18,7 @@ import Foundation
protocol VoiceBroadcastRecorderServiceDelegate: AnyObject {
func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateState state: VoiceBroadcastRecorderState)
func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateRemainingTime remainingTime: UInt)
}
protocol VoiceBroadcastRecorderServiceProtocol {
@@ -53,6 +53,16 @@ struct VoiceBroadcastRecorderView: View {
} icon: {
Image(uiImage: Asset.Images.voiceBroadcastTileLive.image)
}
if let remainingTimeLabel = viewModel.viewState.currentRecordingState.remainingTimeLabel {
Label {
Text(remainingTimeLabel)
.foregroundColor(theme.colors.secondaryContent)
.font(theme.fonts.caption1)
} icon: {
Image(uiImage: Asset.Images.voiceBroadcastTimeLeft.image)
}
}
}.frame(maxWidth: .infinity, alignment: .leading)
Label {
@@ -35,9 +35,15 @@ struct VoiceBroadcastRecorderDetails {
let avatarData: AvatarInputProtocol
}
struct VoiceBroadcastRecordingState {
var remainingTime: UInt
var remainingTimeLabel: String?
}
struct VoiceBroadcastRecorderViewState: BindableState {
var details: VoiceBroadcastRecorderDetails
var recordingState: VoiceBroadcastRecorderState
var currentRecordingState: VoiceBroadcastRecordingState
var bindings: VoiceBroadcastRecorderViewStateBindings
}
@@ -32,7 +32,8 @@ enum MockVoiceBroadcastRecorderScreenState: MockScreenState, CaseIterable {
var screenView: ([Any], AnyView) {
let details = VoiceBroadcastRecorderDetails(senderDisplayName: "", avatarData: AvatarInput(mxContentUri: "", matrixItemId: "!fakeroomid:matrix.org", displayName: "The name of the room"))
let viewModel = MockVoiceBroadcastRecorderViewModel(initialViewState: VoiceBroadcastRecorderViewState(details: details, recordingState: .started, bindings: VoiceBroadcastRecorderViewStateBindings()))
let recordingState = VoiceBroadcastRecordingState(remainingTime: BuildSettings.voiceBroadcastMaxLength)
let viewModel = MockVoiceBroadcastRecorderViewModel(initialViewState: VoiceBroadcastRecorderViewState(details: details, recordingState: .started, currentRecordingState: recordingState, bindings: VoiceBroadcastRecorderViewStateBindings()))
return (
[false, viewModel],
@@ -34,8 +34,10 @@ class VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderViewModelType, Voic
init(details: VoiceBroadcastRecorderDetails,
recorderService: VoiceBroadcastRecorderServiceProtocol) {
self.voiceBroadcastRecorderService = recorderService
let currentRecordingState = VoiceBroadcastRecordingState(remainingTime: BuildSettings.voiceBroadcastMaxLength)
super.init(initialViewState: VoiceBroadcastRecorderViewState(details: details,
recordingState: .stopped,
currentRecordingState: currentRecordingState,
bindings: VoiceBroadcastRecorderViewStateBindings()))
self.voiceBroadcastRecorderService.serviceDelegate = self
@@ -77,10 +79,23 @@ class VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderViewModelType, Voic
self.state.recordingState = .resumed
voiceBroadcastRecorderService.resumeRecordingVoiceBroadcast()
}
private func updateRemainingTime(_ remainingTime: UInt) {
let time = TimeInterval(Double(remainingTime))
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .abbreviated
state.currentRecordingState.remainingTime = remainingTime
state.currentRecordingState.remainingTimeLabel = VectorL10n.voiceBroadcastTimeLeft(formatter.string(from: time) ?? "0s")
}
}
extension VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderServiceDelegate {
func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateState state: VoiceBroadcastRecorderState) {
self.state.recordingState = state
}
func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateRemainingTime remainingTime: UInt) {
self.updateRemainingTime(remainingTime)
}
}