Merge remote-tracking branch 'refs/remotes/origin/master'

This commit is contained in:
Selim Mustafaev 2020-07-28 10:34:12 +03:00
commit 510c6c736d
8 changed files with 238 additions and 143 deletions

View File

@ -80,6 +80,7 @@
7A96AE2F246B2BCD00297C33 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A96AE2E246B2BCD00297C33 /* WebKit.framework */; }; 7A96AE2F246B2BCD00297C33 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A96AE2E246B2BCD00297C33 /* WebKit.framework */; };
7A96AE31246B2FE400297C33 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE30246B2FE400297C33 /* Constants.swift */; }; 7A96AE31246B2FE400297C33 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE30246B2FE400297C33 /* Constants.swift */; };
7A96AE33246C095700297C33 /* Base64FS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE32246C095700297C33 /* Base64FS.swift */; }; 7A96AE33246C095700297C33 /* Base64FS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE32246C095700297C33 /* Base64FS.swift */; };
7AAE6AD324CDDF950023860B /* VehicleEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAE6AD224CDDF950023860B /* VehicleEvent.swift */; };
7AB562BA249C9E9B00473D53 /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB562B9249C9E9B00473D53 /* Region.swift */; }; 7AB562BA249C9E9B00473D53 /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB562B9249C9E9B00473D53 /* Region.swift */; };
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8B2435C38700258F61 /* CustomTextField.swift */; }; 7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8B2435C38700258F61 /* CustomTextField.swift */; };
7AB67E8E2435D1A000258F61 /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8D2435D1A000258F61 /* CustomButton.swift */; }; 7AB67E8E2435D1A000258F61 /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8D2435D1A000258F61 /* CustomButton.swift */; };
@ -157,6 +158,7 @@
7A96AE2E246B2BCD00297C33 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; }; 7A96AE2E246B2BCD00297C33 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; };
7A96AE30246B2FE400297C33 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; }; 7A96AE30246B2FE400297C33 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
7A96AE32246C095700297C33 /* Base64FS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base64FS.swift; sourceTree = "<group>"; }; 7A96AE32246C095700297C33 /* Base64FS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base64FS.swift; sourceTree = "<group>"; };
7AAE6AD224CDDF950023860B /* VehicleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleEvent.swift; sourceTree = "<group>"; };
7AB562B9249C9E9B00473D53 /* Region.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Region.swift; sourceTree = "<group>"; }; 7AB562B9249C9E9B00473D53 /* Region.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Region.swift; sourceTree = "<group>"; };
7AB67E8B2435C38700258F61 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = "<group>"; }; 7AB67E8B2435C38700258F61 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = "<group>"; };
7AB67E8D2435D1A000258F61 /* CustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = "<group>"; }; 7AB67E8D2435D1A000258F61 /* CustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = "<group>"; };
@ -285,6 +287,7 @@
7A333813249A532400D878F1 /* Filter.swift */, 7A333813249A532400D878F1 /* Filter.swift */,
7AB562B9249C9E9B00473D53 /* Region.swift */, 7AB562B9249C9E9B00473D53 /* Region.swift */,
7A659B5824A2B1BA0043A0F2 /* AudioRecord.swift */, 7A659B5824A2B1BA0043A0F2 /* AudioRecord.swift */,
7AAE6AD224CDDF950023860B /* VehicleEvent.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@ -526,6 +529,7 @@
7A43F9F8246C8A6200BA5B49 /* JWT.swift in Sources */, 7A43F9F8246C8A6200BA5B49 /* JWT.swift in Sources */,
7A6DD903242BF4A5009DE740 /* PlateView.swift in Sources */, 7A6DD903242BF4A5009DE740 /* PlateView.swift in Sources */,
7A488C3F24A74B990054D0B2 /* RealmBindObserver.swift in Sources */, 7A488C3F24A74B990054D0B2 /* RealmBindObserver.swift in Sources */,
7AAE6AD324CDDF950023860B /* VehicleEvent.swift in Sources */,
7A11470323FDE7E500B424AF /* SceneDelegate.swift in Sources */, 7A11470323FDE7E500B424AF /* SceneDelegate.swift in Sources */,
7A530B7E24017FEE00CBFE6E /* VehicleCell.swift in Sources */, 7A530B7E24017FEE00CBFE6E /* VehicleCell.swift in Sources */,
7A11474423FF06CA00B424AF /* Api.swift in Sources */, 7A11474423FF06CA00B424AF /* Api.swift in Sources */,

View File

@ -47,5 +47,53 @@
</Locations> </Locations>
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "0F1972A7-94E8-40E5-834C-B873D2578DC7"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "AutoCat/Controllers/RecordsController.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "150"
endingLineNumber = "150"
landmarkName = "onAddVoiceRecord(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "79DDC3DB-613E-40E9-AD33-AEBAA5775C62"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "AutoCat/Controllers/RecordsController.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "144"
endingLineNumber = "144"
landmarkName = "onAddVoiceRecord(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "DBCA971D-D424-4108-BA32-882EEF44B5A8"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "AutoCat/Utils/Location.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "86"
endingLineNumber = "86"
landmarkName = "requestLocation()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints> </Breakpoints>
</Bucket> </Bucket>

View File

@ -23,7 +23,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let config = Realm.Configuration( let config = Realm.Configuration(
schemaVersion: 9, schemaVersion: 10,
migrationBlock: { migration, oldSchemaVersion in migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion <= 3 { if oldSchemaVersion <= 3 {
var numbers: [String] = [] var numbers: [String] = []

View File

@ -15,6 +15,7 @@ class RecordsController: UIViewController, UITableViewDelegate {
var recorder: Recorder? var recorder: Recorder?
var addButton: UIBarButtonItem! var addButton: UIBarButtonItem!
let bag = DisposeBag() let bag = DisposeBag()
var recordDisposable: Disposable?
let validLetters = ["А", "В", "Е", "К", "М", "Н", "О", "Р", "С", "Т", "У", "Х"] let validLetters = ["А", "В", "Е", "К", "М", "Н", "О", "Р", "С", "Т", "У", "Х"]
@ -26,7 +27,7 @@ class RecordsController: UIViewController, UITableViewDelegate {
self.addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(onAddVoiceRecord(_:))) self.addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(onAddVoiceRecord(_:)))
self.navigationItem.rightBarButtonItem = self.addButton self.navigationItem.rightBarButtonItem = self.addButton
self.recorder = try? Recorder() self.recorder = Recorder()
let ds = RxTableViewSectionedAnimatedDataSource<DateSection<AudioRecord>>(configureCell: { dataSource, tableView, indexPath, item in let ds = RxTableViewSectionedAnimatedDataSource<DateSection<AudioRecord>>(configureCell: { dataSource, tableView, indexPath, item in
if let cell = tableView.dequeueReusableCell(withIdentifier: "AudioRecordCell", for: indexPath) as? AudioRecordCell { if let cell = tableView.dequeueReusableCell(withIdentifier: "AudioRecordCell", for: indexPath) as? AudioRecordCell {
@ -59,8 +60,6 @@ class RecordsController: UIViewController, UITableViewDelegate {
} }
self.tableView.rx.setDelegate(self).disposed(by: self.bag) self.tableView.rx.setDelegate(self).disposed(by: self.bag)
LocationManager.requestCurrentLocation().subscribe().disposed(by: self.bag)
} }
override func viewDidAppear(_ animated: Bool) { override func viewDidAppear(_ animated: Bool) {
@ -113,41 +112,44 @@ class RecordsController: UIViewController, UITableViewDelegate {
return return
} }
recorder.requestPermissions { error in var alert: UIAlertController?
DispatchQueue.main.async { var url: URL!
if let error = error {
self.show(error: error) let locationObservable = LocationManager.requestCurrentLocation()
} else { .map(Optional.init)
do { .catchErrorJustReturn(nil)
let alert = UIAlertController(title: "Recording...", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in self.recorder?.cancelRecording() })) let recordObservable: Single<String> = recorder.requestPermissions()
alert.addAction(UIAlertAction(title: "Done", style: .default, handler: { _ in self.recorder?.stopRecording() })) .observeOn(MainScheduler.instance)
self.present(alert, animated: true) .flatMap(self.makeStartSoundIfNeeded)
.flatMap {
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)
let date = Date() let date = Date()
let fileName = "recording-\(date.timeIntervalSince1970).m4a" let fileName = "recording-\(date.timeIntervalSince1970).m4a"
let url = try FileManager.default.url(for: fileName, in: "recordings") url = try FileManager.default.url(for: fileName, in: "recordings")
try self.makeStartSoundIfNeeded {
try recorder.startRecording(to: url) { result in return recorder.startRecording(to: url)
}
self.recordDisposable = Single.zip(locationObservable, recordObservable) { event, text -> AudioRecord in
let asset = AVURLAsset(url: url) let asset = AVURLAsset(url: url)
let duration = TimeInterval(CMTimeGetSeconds(asset.duration)) let duration = TimeInterval(CMTimeGetSeconds(asset.duration))
let record = AudioRecord(path: url.lastPathComponent, number: self.getPlateNumber(from: result), raw: result, duration: duration) return AudioRecord(path: url.lastPathComponent, number: self.getPlateNumber(from: text), raw: text, duration: duration, event: event)
}
.subscribe(onSuccess: { record in
let realm = try? Realm() let realm = try? Realm()
try? realm?.write { try? realm?.write {
realm?.add(record) realm?.add(record)
} }
alert.dismiss(animated: true) alert?.dismiss(animated: true)
print("New record saved to: \(url.path)") }) { error in
}
}
self.donateUserActivity()
} catch {
IHProgressHUD.showError(withStatus: error.localizedDescription) IHProgressHUD.showError(withStatus: error.localizedDescription)
} }
} }
}
}
}
// MARK: - Processing // MARK: - Processing
@ -195,28 +197,18 @@ class RecordsController: UIViewController, UITableViewDelegate {
&& region! < 1000 && region! < 1000
} }
func makeStartSoundIfNeeded(completion: @escaping () throws -> Void) throws { func makeStartSoundIfNeeded() -> Single<Void> {
if !Settings.shared.recordBeep { if !Settings.shared.recordBeep {
try completion() return .just(())
} else { } else {
//let session = AVAudioSession.sharedInstance() return Single<Void>.create { observer in
//try session.setCategory(.playback, mode: .default, options: [.defaultToSpeaker])
//try session.setActive(true)
var err: Error?
var soundId = SystemSoundID() var soundId = SystemSoundID()
let url = URL(fileURLWithPath: "/System/Library/Audio/UISounds/short_double_high.caf") let url = URL(fileURLWithPath: "/System/Library/Audio/UISounds/short_double_high.caf")
AudioServicesCreateSystemSoundID(url as CFURL, &soundId) AudioServicesCreateSystemSoundID(url as CFURL, &soundId)
AudioServicesPlaySystemSoundWithCompletion(soundId) { AudioServicesPlaySystemSoundWithCompletion(soundId) {
do { observer(.success(()))
//try session.setActive(false)
try completion()
} catch {
err = error
} }
} return Disposables.create()
if let error = err {
throw error
} }
} }
} }

View File

@ -9,6 +9,7 @@ class AudioRecord: Object, IdentifiableType {
@objc dynamic var rawText: String = "" @objc dynamic var rawText: String = ""
@objc dynamic var addedDate: TimeInterval = Date().timeIntervalSince1970 @objc dynamic var addedDate: TimeInterval = Date().timeIntervalSince1970
@objc dynamic var duration: TimeInterval = 0 @objc dynamic var duration: TimeInterval = 0
@objc dynamic var event: VehicleEvent?
var identifier: TimeInterval = 0 var identifier: TimeInterval = 0
var identity: TimeInterval { var identity: TimeInterval {
@ -18,11 +19,12 @@ class AudioRecord: Object, IdentifiableType {
return self.identifier return self.identifier
} }
init(path: String, number: String?, raw: String, duration: TimeInterval) { init(path: String, number: String?, raw: String, duration: TimeInterval, event: VehicleEvent?) {
self.path = path self.path = path
self.number = number self.number = number
self.duration = duration self.duration = duration
self.rawText = raw self.rawText = raw
self.event = event
} }
required init() { required init() {

View File

@ -0,0 +1,21 @@
import Foundation
import RealmSwift
class VehicleEvent: Object {
@objc dynamic var date: Date = Date()
@objc dynamic var latitude: Double = 0
@objc dynamic var longitude: Double = 0
@objc dynamic var speed: Double = 0
@objc dynamic var direction: Double = 0
init(lat: Double, lon: Double, speed: Double, dir: Double) {
self.latitude = lat
self.longitude = lon
self.speed = speed
self.direction = dir
}
required init() {
super.init()
}
}

View File

@ -3,14 +3,6 @@ import RxSwift
import RxCocoa import RxCocoa
import CoreLocation import CoreLocation
struct VehicleEvent {
var date: Date
var latitude: Double
var longitude: Double
var speed: Double
var direction: Double
}
class RxLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocationManagerDelegate>, DelegateProxyType, CLLocationManagerDelegate { class RxLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocationManagerDelegate>, DelegateProxyType, CLLocationManagerDelegate {
init(locationManager: ParentObject) { init(locationManager: ParentObject) {
@ -41,20 +33,30 @@ extension Reactive where Base: CLLocationManager {
let sel = #selector((CLLocationManagerDelegate.locationManager(_:didChangeAuthorization:)! as (CLLocationManagerDelegate) -> (CLLocationManager, CLAuthorizationStatus) -> Void)) let sel = #selector((CLLocationManagerDelegate.locationManager(_:didChangeAuthorization:)! as (CLLocationManagerDelegate) -> (CLLocationManager, CLAuthorizationStatus) -> Void))
let source: Observable<CLAuthorizationStatus> = delegate.methodInvoked(sel) let source: Observable<CLAuthorizationStatus> = delegate.methodInvoked(sel)
.map { arg in .map { arg in
let status = arg[1] as! CLAuthorizationStatus let status = CLAuthorizationStatus(rawValue: arg[1] as! Int32)
return status return status!
} }
return ControlEvent(events: source) return ControlEvent(events: source)
} }
var didUpdateLocations: Observable<VehicleEvent> {
let sel = #selector((CLLocationManagerDelegate.locationManager(_:didUpdateLocations:)! as (CLLocationManagerDelegate) -> (CLLocationManager, [CLLocation]) -> Void))
return delegate.methodInvoked(sel)
.map { args in
if let locations = args[1] as? [CLLocation], let location = locations.first {
return VehicleEvent(lat: location.coordinate.latitude, lon: location.coordinate.longitude, speed: location.speed, dir: location.course)
} else {
throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Update location error"])
}
}
}
} }
class LocationManager: { class LocationManager {
static let shared = LocationManager() private static let manager = CLLocationManager()
private static let bag = DisposeBag()
private let manager = CLLocationManager() private static func checkPermissions() -> Single<Void> {
private let bag = DisposeBag()
private func checkPermissions() -> Single<Void> {
return Single<Void>.create { observer in return Single<Void>.create { observer in
switch CLLocationManager.authorizationStatus() { switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse: case .authorizedWhenInUse:
@ -62,21 +64,31 @@ class LocationManager: {
break break
case .notDetermined: case .notDetermined:
self.manager.requestWhenInUseAuthorization() self.manager.requestWhenInUseAuthorization()
_ = self.manager.rx.didChangeAuthorization.first().subscribe(onSuccess: { status in _ = self.manager.rx.didChangeAuthorization.skip(1).first().subscribe(onSuccess: { result in
if let status = result, status == .authorizedWhenInUse {
observer(.success(()))
} else {
observer(.error(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"])))
}
}, onError: { observer(.error($0)) }) }, onError: { observer(.error($0)) })
default: default:
observer(.error(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"]))) observer(.error(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"])))
break break
} }
return Disposables.create { } return Disposables.create()
} }
} }
func requestCurrentLocation() -> Single<VehicleEvent> { private static func requestLocation() -> Single<VehicleEvent> {
return self.checkPermissions().map { return self.manager.rx.didUpdateLocations.take(1).asSingle().do(onSubscribed: {
return VehicleEvent() DispatchQueue.main.async {
} self.manager.requestLocation()
}
})
}
static func requestCurrentLocation() -> Single<VehicleEvent> {
return self.checkPermissions().flatMap(self.requestLocation)
} }
} }

View File

@ -2,6 +2,7 @@ import Foundation
import Speech import Speech
import AVFoundation import AVFoundation
import AudioToolbox import AudioToolbox
import RxSwift
class Recorder { class Recorder {
@ -25,49 +26,57 @@ class Recorder {
init() { init() {
} }
func requestPermissions(completion: @escaping (NSError?) -> Void) { func requestPermissions() -> Single<Void> {
return Single<Void>.create { observer in
AVAudioSession.sharedInstance().requestRecordPermission { allowed in AVAudioSession.sharedInstance().requestRecordPermission { allowed in
if allowed { if allowed {
SFSpeechRecognizer.requestAuthorization { status in SFSpeechRecognizer.requestAuthorization { status in
switch status { switch status {
case .authorized: case .authorized:
completion(nil) observer(.success(()))
break break
case .denied: case .denied:
let error = CocoaError.error("Access denied", suggestion: "Please give permission to use speech recognition in system settings") let error = CocoaError.error("Access denied", suggestion: "Please give permission to use speech recognition in system settings")
completion(error) observer(.error(error))
break break
case .restricted: case .restricted:
let error = CocoaError.error("Access restricted", suggestion: "Speech recognition is restricted on this device") let error = CocoaError.error("Access restricted", suggestion: "Speech recognition is restricted on this device")
completion(error) observer(.error(error))
break break
case .notDetermined: case .notDetermined:
let error = CocoaError.error("Access error", suggestion: "Speech recognition status is not yet determined") let error = CocoaError.error("Access error", suggestion: "Speech recognition status is not yet determined")
completion(error) observer(.error(error))
break break
@unknown default: @unknown default:
let error = CocoaError.error("Access error", suggestion: "Unknown error accessing speech recognizer") let error = CocoaError.error("Access error", suggestion: "Unknown error accessing speech recognizer")
completion(error) observer(.error(error))
break break
} }
} }
} else { } else {
let error = CocoaError.error("Access denied", suggestion: "Please give permission to use microphone in system settings") let error = CocoaError.error("Access denied", suggestion: "Please give permission to use microphone in system settings")
completion(error) observer(.error(error))
}
} }
} }
func startRecording(to file: URL, completion: @escaping (String) -> Void) throws { return Disposables.create()
}
}
func startRecording(to file: URL) -> Single<String> {
return Single<String>.create { observer in
guard let aac = AVAudioFormat(settings: self.recordingSettings) else { guard let aac = AVAudioFormat(settings: self.recordingSettings) else {
throw CocoaError.error("Recording error", suggestion: "Format not supported") observer(.error(CocoaError.error("Recording error", suggestion: "Format not supported")))
return Disposables.create()
} }
ExtAudioFileCreateWithURL(file as CFURL, kAudioFileM4AType, aac.streamDescription, nil, AudioFileFlags.eraseFile.rawValue, &fileRef) ExtAudioFileCreateWithURL(file as CFURL, kAudioFileM4AType, aac.streamDescription, nil, AudioFileFlags.eraseFile.rawValue, &self.fileRef)
guard let fileRef = self.fileRef else { guard let fileRef = self.fileRef else {
throw CocoaError.error(CocoaError.Code.fileWriteUnknown) observer(.error(CocoaError.error(CocoaError.Code.fileWriteUnknown)))
return Disposables.create()
} }
do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: []) try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [])
try AVAudioSession.sharedInstance().setActive(true) try AVAudioSession.sharedInstance().setActive(true)
@ -76,7 +85,6 @@ class Recorder {
self.engine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: inFormat) { buffer, time in self.engine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: inFormat) { buffer, time in
self.request.append(buffer) self.request.append(buffer)
//print(self.recognitionTask?.state.rawValue)
ExtAudioFileWrite(fileRef, buffer.frameLength, buffer.audioBufferList) ExtAudioFileWrite(fileRef, buffer.frameLength, buffer.audioBufferList)
} }
@ -86,18 +94,26 @@ class Recorder {
self.endRecognitionTimer?.invalidate() self.endRecognitionTimer?.invalidate()
self.endRecognitionTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { timer in self.endRecognitionTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { timer in
self.finishRecording() self.finishRecording()
completion(self.result) observer(.success(self.result))
} }
} }
} }
self.endRecognitionTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { timer in self.endRecognitionTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { timer in
self.finishRecording() self.finishRecording()
completion(self.result) observer(.success(self.result))
} }
self.engine.prepare() self.engine.prepare()
try self.engine.start() try self.engine.start()
} catch {
observer(.error(error))
}
return Disposables.create {
self.cancelRecording()
}
}
} }
func cancelRecording() { func cancelRecording() {