From d95bbe51e28647019d51098c0f40aea08d2770fa Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Fri, 4 Sep 2020 22:16:20 +0300 Subject: [PATCH] Fix for recording --- AutoCat.xcodeproj/project.pbxproj | 4 +++ .../xcdebugger/Breakpoints_v2.xcbkptlist | 16 +++++++++ AutoCat/Controllers/RecordsController.swift | 36 +++++++++++++++---- AutoCat/Controllers/SettingsController.swift | 9 ++++- AutoCat/Extensions/AudioEngine.swift | 25 +++++++++++++ AutoCat/Utils/Location.swift | 6 ++-- AutoCat/Utils/Recorder.swift | 17 +++------ 7 files changed, 90 insertions(+), 23 deletions(-) create mode 100644 AutoCat/Extensions/AudioEngine.swift diff --git a/AutoCat.xcodeproj/project.pbxproj b/AutoCat.xcodeproj/project.pbxproj index 425a893..10553d1 100644 --- a/AutoCat.xcodeproj/project.pbxproj +++ b/AutoCat.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ 7A11474923FF2B2D00B424AF /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474823FF2B2D00B424AF /* Response.swift */; }; 7A11474B23FF368B00B424AF /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474A23FF368B00B424AF /* Settings.swift */; }; 7A15051224DB3E3000F39631 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A15051124DB3E3000F39631 /* AnyEncodable.swift */; }; + 7A21112A24FC3D7E003BBF6F /* AudioEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A21112924FC3D7E003BBF6F /* AudioEngine.swift */; }; 7A27ADC7249D43210035F39E /* RegionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADC6249D43210035F39E /* RegionsController.swift */; }; 7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF2249F8B650035F39E /* RecordsController.swift */; }; 7A27ADF5249FD2F90035F39E /* FileManagerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */; }; @@ -116,6 +117,7 @@ 7A11474A23FF368B00B424AF /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; 7A11474D23FFEE8800B424AF /* SVProgressHUD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SVProgressHUD.framework; path = Carthage/Build/iOS/SVProgressHUD.framework; sourceTree = ""; }; 7A15051124DB3E3000F39631 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = ""; }; + 7A21112924FC3D7E003BBF6F /* AudioEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioEngine.swift; sourceTree = ""; }; 7A27ADC6249D43210035F39E /* RegionsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionsController.swift; sourceTree = ""; }; 7A27ADF2249F8B650035F39E /* RecordsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsController.swift; sourceTree = ""; }; 7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManagerExt.swift; sourceTree = ""; }; @@ -320,6 +322,7 @@ 7A27ADF824A09CAD0035F39E /* CocoaError.swift */, 7A659B5A24A3768A0043A0F2 /* Substrings.swift */, 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */, + 7A21112924FC3D7E003BBF6F /* AudioEngine.swift */, ); path = Extensions; sourceTree = ""; @@ -547,6 +550,7 @@ 7A11474423FF06CA00B424AF /* Api.swift in Sources */, 7A488C3D24A74B990054D0B2 /* RxCollectionViewRealmDataSource.swift in Sources */, 7AB67E8E2435D1A000258F61 /* CustomButton.swift in Sources */, + 7A21112A24FC3D7E003BBF6F /* AudioEngine.swift in Sources */, 7A8A220B248D67B60073DFD9 /* VehicleReportImage.swift in Sources */, 7A488C3E24A74B990054D0B2 /* Reactive+RxRealmDataSources.swift in Sources */, 7A27ADC7249D43210035F39E /* RegionsController.swift in Sources */, diff --git a/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index f6d5466..5ba1623 100644 --- a/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -79,5 +79,21 @@ landmarkType = "7"> + + + + diff --git a/AutoCat/Controllers/RecordsController.swift b/AutoCat/Controllers/RecordsController.swift index 542437b..9dfe0d7 100644 --- a/AutoCat/Controllers/RecordsController.swift +++ b/AutoCat/Controllers/RecordsController.swift @@ -16,6 +16,7 @@ class RecordsController: UIViewController, UITableViewDelegate { var addButton: UIBarButtonItem! let bag = DisposeBag() var recordDisposable: Disposable? + var audioSessionObserver: NSObjectProtocol? let validLetters = ["А", "В", "Е", "К", "М", "Н", "О", "Р", "С", "Т", "У", "Х"] @@ -123,13 +124,26 @@ class RecordsController: UIViewController, UITableViewDelegate { .observeOn(MainScheduler.instance) .flatMap(self.makeStartSoundIfNeeded) .flatMap { - DispatchQueue.main.async { - alert = UIAlertController(title: "Recording...", message: nil, preferredStyle: .alert) - alert!.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in self.recordDisposable?.dispose() })) - alert!.addAction(UIAlertAction(title: "Done", style: .default, handler: { _ in self.recorder?.stopRecording() })) - self.present(alert!, animated: true) - } - + #if targetEnvironment(macCatalyst) || targetEnvironment(simulator) + DispatchQueue.main.async { + alert = self.showRecordingAlert() + } + #else + if let observer = self.audioSessionObserver { + NotificationCenter.default.removeObserver(observer, name: AVAudioSession.routeChangeNotification, object: nil) + } + self.audioSessionObserver = NotificationCenter.default.addObserver(forName: AVAudioSession.routeChangeNotification, object: nil, queue: .main) { notification in + guard let dict = notification.userInfo as? [String: Any], + let prev = dict["AVAudioSessionRouteChangePreviousRouteKey"] as? AVAudioSessionRouteDescription, + let reasonInt = dict["AVAudioSessionRouteChangeReasonKey"] as? NSNumber, + let reason = AVAudioSession.RouteChangeReason(rawValue: reasonInt.uintValue), + let session = notification.object as? AVAudioSession else { return } + + if reason == .categoryChange && session.category == .playAndRecord && prev.inputs.isEmpty && !session.currentRoute.inputs.isEmpty { + alert = self.showRecordingAlert() + } + } + #endif let date = Date() let fileName = "recording-\(date.timeIntervalSince1970).m4a" url = try FileManager.default.url(for: fileName, in: "recordings") @@ -160,6 +174,14 @@ class RecordsController: UIViewController, UITableViewDelegate { } } + func showRecordingAlert() -> UIAlertController { + let alert = UIAlertController(title: "Recording...", message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in self.recordDisposable?.dispose() })) + alert.addAction(UIAlertAction(title: "Done", style: .default, handler: { _ in self.recorder?.stopRecording() })) + self.present(alert, animated: true) + return alert + } + // MARK: - Processing func getPlateNumber(from recognizedText: String) -> String? { diff --git a/AutoCat/Controllers/SettingsController.swift b/AutoCat/Controllers/SettingsController.swift index be35cf5..faf5c7b 100644 --- a/AutoCat/Controllers/SettingsController.swift +++ b/AutoCat/Controllers/SettingsController.swift @@ -61,7 +61,7 @@ class SettingsController: FormViewController { } } - +++ Section(footer: "When enabled, you will hear short sound before starting audio recording. This will only work when audio record is started via Siri") + +++ Section(footer: "When enabled, you will hear short sound before starting audio recording. This will only work when audio record is started via Siri") { $0.tag = "BeepRecordSection" } <<< SwitchRow("BeepRecord") { row in row.title = "Beep before record" row.value = Settings.shared.recordBeep @@ -75,6 +75,13 @@ class SettingsController: FormViewController { <<< ButtonRow("SignOut") { $0.title = "Sign Out" }.onCellSelection { cell, row in self.logout() } + + #if targetEnvironment(macCatalyst) + if let beepSection = self.form.sectionBy(tag: "BeepRecordSection") { + beepSection.hidden = true + beepSection.evaluateHidden() + } + #endif } func logout() { diff --git a/AutoCat/Extensions/AudioEngine.swift b/AutoCat/Extensions/AudioEngine.swift new file mode 100644 index 0000000..9418d2f --- /dev/null +++ b/AutoCat/Extensions/AudioEngine.swift @@ -0,0 +1,25 @@ +import Foundation +import AVFoundation +import RxSwift + +extension AVAudioSession { + func setCategoryAsync(_ category: AVAudioSession.Category) -> Single { + if self.category == category { + return .just(()) + } else { + return Single.create { observer in + NotificationCenter.default.addObserver(forName: AVAudioSession.routeChangeNotification, object: self, queue: .main) { notification in + print("") + } + + do { + try self.setCategory(category, mode: .default, options: []) + } catch { + observer(.error(error)) + } + + return Disposables.create() + } + } + } +} diff --git a/AutoCat/Utils/Location.swift b/AutoCat/Utils/Location.swift index 2dedd0c..fc55dd9 100644 --- a/AutoCat/Utils/Location.swift +++ b/AutoCat/Utils/Location.swift @@ -72,14 +72,14 @@ class LocationManager { private static func checkPermissions() -> Single { return Single.create { observer in switch CLLocationManager.authorizationStatus() { - case .authorizedWhenInUse: + case .authorizedWhenInUse, .authorizedAlways: observer(.success(())) break case .notDetermined: self.manager.requestWhenInUseAuthorization() let proxy = RxLocationManagerDelegateProxy.proxy(for: self.manager) - _ = proxy.authSubject.skip(1).first().subscribe(onSuccess: { result in - if let status = result, status == .authorizedWhenInUse { + _ = proxy.authSubject.filter{ $0 != .notDetermined }.first().subscribe(onSuccess: { result in + if let status = result, [.authorizedWhenInUse, .authorizedAlways].contains(status) { observer(.success(())) } else { observer(.error(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"]))) diff --git a/AutoCat/Utils/Recorder.swift b/AutoCat/Utils/Recorder.swift index 409b964..ea517cc 100644 --- a/AutoCat/Utils/Recorder.swift +++ b/AutoCat/Utils/Recorder.swift @@ -3,6 +3,7 @@ import Speech import AVFoundation import AudioToolbox import RxSwift +import os.log class Recorder { @@ -28,15 +29,7 @@ class Recorder { func microphoneAvailable() -> Bool { // FIXME: - // This is primarily for mac catalyst app. - // On iOS this will always return true (as there is always at least one microphone on any iOS device) - let session = AVAudioSession.sharedInstance() -// #if targetEnvironment(macCatalyst) -// for input in session.availableInputs! { -// print(input.portType == .headsetMic) -// } -// #endif - return session.availableInputs?.contains(where: { $0.portType == .builtInMic }) ?? false + return true } func requestPermissions() -> Single { @@ -94,9 +87,6 @@ class Recorder { } do { - try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: []) - try AVAudioSession.sharedInstance().setActive(true) - let inFormat = self.engine.inputNode.outputFormat(forBus: 0) ExtAudioFileSetProperty(fileRef, kExtAudioFileProperty_ClientDataFormat, UInt32(MemoryLayout.size), inFormat.streamDescription) @@ -123,6 +113,9 @@ class Recorder { self.engine.prepare() try self.engine.start() + + try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: []) + try AVAudioSession.sharedInstance().setActive(true) } catch { observer(.error(error)) }