From 751430726ecf30d06f1cee5aa4a8ecf9bef47016 Mon Sep 17 00:00:00 2001 From: gzlboy <332446332@qq.com> Date: Mon, 16 Sep 2024 15:13:41 +0800 Subject: [PATCH 1/9] Fix: When the recording duration is too long (for example, 2 minutes), the waveform view exceeds the screen width. --- .../ChatView/Recording/RecordWaveform.swift | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift b/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift index ae912f8c..30d12fd9 100644 --- a/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift +++ b/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift @@ -12,6 +12,9 @@ struct RecordWaveformWithButtons: View { @Environment(\.chatTheme) private var theme @StateObject var recordPlayer = RecordingPlayer() + //160 is screen left-padding/right-padding and playButton's width. + //To ensure that the view does not exceed the screen, need to subtract + static let viewPadding:CGFloat = 160 var recording: Recording @@ -53,20 +56,29 @@ struct RecordWaveformWithButtons: View { } struct RecordWaveformPlaying: View { - + var samples: [CGFloat] // 0...1 var progress: CGFloat var color: Color var addExtraDots: Bool - - var maxLength: CGFloat { - max((RecordWaveform.spacing + RecordWaveform.width) * CGFloat(samples.count) - RecordWaveform.spacing, 0) + var maxLength: CGFloat = 0.0 + + private var adjustedSamples: [CGFloat] = [] + + init(samples: [CGFloat], progress: CGFloat, color: Color, addExtraDots: Bool) { + self.samples = samples + self.progress = progress + self.color = color + self.addExtraDots = addExtraDots + self.adjustedSamples = adjustedSamples(UIScreen.main.bounds.width) + self.maxLength = max((RecordWaveform.spacing + RecordWaveform.width) * CGFloat(self.adjustedSamples.count) - RecordWaveform.spacing, 0) } var body: some View { + GeometryReader { g in ZStack { - let adjusted = adjustedSamples(g.size.width) + let adjusted = addExtraDots ? adjustedSamples(g.size.width) : adjustedSamples RecordWaveform(samples: adjusted, addExtraDots: addExtraDots) .foregroundColor(color.opacity(0.4)) RecordWaveform(samples: adjusted, addExtraDots: addExtraDots) @@ -77,6 +89,7 @@ struct RecordWaveformPlaying: View { } } .frame(height: RecordWaveform.maxSampleHeight) + } .frame(height: RecordWaveform.maxSampleHeight) .applyIf(!addExtraDots) { @@ -86,27 +99,22 @@ struct RecordWaveformPlaying: View { .fixedSize(horizontal: !addExtraDots, vertical: true) } - func adjustedSamples(_ width: CGFloat) -> [CGFloat] { - let maxWidth = addExtraDots ? width : UIScreen.main.bounds.width - let maxSamples = Int(maxWidth / (RecordWaveform.width + RecordWaveform.spacing)) - - var adjusted = samples - var temp = [CGFloat]() - while adjusted.count > maxSamples { - var i = 0 - while i < adjusted.count { - if i == adjusted.count - 1 { - temp.append(adjusted[i]) - break - } - - temp.append((adjusted[i] + adjusted[i+1])/2) - i+=2 - } - adjusted = temp - temp = [] + func adjustedSamples(_ maxWidth: CGFloat) -> [CGFloat] { + + let maxSamples = Int((maxWidth - RecordWaveformWithButtons.viewPadding) / (RecordWaveform.width + RecordWaveform.spacing)) + let temp = samples + + if temp.count <= maxSamples { + return temp } + //Use ceil to ensure that the adjusted.count will not be greater than maxSamples + let ratio = Int(ceil( Double(temp.count) / Double(maxSamples) )) + let adjusted = stride(from: 0, to: temp.count, by: ratio).map { + temp[$0] + } + return adjusted + } } @@ -126,9 +134,9 @@ struct RecordWaveform: View { Capsule() .frame(width: RecordWaveform.width, height: RecordWaveform.maxSampleHeight * CGFloat(s)) } - - if addExtraDots { - ForEach(samples.count.. Date: Tue, 17 Sep 2024 13:06:21 +0800 Subject: [PATCH 2/9] Only one audio should be played at the same time. --- .../ChatView/Recording/RecordingPlayer.swift | 10 ++++++++++ Sources/ExyteChat/Extensions/Notification+.swift | 12 ++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 Sources/ExyteChat/Extensions/Notification+.swift diff --git a/Sources/ExyteChat/ChatView/Recording/RecordingPlayer.swift b/Sources/ExyteChat/ChatView/Recording/RecordingPlayer.swift index e11a67ad..bcd3f0fe 100644 --- a/Sources/ExyteChat/ChatView/Recording/RecordingPlayer.swift +++ b/Sources/ExyteChat/ChatView/Recording/RecordingPlayer.swift @@ -71,6 +71,7 @@ final class RecordingPlayer: ObservableObject { try? audioSession.setActive(true) player?.play() playing = true + NotificationCenter.default.post(name: .chatAudioIsPlaying, object: self) } private func setupPlayer(for url: URL, trackDuration: Double) { @@ -83,6 +84,15 @@ final class RecordingPlayer: ObservableObject { let playerItem = AVPlayerItem(url: url) player = AVPlayer(playerItem: playerItem) + + NotificationCenter.default.addObserver(forName: .chatAudioIsPlaying, object: nil, queue: .main) { notification in + if let sender = notification.object as? RecordingPlayer { + if sender.recording?.url == self.recording?.url { + return + } + self.pause() + } + } NotificationCenter.default.addObserver( forName: .AVPlayerItemDidPlayToEndTime, diff --git a/Sources/ExyteChat/Extensions/Notification+.swift b/Sources/ExyteChat/Extensions/Notification+.swift new file mode 100644 index 00000000..c741bd1b --- /dev/null +++ b/Sources/ExyteChat/Extensions/Notification+.swift @@ -0,0 +1,12 @@ +// +// File.swift +// +// +// Created by admin on 2024/9/17. +// + +import Foundation + +extension Notification.Name { + static let chatAudioIsPlaying = Notification.Name("chatAudioIsPlaying") +} From 37bb190987883b9cff000568d6adc59dd0a97d35 Mon Sep 17 00:00:00 2001 From: gzlboy <332446332@qq.com> Date: Wed, 18 Sep 2024 10:58:15 +0800 Subject: [PATCH 3/9] format code --- .../ChatView/Recording/RecordWaveform.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift b/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift index 30d12fd9..ae421498 100644 --- a/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift +++ b/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift @@ -12,9 +12,11 @@ struct RecordWaveformWithButtons: View { @Environment(\.chatTheme) private var theme @StateObject var recordPlayer = RecordingPlayer() - //160 is screen left-padding/right-padding and playButton's width. - //To ensure that the view does not exceed the screen, need to subtract - static let viewPadding:CGFloat = 160 + + // 160 is screen left-padding/right-padding and playButton's width. + // ensure that the view does not exceed the screen, need to subtract + // TODO: do not hardcode this value + static let viewPadding: CGFloat = 160 var recording: Recording @@ -56,7 +58,6 @@ struct RecordWaveformWithButtons: View { } struct RecordWaveformPlaying: View { - var samples: [CGFloat] // 0...1 var progress: CGFloat var color: Color @@ -75,7 +76,6 @@ struct RecordWaveformPlaying: View { } var body: some View { - GeometryReader { g in ZStack { let adjusted = addExtraDots ? adjustedSamples(g.size.width) : adjustedSamples @@ -100,14 +100,14 @@ struct RecordWaveformPlaying: View { } func adjustedSamples(_ maxWidth: CGFloat) -> [CGFloat] { - let maxSamples = Int((maxWidth - RecordWaveformWithButtons.viewPadding) / (RecordWaveform.width + RecordWaveform.spacing)) let temp = samples if temp.count <= maxSamples { return temp } - //Use ceil to ensure that the adjusted.count will not be greater than maxSamples + + // use ceil to ensure that the adjusted.count will not be greater than maxSamples let ratio = Int(ceil( Double(temp.count) / Double(maxSamples) )) let adjusted = stride(from: 0, to: temp.count, by: ratio).map { temp[$0] From d2681463a26e14f14607c4db6395634887397914 Mon Sep 17 00:00:00 2001 From: gzlboy <332446332@qq.com> Date: Wed, 18 Sep 2024 20:40:38 +0800 Subject: [PATCH 4/9] add copy button to message action menu --- Sources/ExyteChat/ChatView/ChatViewModel.swift | 3 +++ .../ChatView/MessageView/MessageMenu.swift | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Sources/ExyteChat/ChatView/ChatViewModel.swift b/Sources/ExyteChat/ChatView/ChatViewModel.swift index 48f1e085..36f93f86 100644 --- a/Sources/ExyteChat/ChatView/ChatViewModel.swift +++ b/Sources/ExyteChat/ChatView/ChatViewModel.swift @@ -4,6 +4,7 @@ import Foundation import Combine +import UIKit final class ChatViewModel: ObservableObject { @@ -43,6 +44,8 @@ final class ChatViewModel: ObservableObject { @MainActor func messageMenuActionInternal(message: Message, action: DefaultMessageMenuAction) { switch action { + case .copy: + UIPasteboard.general.string = message.text case .reply: inputViewModel?.attachments.replyMessage = message.toReplyMessage() globalFocusState?.focus = .uuid(inputFieldId) diff --git a/Sources/ExyteChat/ChatView/MessageView/MessageMenu.swift b/Sources/ExyteChat/ChatView/MessageView/MessageMenu.swift index 6d9dede0..04f4ad30 100644 --- a/Sources/ExyteChat/ChatView/MessageView/MessageMenu.swift +++ b/Sources/ExyteChat/ChatView/MessageView/MessageMenu.swift @@ -16,11 +16,14 @@ public protocol MessageMenuAction: Equatable, CaseIterable { public enum DefaultMessageMenuAction: MessageMenuAction { + case copy case reply case edit(saveClosure: (String)->Void) public func title() -> String { switch self { + case .copy: + "Copy" case .reply: "Reply" case .edit: @@ -30,6 +33,8 @@ public enum DefaultMessageMenuAction: MessageMenuAction { public func icon() -> Image { switch self { + case .copy: + Image(systemName: "doc.on.doc") case .reply: Image(.reply) case .edit: @@ -38,17 +43,18 @@ public enum DefaultMessageMenuAction: MessageMenuAction { } public static func == (lhs: DefaultMessageMenuAction, rhs: DefaultMessageMenuAction) -> Bool { - if case .reply = lhs, case .reply = rhs { + switch (lhs, rhs) { + case (.copy, .copy), + (.reply, .reply), + (.edit(_), .edit(_)): return true + default: + return false } - if case .edit(_) = lhs, case .edit(_) = rhs { - return true - } - return false } public static var allCases: [DefaultMessageMenuAction] = [ - .reply, .edit(saveClosure: {_ in}) + .copy, .reply, .edit(saveClosure: {_ in}) ] } From 60e531e973784f2a0ba5b8bc27c9816cf1e76c5b Mon Sep 17 00:00:00 2001 From: gzlboy <332446332@qq.com> Date: Thu, 19 Sep 2024 09:02:52 +0800 Subject: [PATCH 5/9] New feature: supports audio play progress dragging. --- .../ChatView/InputView/InputView.swift | 4 +- .../ChatView/Recording/RecordWaveform.swift | 37 +++++++++++++++++-- .../ChatView/Recording/RecordingPlayer.swift | 21 +++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/Sources/ExyteChat/ChatView/InputView/InputView.swift b/Sources/ExyteChat/ChatView/InputView/InputView.swift index d8de093d..570e3c72 100644 --- a/Sources/ExyteChat/ChatView/InputView/InputView.swift +++ b/Sources/ExyteChat/ChatView/InputView/InputView.swift @@ -503,7 +503,9 @@ struct InputView: View { } .frame(width: 20) - RecordWaveformPlaying(samples: samples, progress: recordingPlayer.progress, color: theme.colors.textLightContext, addExtraDots: true) + RecordWaveformPlaying(samples: samples, progress: recordingPlayer.progress, color: theme.colors.textLightContext, addExtraDots: true) { progress in + recordingPlayer.seek(with: viewModel.attachments.recording!, to: progress) + } } .padding(.horizontal, 8) } diff --git a/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift b/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift index ae421498..e707abbf 100644 --- a/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift +++ b/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift @@ -47,7 +47,9 @@ struct RecordWaveformWithButtons: View { } VStack(alignment: .leading, spacing: 5) { - RecordWaveformPlaying(samples: recording.waveformSamples, progress: recordPlayer.progress, color: colorWaveform, addExtraDots: false) + RecordWaveformPlaying(samples: recording.waveformSamples, progress: recordPlayer.progress, color: colorWaveform, addExtraDots: false) { progress in + recordPlayer.seek(with: recording, to: progress) + } Text(DateFormatter.timeString(duration)) .font(.caption2) .monospacedDigit() @@ -63,14 +65,23 @@ struct RecordWaveformPlaying: View { var color: Color var addExtraDots: Bool var maxLength: CGFloat = 0.0 - + + let progressChangeHandler: (CGFloat) -> Void + + @State private var offset: CGSize = .zero + private var adjustedSamples: [CGFloat] = [] - init(samples: [CGFloat], progress: CGFloat, color: Color, addExtraDots: Bool) { + init(samples: [CGFloat], + progress: CGFloat, + color: Color, + addExtraDots: Bool, + progressChangeHandler: @escaping (CGFloat) -> Void) { self.samples = samples self.progress = progress self.color = color self.addExtraDots = addExtraDots + self.progressChangeHandler = progressChangeHandler self.adjustedSamples = adjustedSamples(UIScreen.main.bounds.width) self.maxLength = max((RecordWaveform.spacing + RecordWaveform.width) * CGFloat(self.adjustedSamples.count) - RecordWaveform.spacing, 0) } @@ -97,6 +108,26 @@ struct RecordWaveformPlaying: View { } .frame(maxWidth: addExtraDots ? .infinity : maxLength) .fixedSize(horizontal: !addExtraDots, vertical: true) + .gesture(addDragGesture) + } + + private var addDragGesture: some Gesture { + DragGesture() + .onChanged { value in + offset = value.translation + } + .onEnded { _ in + let currentPosition = maxLength * progress + // multiply by 0.5 so that the sliding will not be too sensitive + var newPosition: CGFloat = currentPosition + offset.width * 0.5 + if offset.width > 0 { + newPosition = min(newPosition, maxLength) + }else{ + newPosition = max(newPosition, 0) + } + let newProgress = newPosition / maxLength + progressChangeHandler(newProgress) + } } func adjustedSamples(_ maxWidth: CGFloat) -> [CGFloat] { diff --git a/Sources/ExyteChat/ChatView/Recording/RecordingPlayer.swift b/Sources/ExyteChat/ChatView/Recording/RecordingPlayer.swift index bcd3f0fe..ccf9efed 100644 --- a/Sources/ExyteChat/ChatView/Recording/RecordingPlayer.swift +++ b/Sources/ExyteChat/ChatView/Recording/RecordingPlayer.swift @@ -53,6 +53,27 @@ final class RecordingPlayer: ObservableObject { else { play() } } + func seek(with recording: Recording, to progress: Double) { + let goalTime = recording.duration * progress + if self.recording == nil { + self.recording = recording + if let url = recording.url { + setupPlayer(for: url, trackDuration: recording.duration) + } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + self?.player?.seek(to: CMTime(seconds: goalTime, preferredTimescale: 10)) + if self?.playing == nil || self?.playing == false { + self?.play() + } + } + return + } + self.player?.seek(to: CMTime(seconds: goalTime, preferredTimescale: 10)) + if !self.playing { + self.play() + } + } + func seek(to progress: Double) { let goalTime = duration * progress player?.seek(to: CMTime(seconds: goalTime, preferredTimescale: 10)) From 0c644b83378ae1b1c00cb80935025e3a3d0a0410 Mon Sep 17 00:00:00 2001 From: gzlboy <332446332@qq.com> Date: Thu, 19 Sep 2024 15:34:54 +0800 Subject: [PATCH 6/9] New feature: support localizable. --- Package.swift | 1 + .../ChatView/Attachments/AttachmentCell.swift | 2 +- .../Attachments/AttachmentsEditor.swift | 4 +-- .../Attachments/AttachmentsPage.swift | 2 +- Sources/ExyteChat/ChatView/ChatView.swift | 2 +- .../ChatView/InputView/InputView.swift | 6 ++--- .../ChatView/InputView/TextInputView.swift | 2 +- .../ChatView/MessageView/MessageMenu.swift | 2 +- .../ExyteChat/Extensions/String+Size.swift | 5 ++++ .../Resources/en.lproj/Localizable.strings | 26 +++++++++++++++++++ .../zh-Hans.lproj/Localizable.strings | 26 +++++++++++++++++++ 11 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 Sources/ExyteChat/Resources/en.lproj/Localizable.strings create mode 100644 Sources/ExyteChat/Resources/zh-Hans.lproj/Localizable.strings diff --git a/Package.swift b/Package.swift index d1427616..8c0dfb03 100644 --- a/Package.swift +++ b/Package.swift @@ -5,6 +5,7 @@ import PackageDescription let package = Package( name: "Chat", + defaultLocalization: "en", platforms: [ .iOS(.v16) ], diff --git a/Sources/ExyteChat/ChatView/Attachments/AttachmentCell.swift b/Sources/ExyteChat/ChatView/Attachments/AttachmentCell.swift index d84858de..dbccc240 100644 --- a/Sources/ExyteChat/ChatView/Attachments/AttachmentCell.swift +++ b/Sources/ExyteChat/ChatView/Attachments/AttachmentCell.swift @@ -26,7 +26,7 @@ struct AttachmentCell: View { } else { content .overlay { - Text("Unknown") + Text("Unknown", bundle: .module) } } } diff --git a/Sources/ExyteChat/ChatView/Attachments/AttachmentsEditor.swift b/Sources/ExyteChat/ChatView/Attachments/AttachmentsEditor.swift index 188a7af3..abf80766 100644 --- a/Sources/ExyteChat/ChatView/Attachments/AttachmentsEditor.swift +++ b/Sources/ExyteChat/ChatView/Attachments/AttachmentsEditor.swift @@ -134,7 +134,7 @@ struct AttachmentsEditor: View { seleсtedMedias = [] inputViewModel.showPicker = false } label: { - Text("Cancel") + Text("Cancel", bundle: .module) .foregroundColor(.white.opacity(0.7)) } @@ -142,7 +142,7 @@ struct AttachmentsEditor: View { } HStack { - Text("Recents") + Text("Recents", bundle: .module) Image(systemName: "chevron.down") .rotationEffect(Angle(radians: showingAlbums ? .pi : 0)) } diff --git a/Sources/ExyteChat/ChatView/Attachments/AttachmentsPage.swift b/Sources/ExyteChat/ChatView/Attachments/AttachmentsPage.swift index d52f6d9c..4d8af6db 100644 --- a/Sources/ExyteChat/ChatView/Attachments/AttachmentsPage.swift +++ b/Sources/ExyteChat/ChatView/Attachments/AttachmentsPage.swift @@ -31,7 +31,7 @@ struct AttachmentsPage: View { .frame(minWidth: 100, minHeight: 100) .frame(maxHeight: 200) .overlay { - Text("Unknown") + Text("Unknown", bundle: .module) } } } diff --git a/Sources/ExyteChat/ChatView/ChatView.swift b/Sources/ExyteChat/ChatView/ChatView.swift index b5927ec0..e11c0b26 100644 --- a/Sources/ExyteChat/ChatView/ChatView.swift +++ b/Sources/ExyteChat/ChatView/ChatView.swift @@ -223,7 +223,7 @@ public struct ChatView: View { .opacity(0.5) .cornerRadius(12) HStack { - Text(title) + Text(LocalizedStringKey(title), bundle: .module) .foregroundColor(theme.colors.textLightContext) Spacer() icon diff --git a/Sources/ExyteChat/Extensions/String+Size.swift b/Sources/ExyteChat/Extensions/String+Size.swift index 35d5712d..36506413 100644 --- a/Sources/ExyteChat/Extensions/String+Size.swift +++ b/Sources/ExyteChat/Extensions/String+Size.swift @@ -7,6 +7,11 @@ import UIKit extension String { + static func localizedFormat(key: String, _ args: CVarArg...) -> String { + let localizedString = NSLocalizedString(key, comment: "") + return String(format: localizedString, arguments: args) + } + static var markdownOptions = AttributedString.MarkdownParsingOptions( allowsExtendedAttributes: false, interpretedSyntax: .inlineOnlyPreservingWhitespace, diff --git a/Sources/ExyteChat/Resources/en.lproj/Localizable.strings b/Sources/ExyteChat/Resources/en.lproj/Localizable.strings new file mode 100644 index 00000000..666b0505 --- /dev/null +++ b/Sources/ExyteChat/Resources/en.lproj/Localizable.strings @@ -0,0 +1,26 @@ +/* + Localizable.strings + + + Created by admin on 2024/9/19. + +*/ + +// attachments directory +"Unknown" = "Unknown"; +"Cancel" = "Cancel"; +"Recents" = "Recents"; +// inputView +"Type a message..." = "Type a message..."; +"Add signature..." = "Add signature..."; +"Reply to %@" = "Reply to %@"; +"Recording..." = "Recording..."; +// messageView +"Copy" = "Copy"; +"Reply" = "Reply"; +"Edit" = "Edit"; +"Delete" = "Delete"; +"Print" = "Print"; + +"Waiting for network" = "Waiting for network"; + diff --git a/Sources/ExyteChat/Resources/zh-Hans.lproj/Localizable.strings b/Sources/ExyteChat/Resources/zh-Hans.lproj/Localizable.strings new file mode 100644 index 00000000..c78cd7e3 --- /dev/null +++ b/Sources/ExyteChat/Resources/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,26 @@ +/* + Localizable.strings + + + Created by admin on 2024/9/19. + +*/ + +// attachments directory +"Unknown" = "未知"; +"Cancel" = "取消"; +"Recents" = "最近"; +// inputView +"Type a message..." = "输入信息"; +"Add signature..." = "添加签名"; +"Reply to %@" = "回复 %@"; +"Recording..." = "录音中..."; +// messageView +"Copy" = "复制"; +"Reply" = "回复"; +"Edit" = "修改"; +"Delete" = "删除"; +"Print" = "打印"; + +"Waiting for network" = "等待网络..."; + From 6a2070b89d4c53765ab3cfb33f87fbfbbad6b177 Mon Sep 17 00:00:00 2001 From: gzlboy <332446332@qq.com> Date: Fri, 20 Sep 2024 08:49:44 +0800 Subject: [PATCH 7/9] Fix the date localization problem, do not set the DateFormatter region (locale), use the system default settings. --- Sources/ExyteChat/Extensions/DateFormatter+.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/ExyteChat/Extensions/DateFormatter+.swift b/Sources/ExyteChat/Extensions/DateFormatter+.swift index 1bc686b9..2a10e6a2 100644 --- a/Sources/ExyteChat/Extensions/DateFormatter+.swift +++ b/Sources/ExyteChat/Extensions/DateFormatter+.swift @@ -18,7 +18,6 @@ extension DateFormatter { let relativeDateFormatter = DateFormatter() relativeDateFormatter.timeStyle = .none relativeDateFormatter.dateStyle = .full - relativeDateFormatter.locale = Locale(identifier: "en_US") relativeDateFormatter.doesRelativeDateFormatting = true return relativeDateFormatter From 95e35dd9384e0f6f181ff2542ca0c0861ff58151 Mon Sep 17 00:00:00 2001 From: gzlboy <332446332@qq.com> Date: Sat, 21 Sep 2024 21:24:14 +0800 Subject: [PATCH 8/9] Fix audio format bugs and support more custom audio attributes. --- .../ChatView/Recording/Recorder.swift | 70 +++++++++++++++++-- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/Sources/ExyteChat/ChatView/Recording/Recorder.swift b/Sources/ExyteChat/ChatView/Recording/Recorder.swift index 64ce13af..cdb521e4 100644 --- a/Sources/ExyteChat/ChatView/Recording/Recorder.swift +++ b/Sources/ExyteChat/ChatView/Recording/Recorder.swift @@ -42,15 +42,22 @@ final class Recorder { private func startRecordingInternal(_ durationProgressHandler: @escaping ProgressHandler) -> URL? { let settings: [String : Any] = [ - AVFormatIDKey: Int(kAudioFormatMPEG4AAC), + AVFormatIDKey: Int(recorderSetting.audioFormatID), AVSampleRateKey: recorderSetting.sampleRate, AVNumberOfChannelsKey: recorderSetting.numberOfChannels, + AVEncoderBitRateKey: recorderSetting.encoderBitRateKey, AVLinearPCMBitDepthKey: recorderSetting.linearPCMBitDepth, + AVLinearPCMIsFloatKey: recorderSetting.linearPCMIsFloatKey, + AVLinearPCMIsBigEndianKey: recorderSetting.linearPCMIsBigEndianKey, + AVLinearPCMIsNonInterleaved: recorderSetting.linearPCMIsNonInterleaved, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] soundSamples = [] - let recordingUrl = FileManager.tempAudioFile + guard let fileExt = fileExtension(for: recorderSetting.audioFormatID) else{ + return nil + } + let recordingUrl = FileManager.tempDirPath.appendingPathComponent(UUID().uuidString + fileExt) do { try audioSession.setCategory(.record, mode: .default) @@ -91,18 +98,71 @@ final class Recorder { audioTimer?.invalidate() audioTimer = nil } + + private func fileExtension(for formatID: AudioFormatID) -> String? { + switch formatID { + case kAudioFormatMPEG4AAC: + return ".aac" + case kAudioFormatLinearPCM: + return ".wav" + case kAudioFormatMPEGLayer3: + return ".mp3" + case kAudioFormatAppleLossless: + return ".m4a" + case kAudioFormatOpus: + return ".opus" + case kAudioFormatAC3: + return ".ac3" + case kAudioFormatFLAC: + return ".flac" + case kAudioFormatAMR: + return ".amr" + case kAudioFormatMIDIStream: + return ".midi" + case kAudioFormatULaw: + return ".ulaw" + case kAudioFormatALaw: + return ".alaw" + case kAudioFormatAMR_WB: + return ".awb" + case kAudioFormatEnhancedAC3: + return ".eac3" + case kAudioFormatiLBC: + return ".ilbc" + default: + return nil + } + } + } public struct RecorderSetting : Codable,Hashable { - + var audioFormatID: AudioFormatID var sampleRate: CGFloat var numberOfChannels: Int + var encoderBitRateKey: Int + // pcm var linearPCMBitDepth: Int - - public init(sampleRate: CGFloat = 12000, numberOfChannels: Int = 1, linearPCMBitDepth: Int = 16) { + var linearPCMIsFloatKey: Bool + var linearPCMIsBigEndianKey: Bool + var linearPCMIsNonInterleaved: Bool + + public init(audioFormatID: AudioFormatID = kAudioFormatMPEG4AAC, + sampleRate: CGFloat = 12000, + numberOfChannels: Int = 1, + encoderBitRateKey: Int = 128, + linearPCMBitDepth: Int = 16, + linearPCMIsFloatKey: Bool = false, + linearPCMIsBigEndianKey: Bool = false, + linearPCMIsNonInterleaved: Bool = false) { + self.audioFormatID = audioFormatID self.sampleRate = sampleRate self.numberOfChannels = numberOfChannels + self.encoderBitRateKey = encoderBitRateKey self.linearPCMBitDepth = linearPCMBitDepth + self.linearPCMIsFloatKey = linearPCMIsFloatKey + self.linearPCMIsBigEndianKey = linearPCMIsBigEndianKey + self.linearPCMIsNonInterleaved = linearPCMIsNonInterleaved } } From b9966cd0d270804f6f01f4af7bf52245f3b4f0cd Mon Sep 17 00:00:00 2001 From: gzlboy <332446332@qq.com> Date: Sat, 28 Sep 2024 15:34:22 +0800 Subject: [PATCH 9/9] Fixed the issue where the sound still plays after closing chatview. --- Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift b/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift index e707abbf..3f7b8097 100644 --- a/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift +++ b/Sources/ExyteChat/ChatView/Recording/RecordWaveform.swift @@ -56,6 +56,9 @@ struct RecordWaveformWithButtons: View { .foregroundColor(colorWaveform) } } + .onDisappear { + recordPlayer.pause() + } } }