diff --git a/Classes/Swift/Chat/Views/ChatConversationViewSwift.swift b/Classes/Swift/Chat/Views/ChatConversationViewSwift.swift index f56be089b..dc1a7e417 100644 --- a/Classes/Swift/Chat/Views/ChatConversationViewSwift.swift +++ b/Classes/Swift/Chat/Views/ChatConversationViewSwift.swift @@ -154,11 +154,13 @@ import AVFoundation @objc var sharingMedia : Bool = false var isVoiceRecording : Bool = false - var voiceRecorder : OpaquePointer? = nil + var voiceRecorder : Recorder? = nil var showVoiceRecorderView : Bool = false var vrRecordTimer = Timer() + var vrPlayerTimer = Timer() var isPendingVoiceRecord = false var isPlayingVoiceRecording = false + var linphonePlayer : Player? = nil override func viewDidLoad() { super.viewDidLoad( @@ -196,6 +198,8 @@ import AVFoundation messageView.voiceRecordButton.onClickAction = onVrStart recordingStopButton.onClickAction = stopVoiceRecording recordingDeleteButton.onClickAction = cancelVoiceRecording + recordingPlayButton.onClickAction = onvrPlayPauseStop + recordingStopButton.onClickAction = onvrPlayPauseStop chatRoomDelegate = ChatRoomDelegateStub( @@ -233,6 +237,8 @@ import AVFoundation self.handlePendingTransferIfAny() self.shareFile() + + initSharedPlayer() } override func viewWillDisappear(_ animated: Bool) { @@ -255,6 +261,8 @@ import AVFoundation if(self.replyBubble.isHidden == false){ self.replyBubble.isHidden = true } + + cancelVoiceRecording() self.mediaCollectionView = [] self.mediaURLCollection = [] @@ -686,13 +694,17 @@ import AVFoundation func onSendClick() { let rootMessage = !replyBubble.isHidden ? linphone_chat_room_create_reply_message(chatRoom?.getCobject, replyMessage) : linphone_chat_room_create_empty_message(chatRoom?.getCobject) - if isPendingVoiceRecord && (voiceRecorder != nil) && (linphone_recorder_get_file(voiceRecorder) != nil) { - let voiceContent = linphone_recorder_create_content(voiceRecorder) - isPendingVoiceRecord = false - cancelVoiceRecording() - //stopVoiceRecordPlayer() - linphone_chat_message_add_content(rootMessage, voiceContent) - } + if isVoiceRecording { + stopVoiceRecording() + } + + if isPendingVoiceRecord && (voiceRecorder != nil) && (linphone_recorder_get_file(voiceRecorder?.getCobject) != nil) { + print("ChatConversationViewSwift onSendClick isPendingVoiceRecord") + let voiceContent = linphone_recorder_create_content(voiceRecorder?.getCobject) + isPendingVoiceRecord = false + cancelVoiceRecording() + linphone_chat_message_add_content(rootMessage, voiceContent) + } if fileContext.count > 0 { let conference = chatRoom!.hasCapability(mask: Int(LinphoneChatRoomCapabilitiesConference.rawValue)) if (linphone_chat_room_get_capabilities(chatRoom?.getCobject) != 0) && conference { @@ -1617,6 +1629,11 @@ import AVFoundation } func onVrStart() { + stopVoiceRecordPlayer() + self.recordingWaveImageMask.isHidden = false + recordingWaveView.progress = 0.0 + recordingWaveView.setProgress(recordingWaveView.progress, animated: false) + self.messageView.sendButton.isEnabled = true if isVoiceRecording { stopVoiceRecording() } else { @@ -1625,9 +1642,14 @@ import AVFoundation } func createVoiceRecorder() { - let p = linphone_core_create_recorder_params(LinphoneManager.getLc()) - linphone_recorder_params_set_file_format(p, LinphoneRecorderFileFormatMkv) - voiceRecorder = linphone_core_create_recorder(LinphoneManager.getLc(), p) + let core = Core.getSwiftObject(cObject: LinphoneManager.getLc()) + do{ + let p = try core.createRecorderParams() + p.fileFormat = RecorderFileFormat.Mkv + voiceRecorder = try core.createRecorder(params: p) + }catch{ + print(error) + } } func startVoiceRecording() { @@ -1648,37 +1670,38 @@ import AVFoundation isVoiceRecording = true //vrWaveMaskPlayer.frame = CGRect.zero - switch linphone_recorder_get_state(voiceRecorder) { + switch linphone_recorder_get_state(voiceRecorder?.getCobject) { case LinphoneRecorderClosed: let filename = "\(String(describing: LinphoneManager.imagesDirectory()))/voice-recording-\(UUID().uuidString).mkv" - linphone_recorder_open(voiceRecorder, filename) - linphone_recorder_start(voiceRecorder) + linphone_recorder_open(voiceRecorder?.getCobject, filename) + linphone_recorder_start(voiceRecorder?.getCobject) print("[Chat Message Sending] Recorder is closed opening it with \(filename)") case LinphoneRecorderRunning: print("[Chat Message Sending] Recorder is already recording") case LinphoneRecorderPaused: print("[Chat Message Sending] Recorder isn't closed, resuming recording") - linphone_recorder_start(voiceRecorder) + linphone_recorder_start(voiceRecorder?.getCobject) default: break } self.recordingWaveImageMask.transform = CGAffineTransform.identity - recordingDurationTextView.text = ChatConversationViewSwift.formattedDuration(Int(linphone_recorder_get_duration(voiceRecorder))) + recordingDurationTextView.isHidden = false + recordingDurationTextView.text = ChatConversationViewSwift.formattedDuration(Int(linphone_recorder_get_duration(voiceRecorder?.getCobject))) vrRecordTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in self.voiceRecordTimerUpdate() } } func voiceRecordTimerUpdate() { - let recorderDuration = linphone_recorder_get_duration(voiceRecorder) + let recorderDuration = linphone_recorder_get_duration(voiceRecorder?.getCobject) if recorderDuration > LinphoneManager.instance().lpConfigInt(forKey: "voice_recording_max_duration", withDefault: 59999) { print("[Chat Message Sending] Max duration for voice recording exceeded, stopping. (max = %d)", LinphoneManager.instance().lpConfigInt(forKey: "voice_recording_max_duration", withDefault: 59999)) stopVoiceRecording() } else { - recordingDurationTextView.text = ChatConversationViewSwift.formattedDuration(Int(linphone_recorder_get_duration(voiceRecorder))) + recordingDurationTextView.text = ChatConversationViewSwift.formattedDuration(Int(linphone_recorder_get_duration(voiceRecorder?.getCobject))) UIView.animate(withDuration: 10.0, delay: 0.0, options: [.repeat], animations: { - self.recordingWaveImageMask.transform = CGAffineTransform(translationX: 95, y: 1).scaledBy(x: 0.01, y: 1) + self.recordingWaveImageMask.transform = CGAffineTransform(translationX: 98, y: 0).scaledBy(x: 0.01, y: 1) }) } @@ -1686,11 +1709,11 @@ import AVFoundation func stopVoiceRecording() { UIApplication.shared.isIdleTimerDisabled = false - if (voiceRecorder != nil) && linphone_recorder_get_state(voiceRecorder) == LinphoneRecorderRunning { + if (voiceRecorder != nil) && linphone_recorder_get_state(voiceRecorder?.getCobject) == LinphoneRecorderRunning { print("[Chat Message Sending] Pausing / closing voice recorder") - linphone_recorder_pause(voiceRecorder) - linphone_recorder_close(voiceRecorder) - recordingDurationTextView.text = ChatConversationViewSwift.formattedDuration(Int(linphone_recorder_get_duration(voiceRecorder))) + linphone_recorder_pause(voiceRecorder?.getCobject) + linphone_recorder_close(voiceRecorder?.getCobject) + recordingDurationTextView.text = ChatConversationViewSwift.formattedDuration(Int(linphone_recorder_get_duration(voiceRecorder?.getCobject))) } isVoiceRecording = false if LinphoneManager.instance().lpConfigBool(forKey: "voice_recording_send_right_away", withDefault: false) { @@ -1702,7 +1725,7 @@ import AVFoundation messageView.voiceRecordButton.isSelected = false recordingWaveImageMask.layer.removeAllAnimations() vrRecordTimer.invalidate() - isPendingVoiceRecord = linphone_recorder_get_duration(voiceRecorder) > 0 + isPendingVoiceRecord = linphone_recorder_get_duration(voiceRecorder?.getCobject) > 0 setSendButtonState() } @@ -1717,21 +1740,22 @@ import AVFoundation messageView.voiceRecordButton.isSelected = false isPendingVoiceRecord = false isVoiceRecording = false - if (voiceRecorder != nil) && linphone_recorder_get_state(voiceRecorder) != LinphoneRecorderClosed { - linphone_recorder_close(voiceRecorder) - let recordingFile = linphone_recorder_get_file(voiceRecorder) + if (voiceRecorder != nil) && linphone_recorder_get_state(voiceRecorder?.getCobject) != LinphoneRecorderClosed { + linphone_recorder_close(voiceRecorder?.getCobject) + let recordingFile = linphone_recorder_get_file(voiceRecorder?.getCobject) if let recordingFile { AppManager.removeFile(file: String(utf8String: recordingFile)!) } } + stopVoiceRecordPlayer() setSendButtonState() } func setSendButtonState() { - //sendButton.enabled = !isVoiceRecording && ((isPendingVoiceRecord && linphone_recorder_get_duration(voiceRecorder) > 0) || messageField.text().length() > 0 || fileContext.count > 0) + self.messageView.sendButton.isEnabled = !isVoiceRecording && ((isPendingVoiceRecord && linphone_recorder_get_duration(voiceRecorder?.getCobject) > 0) || self.messageView.messageText.text.count > 0 || fileContext.count > 0) } - /* - func onvrPlayPauseStop(_ sender: Any) { + + func onvrPlayPauseStop() { if isVoiceRecording { stopVoiceRecording() } else { @@ -1742,30 +1766,79 @@ import AVFoundation } } } - + func playRecordedMessage() { - vrPlayButton.setImage(UIImage(named: "vr_stop"), for: .normal) - vrWaveMask.frame = CGRect.zero - let r = CGRect.zero - r.size.height = vrInnerView.frame.size.height - vrWaveMaskPlayer.frame = r - vrPlayerTimer = Timer.scheduledTimer( - timeInterval: 1.0, - target: self, - selector: Selector("voicePlayTimerUpdate"), - userInfo: nil, - repeats: true) - startSharedPlayer(linphone_recorder_get_file(voiceRecorder)) - animPlayerOnce() + self.recordingWaveImageMask.isHidden = true + self.recordingPlayButton.isHidden = true + self.recordingStopButton.isHidden = false + startSharedPlayer(voiceRecorder?.file) + self.animPlayerOnce() + vrPlayerTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in + self.animPlayerOnce() + } isPlayingVoiceRecording = true } + func animPlayerOnce() { + self.recordingWaveView.progress += 1.0 / Float(self.linphonePlayer!.duration/1000) + + UIView.animate(withDuration: 1, delay: 0.0, options: [.curveLinear], animations: { + self.recordingWaveView.layoutIfNeeded() + if(self.recordingWaveView.progress >= 1.0){ + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { + self.stopVoiceRecordPlayer() + } + } + }) + + + } + func stopVoiceRecordPlayer() { stopSharedPlayer() - vrPlayButton.setImage(UIImage(named: "vr_play"), for: .normal) + self.recordingWaveView.progress = 0.0 + self.recordingWaveView.setProgress(self.recordingWaveView.progress, animated: false) + self.recordingWaveImageMask.isHidden = false + self.recordingPlayButton.isHidden = false + self.recordingStopButton.isHidden = true isPlayingVoiceRecording = false vrPlayerTimer.invalidate() - vrWaveMaskPlayer.frame = CGRect.zero } - */ + + func stopSharedPlayer() { + print("[Voice Message] Stopping shared player path = \(String(describing: linphone_player_get_user_data(linphonePlayer?.getCobject)))") + do{ + try linphonePlayer?.pause() + try linphonePlayer?.seek(timeMs: 0) + linphonePlayer?.close() + linphonePlayer?.userData = nil + }catch{ + print(error) + } + } + + func initSharedPlayer() { + print("[Voice Message] Creating shared player") + + let core = Core.getSwiftObject(cObject: LinphoneManager.getLc()) + do{ + linphonePlayer = try core.createLocalPlayer(soundCardName: CallManager.instance().getSpeakerSoundCard(), videoDisplayName: nil, windowId: nil) + }catch{ + print(error) + } + } + + func startSharedPlayer(_ path: String?) { + print("[Voice Message] Starting shared player path = \(String(describing: path))") + if ((linphonePlayer!.userData) != nil) { + print("[Voice Message] a play was requested (\(String(describing: path)), but there is already one going (\(String(describing: linphonePlayer?.userData))") + let userInfo = [ + "path": linphonePlayer!.userData + ] + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "LinphoneVoiceMessagePlayerEOF"), object: nil, userInfo: userInfo as [AnyHashable : Any]) + } + CallManager.instance().changeRouteToSpeaker() + linphone_player_open(linphonePlayer?.getCobject, path) + linphone_player_start(linphonePlayer?.getCobject) + } } diff --git a/Classes/Swift/Util/BackActionsNavigationView.swift b/Classes/Swift/Util/BackActionsNavigationView.swift index 76b0338be..a26f999cc 100644 --- a/Classes/Swift/Util/BackActionsNavigationView.swift +++ b/Classes/Swift/Util/BackActionsNavigationView.swift @@ -48,10 +48,13 @@ import SnapKit 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 recordingWaveView = UIProgressView() let recordingDurationTextView = StyledLabel(VoipTheme.chat_conversation_recording_duration) let recordingWaveImage = UIImageView(image: UIImage(named: "vr_wave.png")) let recordingWaveImageMask = UIView() + + let recordingPlayerImage = UIView() + let messageView = MessageView() let mediaSelector = UIView() let mediaSelectorReply = UIView() @@ -235,30 +238,29 @@ import SnapKit recordingView.addSubview(recordingWaveView) recordingWaveView.toRightOf(recordingDeleteButton, withLeftMargin: 10).toLeftOf(recordingStopButton, withRightMargin: 10).alignParentTop(withMargin: 10).alignParentBottom(withMargin: 10).done() + recordingWaveView.progressViewStyle = .bar recordingWaveView.layer.cornerRadius = 5 recordingWaveView.backgroundColor = VoipTheme.backgroundWhiteBlack.get() + recordingWaveView.progressTintColor = .green + recordingWaveView.clipsToBounds = true + recordingWaveView.layer.sublayers![1].cornerRadius = 5 + recordingWaveView.subviews[1].clipsToBounds = true 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() + recordingWaveImage.alignParentTop(withMargin: 10).alignParentBottom(withMargin: 10).alignParentLeft(withMargin: 10).alignParentRight(withMargin: 65).done() recordingWaveView.addSubview(recordingWaveImageMask) - recordingWaveImageMask.alignParentTop(withMargin: 5).alignParentBottom(withMargin: 5).alignParentLeft(withMargin: 10).done() //toLeftOf(recordingDurationTextView, withRightMargin: 10).done() - recordingWaveImageMask.rightAnchor.constraint(equalTo: recordingDurationTextView.leftAnchor, constant: -10).isActive = true + recordingWaveImageMask.alignParentTop(withMargin: 5).alignParentBottom(withMargin: 5).alignParentLeft(withMargin: 10).alignParentRight(withMargin: 65).done() recordingWaveImageMask.backgroundColor = VoipTheme.backgroundWhiteBlack.get() - - - - stackView.addArrangedSubview(mediaSelector) mediaSelector.height(top_bar_height*2).matchParentSideBorders().done() mediaSelector.backgroundColor = VoipTheme.voipToolbarBackgroundColor.get() mediaSelector.isHidden = true - stackView.addArrangedSubview(messageView) messageView.alignParentBottom().height(top_bar_height).matchParentSideBorders().done() @@ -272,12 +274,10 @@ import SnapKit floatingButton.imageEdgeInsets = UIEdgeInsets(top: 45, left: 45, bottom: 45, right: 45) floatingButton.onClickAction = action3 - stackView.centerXAnchor.constraint(equalTo:self.view.centerXAnchor).isActive = true stackView.centerYAnchor.constraint(equalTo:self.view.centerYAnchor).isActive = true view.bringSubviewToFront(topBar) - } override func viewWillAppear(_ animated: Bool) { diff --git a/Classes/Swift/Voip/Theme/VoipTheme.swift b/Classes/Swift/Voip/Theme/VoipTheme.swift index 6da3bdf65..89429b7e4 100644 --- a/Classes/Swift/Voip/Theme/VoipTheme.swift +++ b/Classes/Swift/Voip/Theme/VoipTheme.swift @@ -150,7 +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) + 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: 18.0) // Buttons Background (State colors)