@capgo/capacitor-audio-recorder 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapgoCapacitorAudioRecorder.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +359 -0
- package/android/build.gradle +57 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/app/capgo/audiorecorder/CapacitorAudioRecorderPlugin.java +285 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +646 -0
- package/dist/esm/definitions.d.ts +231 -0
- package/dist/esm/definitions.js +43 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +34 -0
- package/dist/esm/web.js +242 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +298 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +301 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/CapacitorAudioRecorderPlugin/CapacitorAudioRecorderPlugin.swift +321 -0
- package/ios/Tests/CapacitorAudioRecorderPluginTests/CapacitorAudioRecorderPluginTests.swift +9 -0
- package/package.json +89 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/definitions.js","esm/index.js","esm/web.js"],"sourcesContent":["/**\n * The recording status.\n *\n * @since 1.0.0\n */\nexport var RecordingStatus;\n(function (RecordingStatus) {\n RecordingStatus[\"Inactive\"] = \"INACTIVE\";\n RecordingStatus[\"Recording\"] = \"RECORDING\";\n RecordingStatus[\"Paused\"] = \"PAUSED\";\n})(RecordingStatus || (RecordingStatus = {}));\n/**\n * Audio session category options available on iOS.\n *\n * @since 1.0.0\n */\nexport var AudioSessionCategoryOption;\n(function (AudioSessionCategoryOption) {\n AudioSessionCategoryOption[\"AllowAirPlay\"] = \"ALLOW_AIR_PLAY\";\n AudioSessionCategoryOption[\"AllowBluetooth\"] = \"ALLOW_BLUETOOTH\";\n AudioSessionCategoryOption[\"AllowBluetoothA2DP\"] = \"ALLOW_BLUETOOTH_A2DP\";\n AudioSessionCategoryOption[\"DefaultToSpeaker\"] = \"DEFAULT_TO_SPEAKER\";\n AudioSessionCategoryOption[\"DuckOthers\"] = \"DUCK_OTHERS\";\n AudioSessionCategoryOption[\"InterruptSpokenAudioAndMixWithOthers\"] = \"INTERRUPT_SPOKEN_AUDIO_AND_MIX_WITH_OTHERS\";\n AudioSessionCategoryOption[\"MixWithOthers\"] = \"MIX_WITH_OTHERS\";\n AudioSessionCategoryOption[\"OverrideMutedMicrophoneInterruption\"] = \"OVERRIDE_MUTED_MICROPHONE_INTERRUPTION\";\n})(AudioSessionCategoryOption || (AudioSessionCategoryOption = {}));\n/**\n * Audio session modes available on iOS.\n *\n * @since 1.0.0\n */\nexport var AudioSessionMode;\n(function (AudioSessionMode) {\n AudioSessionMode[\"Default\"] = \"DEFAULT\";\n AudioSessionMode[\"GameChat\"] = \"GAME_CHAT\";\n AudioSessionMode[\"Measurement\"] = \"MEASUREMENT\";\n AudioSessionMode[\"SpokenAudio\"] = \"SPOKEN_AUDIO\";\n AudioSessionMode[\"VideoChat\"] = \"VIDEO_CHAT\";\n AudioSessionMode[\"VideoRecording\"] = \"VIDEO_RECORDING\";\n AudioSessionMode[\"VoiceChat\"] = \"VOICE_CHAT\";\n})(AudioSessionMode || (AudioSessionMode = {}));\n//# sourceMappingURL=definitions.js.map","import { registerPlugin } from '@capacitor/core';\nconst CapacitorAudioRecorder = registerPlugin('CapacitorAudioRecorder', {\n web: () => import('./web').then((m) => new m.CapacitorAudioRecorderWeb()),\n});\nexport * from './definitions';\nexport { CapacitorAudioRecorder };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nimport { RecordingStatus, } from './definitions';\nexport class CapacitorAudioRecorderWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.mediaRecorder = null;\n this.mediaStream = null;\n this.recordedChunks = [];\n this.status = RecordingStatus.Inactive;\n this.startTimestamp = null;\n this.pausedTimestamp = null;\n this.accumulatedPauseDuration = 0;\n this.stopResolver = null;\n this.stopRejector = null;\n }\n async startRecording(_options) {\n var _a, _b;\n if (this.status === RecordingStatus.Recording || this.status === RecordingStatus.Paused) {\n throw this.unavailable('Recording already in progress.');\n }\n await this.ensurePermission(true);\n try {\n this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n }\n catch (error) {\n this.handleError(`Unable to acquire microphone: ${(_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : error}`);\n throw error;\n }\n const mimeType = this.pickMimeType();\n try {\n this.mediaRecorder = new MediaRecorder(this.mediaStream, mimeType ? { mimeType } : undefined);\n }\n catch (error) {\n this.cleanupMediaStream();\n this.handleError(`Unable to initialise MediaRecorder: ${(_b = error === null || error === void 0 ? void 0 : error.message) !== null && _b !== void 0 ? _b : error}`);\n throw error;\n }\n this.recordedChunks = [];\n this.startTimestamp = Date.now();\n this.accumulatedPauseDuration = 0;\n this.pausedTimestamp = null;\n this.mediaRecorder.addEventListener('dataavailable', (event) => {\n if (event.data.size > 0) {\n this.recordedChunks.push(event.data);\n }\n });\n this.mediaRecorder.addEventListener('stop', () => {\n const result = this.buildStopResult();\n if (this.stopResolver) {\n this.stopResolver(result);\n }\n this.notifyListeners('recordingStopped', result);\n this.resetStopHandlers();\n this.resetState();\n });\n this.mediaRecorder.addEventListener('error', (event) => {\n var _a, _b;\n const message = (_b = (_a = event === null || event === void 0 ? void 0 : event.error) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : 'Recording error.';\n this.handleError(message);\n if (this.stopRejector) {\n this.stopRejector(new Error(message));\n }\n this.resetStopHandlers();\n this.resetState();\n });\n this.mediaRecorder.start();\n this.status = RecordingStatus.Recording;\n }\n async pauseRecording() {\n if (!this.mediaRecorder || this.status !== RecordingStatus.Recording) {\n throw this.unavailable('No active recording to pause.');\n }\n if (this.supportsRecorderPause()) {\n this.mediaRecorder.pause();\n this.status = RecordingStatus.Paused;\n this.pausedTimestamp = Date.now();\n this.notifyListeners('recordingPaused', {});\n }\n else {\n throw this.unavailable('Pausing recordings is not supported in this browser.');\n }\n }\n async resumeRecording() {\n if (!this.mediaRecorder || this.status !== RecordingStatus.Paused) {\n throw this.unavailable('No paused recording to resume.');\n }\n if (this.supportsRecorderPause()) {\n this.mediaRecorder.resume();\n if (this.pausedTimestamp) {\n this.accumulatedPauseDuration += Date.now() - this.pausedTimestamp;\n }\n this.pausedTimestamp = null;\n this.status = RecordingStatus.Recording;\n }\n else {\n throw this.unavailable('Resuming recordings is not supported in this browser.');\n }\n }\n async stopRecording() {\n var _a;\n if (!this.mediaRecorder || this.status === RecordingStatus.Inactive) {\n throw this.unavailable('No active recording to stop.');\n }\n if (this.status === RecordingStatus.Paused && this.pausedTimestamp) {\n this.accumulatedPauseDuration += Date.now() - this.pausedTimestamp;\n this.pausedTimestamp = null;\n }\n const stopPromise = new Promise((resolve, reject) => {\n this.stopResolver = resolve;\n this.stopRejector = reject;\n });\n try {\n this.mediaRecorder.stop();\n }\n catch (error) {\n this.resetStopHandlers();\n this.resetState();\n this.handleError(`Unable to stop recorder: ${(_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : error}`);\n throw error;\n }\n return stopPromise;\n }\n async cancelRecording() {\n if (!this.mediaRecorder || this.status === RecordingStatus.Inactive) {\n this.resetState();\n return;\n }\n try {\n this.mediaRecorder.stop();\n }\n catch (_a) {\n // Ignored.\n }\n this.resetStopHandlers();\n this.resetState();\n }\n async getRecordingStatus() {\n return { status: this.status };\n }\n async checkPermissions() {\n const state = await this.getPermissionState();\n return { recordAudio: state };\n }\n async requestPermissions() {\n const state = await this.ensurePermission(true);\n return { recordAudio: state };\n }\n async addListener(eventName, listenerFunc) {\n return super.addListener(eventName, listenerFunc);\n }\n async removeAllListeners() {\n await super.removeAllListeners();\n }\n // Helpers\n supportsRecorderPause() {\n return !!this.mediaRecorder && typeof this.mediaRecorder.pause === 'function' && typeof this.mediaRecorder.resume === 'function';\n }\n pickMimeType() {\n const preferred = ['audio/webm;codecs=opus', 'audio/ogg;codecs=opus', 'audio/mp4'];\n for (const type of preferred) {\n if (window.MediaRecorder && MediaRecorder.isTypeSupported && MediaRecorder.isTypeSupported(type)) {\n return type;\n }\n }\n return undefined;\n }\n buildStopResult() {\n const blob = this.recordedChunks.length > 0 ? new Blob(this.recordedChunks, { type: this.pickMimeType() || 'audio/webm' }) : undefined;\n let duration;\n if (this.startTimestamp) {\n duration = Date.now() - this.startTimestamp - this.accumulatedPauseDuration;\n }\n return {\n blob,\n duration,\n };\n }\n resetStopHandlers() {\n this.stopResolver = null;\n this.stopRejector = null;\n }\n resetState() {\n this.status = RecordingStatus.Inactive;\n this.startTimestamp = null;\n this.pausedTimestamp = null;\n this.accumulatedPauseDuration = 0;\n this.recordedChunks = [];\n this.cleanupMediaStream();\n if (this.mediaRecorder) {\n const recorder = this.mediaRecorder;\n recorder.ondataavailable = null;\n recorder.onstop = null;\n recorder.onerror = null;\n }\n this.mediaRecorder = null;\n }\n cleanupMediaStream() {\n if (this.mediaStream) {\n this.mediaStream.getTracks().forEach((track) => track.stop());\n }\n this.mediaStream = null;\n }\n async ensurePermission(request) {\n const currentState = await this.getPermissionState();\n if (currentState === 'granted' || !request) {\n return currentState;\n }\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n stream.getTracks().forEach((track) => track.stop());\n return 'granted';\n }\n catch (error) {\n return 'denied';\n }\n }\n async getPermissionState() {\n var _a;\n if (!((_a = navigator.permissions) === null || _a === void 0 ? void 0 : _a.query)) {\n return 'prompt';\n }\n try {\n const result = await navigator.permissions.query({ name: 'microphone' });\n switch (result.state) {\n case 'granted':\n return 'granted';\n case 'denied':\n return 'denied';\n default:\n return 'prompt';\n }\n }\n catch (_b) {\n return 'prompt';\n }\n }\n handleError(message) {\n this.status = RecordingStatus.Inactive;\n this.notifyListeners('recordingError', { message });\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["RecordingStatus","AudioSessionCategoryOption","AudioSessionMode","registerPlugin","WebPlugin"],"mappings":"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"}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
@objc(CapacitorAudioRecorderPlugin)
|
|
6
|
+
public class CapacitorAudioRecorderPlugin: CAPPlugin, CAPBridgedPlugin, AVAudioRecorderDelegate {
|
|
7
|
+
public let identifier = "CapacitorAudioRecorderPlugin"
|
|
8
|
+
public let jsName = "CapacitorAudioRecorder"
|
|
9
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
10
|
+
CAPPluginMethod(name: "startRecording", returnType: CAPPluginReturnPromise),
|
|
11
|
+
CAPPluginMethod(name: "pauseRecording", returnType: CAPPluginReturnPromise),
|
|
12
|
+
CAPPluginMethod(name: "resumeRecording", returnType: CAPPluginReturnPromise),
|
|
13
|
+
CAPPluginMethod(name: "stopRecording", returnType: CAPPluginReturnPromise),
|
|
14
|
+
CAPPluginMethod(name: "cancelRecording", returnType: CAPPluginReturnPromise),
|
|
15
|
+
CAPPluginMethod(name: "getRecordingStatus", returnType: CAPPluginReturnPromise),
|
|
16
|
+
CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
|
|
17
|
+
CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise),
|
|
18
|
+
CAPPluginMethod(name: "removeAllListeners", returnType: CAPPluginReturnPromise),
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
private enum RecordingStatus: String {
|
|
22
|
+
case inactive = "INACTIVE"
|
|
23
|
+
case recording = "RECORDING"
|
|
24
|
+
case paused = "PAUSED"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private let audioSession = AVAudioSession.sharedInstance()
|
|
28
|
+
private var audioRecorder: AVAudioRecorder?
|
|
29
|
+
private var currentFileURL: URL?
|
|
30
|
+
private var status: RecordingStatus = .inactive
|
|
31
|
+
private var recordingStartDate: Date?
|
|
32
|
+
private var pauseStartDate: Date?
|
|
33
|
+
private var accumulatedPauseDuration: TimeInterval = 0
|
|
34
|
+
private var shouldEmitStoppedEvent = true
|
|
35
|
+
|
|
36
|
+
// MARK: - Plugin methods
|
|
37
|
+
|
|
38
|
+
@objc func startRecording(_ call: CAPPluginCall) {
|
|
39
|
+
guard status == .inactive else {
|
|
40
|
+
call.reject("A recording is already in progress.")
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
ensurePermission { granted in
|
|
45
|
+
if !granted {
|
|
46
|
+
call.reject("Microphone permission not granted.")
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
do {
|
|
51
|
+
try self.configureAudioSession(options: call)
|
|
52
|
+
try self.beginRecording(call)
|
|
53
|
+
call.resolve()
|
|
54
|
+
} catch {
|
|
55
|
+
self.resetRecorder(deleteFile: true)
|
|
56
|
+
call.reject("Failed to start recording.", nil, error)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@objc func pauseRecording(_ call: CAPPluginCall) {
|
|
62
|
+
guard let recorder = audioRecorder, status == .recording else {
|
|
63
|
+
call.reject("No active recording to pause.")
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
recorder.pause()
|
|
68
|
+
pauseStartDate = Date()
|
|
69
|
+
status = .paused
|
|
70
|
+
notifyListeners("recordingPaused", data: [:])
|
|
71
|
+
call.resolve()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@objc func resumeRecording(_ call: CAPPluginCall) {
|
|
75
|
+
guard let recorder = audioRecorder, status == .paused else {
|
|
76
|
+
call.reject("No paused recording to resume.")
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if let pauseStartDate {
|
|
81
|
+
accumulatedPauseDuration += Date().timeIntervalSince(pauseStartDate)
|
|
82
|
+
}
|
|
83
|
+
recorder.record()
|
|
84
|
+
status = .recording
|
|
85
|
+
pauseStartDate = nil
|
|
86
|
+
call.resolve()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@objc func stopRecording(_ call: CAPPluginCall) {
|
|
90
|
+
guard let recorder = audioRecorder, status != .inactive else {
|
|
91
|
+
call.reject("No active recording to stop.")
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
shouldEmitStoppedEvent = false
|
|
96
|
+
recorder.stop()
|
|
97
|
+
deactivateSessionIfNeeded()
|
|
98
|
+
|
|
99
|
+
let durationMilliseconds = recorder.currentTime * 1000
|
|
100
|
+
let uri = currentFileURL?.absoluteString ?? ""
|
|
101
|
+
|
|
102
|
+
let result: [String: Any] = [
|
|
103
|
+
"duration": durationMilliseconds,
|
|
104
|
+
"uri": uri,
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
notifyListeners("recordingStopped", data: result)
|
|
108
|
+
call.resolve(result)
|
|
109
|
+
resetRecorder(deleteFile: false)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@objc func cancelRecording(_ call: CAPPluginCall) {
|
|
113
|
+
guard audioRecorder != nil else {
|
|
114
|
+
resetRecorder(deleteFile: true)
|
|
115
|
+
call.resolve()
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
shouldEmitStoppedEvent = false
|
|
120
|
+
audioRecorder?.stop()
|
|
121
|
+
deactivateSessionIfNeeded()
|
|
122
|
+
resetRecorder(deleteFile: true)
|
|
123
|
+
call.resolve()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@objc func getRecordingStatus(_ call: CAPPluginCall) {
|
|
127
|
+
call.resolve(["status": status.rawValue])
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@objc override public func checkPermissions(_ call: CAPPluginCall) {
|
|
131
|
+
call.resolve(["recordAudio": microphonePermissionState()])
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@objc override public func requestPermissions(_ call: CAPPluginCall) {
|
|
135
|
+
ensurePermission { granted in
|
|
136
|
+
call.resolve(["recordAudio": granted ? "granted" : "denied"])
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@objc override public func removeAllListeners(_ call: CAPPluginCall) {
|
|
141
|
+
super.removeAllListeners(call)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// MARK: - AVAudioRecorderDelegate
|
|
145
|
+
|
|
146
|
+
public func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: Error?) {
|
|
147
|
+
resetRecorder(deleteFile: true)
|
|
148
|
+
let message = error?.localizedDescription ?? "Unknown encoding error."
|
|
149
|
+
notifyListeners("recordingError", data: ["message": message])
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
|
|
153
|
+
guard shouldEmitStoppedEvent else {
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if flag {
|
|
158
|
+
let durationMilliseconds = recorder.currentTime * 1000
|
|
159
|
+
let uri = currentFileURL?.absoluteString ?? ""
|
|
160
|
+
let result: [String: Any] = [
|
|
161
|
+
"duration": durationMilliseconds,
|
|
162
|
+
"uri": uri,
|
|
163
|
+
]
|
|
164
|
+
notifyListeners("recordingStopped", data: result)
|
|
165
|
+
} else {
|
|
166
|
+
notifyListeners("recordingError", data: ["message": "Recording finished unsuccessfully."])
|
|
167
|
+
}
|
|
168
|
+
resetRecorder(deleteFile: !flag)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// MARK: - Helpers
|
|
172
|
+
|
|
173
|
+
private func configureAudioSession(options call: CAPPluginCall) throws {
|
|
174
|
+
var categoryOptions: AVAudioSession.CategoryOptions = []
|
|
175
|
+
if let options = call.getArray("audioSessionCategoryOptions", String.self) {
|
|
176
|
+
options.forEach {
|
|
177
|
+
if let option = mapCategoryOption(from: $0) {
|
|
178
|
+
categoryOptions.insert(option)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
categoryOptions.insert(.duckOthers)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let mode = mapSessionMode(from: call.getString("audioSessionMode")) ?? .measurement
|
|
186
|
+
|
|
187
|
+
try audioSession.setCategory(.playAndRecord, mode: mode, options: categoryOptions.union([.allowBluetooth, .defaultToSpeaker]))
|
|
188
|
+
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private func beginRecording(_ call: CAPPluginCall) throws {
|
|
192
|
+
let bitRate = call.getDouble("bitRate") ?? 192_000
|
|
193
|
+
let sampleRate = call.getDouble("sampleRate") ?? 44_100
|
|
194
|
+
|
|
195
|
+
let directoryURL = FileManager.default.temporaryDirectory.appendingPathComponent("CapacitorAudioRecorder", isDirectory: true)
|
|
196
|
+
if !FileManager.default.fileExists(atPath: directoryURL.path) {
|
|
197
|
+
try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let fileURL = directoryURL.appendingPathComponent("\(UUID().uuidString).m4a")
|
|
201
|
+
let settings: [String: Any] = [
|
|
202
|
+
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
|
|
203
|
+
AVSampleRateKey: sampleRate,
|
|
204
|
+
AVNumberOfChannelsKey: 1,
|
|
205
|
+
AVEncoderBitRateKey: bitRate,
|
|
206
|
+
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
let recorder = try AVAudioRecorder(url: fileURL, settings: settings)
|
|
210
|
+
recorder.delegate = self
|
|
211
|
+
recorder.prepareToRecord()
|
|
212
|
+
recorder.record()
|
|
213
|
+
|
|
214
|
+
audioRecorder = recorder
|
|
215
|
+
currentFileURL = fileURL
|
|
216
|
+
status = .recording
|
|
217
|
+
recordingStartDate = Date()
|
|
218
|
+
accumulatedPauseDuration = 0
|
|
219
|
+
pauseStartDate = nil
|
|
220
|
+
shouldEmitStoppedEvent = true
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private func resetRecorder(deleteFile: Bool) {
|
|
224
|
+
if deleteFile, let url = currentFileURL {
|
|
225
|
+
try? FileManager.default.removeItem(at: url)
|
|
226
|
+
}
|
|
227
|
+
audioRecorder = nil
|
|
228
|
+
currentFileURL = nil
|
|
229
|
+
status = .inactive
|
|
230
|
+
recordingStartDate = nil
|
|
231
|
+
pauseStartDate = nil
|
|
232
|
+
accumulatedPauseDuration = 0
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private func deactivateSessionIfNeeded() {
|
|
236
|
+
do {
|
|
237
|
+
try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
|
|
238
|
+
} catch {
|
|
239
|
+
CAPLog.print("CapacitorAudioRecorderPlugin", "Failed to deactivate audio session: \(error.localizedDescription)")
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private func ensurePermission(completion: @escaping (Bool) -> Void) {
|
|
244
|
+
switch audioSession.recordPermission {
|
|
245
|
+
case .granted:
|
|
246
|
+
completion(true)
|
|
247
|
+
case .denied:
|
|
248
|
+
completion(false)
|
|
249
|
+
case .undetermined:
|
|
250
|
+
audioSession.requestRecordPermission { granted in
|
|
251
|
+
DispatchQueue.main.async {
|
|
252
|
+
completion(granted)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
@unknown default:
|
|
256
|
+
completion(false)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private func microphonePermissionState() -> String {
|
|
261
|
+
switch audioSession.recordPermission {
|
|
262
|
+
case .granted:
|
|
263
|
+
return "granted"
|
|
264
|
+
case .denied:
|
|
265
|
+
return "denied"
|
|
266
|
+
case .undetermined:
|
|
267
|
+
return "prompt"
|
|
268
|
+
@unknown default:
|
|
269
|
+
return "prompt"
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private func mapCategoryOption(from value: String) -> AVAudioSession.CategoryOptions? {
|
|
274
|
+
switch value.uppercased() {
|
|
275
|
+
case "ALLOW_AIR_PLAY":
|
|
276
|
+
return .allowAirPlay
|
|
277
|
+
case "ALLOW_BLUETOOTH":
|
|
278
|
+
return .allowBluetooth
|
|
279
|
+
case "ALLOW_BLUETOOTH_A2DP":
|
|
280
|
+
if #available(iOS 10.0, *) {
|
|
281
|
+
return .allowBluetoothA2DP
|
|
282
|
+
}
|
|
283
|
+
return nil
|
|
284
|
+
case "DEFAULT_TO_SPEAKER":
|
|
285
|
+
return .defaultToSpeaker
|
|
286
|
+
case "DUCK_OTHERS":
|
|
287
|
+
return .duckOthers
|
|
288
|
+
case "INTERRUPT_SPOKEN_AUDIO_AND_MIX_WITH_OTHERS":
|
|
289
|
+
return .interruptSpokenAudioAndMixWithOthers
|
|
290
|
+
case "MIX_WITH_OTHERS":
|
|
291
|
+
return .mixWithOthers
|
|
292
|
+
case "OVERRIDE_MUTED_MICROPHONE_INTERRUPTION":
|
|
293
|
+
if #available(iOS 14.5, *) {
|
|
294
|
+
return .overrideMutedMicrophoneInterruption
|
|
295
|
+
}
|
|
296
|
+
return nil
|
|
297
|
+
default:
|
|
298
|
+
return nil
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private func mapSessionMode(from value: String?) -> AVAudioSession.Mode? {
|
|
303
|
+
guard let value else { return nil }
|
|
304
|
+
switch value.uppercased() {
|
|
305
|
+
case "GAME_CHAT":
|
|
306
|
+
return .gameChat
|
|
307
|
+
case "MEASUREMENT":
|
|
308
|
+
return .measurement
|
|
309
|
+
case "SPOKEN_AUDIO":
|
|
310
|
+
return .spokenAudio
|
|
311
|
+
case "VIDEO_CHAT":
|
|
312
|
+
return .videoChat
|
|
313
|
+
case "VIDEO_RECORDING":
|
|
314
|
+
return .videoRecording
|
|
315
|
+
case "VOICE_CHAT":
|
|
316
|
+
return .voiceChat
|
|
317
|
+
default:
|
|
318
|
+
return .default
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import CapacitorAudioRecorderPlugin
|
|
3
|
+
|
|
4
|
+
final class CapacitorAudioRecorderPluginTests: XCTestCase {
|
|
5
|
+
func testPluginInitialises() {
|
|
6
|
+
let plugin = CapacitorAudioRecorderPlugin()
|
|
7
|
+
XCTAssertEqual(plugin.identifier, "CapacitorAudioRecorderPlugin")
|
|
8
|
+
}
|
|
9
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capgo/capacitor-audio-recorder",
|
|
3
|
+
"version": "7.0.0",
|
|
4
|
+
"description": "Record audio on iOS, Android, and Web with Capacitor",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"dist/",
|
|
13
|
+
"ios/Sources",
|
|
14
|
+
"ios/Tests",
|
|
15
|
+
"Package.swift",
|
|
16
|
+
"CapgoCapacitorAudioRecorder.podspec"
|
|
17
|
+
],
|
|
18
|
+
"author": "Cap-go <contact@capgo.app>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Cap-go/capacitor-audio-recorder.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/Cap-go/capacitor-audio-recorder/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"capacitor",
|
|
29
|
+
"plugin",
|
|
30
|
+
"native",
|
|
31
|
+
"audio",
|
|
32
|
+
"recording",
|
|
33
|
+
"microphone",
|
|
34
|
+
"capgo",
|
|
35
|
+
"capacitor"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
39
|
+
"verify:ios": "xcodebuild -scheme CapgoCapacitorAudioRecorder -destination generic/platform=iOS",
|
|
40
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
41
|
+
"verify:web": "npm run build",
|
|
42
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
43
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --autocorrect --format",
|
|
44
|
+
"eslint": "eslint . --ext .ts",
|
|
45
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
46
|
+
"swiftlint": "node-swiftlint",
|
|
47
|
+
"docgen": "docgen --api CapacitorAudioRecorderPlugin --output-readme README.md --output-json dist/docs.json",
|
|
48
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
49
|
+
"clean": "rimraf ./dist",
|
|
50
|
+
"watch": "tsc --watch",
|
|
51
|
+
"prepublishOnly": "npm run build"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@capacitor/android": "^7.0.0",
|
|
55
|
+
"@capacitor/cli": "^7.0.0",
|
|
56
|
+
"@capacitor/core": "^7.0.0",
|
|
57
|
+
"@capacitor/docgen": "^0.3.0",
|
|
58
|
+
"@capacitor/ios": "^7.0.0",
|
|
59
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
60
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
61
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
62
|
+
"@types/node": "^22.13.1",
|
|
63
|
+
"eslint": "^8.57.0",
|
|
64
|
+
"eslint-plugin-import": "^2.31.0",
|
|
65
|
+
"husky": "^9.1.7",
|
|
66
|
+
"prettier": "^3.4.2",
|
|
67
|
+
"prettier-plugin-java": "^2.6.7",
|
|
68
|
+
"rimraf": "^6.0.1",
|
|
69
|
+
"rollup": "^4.34.6",
|
|
70
|
+
"swiftlint": "^2.0.0",
|
|
71
|
+
"typescript": "^5.7.3"
|
|
72
|
+
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"@capacitor/core": ">=7.0.0"
|
|
75
|
+
},
|
|
76
|
+
"eslintConfig": {
|
|
77
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
78
|
+
},
|
|
79
|
+
"prettier": "@ionic/prettier-config",
|
|
80
|
+
"swiftlint": "@ionic/swiftlint-config",
|
|
81
|
+
"capacitor": {
|
|
82
|
+
"ios": {
|
|
83
|
+
"src": "ios"
|
|
84
|
+
},
|
|
85
|
+
"android": {
|
|
86
|
+
"src": "android"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|