From af9339fe23567427da97c7ab9da4c0017da1875f Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Fri, 10 Feb 2023 09:50:45 +0100 Subject: [PATCH] Add voiceRecorder --- .../Views/ChatConversationViewSwift.swift | 130 +++++++++++++----- Classes/Swift/Chat/Views/MessageView.swift | 2 + .../Util/BackActionsNavigationView.swift | 42 ++++++ Classes/Swift/Voip/Theme/VoipTheme.swift | 1 + 4 files changed, 138 insertions(+), 37 deletions(-) diff --git a/Classes/Swift/Chat/Views/ChatConversationViewSwift.swift b/Classes/Swift/Chat/Views/ChatConversationViewSwift.swift index 3253359e9..985a74d38 100644 --- a/Classes/Swift/Chat/Views/ChatConversationViewSwift.swift +++ b/Classes/Swift/Chat/Views/ChatConversationViewSwift.swift @@ -154,6 +154,10 @@ import AVFoundation @objc var sharingMedia : Bool = false var isVoiceRecording : Bool = false + var voiceRecorder : OpaquePointer? = nil + var showVoiceRecorderView : Bool = false + var vrRecordTimer = Timer() + var isPendingVoiceRecord = false override func viewDidLoad() { super.viewDidLoad( @@ -188,6 +192,9 @@ import AVFoundation tableController.toggleSelectionButton = action1SelectAllButton messageView.sendButton.onClickAction = onSendClick messageView.pictureButton.onClickAction = alertAction + messageView.voiceRecordButton.onClickAction = onVrStart + recordingStopButton.onClickAction = stopVoiceRecording + recordingDeleteButton.onClickAction = cancelVoiceRecording chatRoomDelegate = ChatRoomDelegateStub( @@ -859,6 +866,19 @@ import AVFoundation } } + func setRecordingVisible(visible : Bool) { + var isBottomOfView = false + if (tableController.tableView.contentOffset.y + 1) >= (tableController.tableView.contentSize.height - tableController.tableView.frame.size.height) { + isBottomOfView = true + } + UIView.animate(withDuration: 0.3, animations: { + self.recordingView.isHidden = visible + }) + if(isBottomOfView){ + tableController.scroll(toBottom: false) + } + } + func initReplyView(_ visible: Bool, message: OpaquePointer?) { if visible { let addresses = ChatMessage.getSwiftObject(cObject: message!).fromAddress @@ -1596,8 +1616,7 @@ import AVFoundation return isBasic } - /* - func onVrStart(_ sender: Any) { + func onVrStart() { if isVoiceRecording { stopVoiceRecording() } else { @@ -1606,66 +1625,97 @@ import AVFoundation } func createVoiceRecorder() { - let p = linphone_core_create_recorder_params(LC) + let p = linphone_core_create_recorder_params(LinphoneManager.getLc()) linphone_recorder_params_set_file_format(p, LinphoneRecorderFileFormatMkv) - voiceRecorder = linphone_core_create_recorder(LC, p) + voiceRecorder = linphone_core_create_recorder(LinphoneManager.getLc(), p) } func startVoiceRecording() { UIApplication.shared.isIdleTimerDisabled = true - - if !voiceRecorder { + setRecordingVisible(visible: false) + if (voiceRecorder == nil) { createVoiceRecorder() } - CallManager.instance.activateAudioSession() - toggleRecord.selected = true - vrPlayButton.setImage(UIImage(named: "vr_stop"), for: .normal) + CallManager.instance().activateAudioSession() + + messageView.voiceRecordButton.isSelected = true + + recordingStopButton.isHidden = false + recordingPlayButton.isHidden = true showVoiceRecorderView = true - updateFramesInclRecordingAndReplyView() isVoiceRecording = true - vrWaveMaskPlayer.frame = CGRect.zero + //vrWaveMaskPlayer.frame = CGRect.zero switch linphone_recorder_get_state(voiceRecorder) { case LinphoneRecorderClosed: - let filename = "\(LinphoneManager.imagesDirectory())/voice-recording-\(UUID().uuidString).mkv" - linphone_recorder_open(voiceRecorder, filename.utf8CString) + let filename = "\(String(describing: LinphoneManager.imagesDirectory()))/voice-recording-\(UUID().uuidString).mkv" + linphone_recorder_open(voiceRecorder, filename) linphone_recorder_start(voiceRecorder) - LOGW("[Chat Message Sending] Recorder is closed opening it with %@", filename) + print("[Chat Message Sending] Recorder is closed opening it with \(filename)") case LinphoneRecorderRunning: - LOGW("[Chat Message Sending] Recorder is already recording") + print("[Chat Message Sending] Recorder is already recording") case LinphoneRecorderPaused: - LOGW("[Chat Message Sending] Recorder isn't closed, resuming recording") + print("[Chat Message Sending] Recorder isn't closed, resuming recording") linphone_recorder_start(voiceRecorder) default: break } - vrWaveMask.frame = vrWave.frame - vrDurationLabel.text = formattedDuration(linphone_recorder_get_duration(voiceRecorder)) - vrRecordTimer = Timer.scheduledTimer( - timeInterval: 1.0, - target: self, - selector: Selector("voiceRecordTimerUpdate"), - userInfo: nil, - repeats: true) + recordingWaveImageMask.frame = recordingWaveImage.frame + recordingDurationTextView.text = ChatConversationViewSwift.formattedDuration(Int(linphone_recorder_get_duration(voiceRecorder))) + vrRecordTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in + self.voiceRecordTimerUpdate() + } + } + + func voiceRecordTimerUpdate() { + let recorderDuration = linphone_recorder_get_duration(voiceRecorder) + if recorderDuration > LinphoneManager.instance().lpConfigInt(forKey: "voice_recording_max_duration", withDefault: 60000) { + print("[Chat Message Sending] Max duration for voice recording exceeded, stopping. (max = %d)", LinphoneManager.instance().lpConfigInt(forKey: "voice_recording_max_duration", withDefault: 60000)) + stopVoiceRecording() + } else { + recordingDurationTextView.text = ChatConversationViewSwift.formattedDuration(Int(linphone_recorder_get_duration(voiceRecorder))) + + var r = recordingWaveImageMask.frame + //recordingWaveImageMask.frame = CGRect(x: r.origin.x+r.size.width, y: r.origin.y, width: 0, height: r.size.height) + //r.origin.x += 20 + //r.size.width -= 20 + //r = CGRect(x: r.origin.x-200, y: r.origin.y, width: r.size.width+200, height: r.size.height) + r = CGRect(x: r.origin.x-200, y: r.origin.y, width: r.size.width, height: r.size.height) + if r.origin.x > recordingWaveImage.frame.size.width { + r = recordingWaveImage.frame + recordingWaveImageMask.frame = r + } else { + UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveLinear, animations: { + //self.recordingWaveImageMask.frame = r + self.recordingWaveImageMask.transform = CGAffineTransform(scaleX: 0.1, y: 1) + //self.recordingWaveImageMask.transform = CGAffineTransform(translationX: 100, y: 1) + //self.recordingWaveImageMask.frame = CGRect(x: 0, y: r.origin.y, width: 10, height: r.size.height) + }) { finished in + } + } + + } } func stopVoiceRecording() { UIApplication.shared.isIdleTimerDisabled = false - if voiceRecorder && linphone_recorder_get_state(voiceRecorder) == LinphoneRecorderRunning { - LOGI("[Chat Message Sending] Pausing / closing voice recorder") + if (voiceRecorder != nil) && linphone_recorder_get_state(voiceRecorder) == LinphoneRecorderRunning { + print("[Chat Message Sending] Pausing / closing voice recorder") linphone_recorder_pause(voiceRecorder) linphone_recorder_close(voiceRecorder) - vrDurationLabel.text = formattedDuration(linphone_recorder_get_duration(voiceRecorder)) + recordingDurationTextView.text = ChatConversationViewSwift.formattedDuration(Int(linphone_recorder_get_duration(voiceRecorder))) } isVoiceRecording = false - if LinphoneManager.instance.lpConfigBool(forKey: "voice_recording_send_right_away", withDefault: false) { - onSendClick(nil) + if LinphoneManager.instance().lpConfigBool(forKey: "voice_recording_send_right_away", withDefault: false) { + onSendClick() } - vrPlayButton.setImage(UIImage(named: "vr_play"), for: .normal) - toggleRecord.selected = false - vrWaveMask.frame = CGRect.zero + recordingStopButton.isHidden = true + recordingPlayButton.isHidden = false + + messageView.voiceRecordButton.isSelected = false + recordingWaveImageMask.frame = CGRect.zero vrRecordTimer.invalidate() isPendingVoiceRecord = linphone_recorder_get_duration(voiceRecorder) > 0 setSendButtonState() @@ -1674,18 +1724,24 @@ import AVFoundation func cancelVoiceRecording() { UIApplication.shared.isIdleTimerDisabled = false + setRecordingVisible(visible: true) + recordingStopButton.isHidden = false + recordingPlayButton.isHidden = true showVoiceRecorderView = false - toggleRecord.selected = false - updateFramesInclRecordingAndReplyView() + messageView.voiceRecordButton.isSelected = false isPendingVoiceRecord = false isVoiceRecording = false - if voiceRecorder && linphone_recorder_get_state(voiceRecorder) != LinphoneRecorderClosed { + if (voiceRecorder != nil) && linphone_recorder_get_state(voiceRecorder) != LinphoneRecorderClosed { linphone_recorder_close(voiceRecorder) let recordingFile = linphone_recorder_get_file(voiceRecorder) if let recordingFile { - AppManager.removeFile(withFile: String(utf8String: recordingFile)) + AppManager.removeFile(file: String(utf8String: recordingFile)!) } } setSendButtonState() - }*/ + } + + func setSendButtonState() { + //sendButton.enabled = !isVoiceRecording && ((isPendingVoiceRecord && linphone_recorder_get_duration(voiceRecorder) > 0) || messageField.text().length() > 0 || fileContext.count > 0) + } } diff --git a/Classes/Swift/Chat/Views/MessageView.swift b/Classes/Swift/Chat/Views/MessageView.swift index 56f5e8665..dfb860a40 100644 --- a/Classes/Swift/Chat/Views/MessageView.swift +++ b/Classes/Swift/Chat/Views/MessageView.swift @@ -60,6 +60,8 @@ class MessageView: UIView, UITextViewDelegate { addSubview(voiceRecordButton) voiceRecordButton.toRightOf(pictureButton, withLeftMargin: 10).matchParentHeight().done() voiceRecordButton.size(w: 30, h: 30).done() + voiceRecordButton.setImage(UIImage(named:"vr_off.png"), for: .normal) + voiceRecordButton.setImage(UIImage(named:"vr_on.png"), for: .selected) voiceRecordButton.onClickAction = action3 addSubview(ephemeralIndicator) diff --git a/Classes/Swift/Util/BackActionsNavigationView.swift b/Classes/Swift/Util/BackActionsNavigationView.swift index 7aa6c75a3..35d92a6f9 100644 --- a/Classes/Swift/Util/BackActionsNavigationView.swift +++ b/Classes/Swift/Util/BackActionsNavigationView.swift @@ -44,6 +44,14 @@ import SnapKit let replyContentForMeetingTextView = StyledLabel(VoipTheme.chat_conversation_reply_content) let replyDeleteButton = CallControlButton(width: 22, height: 22, buttonTheme:VoipTheme.nav_black_button("reply_cancel")) let replyMeetingSchedule = UIImageView() + let recordingView = UIView() + let recordingDeleteButton = CallControlButton(width: 40, height: 40, buttonTheme:VoipTheme.nav_black_button("delete_default")) + let recordingPlayButton = CallControlButton(width: 40, height: 40, buttonTheme:VoipTheme.nav_black_button("vr_play")) + let recordingStopButton = CallControlButton(width: 40, height: 40, buttonTheme:VoipTheme.nav_black_button("vr_stop")) + let recordingWaveView = UIView() + let recordingDurationTextView = StyledLabel(VoipTheme.chat_conversation_recording_duration) + let recordingWaveImage = UIImageView(image: UIImage(named: "vr_wave.png")) + let recordingWaveImageMask = UIView() let messageView = MessageView() let mediaSelector = UIView() let mediaSelectorReply = UIView() @@ -210,6 +218,40 @@ import SnapKit replyBubble.addSubview(replyDeleteButton) replyDeleteButton.alignParentRight(withMargin: 15).centerY().done() + stackView.addArrangedSubview(recordingView) + recordingView.height(top_bar_height).wrapContentY().matchParentSideBorders().done() + recordingView.backgroundColor = VoipTheme.voipToolbarBackgroundColor.get() + recordingView.isHidden = true + + recordingView.addSubview(recordingDeleteButton) + recordingDeleteButton.alignParentLeft(withMargin: 10).matchParentHeight().done() + + recordingView.addSubview(recordingPlayButton) + recordingPlayButton.alignParentRight(withMargin: 10).matchParentHeight().done() + recordingPlayButton.isHidden = true + + recordingView.addSubview(recordingStopButton) + recordingStopButton.alignParentRight(withMargin: 10).matchParentHeight().done() + + recordingView.addSubview(recordingWaveView) + recordingWaveView.toRightOf(recordingDeleteButton, withLeftMargin: 10).toLeftOf(recordingStopButton, withRightMargin: 10).alignParentTop(withMargin: 10).alignParentBottom(withMargin: 10).done() + recordingWaveView.layer.cornerRadius = 5 + recordingWaveView.backgroundColor = VoipTheme.backgroundWhiteBlack.get() + + recordingWaveView.addSubview(recordingDurationTextView) + recordingDurationTextView.alignParentRight(withMargin: 10).matchParentHeight().done() + + recordingWaveView.addSubview(recordingWaveImage) + recordingWaveImage.alignParentTop(withMargin: 10).alignParentBottom(withMargin: 10).alignParentLeft(withMargin: 10).toLeftOf(recordingDurationTextView, withRightMargin: 10).done() + + recordingWaveView.addSubview(recordingWaveImageMask) + recordingWaveImageMask.alignParentTop(withMargin: 10).alignParentBottom(withMargin: 10).alignParentLeft(withMargin: 10).done() //toLeftOf(recordingDurationTextView, withRightMargin: 10).done() + recordingWaveImageMask.rightAnchor.constraint(equalTo: recordingDurationTextView.leftAnchor, constant: -10).isActive = true + recordingWaveImageMask.backgroundColor = .blue //VoipTheme.backgroundWhiteBlack.get() + + + + stackView.addArrangedSubview(mediaSelector) mediaSelector.height(top_bar_height*2).matchParentSideBorders().done() diff --git a/Classes/Swift/Voip/Theme/VoipTheme.swift b/Classes/Swift/Voip/Theme/VoipTheme.swift index fecc92e11..6da3bdf65 100644 --- a/Classes/Swift/Voip/Theme/VoipTheme.swift +++ b/Classes/Swift/Voip/Theme/VoipTheme.swift @@ -150,6 +150,7 @@ import UIKit static let chat_conversation_operation_in_progress_wait = TextStyle(fgColor: LightDarkColor(primary_color,primary_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Bold", size: 18.0) static let chat_conversation_reply_label = TextStyle(fgColor: LightDarkColor(voip_dark_gray,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 14.0) static let chat_conversation_reply_content = TextStyle(fgColor: LightDarkColor(voip_dark_gray,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 14.0) + static let chat_conversation_recording_duration = TextStyle(fgColor: LightDarkColor(voip_dark_gray,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 20.0) // Buttons Background (State colors)