capacitor-camera-view 2.2.0-rc.1 → 2.2.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.
Files changed (28) hide show
  1. package/README.md +17 -202
  2. package/android/build.gradle +0 -1
  3. package/android/src/main/AndroidManifest.xml +0 -1
  4. package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt +4 -287
  5. package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt +8 -112
  6. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/BarcodeDetectionResult.kt +1 -0
  7. package/android/src/main/java/com/michaelwolz/capacitorcameraview/utils.kt +1 -21
  8. package/dist/docs.json +21 -201
  9. package/dist/esm/definitions.d.ts +23 -85
  10. package/dist/esm/definitions.js.map +1 -1
  11. package/dist/esm/web.d.ts +4 -20
  12. package/dist/esm/web.js +16 -151
  13. package/dist/esm/web.js.map +1 -1
  14. package/dist/plugin.cjs.js +16 -151
  15. package/dist/plugin.cjs.js.map +1 -1
  16. package/dist/plugin.js +16 -151
  17. package/dist/plugin.js.map +1 -1
  18. package/ios/Sources/CameraViewPlugin/CameraError.swift +0 -28
  19. package/ios/Sources/CameraViewPlugin/CameraEvents.swift +27 -3
  20. package/ios/Sources/CameraViewPlugin/CameraSessionConfiguration.swift +8 -8
  21. package/ios/Sources/CameraViewPlugin/CameraViewManager+BarcodeScan.swift +34 -3
  22. package/ios/Sources/CameraViewPlugin/CameraViewManager+PhotoCapture.swift +14 -13
  23. package/ios/Sources/CameraViewPlugin/CameraViewManager.swift +150 -159
  24. package/ios/Sources/CameraViewPlugin/CameraViewPlugin.swift +15 -114
  25. package/ios/Sources/CameraViewPlugin/TempFileManager.swift +34 -68
  26. package/package.json +12 -12
  27. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/VideoRecordingQuality.kt +0 -10
  28. package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoRecording.swift +0 -302
@@ -1,302 +0,0 @@
1
- import AVFoundation
2
- import Foundation
3
-
4
- public enum VideoRecordingQuality: String {
5
- case lowest
6
- case sd
7
- case hd
8
- case fhd
9
- case uhd
10
- case highest
11
- }
12
-
13
- extension CameraViewManager: AVCaptureFileOutputRecordingDelegate {
14
-
15
- // MARK: - Public API
16
-
17
- /// Starts video recording to a temporary file.
18
- ///
19
- /// - Parameters:
20
- /// - enableAudio: Whether to include audio in the recording
21
- /// - videoQuality: Desired video recording quality preset
22
- /// - completion: Called when recording starts (nil) or fails (error)
23
- public func startRecording(
24
- enableAudio: Bool,
25
- videoQuality: VideoRecordingQuality,
26
- completion: @escaping (Error?) -> Void
27
- ) {
28
- sessionQueue.async { [weak self] in
29
- guard let self = self else { return }
30
-
31
- guard self.captureSession.isRunning else {
32
- // Session may be temporarily stopped (e.g. iOS stops the capture session
33
- // when reconfiguring audio after a microphone permission grant). Wait for
34
- // it to resume and retry rather than failing immediately.
35
- self.waitForSessionThenStartRecording(
36
- enableAudio: enableAudio,
37
- videoQuality: videoQuality,
38
- completion: completion
39
- )
40
- return
41
- }
42
-
43
- guard !self.avMovieOutput.isRecording else {
44
- DispatchQueue.main.async { completion(CameraError.recordingAlreadyInProgress) }
45
- return
46
- }
47
-
48
- self.captureSession.beginConfiguration()
49
- self.sessionPresetBeforeRecording = self.captureSession.sessionPreset
50
-
51
- let recordingPreset = self.resolveRecordingPreset(for: videoQuality)
52
- if self.captureSession.canSetSessionPreset(recordingPreset) {
53
- self.captureSession.sessionPreset = recordingPreset
54
- }
55
-
56
- // Add movie output if not already added
57
- if !self.captureSession.outputs.contains(self.avMovieOutput) {
58
- guard self.captureSession.canAddOutput(self.avMovieOutput) else {
59
- self.restoreSessionPreset()
60
- self.captureSession.commitConfiguration()
61
- DispatchQueue.main.async {
62
- completion(CameraError.outputAdditionFailed)
63
- }
64
- return
65
- }
66
- self.captureSession.addOutput(self.avMovieOutput)
67
- }
68
-
69
- // Add audio input if requested
70
- if enableAudio {
71
- do {
72
- try self.addAudioInput()
73
- } catch {
74
- self.restoreSessionPreset()
75
- self.captureSession.commitConfiguration()
76
- DispatchQueue.main.async {
77
- completion(error)
78
- }
79
- return
80
- }
81
- }
82
-
83
- self.captureSession.commitConfiguration()
84
-
85
- // Set orientation on the movie output connection
86
- if let connection = self.avMovieOutput.connection(with: .video),
87
- let previewConnection = self.videoPreviewLayer.connection {
88
- if connection.isVideoOrientationSupported {
89
- connection.videoOrientation = previewConnection.videoOrientation
90
- }
91
- if connection.isVideoMirroringSupported {
92
- connection.isVideoMirrored = self.currentCameraDevice?.position == .front
93
- }
94
- }
95
-
96
- // Create a temp file URL and start recording
97
- let outputURL = TempFileManager.shared.createTempVideoFile()
98
- self.avMovieOutput.startRecording(to: outputURL, recordingDelegate: self)
99
-
100
- DispatchQueue.main.async {
101
- completion(nil)
102
- }
103
- }
104
- }
105
-
106
- /// Stops the current video recording.
107
- ///
108
- /// - Parameter completion: Called with the output file URL or an error
109
- public func stopRecording(completion: @escaping (URL?, Error?) -> Void) {
110
- // The entire body runs on sessionQueue so that the isRecording
111
- // check and the handler assignment are serialised with startRecording
112
- // and with the delegate callback that reads the handler.
113
- sessionQueue.async { [weak self] in
114
- guard let self = self else { return }
115
-
116
- guard self.avMovieOutput.isRecording else {
117
- DispatchQueue.main.async { completion(nil, CameraError.noRecordingInProgress) }
118
- return
119
- }
120
-
121
- self.videoRecordingCompletionHandler = completion
122
- self.avMovieOutput.stopRecording()
123
- }
124
- }
125
-
126
- // MARK: - AVCaptureFileOutputRecordingDelegate
127
-
128
- public func fileOutput(
129
- _ output: AVCaptureFileOutput,
130
- didFinishRecordingTo outputFileURL: URL,
131
- from connections: [AVCaptureConnection],
132
- error: Error?
133
- ) {
134
- // AVFoundation may invoke this delegate on an arbitrary thread.
135
- // Use sessionQueue.sync to read and clear the handler and the
136
- // recordingWithAudio flag while holding the same serial queue that
137
- // stopRecording uses to write them, preventing a data race.
138
- var handler: ((URL?, Error?) -> Void)?
139
- var hadAudio = false
140
-
141
- sessionQueue.sync { [weak self] in
142
- guard let self = self else { return }
143
- handler = self.videoRecordingCompletionHandler
144
- self.videoRecordingCompletionHandler = nil
145
- hadAudio = self.recordingWithAudio
146
- }
147
-
148
- // Remove movie output (and audio input if it was added) from session.
149
- sessionQueue.async { [weak self] in
150
- guard let self = self else { return }
151
- self.captureSession.beginConfiguration()
152
- if hadAudio {
153
- self.removeAudioInput()
154
- }
155
- self.captureSession.removeOutput(self.avMovieOutput)
156
- self.restoreSessionPreset()
157
- self.recordingWithAudio = false
158
- self.captureSession.commitConfiguration()
159
- }
160
-
161
- if let error = error {
162
- DispatchQueue.main.async {
163
- handler?(nil, error)
164
- }
165
- return
166
- }
167
-
168
- DispatchQueue.main.async {
169
- handler?(outputFileURL, nil)
170
- }
171
- }
172
-
173
- // MARK: - Private Helpers
174
-
175
- /// Waits for the capture session to start running, then retries `startRecording`.
176
- ///
177
- /// Called when `startRecording` finds the session temporarily stopped (e.g. after
178
- /// iOS reconfigures audio following a first-time microphone permission grant).
179
- /// Both the notification path and the timeout path serialize through `sessionQueue`,
180
- /// so `handled` is accessed on a single serial queue and needs no additional lock.
181
- private func waitForSessionThenStartRecording(
182
- enableAudio: Bool,
183
- videoQuality: VideoRecordingQuality,
184
- completion: @escaping (Error?) -> Void
185
- ) {
186
- let sessionQueue = self.sessionQueue
187
- // Keep the token so we can remove the observer on success and timeout paths.
188
- var observerToken: NSObjectProtocol?
189
- var handled = false
190
-
191
- observerToken = NotificationCenter.default.addObserver(
192
- forName: .AVCaptureSessionDidStartRunning,
193
- object: captureSession,
194
- queue: nil
195
- ) { [weak self] _ in
196
- sessionQueue.async {
197
- guard !handled else { return }
198
- handled = true
199
- if let token = observerToken {
200
- NotificationCenter.default.removeObserver(token)
201
- observerToken = nil
202
- }
203
- guard let self = self else { return }
204
- self.startRecording(
205
- enableAudio: enableAudio,
206
- videoQuality: videoQuality,
207
- completion: completion
208
- )
209
- }
210
- }
211
-
212
- // Timeout: if the session hasn't restarted within 2 seconds, give up.
213
- sessionQueue.asyncAfter(deadline: .now() + 2.0) { [weak self] in
214
- guard !handled else { return }
215
- handled = true
216
- if let token = observerToken {
217
- NotificationCenter.default.removeObserver(token)
218
- observerToken = nil
219
- }
220
- guard let self = self else { return }
221
- // One final check in case the session started just as we timed out.
222
- if self.captureSession.isRunning {
223
- self.startRecording(
224
- enableAudio: enableAudio,
225
- videoQuality: videoQuality,
226
- completion: completion
227
- )
228
- } else {
229
- DispatchQueue.main.async { completion(CameraError.sessionNotRunning) }
230
- }
231
- }
232
- }
233
-
234
- /// Resolves the appropriate AVCaptureSession.Preset for the given VideoRecordingQuality,
235
- private func resolveRecordingPreset(for videoQuality: VideoRecordingQuality) -> AVCaptureSession.Preset {
236
- let preferredPresets: [AVCaptureSession.Preset]
237
- switch videoQuality {
238
- case .lowest:
239
- preferredPresets = [.low]
240
- case .sd:
241
- preferredPresets = [.vga640x480, .medium, .low]
242
- case .hd:
243
- preferredPresets = [.hd1280x720, .high, .medium]
244
- case .fhd:
245
- preferredPresets = [.hd1920x1080, .hd1280x720, .high]
246
- case .uhd:
247
- preferredPresets = [.hd4K3840x2160, .hd1920x1080, .hd1280x720, .high]
248
- case .highest:
249
- preferredPresets = [.hd4K3840x2160, .hd1920x1080, .hd1280x720, .high, .medium, .low]
250
- }
251
-
252
- for preset in preferredPresets where captureSession.canSetSessionPreset(preset) {
253
- return preset
254
- }
255
-
256
- return captureSession.sessionPreset
257
- }
258
-
259
- /// Restores the session preset to its previous value before recording if it was changed for recording.
260
- private func restoreSessionPreset() {
261
- if let previousPreset = sessionPresetBeforeRecording,
262
- captureSession.canSetSessionPreset(previousPreset) {
263
- captureSession.sessionPreset = previousPreset
264
- }
265
- sessionPresetBeforeRecording = nil
266
- }
267
-
268
- /// Adds microphone input to the capture session.
269
- ///
270
- /// - Throws: `CameraError.audioDeviceUnavailable` if no microphone is present,
271
- /// `CameraError.audioInputAdditionFailed` if the session rejects the input,
272
- /// or the underlying `AVCaptureDeviceInput` error if device configuration fails.
273
- private func addAudioInput() throws {
274
- // Check if audio input already exists; nothing to do if so.
275
- let hasAudioInput = captureSession.inputs.contains { input in
276
- (input as? AVCaptureDeviceInput)?.device.hasMediaType(.audio) == true
277
- }
278
-
279
- guard !hasAudioInput else { return }
280
-
281
- guard let microphone = AVCaptureDevice.default(for: .audio) else {
282
- throw CameraError.audioDeviceUnavailable
283
- }
284
-
285
- let audioInput = try AVCaptureDeviceInput(device: microphone)
286
-
287
- guard captureSession.canAddInput(audioInput) else {
288
- throw CameraError.audioInputAdditionFailed
289
- }
290
-
291
- captureSession.addInput(audioInput)
292
- recordingWithAudio = true
293
- }
294
-
295
- /// Removes microphone input from the capture session.
296
- private func removeAudioInput() {
297
- captureSession.inputs
298
- .compactMap { $0 as? AVCaptureDeviceInput }
299
- .filter { $0.device.hasMediaType(.audio) }
300
- .forEach { captureSession.removeInput($0) }
301
- }
302
- }