Merge pull request #7103 from vector-im/phlpro/voicebroadcast_left_time

Add the left time in the Voice Broadcast tile recorder
This commit is contained in:
Phl-Pro
2022-11-25 09:28:21 +01:00
committed by GitHub
15 changed files with 118 additions and 14 deletions
+1 -1
View File
@@ -409,7 +409,7 @@ final class BuildSettings: NSObject {
// MARK: - Voice Broadcast // MARK: - Voice Broadcast
static let voiceBroadcastChunkLength: Int = 120 static let voiceBroadcastChunkLength: Int = 120
static let voiceBroadcastMaxLength: UInt64 = 144000 static let voiceBroadcastMaxLength: UInt = 14400 // 240min.
// MARK: - MXKAppSettings // MARK: - MXKAppSettings
static let enableBotCreation: Bool = false static let enableBotCreation: Bool = false
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "voice_broadcast_time_left.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 1H6V2.33333H10V1ZM7.33333 9.66667H8.66667V5.66667H7.33333V9.66667ZM12.6867 5.26L13.6333 4.31333C13.3467 3.97333 13.0333 3.65333 12.6933 3.37333L11.7467 4.32C10.7133 3.49333 9.41333 3 8 3C4.68667 3 2 5.68667 2 9C2 12.3133 4.68 15 8 15C11.32 15 14 12.3133 14 9C14 7.58667 13.5067 6.28667 12.6867 5.26ZM8 13.6667C5.42 13.6667 3.33333 11.58 3.33333 9C3.33333 6.42 5.42 4.33333 8 4.33333C10.58 4.33333 12.6667 6.42 12.6667 9C12.6667 11.58 10.58 13.6667 8 13.6667Z" fill="#737D8C"/>
</svg>

After

Width:  |  Height:  |  Size: 593 B

+1
View File
@@ -2202,6 +2202,7 @@ Tap the + to start adding people.";
"voice_broadcast_playback_loading_error" = "Unable to play this voice broadcast."; "voice_broadcast_playback_loading_error" = "Unable to play this voice broadcast.";
"voice_broadcast_live" = "Live"; "voice_broadcast_live" = "Live";
"voice_broadcast_tile" = "Voice broadcast"; "voice_broadcast_tile" = "Voice broadcast";
"voice_broadcast_time_left" = "%@ left";
// Mark: - Version check // Mark: - Version check
+1
View File
@@ -347,6 +347,7 @@ internal class Asset: NSObject {
internal static let voiceBroadcastStop = ImageAsset(name: "voice_broadcast_stop") internal static let voiceBroadcastStop = ImageAsset(name: "voice_broadcast_stop")
internal static let voiceBroadcastTileLive = ImageAsset(name: "voice_broadcast_tile_live") internal static let voiceBroadcastTileLive = ImageAsset(name: "voice_broadcast_tile_live")
internal static let voiceBroadcastTileMic = ImageAsset(name: "voice_broadcast_tile_mic") internal static let voiceBroadcastTileMic = ImageAsset(name: "voice_broadcast_tile_mic")
internal static let voiceBroadcastTimeLeft = ImageAsset(name: "voice_broadcast_time_left")
internal static let launchScreenLogo = ImageAsset(name: "launch_screen_logo") internal static let launchScreenLogo = ImageAsset(name: "launch_screen_logo")
} }
@objcMembers @objcMembers
+4
View File
@@ -9155,6 +9155,10 @@ public class VectorL10n: NSObject {
public static var voiceBroadcastTile: String { public static var voiceBroadcastTile: String {
return VectorL10n.tr("Vector", "voice_broadcast_tile") return VectorL10n.tr("Vector", "voice_broadcast_tile")
} }
/// %@ left
public static func voiceBroadcastTimeLeft(_ p1: String) -> String {
return VectorL10n.tr("Vector", "voice_broadcast_time_left", p1)
}
/// Can't start a new voice broadcast /// Can't start a new voice broadcast
public static var voiceBroadcastUnauthorizedTitle: String { public static var voiceBroadcastUnauthorizedTitle: String {
return VectorL10n.tr("Vector", "voice_broadcast_unauthorized_title") return VectorL10n.tr("Vector", "voice_broadcast_unauthorized_title")
@@ -65,6 +65,12 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType {
return self.height(for: roomBubbleCellData, fitting: maxWidth) return self.height(for: roomBubbleCellData, fitting: maxWidth)
} }
override func prepareForReuse() {
cleanContentVC()
super.prepareForReuse()
}
// MARK - SizableBaseRoomCellType // MARK - SizableBaseRoomCellType
@@ -173,10 +179,21 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType {
} }
return height return height
} }
private func cleanContentVC() {
contentVC?.removeFromParent()
contentVC?.view.removeFromSuperview()
contentVC?.didMove(toParent: nil)
contentVC = nil
}
// MARK: - Public
func addContentViewController(_ controller: UIViewController, on contentView: UIView) { func addContentViewController(_ controller: UIViewController, on contentView: UIView) {
controller.view.invalidateIntrinsicContentSize() controller.view.invalidateIntrinsicContentSize()
cleanContentVC()
let parent = vc_parentViewController let parent = vc_parentViewController
parent?.addChild(controller) parent?.addChild(controller)
@@ -185,13 +202,4 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType {
contentVC = controller contentVC = controller
} }
override func prepareForReuse() {
contentVC?.removeFromParent()
contentVC?.view.removeFromSuperview()
contentVC?.didMove(toParent: nil)
contentVC = nil
super.prepareForReuse()
}
} }
@@ -34,7 +34,13 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
private var chunkFile: AVAudioFile! = nil private var chunkFile: AVAudioFile! = nil
private var chunkFrames: AVAudioFrameCount = 0 private var chunkFrames: AVAudioFrameCount = 0
private var chunkFileNumber: Int = 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 // MARK: Public
weak var serviceDelegate: VoiceBroadcastRecorderServiceDelegate? weak var serviceDelegate: VoiceBroadcastRecorderServiceDelegate?
@@ -67,12 +73,14 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
} }
try audioEngine.start() try audioEngine.start()
startTimer()
// Disable the sleep mode during the recording until we are able to handle it // Disable the sleep mode during the recording until we are able to handle it
UIApplication.shared.isIdleTimerDisabled = true UIApplication.shared.isIdleTimerDisabled = true
} catch { } catch {
MXLog.debug("[VoiceBroadcastRecorderService] startRecordingVoiceBroadcast error", context: error) MXLog.debug("[VoiceBroadcastRecorderService] startRecordingVoiceBroadcast error", context: error)
stopRecordingVoiceBroadcast() stopRecordingVoiceBroadcast()
invalidateTimer()
} }
} }
@@ -81,6 +89,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
audioEngine.stop() audioEngine.stop()
audioEngine.inputNode.removeTap(onBus: audioNodeBus) audioEngine.inputNode.removeTap(onBus: audioNodeBus)
UIApplication.shared.isIdleTimerDisabled = false UIApplication.shared.isIdleTimerDisabled = false
invalidateTimer()
voiceBroadcastService?.stopVoiceBroadcast(success: { [weak self] _ in voiceBroadcastService?.stopVoiceBroadcast(success: { [weak self] _ in
MXLog.debug("[VoiceBroadcastRecorderService] Stopped") MXLog.debug("[VoiceBroadcastRecorderService] Stopped")
@@ -110,6 +119,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
func pauseRecordingVoiceBroadcast() { func pauseRecordingVoiceBroadcast() {
audioEngine.pause() audioEngine.pause()
UIApplication.shared.isIdleTimerDisabled = false UIApplication.shared.isIdleTimerDisabled = false
invalidateTimer()
voiceBroadcastService?.pauseVoiceBroadcast(success: { [weak self] _ in voiceBroadcastService?.pauseVoiceBroadcast(success: { [weak self] _ in
guard let self = self else { return } guard let self = self else { return }
@@ -126,6 +136,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
func resumeRecordingVoiceBroadcast() { func resumeRecordingVoiceBroadcast() {
try? audioEngine.start() try? audioEngine.start()
startTimer()
voiceBroadcastService?.resumeVoiceBroadcast(success: { [weak self] _ in voiceBroadcastService?.resumeVoiceBroadcast(success: { [weak self] _ in
guard let self = self else { return } guard let self = self else { return }
@@ -143,12 +154,14 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
private func resetValues() { private func resetValues() {
chunkFrames = 0 chunkFrames = 0
chunkFileNumber = 0 chunkFileNumber = 0
currentElapsedTime = 0
} }
/// Release the service /// Release the service
private func tearDownVoiceBroadcastService() { private func tearDownVoiceBroadcastService() {
resetValues() resetValues()
session.tearDownVoiceBroadcastService() session.tearDownVoiceBroadcastService()
invalidateTimer()
do { do {
try AVAudioSession.sharedInstance().setActive(false) 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. /// Write audio buffer to chunk file.
private func writeBuffer(_ buffer: AVAudioPCMBuffer) { private func writeBuffer(_ buffer: AVAudioPCMBuffer) {
let sampleRate = buffer.format.sampleRate let sampleRate = buffer.format.sampleRate
@@ -18,6 +18,7 @@ import Foundation
protocol VoiceBroadcastRecorderServiceDelegate: AnyObject { protocol VoiceBroadcastRecorderServiceDelegate: AnyObject {
func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateState state: VoiceBroadcastRecorderState) func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateState state: VoiceBroadcastRecorderState)
func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateRemainingTime remainingTime: UInt)
} }
protocol VoiceBroadcastRecorderServiceProtocol { protocol VoiceBroadcastRecorderServiceProtocol {
@@ -53,6 +53,14 @@ struct VoiceBroadcastRecorderView: View {
} icon: { } icon: {
Image(uiImage: Asset.Images.voiceBroadcastTileLive.image) Image(uiImage: Asset.Images.voiceBroadcastTileLive.image)
} }
Label {
Text(viewModel.viewState.currentRecordingState.remainingTimeLabel)
.foregroundColor(theme.colors.secondaryContent)
.font(theme.fonts.caption1)
} icon: {
Image(uiImage: Asset.Images.voiceBroadcastTimeLeft.image)
}
}.frame(maxWidth: .infinity, alignment: .leading) }.frame(maxWidth: .infinity, alignment: .leading)
Label { Label {
@@ -35,9 +35,15 @@ struct VoiceBroadcastRecorderDetails {
let avatarData: AvatarInputProtocol let avatarData: AvatarInputProtocol
} }
struct VoiceBroadcastRecordingState {
var remainingTime: UInt
var remainingTimeLabel: String
}
struct VoiceBroadcastRecorderViewState: BindableState { struct VoiceBroadcastRecorderViewState: BindableState {
var details: VoiceBroadcastRecorderDetails var details: VoiceBroadcastRecorderDetails
var recordingState: VoiceBroadcastRecorderState var recordingState: VoiceBroadcastRecorderState
var currentRecordingState: VoiceBroadcastRecordingState
var bindings: VoiceBroadcastRecorderViewStateBindings var bindings: VoiceBroadcastRecorderViewStateBindings
} }
@@ -32,7 +32,8 @@ enum MockVoiceBroadcastRecorderScreenState: MockScreenState, CaseIterable {
var screenView: ([Any], AnyView) { var screenView: ([Any], AnyView) {
let details = VoiceBroadcastRecorderDetails(senderDisplayName: "", avatarData: AvatarInput(mxContentUri: "", matrixItemId: "!fakeroomid:matrix.org", displayName: "The name of the room")) 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, remainingTimeLabel: "1h 20m 47s left")
let viewModel = MockVoiceBroadcastRecorderViewModel(initialViewState: VoiceBroadcastRecorderViewState(details: details, recordingState: .started, currentRecordingState: recordingState, bindings: VoiceBroadcastRecorderViewStateBindings()))
return ( return (
[false, viewModel], [false, viewModel],
@@ -34,8 +34,10 @@ class VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderViewModelType, Voic
init(details: VoiceBroadcastRecorderDetails, init(details: VoiceBroadcastRecorderDetails,
recorderService: VoiceBroadcastRecorderServiceProtocol) { recorderService: VoiceBroadcastRecorderServiceProtocol) {
self.voiceBroadcastRecorderService = recorderService self.voiceBroadcastRecorderService = recorderService
let currentRecordingState = VoiceBroadcastRecorderViewModel.currentRecordingState(from: BuildSettings.voiceBroadcastMaxLength)
super.init(initialViewState: VoiceBroadcastRecorderViewState(details: details, super.init(initialViewState: VoiceBroadcastRecorderViewState(details: details,
recordingState: .stopped, recordingState: .stopped,
currentRecordingState: currentRecordingState,
bindings: VoiceBroadcastRecorderViewStateBindings())) bindings: VoiceBroadcastRecorderViewStateBindings()))
self.voiceBroadcastRecorderService.serviceDelegate = self self.voiceBroadcastRecorderService.serviceDelegate = self
@@ -77,10 +79,27 @@ class VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderViewModelType, Voic
self.state.recordingState = .resumed self.state.recordingState = .resumed
voiceBroadcastRecorderService.resumeRecordingVoiceBroadcast() voiceBroadcastRecorderService.resumeRecordingVoiceBroadcast()
} }
private func updateRemainingTime(_ remainingTime: UInt) {
state.currentRecordingState = VoiceBroadcastRecorderViewModel.currentRecordingState(from: remainingTime)
}
private static func currentRecordingState(from remainingTime: UInt) -> VoiceBroadcastRecordingState {
let time = TimeInterval(Double(remainingTime))
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .abbreviated
return VoiceBroadcastRecordingState(remainingTime: remainingTime,
remainingTimeLabel: VectorL10n.voiceBroadcastTimeLeft(formatter.string(from: time) ?? "0s"))
}
} }
extension VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderServiceDelegate { extension VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderServiceDelegate {
func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateState state: VoiceBroadcastRecorderState) { func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateState state: VoiceBroadcastRecorderState) {
self.state.recordingState = state self.state.recordingState = state
} }
func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateRemainingTime remainingTime: UInt) {
self.updateRemainingTime(remainingTime)
}
} }
+1
View File
@@ -0,0 +1 @@
Add the left time in the Voice Broadcast tile recorder.
+1
View File
@@ -0,0 +1 @@
Fix scroll issues with VoiceBroadcast and Poll cells