@independo/capacitor-voice-recorder 8.1.0-dev.2 → 8.1.0-dev.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Independo GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -82,7 +82,7 @@ tasks.register('jacocoTestReport', JacocoReport) {
82
82
  '**/*Test*.*',
83
83
  'android/**/*.*',
84
84
  ]
85
- def javaClasses = fileTree(dir: "$buildDir/intermediates/javac/debug/classes", excludes: fileFilter)
85
+ def javaClasses = fileTree(dir: "$buildDir/intermediates/javac/debug", excludes: fileFilter)
86
86
  def kotlinClasses = fileTree(dir: "$buildDir/tmp/kotlin-classes/debug", excludes: fileFilter)
87
87
  classDirectories.setFrom(files([javaClasses, kotlinClasses]))
88
88
  sourceDirectories.setFrom(files(['src/main/java', 'src/main/kotlin']))
@@ -1,15 +1,36 @@
1
1
  import Foundation
2
2
  import AVFoundation
3
3
 
4
+ protocol AudioSessionProtocol: AnyObject {
5
+ var category: AVAudioSession.Category { get }
6
+ func setCategory(_ category: AVAudioSession.Category) throws
7
+ func setActive(_ active: Bool, options: AVAudioSession.SetActiveOptions) throws
8
+ }
9
+
10
+ protocol AudioRecorderProtocol: AnyObject {
11
+ @discardableResult
12
+ func record() -> Bool
13
+ func stop()
14
+ func pause()
15
+ }
16
+
17
+ typealias AudioRecorderFactory = (_ url: URL, _ settings: [String: Any]) throws -> AudioRecorderProtocol
18
+
19
+ extension AVAudioSession: AudioSessionProtocol {}
20
+ extension AVAudioRecorder: AudioRecorderProtocol {}
21
+
4
22
  /// AVAudioRecorder wrapper that supports interruptions and segment merging.
5
23
  class CustomMediaRecorder: RecorderAdapter {
6
24
 
25
+ private let audioSessionProvider: () -> AudioSessionProtocol
26
+ private let audioRecorderFactory: AudioRecorderFactory
27
+
7
28
  /// Options provided by the service layer.
8
29
  public var options: RecordOptions?
9
30
  /// Active audio session for recording.
10
- private var recordingSession: AVAudioSession!
31
+ private var recordingSession: AudioSessionProtocol!
11
32
  /// Active recorder instance for the current segment.
12
- private var audioRecorder: AVAudioRecorder!
33
+ private var audioRecorder: AudioRecorderProtocol!
13
34
  /// Base file path for the merged recording.
14
35
  private var baseAudioFilePath: URL!
15
36
  /// List of segment files created during interruptions.
@@ -26,13 +47,23 @@ class CustomMediaRecorder: RecorderAdapter {
26
47
  var onInterruptionEnded: (() -> Void)?
27
48
 
28
49
  /// Recorder settings used for all segments.
29
- private let settings = [
50
+ private let settings: [String: Any] = [
30
51
  AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
31
52
  AVSampleRateKey: 44100,
32
53
  AVNumberOfChannelsKey: 1,
33
54
  AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
34
55
  ]
35
56
 
57
+ init(
58
+ audioSessionProvider: @escaping () -> AudioSessionProtocol = { AVAudioSession.sharedInstance() },
59
+ audioRecorderFactory: @escaping AudioRecorderFactory = { url, settings in
60
+ return try AVAudioRecorder(url: url, settings: settings)
61
+ }
62
+ ) {
63
+ self.audioSessionProvider = audioSessionProvider
64
+ self.audioRecorderFactory = audioRecorderFactory
65
+ }
66
+
36
67
  /// Resolves the directory where audio files should be saved.
37
68
  private func getDirectoryToSaveAudioFile() -> URL {
38
69
  if options?.directory != nil,
@@ -60,13 +91,13 @@ class CustomMediaRecorder: RecorderAdapter {
60
91
  public func startRecording(recordOptions: RecordOptions?) -> Bool {
61
92
  do {
62
93
  options = recordOptions
63
- recordingSession = AVAudioSession.sharedInstance()
94
+ recordingSession = audioSessionProvider()
64
95
  originalRecordingSessionCategory = recordingSession.category
65
96
  try recordingSession.setCategory(AVAudioSession.Category.playAndRecord)
66
- try recordingSession.setActive(true)
97
+ try recordingSession.setActive(true, options: [])
67
98
  baseAudioFilePath = getDirectoryToSaveAudioFile().appendingPathComponent("recording-\(Int(Date().timeIntervalSince1970 * 1000)).aac")
68
99
  audioFileSegments = [baseAudioFilePath]
69
- audioRecorder = try AVAudioRecorder(url: baseAudioFilePath, settings: settings)
100
+ audioRecorder = try audioRecorderFactory(baseAudioFilePath, settings)
70
101
  setupInterruptionHandling()
71
102
  audioRecorder.record()
72
103
  status = CurrentRecordingStatus.RECORDING
@@ -88,7 +119,7 @@ class CustomMediaRecorder: RecorderAdapter {
88
119
  }
89
120
 
90
121
  do {
91
- try self.recordingSession.setActive(false)
122
+ try self.recordingSession.setActive(false, options: [])
92
123
  try self.recordingSession.setCategory(self.originalRecordingSessionCategory)
93
124
  } catch {
94
125
  }
@@ -151,13 +182,13 @@ class CustomMediaRecorder: RecorderAdapter {
151
182
  if(status == CurrentRecordingStatus.PAUSED || status == CurrentRecordingStatus.INTERRUPTED) {
152
183
  let wasInterrupted = status == CurrentRecordingStatus.INTERRUPTED
153
184
  do {
154
- try recordingSession.setActive(true)
185
+ try recordingSession.setActive(true, options: [])
155
186
  if status == CurrentRecordingStatus.INTERRUPTED {
156
187
  let directory = getDirectoryToSaveAudioFile()
157
188
  let timestamp = Int(Date().timeIntervalSince1970 * 1000)
158
189
  let segmentNumber = audioFileSegments.count
159
190
  let segmentPath = directory.appendingPathComponent("recording-\(timestamp)-segment-\(segmentNumber).aac")
160
- audioRecorder = try AVAudioRecorder(url: segmentPath, settings: settings)
191
+ audioRecorder = try audioRecorderFactory(segmentPath, settings)
161
192
  audioFileSegments.append(segmentPath)
162
193
  }
163
194
  audioRecorder.record()
@@ -165,7 +196,7 @@ class CustomMediaRecorder: RecorderAdapter {
165
196
  return true
166
197
  } catch {
167
198
  if wasInterrupted {
168
- try? recordingSession.setActive(false)
199
+ try? recordingSession.setActive(false, options: [])
169
200
  }
170
201
  return false
171
202
  }
@@ -183,7 +214,7 @@ class CustomMediaRecorder: RecorderAdapter {
183
214
  private func setupInterruptionHandling() {
184
215
  interruptionObserver = NotificationCenter.default.addObserver(
185
216
  forName: AVAudioSession.interruptionNotification,
186
- object: AVAudioSession.sharedInstance(),
217
+ object: recordingSession,
187
218
  queue: .main
188
219
  ) { [weak self] notification in
189
220
  self?.handleInterruption(notification: notification)
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "url": "https://github.com/independo-gmbh/capacitor-voice-recorder.git"
14
14
  },
15
15
  "description": "Capacitor plugin for voice recording",
16
- "version": "8.1.0-dev.2",
16
+ "version": "8.1.0-dev.3",
17
17
  "devDependencies": {
18
18
  "@capacitor/android": "^8.0.0",
19
19
  "conventional-changelog-conventionalcommits": "^9.1.0",