@scr2em/capacitor-plugin-recorder 0.0.1

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.
@@ -0,0 +1,318 @@
1
+ import Foundation
2
+ import AVFoundation
3
+
4
+ public enum RecordingStatus: String {
5
+ case recording = "recording"
6
+ case paused = "paused"
7
+ case stopped = "stopped"
8
+ }
9
+
10
+ public enum PlaybackStatus: String {
11
+ case playing = "playing"
12
+ case paused = "paused"
13
+ case stopped = "stopped"
14
+ case ended = "ended"
15
+ }
16
+
17
+ public protocol RecorderPlayerDelegate: AnyObject {
18
+ func onRecordingStatusChange(status: RecordingStatus, duration: Int)
19
+ func onPlaybackStatusChange(playerId: String, status: PlaybackStatus, currentPosition: Int, duration: Int)
20
+ }
21
+
22
+ // Internal class to hold player state
23
+ private class PlayerState: NSObject, AVAudioPlayerDelegate {
24
+ let playerId: String
25
+ let audioPlayer: AVAudioPlayer
26
+ var status: PlaybackStatus = .stopped
27
+ var playbackTimer: Timer?
28
+ weak var delegate: RecorderPlayerDelegate?
29
+
30
+ init(playerId: String, audioPlayer: AVAudioPlayer) {
31
+ self.playerId = playerId
32
+ self.audioPlayer = audioPlayer
33
+ super.init()
34
+ self.audioPlayer.delegate = self
35
+ }
36
+
37
+ func startTimer() {
38
+ stopTimer()
39
+ playbackTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { [weak self] _ in
40
+ self?.notifyStatusChange()
41
+ }
42
+ }
43
+
44
+ func stopTimer() {
45
+ playbackTimer?.invalidate()
46
+ playbackTimer = nil
47
+ }
48
+
49
+ func notifyStatusChange() {
50
+ let currentPosition = Int(audioPlayer.currentTime * 1000)
51
+ let duration = Int(audioPlayer.duration * 1000)
52
+ delegate?.onPlaybackStatusChange(playerId: playerId, status: status, currentPosition: currentPosition, duration: duration)
53
+ }
54
+
55
+ // AVAudioPlayerDelegate
56
+ func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
57
+ status = .ended
58
+ stopTimer()
59
+ notifyStatusChange()
60
+ }
61
+
62
+ func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
63
+ status = .stopped
64
+ stopTimer()
65
+ notifyStatusChange()
66
+ }
67
+
68
+ deinit {
69
+ stopTimer()
70
+ }
71
+ }
72
+
73
+ @objc public class RecorderPlayer: NSObject {
74
+ private var audioRecorder: AVAudioRecorder?
75
+ private var recordingSession: AVAudioSession?
76
+
77
+ private var recordingStatus: RecordingStatus = .stopped
78
+
79
+ private var recordingStartTime: Date?
80
+ private var pausedDuration: TimeInterval = 0
81
+ private var currentRecordingPath: URL?
82
+
83
+ // Multiple players support
84
+ private var players: [String: PlayerState] = [:]
85
+ private var playerIdCounter: Int = 0
86
+
87
+ public weak var delegate: RecorderPlayerDelegate?
88
+
89
+ public override init() {
90
+ super.init()
91
+ setupAudioSession()
92
+ }
93
+
94
+ private func setupAudioSession() {
95
+ recordingSession = AVAudioSession.sharedInstance()
96
+ do {
97
+ try recordingSession?.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker, .allowBluetooth, .mixWithOthers])
98
+ try recordingSession?.setActive(true)
99
+ } catch {
100
+ print("Failed to setup audio session: \(error)")
101
+ }
102
+ }
103
+
104
+ private func generatePlayerId() -> String {
105
+ playerIdCounter += 1
106
+ return "player-\(playerIdCounter)-\(Int(Date().timeIntervalSince1970 * 1000))"
107
+ }
108
+
109
+ @objc public func startRecord(path: String?) throws {
110
+ guard recordingStatus == .stopped else {
111
+ throw NSError(domain: "RecorderPlayer", code: 1, userInfo: [NSLocalizedDescriptionKey: "Recording already in progress"])
112
+ }
113
+
114
+ // Use Application Support directory + bundle identifier (matches Capacitor's Directory.Data)
115
+ let appSupportDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
116
+ let dataDir = appSupportDir.appendingPathComponent(Bundle.main.bundleIdentifier ?? "")
117
+ // Create directory if it doesn't exist
118
+ try? FileManager.default.createDirectory(at: dataDir, withIntermediateDirectories: true)
119
+
120
+ let audioPath: URL
121
+ if let path = path {
122
+ // Use relative path within data directory
123
+ audioPath = dataDir.appendingPathComponent(path)
124
+ // Create parent directories if needed
125
+ let parentDir = audioPath.deletingLastPathComponent()
126
+ try? FileManager.default.createDirectory(at: parentDir, withIntermediateDirectories: true)
127
+ } else {
128
+ let audioFilename = "recording_\(Int(Date().timeIntervalSince1970)).m4a"
129
+ audioPath = dataDir.appendingPathComponent(audioFilename)
130
+ }
131
+
132
+ currentRecordingPath = audioPath
133
+
134
+ let settings: [String: Any] = [
135
+ AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
136
+ AVSampleRateKey: 44100,
137
+ AVNumberOfChannelsKey: 1,
138
+ AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
139
+ ]
140
+
141
+ do {
142
+ audioRecorder = try AVAudioRecorder(url: audioPath, settings: settings)
143
+ audioRecorder?.record()
144
+ recordingStartTime = Date()
145
+ pausedDuration = 0
146
+ recordingStatus = .recording
147
+ notifyRecordingStatusChange()
148
+ } catch {
149
+ throw NSError(domain: "RecorderPlayer", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to start recording: \(error.localizedDescription)"])
150
+ }
151
+ }
152
+
153
+ @objc public func stopRecord() throws -> (path: String, duration: Int) {
154
+ guard let recorder = audioRecorder, recordingStatus != .stopped else {
155
+ throw NSError(domain: "RecorderPlayer", code: 3, userInfo: [NSLocalizedDescriptionKey: "No recording in progress"])
156
+ }
157
+
158
+ let duration = Int(recorder.currentTime * 1000)
159
+ recorder.stop()
160
+
161
+ let path = currentRecordingPath?.path ?? ""
162
+
163
+ audioRecorder = nil
164
+ recordingStatus = .stopped
165
+ notifyRecordingStatusChange()
166
+
167
+ return (path, duration)
168
+ }
169
+
170
+ @objc public func pauseRecord() throws {
171
+ guard let recorder = audioRecorder, recordingStatus == .recording else {
172
+ throw NSError(domain: "RecorderPlayer", code: 4, userInfo: [NSLocalizedDescriptionKey: "No active recording to pause"])
173
+ }
174
+
175
+ recorder.pause()
176
+ pausedDuration = recorder.currentTime
177
+ recordingStatus = .paused
178
+ notifyRecordingStatusChange()
179
+ }
180
+
181
+ @objc public func resumeRecord() throws {
182
+ guard let recorder = audioRecorder, recordingStatus == .paused else {
183
+ throw NSError(domain: "RecorderPlayer", code: 5, userInfo: [NSLocalizedDescriptionKey: "No paused recording to resume"])
184
+ }
185
+
186
+ recorder.record()
187
+ recordingStatus = .recording
188
+ notifyRecordingStatusChange()
189
+ }
190
+
191
+ @objc public func preparePlay(path: String) throws -> (playerId: String, duration: Int) {
192
+ // Use Application Support directory + bundle identifier (matches Capacitor's Directory.Data)
193
+ let appSupportDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
194
+ let dataDir = appSupportDir.appendingPathComponent(Bundle.main.bundleIdentifier ?? "")
195
+ let url = dataDir.appendingPathComponent(path)
196
+
197
+ guard FileManager.default.fileExists(atPath: url.path) else {
198
+ throw NSError(domain: "RecorderPlayer", code: 6, userInfo: [NSLocalizedDescriptionKey: "Audio file not found at path: \(path)"])
199
+ }
200
+
201
+ do {
202
+ let audioPlayer = try AVAudioPlayer(contentsOf: url)
203
+ audioPlayer.prepareToPlay()
204
+
205
+ let playerId = generatePlayerId()
206
+ let playerState = PlayerState(playerId: playerId, audioPlayer: audioPlayer)
207
+ playerState.delegate = delegate
208
+ players[playerId] = playerState
209
+
210
+ let duration = Int(audioPlayer.duration * 1000)
211
+ playerState.notifyStatusChange()
212
+
213
+ return (playerId, duration)
214
+ } catch {
215
+ throw NSError(domain: "RecorderPlayer", code: 7, userInfo: [NSLocalizedDescriptionKey: "Failed to load audio: \(error.localizedDescription)"])
216
+ }
217
+ }
218
+
219
+ @objc public func play(playerId: String) throws {
220
+ guard let playerState = players[playerId] else {
221
+ throw NSError(domain: "RecorderPlayer", code: 8, userInfo: [NSLocalizedDescriptionKey: "No player found with ID: \(playerId)"])
222
+ }
223
+
224
+ playerState.audioPlayer.play()
225
+ playerState.status = .playing
226
+ playerState.startTimer()
227
+ playerState.notifyStatusChange()
228
+ }
229
+
230
+ @objc public func pausePlay(playerId: String) throws {
231
+ guard let playerState = players[playerId] else {
232
+ throw NSError(domain: "RecorderPlayer", code: 9, userInfo: [NSLocalizedDescriptionKey: "No player found with ID: \(playerId)"])
233
+ }
234
+
235
+ guard playerState.status == .playing else {
236
+ throw NSError(domain: "RecorderPlayer", code: 9, userInfo: [NSLocalizedDescriptionKey: "No audio playing to pause"])
237
+ }
238
+
239
+ playerState.audioPlayer.pause()
240
+ playerState.status = .paused
241
+ playerState.stopTimer()
242
+ playerState.notifyStatusChange()
243
+ }
244
+
245
+ @objc public func resumePlay(playerId: String) throws {
246
+ guard let playerState = players[playerId] else {
247
+ throw NSError(domain: "RecorderPlayer", code: 10, userInfo: [NSLocalizedDescriptionKey: "No player found with ID: \(playerId)"])
248
+ }
249
+
250
+ guard playerState.status == .paused else {
251
+ throw NSError(domain: "RecorderPlayer", code: 10, userInfo: [NSLocalizedDescriptionKey: "No paused audio to resume"])
252
+ }
253
+
254
+ playerState.audioPlayer.play()
255
+ playerState.status = .playing
256
+ playerState.startTimer()
257
+ playerState.notifyStatusChange()
258
+ }
259
+
260
+ @objc public func stopPlay(playerId: String) throws {
261
+ guard let playerState = players[playerId] else {
262
+ throw NSError(domain: "RecorderPlayer", code: 11, userInfo: [NSLocalizedDescriptionKey: "No player found with ID: \(playerId)"])
263
+ }
264
+
265
+ playerState.audioPlayer.stop()
266
+ playerState.audioPlayer.currentTime = 0
267
+ playerState.status = .stopped
268
+ playerState.stopTimer()
269
+ playerState.notifyStatusChange()
270
+ }
271
+
272
+ @objc public func seekTo(playerId: String, position: Int) throws {
273
+ guard let playerState = players[playerId] else {
274
+ throw NSError(domain: "RecorderPlayer", code: 12, userInfo: [NSLocalizedDescriptionKey: "No player found with ID: \(playerId)"])
275
+ }
276
+
277
+ let positionInSeconds = TimeInterval(position) / 1000.0
278
+ guard positionInSeconds >= 0 && positionInSeconds <= playerState.audioPlayer.duration else {
279
+ throw NSError(domain: "RecorderPlayer", code: 13, userInfo: [NSLocalizedDescriptionKey: "Invalid seek position"])
280
+ }
281
+
282
+ playerState.audioPlayer.currentTime = positionInSeconds
283
+ playerState.notifyStatusChange()
284
+ }
285
+
286
+ @objc public func getPlaybackStatus(playerId: String) -> (status: String, currentPosition: Int, duration: Int)? {
287
+ guard let playerState = players[playerId] else {
288
+ return nil
289
+ }
290
+
291
+ let currentPosition = Int(playerState.audioPlayer.currentTime * 1000)
292
+ let duration = Int(playerState.audioPlayer.duration * 1000)
293
+ return (playerState.status.rawValue, currentPosition, duration)
294
+ }
295
+
296
+ @objc public func getRecordingStatus() -> (status: String, duration: Int) {
297
+ var duration = 0
298
+ if let recorder = audioRecorder {
299
+ duration = Int(recorder.currentTime * 1000)
300
+ }
301
+ return (recordingStatus.rawValue, duration)
302
+ }
303
+
304
+ @objc public func destroyPlayer(playerId: String) {
305
+ guard let playerState = players[playerId] else {
306
+ return
307
+ }
308
+
309
+ playerState.audioPlayer.stop()
310
+ playerState.stopTimer()
311
+ players.removeValue(forKey: playerId)
312
+ }
313
+
314
+ private func notifyRecordingStatusChange() {
315
+ let status = getRecordingStatus()
316
+ delegate?.onRecordingStatusChange(status: recordingStatus, duration: status.duration)
317
+ }
318
+ }
@@ -0,0 +1,249 @@
1
+ import Foundation
2
+ import Capacitor
3
+ import AVFoundation
4
+
5
+ @objc(RecorderPlayerPlugin)
6
+ public class RecorderPlayerPlugin: CAPPlugin, CAPBridgedPlugin, RecorderPlayerDelegate {
7
+ public let identifier = "RecorderPlayerPlugin"
8
+ public let jsName = "RecorderPlayer"
9
+ public let pluginMethods: [CAPPluginMethod] = [
10
+ CAPPluginMethod(name: "requestPermission", returnType: CAPPluginReturnPromise),
11
+ CAPPluginMethod(name: "checkPermission", returnType: CAPPluginReturnPromise),
12
+ CAPPluginMethod(name: "startRecord", returnType: CAPPluginReturnPromise),
13
+ CAPPluginMethod(name: "stopRecord", returnType: CAPPluginReturnPromise),
14
+ CAPPluginMethod(name: "pauseRecord", returnType: CAPPluginReturnPromise),
15
+ CAPPluginMethod(name: "resumeRecord", returnType: CAPPluginReturnPromise),
16
+ CAPPluginMethod(name: "preparePlay", returnType: CAPPluginReturnPromise),
17
+ CAPPluginMethod(name: "play", returnType: CAPPluginReturnPromise),
18
+ CAPPluginMethod(name: "pausePlay", returnType: CAPPluginReturnPromise),
19
+ CAPPluginMethod(name: "resumePlay", returnType: CAPPluginReturnPromise),
20
+ CAPPluginMethod(name: "stopPlay", returnType: CAPPluginReturnPromise),
21
+ CAPPluginMethod(name: "seekTo", returnType: CAPPluginReturnPromise),
22
+ CAPPluginMethod(name: "getPlaybackStatus", returnType: CAPPluginReturnPromise),
23
+ CAPPluginMethod(name: "getRecordingStatus", returnType: CAPPluginReturnPromise),
24
+ CAPPluginMethod(name: "destroyPlayer", returnType: CAPPluginReturnPromise)
25
+ ]
26
+
27
+ private lazy var implementation: RecorderPlayer = {
28
+ let recorder = RecorderPlayer()
29
+ recorder.delegate = self
30
+ return recorder
31
+ }()
32
+
33
+ @objc func requestPermission(_ call: CAPPluginCall) {
34
+ AVAudioSession.sharedInstance().requestRecordPermission { granted in
35
+ DispatchQueue.main.async {
36
+ call.resolve([
37
+ "microphone": granted ? "granted" : "denied"
38
+ ])
39
+ }
40
+ }
41
+ }
42
+
43
+ @objc func checkPermission(_ call: CAPPluginCall) {
44
+ let status = AVAudioSession.sharedInstance().recordPermission
45
+ var permissionStatus: String
46
+
47
+ switch status {
48
+ case .granted:
49
+ permissionStatus = "granted"
50
+ case .denied:
51
+ permissionStatus = "denied"
52
+ case .undetermined:
53
+ permissionStatus = "prompt"
54
+ @unknown default:
55
+ permissionStatus = "prompt"
56
+ }
57
+
58
+ call.resolve([
59
+ "microphone": permissionStatus
60
+ ])
61
+ }
62
+
63
+ @objc func startRecord(_ call: CAPPluginCall) {
64
+ let path = call.getString("path")
65
+ do {
66
+ try implementation.startRecord(path: path)
67
+ call.resolve()
68
+ } catch {
69
+ call.reject(error.localizedDescription)
70
+ }
71
+ }
72
+
73
+ @objc func stopRecord(_ call: CAPPluginCall) {
74
+ do {
75
+ let result = try implementation.stopRecord()
76
+ call.resolve([
77
+ "path": result.path,
78
+ "duration": result.duration
79
+ ])
80
+ } catch {
81
+ call.reject(error.localizedDescription)
82
+ }
83
+ }
84
+
85
+ @objc func pauseRecord(_ call: CAPPluginCall) {
86
+ do {
87
+ try implementation.pauseRecord()
88
+ call.resolve()
89
+ } catch {
90
+ call.reject(error.localizedDescription)
91
+ }
92
+ }
93
+
94
+ @objc func resumeRecord(_ call: CAPPluginCall) {
95
+ do {
96
+ try implementation.resumeRecord()
97
+ call.resolve()
98
+ } catch {
99
+ call.reject(error.localizedDescription)
100
+ }
101
+ }
102
+
103
+ @objc func preparePlay(_ call: CAPPluginCall) {
104
+ guard let path = call.getString("path") else {
105
+ call.reject("Path is required")
106
+ return
107
+ }
108
+
109
+ do {
110
+ let result = try implementation.preparePlay(path: path)
111
+ call.resolve([
112
+ "playerId": result.playerId,
113
+ "duration": result.duration
114
+ ])
115
+ } catch {
116
+ call.reject(error.localizedDescription)
117
+ }
118
+ }
119
+
120
+ @objc func play(_ call: CAPPluginCall) {
121
+ guard let playerId = call.getString("playerId") else {
122
+ call.reject("playerId is required")
123
+ return
124
+ }
125
+
126
+ do {
127
+ try implementation.play(playerId: playerId)
128
+ call.resolve()
129
+ } catch {
130
+ call.reject(error.localizedDescription)
131
+ }
132
+ }
133
+
134
+ @objc func pausePlay(_ call: CAPPluginCall) {
135
+ guard let playerId = call.getString("playerId") else {
136
+ call.reject("playerId is required")
137
+ return
138
+ }
139
+
140
+ do {
141
+ try implementation.pausePlay(playerId: playerId)
142
+ call.resolve()
143
+ } catch {
144
+ call.reject(error.localizedDescription)
145
+ }
146
+ }
147
+
148
+ @objc func resumePlay(_ call: CAPPluginCall) {
149
+ guard let playerId = call.getString("playerId") else {
150
+ call.reject("playerId is required")
151
+ return
152
+ }
153
+
154
+ do {
155
+ try implementation.resumePlay(playerId: playerId)
156
+ call.resolve()
157
+ } catch {
158
+ call.reject(error.localizedDescription)
159
+ }
160
+ }
161
+
162
+ @objc func stopPlay(_ call: CAPPluginCall) {
163
+ guard let playerId = call.getString("playerId") else {
164
+ call.reject("playerId is required")
165
+ return
166
+ }
167
+
168
+ do {
169
+ try implementation.stopPlay(playerId: playerId)
170
+ call.resolve()
171
+ } catch {
172
+ call.reject(error.localizedDescription)
173
+ }
174
+ }
175
+
176
+ @objc func seekTo(_ call: CAPPluginCall) {
177
+ guard let playerId = call.getString("playerId") else {
178
+ call.reject("playerId is required")
179
+ return
180
+ }
181
+
182
+ guard let position = call.getInt("position") else {
183
+ call.reject("Position is required")
184
+ return
185
+ }
186
+
187
+ do {
188
+ try implementation.seekTo(playerId: playerId, position: position)
189
+ call.resolve()
190
+ } catch {
191
+ call.reject(error.localizedDescription)
192
+ }
193
+ }
194
+
195
+ @objc func getPlaybackStatus(_ call: CAPPluginCall) {
196
+ guard let playerId = call.getString("playerId") else {
197
+ call.reject("playerId is required")
198
+ return
199
+ }
200
+
201
+ guard let status = implementation.getPlaybackStatus(playerId: playerId) else {
202
+ call.reject("No player found with ID: \(playerId)")
203
+ return
204
+ }
205
+
206
+ call.resolve([
207
+ "playerId": playerId,
208
+ "status": status.status,
209
+ "currentPosition": status.currentPosition,
210
+ "duration": status.duration
211
+ ])
212
+ }
213
+
214
+ @objc func getRecordingStatus(_ call: CAPPluginCall) {
215
+ let status = implementation.getRecordingStatus()
216
+ call.resolve([
217
+ "status": status.status,
218
+ "duration": status.duration
219
+ ])
220
+ }
221
+
222
+ @objc func destroyPlayer(_ call: CAPPluginCall) {
223
+ guard let playerId = call.getString("playerId") else {
224
+ call.reject("playerId is required")
225
+ return
226
+ }
227
+
228
+ implementation.destroyPlayer(playerId: playerId)
229
+ call.resolve()
230
+ }
231
+
232
+ // MARK: - RecorderPlayerDelegate
233
+
234
+ public func onRecordingStatusChange(status: RecordingStatus, duration: Int) {
235
+ notifyListeners("recordingStatusChange", data: [
236
+ "status": status.rawValue,
237
+ "duration": duration
238
+ ])
239
+ }
240
+
241
+ public func onPlaybackStatusChange(playerId: String, status: PlaybackStatus, currentPosition: Int, duration: Int) {
242
+ notifyListeners("playbackStatusChange", data: [
243
+ "playerId": playerId,
244
+ "status": status.rawValue,
245
+ "currentPosition": currentPosition,
246
+ "duration": duration
247
+ ])
248
+ }
249
+ }
@@ -0,0 +1,15 @@
1
+ import XCTest
2
+ @testable import RecorderPlayerPlugin
3
+
4
+ class RecorderPlayerTests: XCTestCase {
5
+ func testEcho() {
6
+ // This is an example of a functional test case for a plugin.
7
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
8
+
9
+ let implementation = RecorderPlayer()
10
+ let value = "Hello, World!"
11
+ let result = implementation.echo(value)
12
+
13
+ XCTAssertEqual(value, result)
14
+ }
15
+ }
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@scr2em/capacitor-plugin-recorder",
3
+ "version": "0.0.1",
4
+ "description": "ee",
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
+ "CapacitorPluginRecorder.podspec"
17
+ ],
18
+ "author": "me",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/issues"
26
+ },
27
+ "keywords": [
28
+ "capacitor",
29
+ "plugin",
30
+ "native"
31
+ ],
32
+ "scripts": {
33
+ "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
34
+ "verify:ios": "xcodebuild -scheme CapacitorPluginRecorder -destination generic/platform=iOS",
35
+ "verify:android": "cd android && ./gradlew clean build test && cd ..",
36
+ "verify:web": "npm run build",
37
+ "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
38
+ "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
39
+ "eslint": "eslint . --ext ts",
40
+ "prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
41
+ "swiftlint": "node-swiftlint",
42
+ "docgen": "docgen --api RecorderPlayerPlugin --output-readme README.md --output-json dist/docs.json",
43
+ "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
44
+ "clean": "rimraf ./dist",
45
+ "watch": "tsc --watch",
46
+ "prepublishOnly": "npm run build"
47
+ },
48
+ "devDependencies": {
49
+ "@capacitor/android": "^8.0.0",
50
+ "@capacitor/core": "^8.0.0",
51
+ "@capacitor/docgen": "^0.3.1",
52
+ "@capacitor/ios": "^8.0.0",
53
+ "@ionic/eslint-config": "^0.4.0",
54
+ "@ionic/prettier-config": "^4.0.0",
55
+ "@ionic/swiftlint-config": "^2.0.0",
56
+ "eslint": "^8.57.1",
57
+ "prettier": "^3.6.2",
58
+ "prettier-plugin-java": "^2.7.7",
59
+ "rimraf": "^6.1.0",
60
+ "rollup": "^4.53.2",
61
+ "swiftlint": "^2.0.0",
62
+ "typescript": "^5.9.3"
63
+ },
64
+ "peerDependencies": {
65
+ "@capacitor/core": ">=8.0.0"
66
+ },
67
+ "prettier": "@ionic/prettier-config",
68
+ "swiftlint": "@ionic/swiftlint-config",
69
+ "eslintConfig": {
70
+ "extends": "@ionic/eslint-config/recommended"
71
+ },
72
+ "capacitor": {
73
+ "ios": {
74
+ "src": "ios"
75
+ },
76
+ "android": {
77
+ "src": "android"
78
+ }
79
+ }
80
+ }