expo-camera 13.5.1 → 13.6.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/CHANGELOG.md CHANGED
@@ -10,6 +10,18 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 13.6.0 — 2023-09-04
14
+
15
+ ### 🎉 New features
16
+
17
+ - Added support for React Native 0.73. ([#24018](https://github.com/expo/expo/pull/24018) by [@kudo](https://github.com/kudo))
18
+
19
+ ### 🐛 Bug fixes
20
+
21
+ - Fixed flash is not enabled during recordings. ([#23776](https://github.com/expo/expo/pull/23776) by [@tszheichoi](https://github.com/tszheichoi))
22
+ - On iOS, fix dead frames when starting a video recording. ([#22037](https://github.com/expo/expo/pull/22037) by [@alanjhughes](https://github.com/alanjhughes))
23
+ - Remove @koale/useworker. ([#23967](https://github.com/expo/expo/pull/23967) by [@marklawlor](https://github.com/marklawlor))
24
+
13
25
  ## 13.5.1 — 2023-08-02
14
26
 
15
27
  _This version does not introduce any user-facing changes._
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
3
3
  apply plugin: 'maven-publish'
4
4
 
5
5
  group = 'host.exp.exponent'
6
- version = '13.5.1'
6
+ version = '13.6.0'
7
7
 
8
8
  buildscript {
9
9
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
@@ -53,13 +53,16 @@ afterEvaluate {
53
53
  android {
54
54
  compileSdkVersion safeExtGet("compileSdkVersion", 33)
55
55
 
56
- compileOptions {
57
- sourceCompatibility JavaVersion.VERSION_11
58
- targetCompatibility JavaVersion.VERSION_11
59
- }
56
+ def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
57
+ if (agpVersion.tokenize('.')[0].toInteger() < 8) {
58
+ compileOptions {
59
+ sourceCompatibility JavaVersion.VERSION_11
60
+ targetCompatibility JavaVersion.VERSION_11
61
+ }
60
62
 
61
- kotlinOptions {
62
- jvmTarget = JavaVersion.VERSION_11.majorVersion
63
+ kotlinOptions {
64
+ jvmTarget = JavaVersion.VERSION_11.majorVersion
65
+ }
63
66
  }
64
67
 
65
68
  namespace "expo.modules.camera"
@@ -67,7 +70,7 @@ android {
67
70
  minSdkVersion safeExtGet("minSdkVersion", 21)
68
71
  targetSdkVersion safeExtGet("targetSdkVersion", 33)
69
72
  versionCode 32
70
- versionName "13.5.1"
73
+ versionName "13.6.0"
71
74
  }
72
75
 
73
76
  lintOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"useWebQRScanner.d.ts","sourceRoot":"","sources":["../src/useWebQRScanner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AA2CjG,wBAAgB,eAAe,CAC7B,KAAK,EAAE,KAAK,CAAC,gBAAgB,CAAC,gBAAgB,GAAG,IAAI,CAAC,EACtD,EACE,SAAS,EACT,cAAc,EACd,QAAQ,EACR,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,eAAe,CAAC,CAAC;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,CAAC,cAAc,EAAE;QAAE,WAAW,EAAE,qBAAqB,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7E,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B,QA8DF"}
1
+ {"version":3,"file":"useWebQRScanner.d.ts","sourceRoot":"","sources":["../src/useWebQRScanner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAwEjG,wBAAgB,eAAe,CAC7B,KAAK,EAAE,KAAK,CAAC,gBAAgB,CAAC,gBAAgB,GAAG,IAAI,CAAC,EACtD,EACE,SAAS,EACT,cAAc,EACd,QAAQ,EACR,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,eAAe,CAAC,CAAC;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,CAAC,cAAc,EAAE;QAAE,WAAW,EAAE,qBAAqB,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7E,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B,QAyDF"}
@@ -1,4 +1,3 @@
1
- import { useWorker } from '@koale/useworker';
2
1
  import * as React from 'react';
3
2
  import { captureImageData } from './WebCameraUtils';
4
3
  const qrWorkerMethod = ({ data, width, height }) => {
@@ -32,16 +31,35 @@ const qrWorkerMethod = ({ data, width, height }) => {
32
31
  }
33
32
  return parsed;
34
33
  };
35
- function useRemoteJsQR() {
36
- return useWorker(qrWorkerMethod, {
37
- remoteDependencies: ['https://cdn.jsdelivr.net/npm/jsqr@1.2.0/dist/jsQR.min.js'],
38
- autoTerminate: false,
39
- });
40
- }
34
+ const createWorkerAsyncFunction = (fn, deps) => {
35
+ const stringifiedFn = [
36
+ `self.func = ${fn.toString()};`,
37
+ 'self.onmessage = (e) => {',
38
+ ' const result = self.func(e.data);',
39
+ ' self.postMessage(result);',
40
+ '};',
41
+ ];
42
+ if (deps.length > 0) {
43
+ stringifiedFn.unshift(`importScripts(${deps.map((dep) => `'${dep}'`).join(', ')});`);
44
+ }
45
+ const blob = new Blob(stringifiedFn, { type: 'text/javascript' });
46
+ const worker = new Worker(URL.createObjectURL(blob));
47
+ // First-In First-Out queue of promises
48
+ const promises = [];
49
+ worker.onmessage = (e) => promises.shift()?.resolve(e.data);
50
+ return (data) => {
51
+ return new Promise((resolve, reject) => {
52
+ promises.push({ resolve, reject });
53
+ worker.postMessage(data);
54
+ });
55
+ };
56
+ };
57
+ const decode = createWorkerAsyncFunction(qrWorkerMethod, [
58
+ 'https://cdn.jsdelivr.net/npm/jsqr@1.2.0/dist/jsQR.min.js',
59
+ ]);
41
60
  export function useWebQRScanner(video, { isEnabled, captureOptions, interval, onScanned, onError, }) {
42
61
  const isRunning = React.useRef(false);
43
62
  const timeout = React.useRef(undefined);
44
- const [decode, clearWorker] = useRemoteJsQR();
45
63
  async function scanAsync() {
46
64
  // If interval is 0 then only scan once.
47
65
  if (!isRunning.current || !onScanned) {
@@ -86,15 +104,11 @@ export function useWebQRScanner(video, { isEnabled, captureOptions, interval, on
86
104
  isRunning.current = true;
87
105
  scanAsync();
88
106
  }
89
- else {
90
- stop();
91
- }
92
- }, [isEnabled]);
93
- React.useEffect(() => {
94
107
  return () => {
95
- stop();
96
- clearWorker.kill();
108
+ if (isEnabled) {
109
+ stop();
110
+ }
97
111
  };
98
- }, []);
112
+ }, [isEnabled]);
99
113
  }
100
114
  //# sourceMappingURL=useWebQRScanner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useWebQRScanner.js","sourceRoot":"","sources":["../src/useWebQRScanner.ts"],"names":[],"mappings":"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","sourcesContent":["import { useWorker } from '@koale/useworker';\nimport * as React from 'react';\n\nimport { BarCodeScanningResult, CameraPictureOptions, MountErrorListener } from './Camera.types';\nimport { captureImageData } from './WebCameraUtils';\n\nconst qrWorkerMethod = ({ data, width, height }: ImageData): any => {\n // eslint-disable-next-line no-undef\n const decoded = (self as any).jsQR(data, width, height, {\n inversionAttempts: 'attemptBoth',\n });\n\n let parsed;\n try {\n parsed = JSON.parse(decoded);\n } catch {\n parsed = decoded;\n }\n\n if (parsed?.data) {\n const nativeEvent: BarCodeScanningResult = {\n type: 'qr',\n data: parsed.data,\n cornerPoints: [],\n bounds: { origin: { x: 0, y: 0 }, size: { width: 0, height: 0 } },\n };\n if (parsed.location) {\n nativeEvent.cornerPoints = [\n parsed.location.topLeftCorner,\n parsed.location.bottomLeftCorner,\n parsed.location.topRightCorner,\n parsed.location.bottomRightCorner,\n ];\n }\n return nativeEvent;\n }\n return parsed;\n};\n\nfunction useRemoteJsQR() {\n return useWorker(qrWorkerMethod, {\n remoteDependencies: ['https://cdn.jsdelivr.net/npm/jsqr@1.2.0/dist/jsQR.min.js'],\n autoTerminate: false,\n });\n}\n\nexport function useWebQRScanner(\n video: React.MutableRefObject<HTMLVideoElement | null>,\n {\n isEnabled,\n captureOptions,\n interval,\n onScanned,\n onError,\n }: {\n isEnabled: boolean;\n captureOptions: Pick<CameraPictureOptions, 'scale' | 'isImageMirror'>;\n interval?: number;\n onScanned?: (scanningResult: { nativeEvent: BarCodeScanningResult }) => void;\n onError?: MountErrorListener;\n }\n) {\n const isRunning = React.useRef<boolean>(false);\n const timeout = React.useRef<number | undefined>(undefined);\n\n const [decode, clearWorker] = useRemoteJsQR();\n\n async function scanAsync() {\n // If interval is 0 then only scan once.\n if (!isRunning.current || !onScanned) {\n stop();\n return;\n }\n try {\n const data = captureImageData(video.current, captureOptions);\n\n if (data) {\n const nativeEvent: BarCodeScanningResult | any = await decode(data);\n if (nativeEvent?.data) {\n onScanned({\n nativeEvent,\n });\n }\n }\n } catch (error) {\n if (onError) {\n onError({ nativeEvent: error });\n }\n } finally {\n // If interval is 0 then only scan once.\n if (interval === 0) {\n stop();\n return;\n }\n const intervalToUse = !interval || interval < 0 ? 16 : interval;\n // @ts-ignore: Type 'Timeout' is not assignable to type 'number'\n timeout.current = setTimeout(() => {\n scanAsync();\n }, intervalToUse);\n }\n }\n\n function stop() {\n isRunning.current = false;\n clearTimeout(timeout.current);\n }\n\n React.useEffect(() => {\n if (isEnabled) {\n isRunning.current = true;\n scanAsync();\n } else {\n stop();\n }\n }, [isEnabled]);\n\n React.useEffect(() => {\n return () => {\n stop();\n clearWorker.kill();\n };\n }, []);\n}\n"]}
1
+ {"version":3,"file":"useWebQRScanner.js","sourceRoot":"","sources":["../src/useWebQRScanner.ts"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\n\nimport { BarCodeScanningResult, CameraPictureOptions, MountErrorListener } from './Camera.types';\nimport { captureImageData } from './WebCameraUtils';\n\nconst qrWorkerMethod = ({ data, width, height }: ImageData): any => {\n // eslint-disable-next-line no-undef\n const decoded = (self as any).jsQR(data, width, height, {\n inversionAttempts: 'attemptBoth',\n });\n\n let parsed;\n try {\n parsed = JSON.parse(decoded);\n } catch {\n parsed = decoded;\n }\n\n if (parsed?.data) {\n const nativeEvent: BarCodeScanningResult = {\n type: 'qr',\n data: parsed.data,\n cornerPoints: [],\n bounds: { origin: { x: 0, y: 0 }, size: { width: 0, height: 0 } },\n };\n if (parsed.location) {\n nativeEvent.cornerPoints = [\n parsed.location.topLeftCorner,\n parsed.location.bottomLeftCorner,\n parsed.location.topRightCorner,\n parsed.location.bottomRightCorner,\n ];\n }\n return nativeEvent;\n }\n return parsed;\n};\n\nconst createWorkerAsyncFunction = <T extends (data: any) => any>(fn: T, deps: string[]) => {\n const stringifiedFn = [\n `self.func = ${fn.toString()};`,\n 'self.onmessage = (e) => {',\n ' const result = self.func(e.data);',\n ' self.postMessage(result);',\n '};',\n ];\n\n if (deps.length > 0) {\n stringifiedFn.unshift(`importScripts(${deps.map((dep) => `'${dep}'`).join(', ')});`);\n }\n\n const blob = new Blob(stringifiedFn, { type: 'text/javascript' });\n const worker = new Worker(URL.createObjectURL(blob));\n\n // First-In First-Out queue of promises\n const promises: {\n resolve: (value: ReturnType<T>) => void;\n reject: (reason?: any) => void;\n }[] = [];\n\n worker.onmessage = (e) => promises.shift()?.resolve(e.data);\n\n return (data: Parameters<T>[0]) => {\n return new Promise<ReturnType<T>>((resolve, reject) => {\n promises.push({ resolve, reject });\n worker.postMessage(data);\n });\n };\n};\n\nconst decode = createWorkerAsyncFunction(qrWorkerMethod, [\n 'https://cdn.jsdelivr.net/npm/jsqr@1.2.0/dist/jsQR.min.js',\n]);\n\nexport function useWebQRScanner(\n video: React.MutableRefObject<HTMLVideoElement | null>,\n {\n isEnabled,\n captureOptions,\n interval,\n onScanned,\n onError,\n }: {\n isEnabled: boolean;\n captureOptions: Pick<CameraPictureOptions, 'scale' | 'isImageMirror'>;\n interval?: number;\n onScanned?: (scanningResult: { nativeEvent: BarCodeScanningResult }) => void;\n onError?: MountErrorListener;\n }\n) {\n const isRunning = React.useRef<boolean>(false);\n const timeout = React.useRef<number | undefined>(undefined);\n\n async function scanAsync() {\n // If interval is 0 then only scan once.\n if (!isRunning.current || !onScanned) {\n stop();\n return;\n }\n try {\n const data = captureImageData(video.current, captureOptions);\n\n if (data) {\n const nativeEvent: BarCodeScanningResult | any = await decode(data);\n if (nativeEvent?.data) {\n onScanned({\n nativeEvent,\n });\n }\n }\n } catch (error) {\n if (onError) {\n onError({ nativeEvent: error });\n }\n } finally {\n // If interval is 0 then only scan once.\n if (interval === 0) {\n stop();\n return;\n }\n const intervalToUse = !interval || interval < 0 ? 16 : interval;\n // @ts-ignore: Type 'Timeout' is not assignable to type 'number'\n timeout.current = setTimeout(() => {\n scanAsync();\n }, intervalToUse);\n }\n }\n\n function stop() {\n isRunning.current = false;\n clearTimeout(timeout.current);\n }\n\n React.useEffect(() => {\n if (isEnabled) {\n isRunning.current = true;\n scanAsync();\n }\n\n return () => {\n if (isEnabled) {\n stop();\n }\n };\n }, [isEnabled]);\n}\n"]}
@@ -62,13 +62,12 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
62
62
  _previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
63
63
  _previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
64
64
  _previewLayer.needsDisplayOnBoundsChange = YES;
65
+ _previewLayer.session = _session;
65
66
  #endif
66
67
  _paused = NO;
67
68
  _isValidVideoOptions = YES;
68
69
  _pictureSize = AVCaptureSessionPresetHigh;
69
70
  [self changePreviewOrientation:[UIApplication sharedApplication].statusBarOrientation];
70
- [self initializeCaptureSessionInput];
71
- [self startSession];
72
71
  [[NSNotificationCenter defaultCenter] addObserver:self
73
72
  selector:@selector(orientationChanged:)
74
73
  name:UIDeviceOrientationDidChangeNotification
@@ -77,6 +76,8 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
77
76
  _motionManager = [[CMMotionManager alloc] init];
78
77
  _motionManager.accelerometerUpdateInterval = 0.2;
79
78
  _motionManager.gyroUpdateInterval = 0.2;
79
+ [self initializeCaptureSessionInput];
80
+ [self startSession];
80
81
  }
81
82
  return self;
82
83
  }
@@ -322,7 +323,11 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
322
323
 
323
324
  - (void)updatePictureSize
324
325
  {
325
- [self updateSessionPreset:_pictureSize];
326
+ EX_WEAKIFY(self);
327
+ dispatch_async(self.sessionQueue, ^{
328
+ EX_STRONGIFY(self);
329
+ [self updateSessionPreset:_pictureSize];
330
+ });
326
331
  }
327
332
 
328
333
  - (void)updateResponsiveOrientationWhenOrientationLocked
@@ -561,30 +566,48 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
561
566
 
562
567
  - (void)record:(NSDictionary *)options resolve:(EXPromiseResolveBlock)resolve reject:(EXPromiseRejectBlock)reject
563
568
  {
564
- if (_movieFileOutput == nil) {
565
- // At the time of writing AVCaptureMovieFileOutput and AVCaptureVideoDataOutput (> GMVDataOutput)
566
- // cannot coexist on the same AVSession (see: https://stackoverflow.com/a/4986032/1123156).
567
- // We stop face detection here and restart it in when AVCaptureMovieFileOutput finishes recording.
568
- if (_faceDetectorManager) {
569
- [_faceDetectorManager stopFaceDetection];
570
- }
571
- [self setupMovieFileCapture];
569
+ if (_faceDetectorManager) {
570
+ [_faceDetectorManager stopFaceDetection];
572
571
  }
572
+
573
+ EX_WEAKIFY(self);
574
+ dispatch_async(self.sessionQueue, ^{
575
+ EX_STRONGIFY(self);
576
+ [self configureVideoSessionWith:options reject:reject];
573
577
 
574
- if (_movieFileOutput != nil && !_movieFileOutput.isRecording && _videoRecordedResolve == nil && _videoRecordedReject == nil) {
575
- bool shouldBeMuted = options[@"mute"] && [options[@"mute"] boolValue];
576
- [self updateSessionAudioIsMuted:shouldBeMuted];
577
-
578
- AVCaptureConnection *connection = [_movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
579
- // TODO: Add support for videoStabilizationMode (right now it is not only read, never written to)
580
- if (connection.isVideoStabilizationSupported == NO) {
581
- EXLogWarn(@"%s: Video Stabilization is not supported on this device.", __func__);
582
- } else {
583
- [connection setPreferredVideoStabilizationMode:self.videoStabilizationMode];
578
+ // it is possible that the session has been invalidated at this point
579
+ // for example, the video codec option is invalid and so this call has already rejected
580
+ if (!self.isValidVideoOptions) {
581
+ return;
584
582
  }
585
- UIDeviceOrientation deviceOrientation = self.responsiveOrientationWhenOrientationLocked ? self.physicalOrientation : [[UIDevice currentDevice] orientation];
586
- [connection setVideoOrientation:[EXCameraUtils videoOrientationForDeviceOrientation:deviceOrientation]];
587
583
 
584
+ if (!self) {
585
+ reject(@"E_IMAGE_SAVE_FAILED", @"Camera view has been unmounted.", nil);
586
+ return;
587
+ }
588
+ if (!self.fileSystem) {
589
+ reject(@"E_IMAGE_SAVE_FAILED", @"No file system module", nil);
590
+ return;
591
+ }
592
+ NSString *directory = [self.fileSystem.cachesDirectory stringByAppendingPathComponent:@"Camera"];
593
+ NSString *path = [self.fileSystem generatePathInDirectory:directory withExtension:@".mov"];
594
+ NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:path];
595
+ self.videoRecordedResolve = resolve;
596
+ self.videoRecordedReject = reject;
597
+ [self.movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
598
+ });
599
+ }
600
+
601
+ - (void)configureVideoSessionWith:(NSDictionary *)options reject:(EXPromiseRejectBlock)reject
602
+ {
603
+ [self.session beginConfiguration];
604
+ bool shouldBeMuted = options[@"mute"] && [options[@"mute"] boolValue];
605
+ [self setupMovieFileCapture];
606
+ [self updateSessionAudioIsMuted:shouldBeMuted];
607
+
608
+ AVCaptureConnection *connection = [_movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
609
+ // TODO: Add support for videoStabilizationMode (right now it is not only read, never written to)
610
+ if (connection) {
588
611
  AVCaptureSessionPreset preset;
589
612
  if (options[@"quality"]) {
590
613
  EXCameraVideoResolution resolution = [options[@"quality"] integerValue];
@@ -592,11 +615,19 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
592
615
  } else if ([self.session.sessionPreset isEqual:AVCaptureSessionPresetPhoto]) {
593
616
  preset = AVCaptureSessionPresetHigh;
594
617
  }
595
-
618
+
596
619
  if (preset != nil) {
597
620
  [self updateSessionPreset:preset];
598
621
  }
599
622
 
623
+ if (connection.isVideoStabilizationSupported == NO) {
624
+ EXLogWarn(@"%s: Video Stabilization is not supported on this device.", __func__);
625
+ } else {
626
+ [connection setPreferredVideoStabilizationMode:self.videoStabilizationMode];
627
+ }
628
+ UIDeviceOrientation deviceOrientation = self.responsiveOrientationWhenOrientationLocked ? self.physicalOrientation : [[UIDevice currentDevice] orientation];
629
+ [connection setVideoOrientation:[EXCameraUtils videoOrientationForDeviceOrientation:deviceOrientation]];
630
+
600
631
  [self setVideoOptions:options forConnection:connection onReject:reject];
601
632
 
602
633
  bool canBeMirrored = connection.isVideoMirroringSupported;
@@ -604,74 +635,47 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
604
635
  if (canBeMirrored && shouldBeMirrored) {
605
636
  [connection setVideoMirrored:shouldBeMirrored];
606
637
  }
607
-
608
- EX_WEAKIFY(self);
609
- dispatch_async(self.sessionQueue, ^{
610
- EX_STRONGIFY(self);
611
- // it is possible that the session has been invalidated at this point
612
- // for example, the video codec option is invalid and so this call has already rejected
613
- if (!self.isValidVideoOptions) {
614
- return;
615
- }
616
-
617
- if (!self) {
618
- reject(@"E_IMAGE_SAVE_FAILED", @"Camera view has been unmounted.", nil);
619
- return;
620
- }
621
- if (!self.fileSystem) {
622
- reject(@"E_IMAGE_SAVE_FAILED", @"No file system module", nil);
623
- return;
624
- }
625
- NSString *directory = [self.fileSystem.cachesDirectory stringByAppendingPathComponent:@"Camera"];
626
- NSString *path = [self.fileSystem generatePathInDirectory:directory withExtension:@".mov"];
627
- NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:path];
628
- [self.movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
629
- self.videoRecordedResolve = resolve;
630
- self.videoRecordedReject = reject;
631
- });
632
638
  }
639
+
640
+ [self updateFlashMode];
641
+ [self.session commitConfiguration];
633
642
  }
634
643
 
635
644
  // Video options are set in an async block to prevent the possible race condition outlined here:
636
645
  // https://github.com/react-native-camera/react-native-camera/pull/2694
637
646
  - (void)setVideoOptions:(NSDictionary *)options forConnection:(AVCaptureConnection *)connection onReject:(EXPromiseRejectBlock)reject
638
647
  {
639
- EX_WEAKIFY(self);
640
- dispatch_async(_sessionQueue, ^{
641
- EX_STRONGIFY(self);
642
648
  // Reset validation flag
643
- self.isValidVideoOptions = YES;
644
-
645
- if (options[@"maxDuration"]) {
646
- Float64 maxDuration = [options[@"maxDuration"] floatValue];
647
- self.movieFileOutput.maxRecordedDuration = CMTimeMakeWithSeconds(maxDuration, 30);
648
- }
649
-
650
- if (options[@"maxFileSize"]) {
651
- self.movieFileOutput.maxRecordedFileSize = [options[@"maxFileSize"] integerValue];
652
- }
649
+ self.isValidVideoOptions = YES;
650
+
651
+ if (options[@"maxDuration"]) {
652
+ Float64 maxDuration = [options[@"maxDuration"] floatValue];
653
+ self.movieFileOutput.maxRecordedDuration = CMTimeMakeWithSeconds(maxDuration, 30);
654
+ }
655
+
656
+ if (options[@"maxFileSize"]) {
657
+ self.movieFileOutput.maxRecordedFileSize = [options[@"maxFileSize"] integerValue];
658
+ }
659
+
660
+ if (options[@"codec"]) {
661
+ AVVideoCodecType videoCodecType = [EXCameraUtils videoCodecForType: [options[@"codec"] integerValue]];
653
662
 
654
- if (options[@"codec"]) {
655
- AVVideoCodecType videoCodecType = [EXCameraUtils videoCodecForType: [options[@"codec"] integerValue]];
656
-
657
- if ([self.movieFileOutput.availableVideoCodecTypes containsObject:videoCodecType]) {
658
- [self.movieFileOutput setOutputSettings: @{AVVideoCodecKey: videoCodecType} forConnection: connection];
659
- self.videoCodecType = videoCodecType;
660
- } else {
661
- if ([videoCodecType isEqualToString: @"VIDEO_CODEC_UNKNOWN"]) {
662
- videoCodecType = options[@"codec"];
663
- }
664
- NSString *videoCodecErrorMessage = [NSString stringWithFormat: @"Video Codec '%@' is not supported on this device", videoCodecType];
665
- reject(@"E_RECORDING_FAILED", videoCodecErrorMessage, nil);
666
-
667
- [self cleanupMovieFileCapture];
668
- self.videoRecordedResolve = nil;
669
- self.videoRecordedReject = nil;
670
- self.isValidVideoOptions = NO;
663
+ if ([self.movieFileOutput.availableVideoCodecTypes containsObject:videoCodecType]) {
664
+ [self.movieFileOutput setOutputSettings: @{AVVideoCodecKey: videoCodecType} forConnection: connection];
665
+ self.videoCodecType = videoCodecType;
666
+ } else {
667
+ if ([videoCodecType isEqualToString: @"VIDEO_CODEC_UNKNOWN"]) {
668
+ videoCodecType = options[@"codec"];
671
669
  }
670
+ NSString *videoCodecErrorMessage = [NSString stringWithFormat: @"Video Codec '%@' is not supported on this device", videoCodecType];
671
+ reject(@"E_RECORDING_FAILED", videoCodecErrorMessage, nil);
672
+
673
+ [self cleanupMovieFileCapture];
674
+ self.videoRecordedResolve = nil;
675
+ self.videoRecordedReject = nil;
676
+ self.isValidVideoOptions = NO;
672
677
  }
673
-
674
- });
678
+ }
675
679
  }
676
680
 
677
681
  - (void)maybeStartFaceDetection:(BOOL)mirrored {
@@ -690,7 +694,11 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
690
694
 
691
695
  - (void)stopRecording
692
696
  {
693
- [_movieFileOutput stopRecording];
697
+ EX_WEAKIFY(self);
698
+ dispatch_async(self.sessionQueue, ^{
699
+ EX_STRONGIFY(self);
700
+ [self.movieFileOutput stopRecording];
701
+ });
694
702
  }
695
703
 
696
704
  - (void)resumePreview
@@ -715,12 +723,12 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
715
723
  return;
716
724
  }
717
725
  EX_WEAKIFY(self);
718
- dispatch_async(_sessionQueue, ^{
719
- EX_ENSURE_STRONGIFY(self);
720
-
726
+ dispatch_async(self.sessionQueue, ^{
727
+ EX_STRONGIFY(self);
721
728
  if (self.presetCamera == AVCaptureDevicePositionUnspecified) {
722
729
  return;
723
730
  }
731
+ [self.session beginConfiguration];
724
732
 
725
733
  AVCapturePhotoOutput *photoOutput = [AVCapturePhotoOutput new];
726
734
  photoOutput.highResolutionCaptureEnabled = YES;
@@ -729,12 +737,12 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
729
737
  [self.session addOutput:photoOutput];
730
738
  self.photoOutput = photoOutput;
731
739
  }
740
+ [self.session commitConfiguration];
732
741
 
733
742
  [self setRuntimeErrorHandlingObserver:
734
743
  [[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureSessionRuntimeErrorNotification object:self.session queue:nil usingBlock:^(NSNotification *note) {
735
- EX_ENSURE_STRONGIFY(self);
736
744
  dispatch_async(self.sessionQueue, ^{
737
- EX_ENSURE_STRONGIFY(self)
745
+ EX_STRONGIFY(self);
738
746
  // Manually restarting the session since it must
739
747
  // have been stopped due to an error.
740
748
  [self.session startRunning];
@@ -746,7 +754,7 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
746
754
  // when BarCodeScanner is enabled since the beginning of camera component lifecycle,
747
755
  // some race condition occurs in reconfiguration and barcodes aren't scanned at all
748
756
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 50 * NSEC_PER_USEC), self.sessionQueue, ^{
749
- EX_ENSURE_STRONGIFY(self);
757
+ EX_STRONGIFY(self);
750
758
  [self maybeStartFaceDetection:self.presetCamera!=1];
751
759
  if (self.barCodeScanner) {
752
760
  [self.barCodeScanner maybeStartBarCodeScanning];
@@ -757,6 +765,7 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
757
765
  [self onReady:nil];
758
766
  });
759
767
  });
768
+
760
769
  #pragma clang diagnostic pop
761
770
  }
762
771
 
@@ -766,9 +775,8 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
766
775
  return;
767
776
  #endif
768
777
  EX_WEAKIFY(self);
769
- dispatch_async(_sessionQueue, ^{
770
- EX_ENSURE_STRONGIFY(self);
771
-
778
+ dispatch_async(self.sessionQueue, ^{
779
+ EX_STRONGIFY(self);
772
780
  if (self.faceDetectorManager) {
773
781
  [self.faceDetectorManager stopFaceDetection];
774
782
  }
@@ -777,8 +785,7 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
777
785
  }
778
786
  [self.motionManager stopAccelerometerUpdates];
779
787
  [self.previewLayer removeFromSuperlayer];
780
- [self.session commitConfiguration];
781
- [self.session stopRunning];
788
+ [self.session beginConfiguration];
782
789
  for (AVCaptureInput *input in self.session.inputs) {
783
790
  [self.session removeInput:input];
784
791
  }
@@ -786,6 +793,8 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
786
793
  for (AVCaptureOutput *output in self.session.outputs) {
787
794
  [self.session removeOutput:output];
788
795
  }
796
+ [self.session commitConfiguration];
797
+ [self.session stopRunning];
789
798
  });
790
799
  }
791
800
 
@@ -802,9 +811,8 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
802
811
  AVCaptureVideoOrientation orientation = [EXCameraUtils videoOrientationForInterfaceOrientation:interfaceOrientation];
803
812
 
804
813
  EX_WEAKIFY(self);
805
- dispatch_async(_sessionQueue, ^{
806
- EX_ENSURE_STRONGIFY(self);
807
-
814
+ dispatch_async(self.sessionQueue, ^{
815
+ EX_STRONGIFY(self);
808
816
  [self.session beginConfiguration];
809
817
 
810
818
  NSError *error = nil;
@@ -843,8 +851,8 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
843
851
  - (void)ensureSessionConfiguration
844
852
  {
845
853
  EX_WEAKIFY(self);
846
- dispatch_async(_sessionQueue, ^{
847
- EX_ENSURE_STRONGIFY(self);
854
+ dispatch_async(self.sessionQueue, ^{
855
+ EX_STRONGIFY(self);
848
856
  [self updateFlashMode];
849
857
  });
850
858
  }
@@ -855,54 +863,41 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
855
863
  {
856
864
  #if !(TARGET_IPHONE_SIMULATOR)
857
865
  if (preset) {
858
- EX_WEAKIFY(self);
859
- dispatch_async(_sessionQueue, ^{
860
- EX_ENSURE_STRONGIFY(self);
861
- [self.session beginConfiguration];
862
- if ([self.session canSetSessionPreset:preset]) {
863
- self.session.sessionPreset = preset;
864
- }
865
- [self.session commitConfiguration];
866
- });
866
+ [self.session beginConfiguration];
867
+ if ([self.session canSetSessionPreset:preset]) {
868
+ self.session.sessionPreset = preset;
869
+ }
870
+ [self.session commitConfiguration];
867
871
  }
868
872
  #endif
869
873
  }
870
874
 
871
875
  - (void)updateSessionAudioIsMuted:(BOOL)isMuted
872
876
  {
873
- EX_WEAKIFY(self);
874
- dispatch_async(_sessionQueue, ^{
875
- EX_ENSURE_STRONGIFY(self);
876
- [self.session beginConfiguration];
877
-
878
- for (AVCaptureDeviceInput* input in [self.session inputs]) {
879
- if ([input.device hasMediaType:AVMediaTypeAudio]) {
880
- if (isMuted) {
881
- [self.session removeInput:input];
882
- }
883
- [self.session commitConfiguration];
884
- return;
877
+ for (AVCaptureDeviceInput* input in [self.session inputs]) {
878
+ if ([input.device hasMediaType:AVMediaTypeAudio]) {
879
+ if (isMuted) {
880
+ [self.session removeInput:input];
885
881
  }
882
+ return;
886
883
  }
887
-
888
- if (!isMuted) {
889
- NSError *error = nil;
890
-
891
- AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
892
- AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioCaptureDevice error:&error];
893
-
894
- if (error || audioDeviceInput == nil) {
895
- EXLogInfo(@"%s: %@", __func__, error);
896
- return;
897
- }
898
-
899
- if ([self.session canAddInput:audioDeviceInput]) {
900
- [self.session addInput:audioDeviceInput];
901
- }
884
+ }
885
+
886
+ if (!isMuted) {
887
+ NSError *error = nil;
888
+
889
+ AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
890
+ AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioCaptureDevice error:&error];
891
+
892
+ if (error || audioDeviceInput == nil) {
893
+ EXLogInfo(@"%s: %@", __func__, error);
894
+ return;
902
895
  }
903
-
904
- [self.session commitConfiguration];
905
- });
896
+
897
+ if ([self.session canAddInput:audioDeviceInput]) {
898
+ [self.session addInput:audioDeviceInput];
899
+ }
900
+ }
906
901
  }
907
902
 
908
903
  - (void)onAppForegrounded
@@ -910,8 +905,8 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
910
905
  if (![_session isRunning] && [self isSessionPaused]) {
911
906
  _paused = NO;
912
907
  EX_WEAKIFY(self);
913
- dispatch_async(_sessionQueue, ^{
914
- EX_ENSURE_STRONGIFY(self);
908
+ dispatch_async(self.sessionQueue, ^{
909
+ EX_STRONGIFY(self);
915
910
  [self.session startRunning];
916
911
  [self ensureSessionConfiguration];
917
912
  });
@@ -923,8 +918,8 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
923
918
  if ([_session isRunning] && ![self isSessionPaused]) {
924
919
  _paused = YES;
925
920
  EX_WEAKIFY(self);
926
- dispatch_async(_sessionQueue, ^{
927
- EX_ENSURE_STRONGIFY(self);
921
+ dispatch_async(self.sessionQueue, ^{
922
+ EX_STRONGIFY(self);
928
923
  [self.session stopRunning];
929
924
  });
930
925
  }
@@ -953,7 +948,6 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
953
948
  - (void)setupMovieFileCapture
954
949
  {
955
950
  AVCaptureMovieFileOutput *movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
956
-
957
951
  if ([_session canAddOutput:movieFileOutput]) {
958
952
  [_session addOutput:movieFileOutput];
959
953
  _movieFileOutput = movieFileOutput;
@@ -963,8 +957,10 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
963
957
  - (void)cleanupMovieFileCapture
964
958
  {
965
959
  if ([_session.outputs containsObject:_movieFileOutput]) {
960
+ [_session beginConfiguration];
966
961
  [_session removeOutput:_movieFileOutput];
967
962
  _movieFileOutput = nil;
963
+ [_session commitConfiguration];
968
964
  }
969
965
  }
970
966
 
@@ -986,13 +982,17 @@ previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
986
982
  _videoRecordedReject = nil;
987
983
  _videoCodecType = nil;
988
984
 
989
- [self cleanupMovieFileCapture];
990
985
  // If face detection has been running prior to recording to file
991
986
  // we reenable it here (see comment in -record).
992
987
  [self maybeStartFaceDetection:false];
993
988
 
994
989
  if (_session.sessionPreset != _pictureSize) {
995
- [self updateSessionPreset:_pictureSize];
990
+ EX_WEAKIFY(self);
991
+ dispatch_async(self.sessionQueue, ^{
992
+ EX_STRONGIFY(self);
993
+ [self updateSessionPreset:_pictureSize];
994
+ [self cleanupMovieFileCapture];
995
+ });
996
996
  }
997
997
  }
998
998
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-camera",
3
- "version": "13.5.1",
3
+ "version": "13.6.0",
4
4
  "description": "A React component that renders a preview for the device's either front or back camera. Camera's parameters like zoom, auto focus, white balance and flash mode are adjustable. With expo-camera, one can also take photos and record videos that are saved to the app's cache. Morever, the component is also capable of detecting faces and bar codes appearing on the preview.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -34,7 +34,6 @@
34
34
  "preset": "expo-module-scripts"
35
35
  },
36
36
  "dependencies": {
37
- "@koale/useworker": "^4.0.2",
38
37
  "invariant": "^2.2.4"
39
38
  },
40
39
  "devDependencies": {
@@ -43,5 +42,5 @@
43
42
  "peerDependencies": {
44
43
  "expo": "*"
45
44
  },
46
- "gitHead": "2240630a92eb79a4e4bf73e1439916c394876478"
45
+ "gitHead": "79607a7325f47aa17c36d266100d09a4ff2cc544"
47
46
  }
@@ -1,4 +1,3 @@
1
- import { useWorker } from '@koale/useworker';
2
1
  import * as React from 'react';
3
2
 
4
3
  import { BarCodeScanningResult, CameraPictureOptions, MountErrorListener } from './Camera.types';
@@ -37,12 +36,41 @@ const qrWorkerMethod = ({ data, width, height }: ImageData): any => {
37
36
  return parsed;
38
37
  };
39
38
 
40
- function useRemoteJsQR() {
41
- return useWorker(qrWorkerMethod, {
42
- remoteDependencies: ['https://cdn.jsdelivr.net/npm/jsqr@1.2.0/dist/jsQR.min.js'],
43
- autoTerminate: false,
44
- });
45
- }
39
+ const createWorkerAsyncFunction = <T extends (data: any) => any>(fn: T, deps: string[]) => {
40
+ const stringifiedFn = [
41
+ `self.func = ${fn.toString()};`,
42
+ 'self.onmessage = (e) => {',
43
+ ' const result = self.func(e.data);',
44
+ ' self.postMessage(result);',
45
+ '};',
46
+ ];
47
+
48
+ if (deps.length > 0) {
49
+ stringifiedFn.unshift(`importScripts(${deps.map((dep) => `'${dep}'`).join(', ')});`);
50
+ }
51
+
52
+ const blob = new Blob(stringifiedFn, { type: 'text/javascript' });
53
+ const worker = new Worker(URL.createObjectURL(blob));
54
+
55
+ // First-In First-Out queue of promises
56
+ const promises: {
57
+ resolve: (value: ReturnType<T>) => void;
58
+ reject: (reason?: any) => void;
59
+ }[] = [];
60
+
61
+ worker.onmessage = (e) => promises.shift()?.resolve(e.data);
62
+
63
+ return (data: Parameters<T>[0]) => {
64
+ return new Promise<ReturnType<T>>((resolve, reject) => {
65
+ promises.push({ resolve, reject });
66
+ worker.postMessage(data);
67
+ });
68
+ };
69
+ };
70
+
71
+ const decode = createWorkerAsyncFunction(qrWorkerMethod, [
72
+ 'https://cdn.jsdelivr.net/npm/jsqr@1.2.0/dist/jsQR.min.js',
73
+ ]);
46
74
 
47
75
  export function useWebQRScanner(
48
76
  video: React.MutableRefObject<HTMLVideoElement | null>,
@@ -63,8 +91,6 @@ export function useWebQRScanner(
63
91
  const isRunning = React.useRef<boolean>(false);
64
92
  const timeout = React.useRef<number | undefined>(undefined);
65
93
 
66
- const [decode, clearWorker] = useRemoteJsQR();
67
-
68
94
  async function scanAsync() {
69
95
  // If interval is 0 then only scan once.
70
96
  if (!isRunning.current || !onScanned) {
@@ -109,15 +135,12 @@ export function useWebQRScanner(
109
135
  if (isEnabled) {
110
136
  isRunning.current = true;
111
137
  scanAsync();
112
- } else {
113
- stop();
114
138
  }
115
- }, [isEnabled]);
116
139
 
117
- React.useEffect(() => {
118
140
  return () => {
119
- stop();
120
- clearWorker.kill();
141
+ if (isEnabled) {
142
+ stop();
143
+ }
121
144
  };
122
- }, []);
145
+ }, [isEnabled]);
123
146
  }