@capgo/capacitor-screen-recorder 8.3.7 → 8.3.9

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/README.md CHANGED
@@ -45,7 +45,7 @@ npx cap sync
45
45
 
46
46
  ## IOS
47
47
 
48
- add NSPhotoLibraryUsageDescription in your info.plist
48
+ add NSPhotoLibraryAddUsageDescription in your info.plist
49
49
 
50
50
  ## Android
51
51
  increase project's minSdk version to 23, it's required by the dependency HBRecorder
@@ -10,7 +10,7 @@ import dev.bmcreations.scrcast.config.Options;
10
10
  @CapacitorPlugin(name = "ScreenRecorder")
11
11
  public class ScreenRecorderPlugin extends Plugin {
12
12
 
13
- private final String pluginVersion = "8.3.7";
13
+ private final String pluginVersion = "8.3.9";
14
14
 
15
15
  private CapgoScrCast videoRecorder;
16
16
  private CapgoScrCast audioRecorder;
@@ -7,7 +7,7 @@ import Capacitor
7
7
  */
8
8
  @objc(ScreenRecorderPlugin)
9
9
  public class ScreenRecorderPlugin: CAPPlugin, CAPBridgedPlugin {
10
- private let pluginVersion: String = "8.3.7"
10
+ private let pluginVersion: String = "8.3.9"
11
11
  public let identifier = "ScreenRecorderPlugin"
12
12
  public let jsName = "ScreenRecorder"
13
13
  public let pluginMethods: [CAPPluginMethod] = [
@@ -15,6 +15,7 @@ import UIKit
15
15
  public enum ScreenRecorderError: Error {
16
16
  case notAvailable
17
17
  case photoLibraryAccessNotGranted
18
+ case alreadyRecording
18
19
  }
19
20
 
20
21
  public enum VideoContainerFormat {
@@ -51,6 +52,7 @@ public enum VideoContainerFormat {
51
52
 
52
53
  public final class ScreenRecorder {
53
54
  private var videoOutputURL: URL?
55
+ private var didCreateOutputFile = false
54
56
  private var videoWriter: AVAssetWriter?
55
57
  private var videoWriterInput: AVAssetWriterInput?
56
58
  private var micAudioWriterInput: AVAssetWriterInput?
@@ -58,6 +60,7 @@ public final class ScreenRecorder {
58
60
  private var saveToCameraRoll = false
59
61
  private var recordAudio = false
60
62
  private var videoFormat: VideoContainerFormat = .mp4
63
+ private var isRecording = false
61
64
  let recorder = RPScreenRecorder.shared()
62
65
 
63
66
  public func startRecording(to outputURL: URL? = nil,
@@ -66,6 +69,11 @@ public final class ScreenRecorder {
66
69
  recordAudio: Bool = false,
67
70
  videoFormat: VideoContainerFormat = .mp4,
68
71
  handler: @escaping (Error?) -> Void) {
72
+ guard !isRecording else {
73
+ return handler(ScreenRecorderError.alreadyRecording)
74
+ }
75
+ isRecording = true
76
+
69
77
  self.saveToCameraRoll = saveToCameraRoll
70
78
  self.recordAudio = recordAudio
71
79
  self.videoFormat = videoFormat
@@ -73,11 +81,16 @@ public final class ScreenRecorder {
73
81
 
74
82
  recorder.isMicrophoneEnabled = recordAudio
75
83
 
84
+ var attemptOutputURL: URL?
85
+ var attemptDidCreate = false
86
+
76
87
  do {
77
88
  if recordAudio {
78
89
  try configureAudioSession()
79
90
  }
80
91
  try createVideoWriter(in: outputURL)
92
+ attemptOutputURL = self.videoOutputURL
93
+ attemptDidCreate = self.didCreateOutputFile
81
94
  addVideoWriterInput(size: size)
82
95
  if recordAudio {
83
96
  self.micAudioWriterInput = createAndAddAudioInput()
@@ -85,6 +98,12 @@ public final class ScreenRecorder {
85
98
  }
86
99
  startCapture(handler: handler)
87
100
  } catch let err {
101
+ isRecording = false
102
+ if let cleanupURL = attemptOutputURL {
103
+ self.deleteOutputFileIfNeeded(outputURL: cleanupURL, didCreate: attemptDidCreate)
104
+ } else if self.didCreateOutputFile, let cleanupURL = self.videoOutputURL {
105
+ self.deleteOutputFileIfNeeded(outputURL: cleanupURL, didCreate: true)
106
+ }
88
107
  handler(err)
89
108
  }
90
109
  }
@@ -94,6 +113,8 @@ public final class ScreenRecorder {
94
113
  videoWriterInput = nil
95
114
  micAudioWriterInput = nil
96
115
  appAudioWriterInput = nil
116
+ videoOutputURL = nil
117
+ didCreateOutputFile = false
97
118
  }
98
119
 
99
120
  private func configureAudioSession() throws {
@@ -106,12 +127,14 @@ public final class ScreenRecorder {
106
127
  let newVideoOutputURL: URL
107
128
 
108
129
  if let passedVideoOutput = outputURL {
130
+ self.didCreateOutputFile = false
109
131
  self.videoOutputURL = passedVideoOutput
110
132
  newVideoOutputURL = passedVideoOutput
111
133
  } else {
112
134
  let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
113
- let fileName = "WylerNewVideo.\(videoFormat.fileExtension)"
135
+ let fileName = "WylerNewVideo-\(UUID().uuidString).\(videoFormat.fileExtension)"
114
136
  newVideoOutputURL = URL(fileURLWithPath: documentsPath.appendingPathComponent(fileName))
137
+ self.didCreateOutputFile = true
115
138
  self.videoOutputURL = newVideoOutputURL
116
139
  }
117
140
 
@@ -148,13 +171,26 @@ public final class ScreenRecorder {
148
171
  }
149
172
 
150
173
  private func startCapture(handler: @escaping (Error?) -> Void) {
174
+ let outputURL = self.videoOutputURL
175
+ let didCreate = self.didCreateOutputFile
176
+
151
177
  guard recorder.isAvailable else {
178
+ self.deleteOutputFileIfNeeded(outputURL: outputURL, didCreate: didCreate)
179
+ self.isRecording = false
152
180
  return handler(ScreenRecorderError.notAvailable)
153
181
  }
154
182
  var sent = false
155
183
  recorder.startCapture(handler: { (sampleBuffer, sampleType, passedError) in
156
184
  if let passedError = passedError {
185
+ self.videoWriterInput?.markAsFinished()
186
+ self.micAudioWriterInput?.markAsFinished()
187
+ self.appAudioWriterInput?.markAsFinished()
188
+ if self.videoWriter?.status == .writing {
189
+ self.videoWriter?.cancelWriting()
190
+ }
191
+ self.deleteOutputFileIfNeeded(outputURL: outputURL, didCreate: didCreate)
157
192
  if !sent {
193
+ self.isRecording = false
158
194
  handler(passedError)
159
195
  sent = true
160
196
  }
@@ -202,8 +238,23 @@ public final class ScreenRecorder {
202
238
 
203
239
  public func stoprecording(handler: @escaping (Error?) -> Void) {
204
240
  recorder.stopCapture(handler: { error in
241
+ let outputURL = self.videoOutputURL
242
+ let didCreate = self.didCreateOutputFile
243
+ let shouldSaveToCameraRoll = self.saveToCameraRoll
244
+ let complete: (Error?) -> Void = { stopError in
245
+ self.isRecording = false
246
+ handler(stopError)
247
+ }
248
+
205
249
  if let error = error {
206
- handler(error)
250
+ self.videoWriterInput?.markAsFinished()
251
+ self.micAudioWriterInput?.markAsFinished()
252
+ self.appAudioWriterInput?.markAsFinished()
253
+ if self.videoWriter?.status == .writing {
254
+ self.videoWriter?.cancelWriting()
255
+ }
256
+ self.deleteOutputFileIfNeeded(outputURL: outputURL, didCreate: didCreate)
257
+ complete(error)
207
258
  return
208
259
  }
209
260
 
@@ -212,61 +263,108 @@ public final class ScreenRecorder {
212
263
  self.appAudioWriterInput?.markAsFinished()
213
264
 
214
265
  guard let writer = self.videoWriter else {
215
- handler(nil)
266
+ complete(nil)
216
267
  return
217
268
  }
218
269
 
219
270
  if writer.status == .writing {
220
271
  writer.finishWriting {
221
272
  if let finishError = writer.error {
222
- handler(finishError)
273
+ self.deleteOutputFileIfNeeded(outputURL: outputURL, didCreate: didCreate)
274
+ complete(finishError)
223
275
  return
224
276
  }
225
- if self.saveToCameraRoll {
226
- self.saveVideoToCameraRollAfterAuthorized(handler: handler)
277
+ if shouldSaveToCameraRoll {
278
+ self.saveVideoToCameraRollAfterAuthorized(outputURL: outputURL,
279
+ didCreate: didCreate,
280
+ handler: complete)
227
281
  } else {
228
- handler(nil)
282
+ self.deleteOutputFileIfNeeded(outputURL: outputURL, didCreate: didCreate)
283
+ complete(nil)
229
284
  }
230
285
  }
231
286
  } else if writer.status == .failed {
232
- handler(writer.error)
287
+ self.deleteOutputFileIfNeeded(outputURL: outputURL, didCreate: didCreate)
288
+ complete(writer.error)
233
289
  } else {
234
- if self.saveToCameraRoll {
235
- self.saveVideoToCameraRollAfterAuthorized(handler: handler)
290
+ if shouldSaveToCameraRoll {
291
+ self.saveVideoToCameraRollAfterAuthorized(outputURL: outputURL,
292
+ didCreate: didCreate,
293
+ handler: complete)
236
294
  } else {
237
- handler(nil)
295
+ self.deleteOutputFileIfNeeded(outputURL: outputURL, didCreate: didCreate)
296
+ complete(nil)
238
297
  }
239
298
  }
240
299
  })
241
300
  }
242
301
 
243
- private func saveVideoToCameraRollAfterAuthorized(handler: @escaping (Error?) -> Void) {
244
- if PHPhotoLibrary.authorizationStatus() == .authorized {
245
- self.saveVideoToCameraRoll(handler: handler)
302
+ private func canSaveToPhotoLibrary(_ status: PHAuthorizationStatus) -> Bool {
303
+ switch status {
304
+ case .authorized, .limited:
305
+ return true
306
+ default:
307
+ return false
308
+ }
309
+ }
310
+
311
+ private func saveVideoToCameraRollAfterAuthorized(outputURL: URL? = nil,
312
+ didCreate: Bool? = nil,
313
+ handler: @escaping (Error?) -> Void) {
314
+ let capturedOutputURL = outputURL ?? self.videoOutputURL
315
+ let capturedDidCreate = didCreate ?? self.didCreateOutputFile
316
+ let status = PHPhotoLibrary.authorizationStatus(for: .addOnly)
317
+
318
+ if canSaveToPhotoLibrary(status) {
319
+ self.saveVideoToCameraRoll(outputURL: capturedOutputURL,
320
+ didCreate: capturedDidCreate,
321
+ handler: handler)
246
322
  } else {
247
- PHPhotoLibrary.requestAuthorization({ (status) in
248
- if status == .authorized {
249
- self.saveVideoToCameraRoll(handler: handler)
323
+ PHPhotoLibrary.requestAuthorization(for: .addOnly, handler: { (status) in
324
+ if self.canSaveToPhotoLibrary(status) {
325
+ self.saveVideoToCameraRoll(outputURL: capturedOutputURL,
326
+ didCreate: capturedDidCreate,
327
+ handler: handler)
250
328
  } else {
329
+ self.deleteOutputFileIfNeeded(outputURL: capturedOutputURL, didCreate: capturedDidCreate)
251
330
  handler(ScreenRecorderError.photoLibraryAccessNotGranted)
252
331
  }
253
332
  })
254
333
  }
255
334
  }
256
335
 
257
- private func saveVideoToCameraRoll(handler: @escaping (Error?) -> Void) {
258
- guard let videoOutputURL = self.videoOutputURL else {
336
+ private func saveVideoToCameraRoll(outputURL: URL?,
337
+ didCreate: Bool,
338
+ handler: @escaping (Error?) -> Void) {
339
+ guard let videoOutputURL = outputURL else {
259
340
  return handler(nil)
260
341
  }
261
342
 
262
343
  PHPhotoLibrary.shared().performChanges({
263
344
  PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoOutputURL)
264
- }, completionHandler: { _, error in
345
+ }, completionHandler: { success, error in
265
346
  if let error = error {
347
+ self.deleteOutputFileIfNeeded(outputURL: videoOutputURL, didCreate: didCreate)
266
348
  handler(error)
267
- } else {
349
+ } else if success {
350
+ self.deleteOutputFileIfNeeded(outputURL: videoOutputURL, didCreate: didCreate)
268
351
  handler(nil)
352
+ } else {
353
+ self.deleteOutputFileIfNeeded(outputURL: videoOutputURL, didCreate: didCreate)
354
+ handler(NSError(domain: "ScreenRecorder",
355
+ code: -1,
356
+ userInfo: [NSLocalizedDescriptionKey: "Failed to save video to photo library"]))
269
357
  }
270
358
  })
271
359
  }
360
+
361
+ private func deleteOutputFileIfNeeded(outputURL: URL? = nil, didCreate: Bool? = nil) {
362
+ let shouldDelete = didCreate ?? didCreateOutputFile
363
+ guard shouldDelete, let fileURL = outputURL ?? self.videoOutputURL else { return }
364
+ do {
365
+ try FileManager.default.removeItem(at: fileURL)
366
+ } catch {
367
+ debugPrint("Failed to delete recording file \(fileURL): \(error)")
368
+ }
369
+ }
272
370
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-screen-recorder",
3
- "version": "8.3.7",
3
+ "version": "8.3.9",
4
4
  "description": "Record device's screen",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
@@ -66,7 +66,7 @@
66
66
  "@ionic/eslint-config": "^0.4.0",
67
67
  "@ionic/prettier-config": "^4.0.0",
68
68
  "@ionic/swiftlint-config": "^2.0.0",
69
- "@types/node": "^24.10.1",
69
+ "@types/node": "^26.0.0",
70
70
  "eslint": "^8.57.1",
71
71
  "eslint-plugin-import": "^2.31.0",
72
72
  "prettier": "^3.6.2",