expo-camera 13.5.1 → 13.7.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 +22 -0
- package/android/build.gradle +11 -8
- package/build/useWebQRScanner.d.ts.map +1 -1
- package/build/useWebQRScanner.js +30 -16
- package/build/useWebQRScanner.js.map +1 -1
- package/ios/EXCamera/EXCamera.m +144 -144
- package/package.json +2 -3
- package/src/useWebQRScanner.ts +39 -16
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,28 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 13.7.0 — 2023-09-15
|
|
14
|
+
|
|
15
|
+
_This version does not introduce any user-facing changes._
|
|
16
|
+
|
|
17
|
+
## 13.4.4 — 2023-09-11
|
|
18
|
+
|
|
19
|
+
### 🐛 Bug fixes
|
|
20
|
+
|
|
21
|
+
- Remove @koale/useworker. ([#23967](https://github.com/expo/expo/pull/23967) by [@marklawlor](https://github.com/marklawlor))
|
|
22
|
+
|
|
23
|
+
## 13.6.0 — 2023-09-04
|
|
24
|
+
|
|
25
|
+
### 🎉 New features
|
|
26
|
+
|
|
27
|
+
- Added support for React Native 0.73. ([#24018](https://github.com/expo/expo/pull/24018) by [@kudo](https://github.com/kudo))
|
|
28
|
+
|
|
29
|
+
### 🐛 Bug fixes
|
|
30
|
+
|
|
31
|
+
- Fixed flash is not enabled during recordings. ([#23776](https://github.com/expo/expo/pull/23776) by [@tszheichoi](https://github.com/tszheichoi))
|
|
32
|
+
- On iOS, fix dead frames when starting a video recording. ([#22037](https://github.com/expo/expo/pull/22037) by [@alanjhughes](https://github.com/alanjhughes))
|
|
33
|
+
- Remove @koale/useworker. ([#23967](https://github.com/expo/expo/pull/23967) by [@marklawlor](https://github.com/marklawlor))
|
|
34
|
+
|
|
13
35
|
## 13.5.1 — 2023-08-02
|
|
14
36
|
|
|
15
37
|
_This version does not introduce any user-facing changes._
|
package/android/build.gradle
CHANGED
|
@@ -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.
|
|
6
|
+
version = '13.7.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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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.
|
|
73
|
+
versionName "13.7.0"
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
lintOptions {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebQRScanner.d.ts","sourceRoot":"","sources":["../src/useWebQRScanner.ts"],"names":[],"mappings":"
|
|
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"}
|
package/build/useWebQRScanner.js
CHANGED
|
@@ -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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"useWebQRScanner.js","sourceRoot":"","sources":["../src/useWebQRScanner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,cAAc,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAa,EAAO,EAAE;IACjE,oCAAoC;IACpC,MAAM,OAAO,GAAI,IAAY,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;QACtD,iBAAiB,EAAE,aAAa;KACjC,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC;IACX,IAAI;QACF,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;KAC9B;IAAC,MAAM;QACN,MAAM,GAAG,OAAO,CAAC;KAClB;IAED,IAAI,MAAM,EAAE,IAAI,EAAE;QAChB,MAAM,WAAW,GAA0B;YACzC,IAAI,EAAE,IAAI;YACV,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,YAAY,EAAE,EAAE;YAChB,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE;SAClE,CAAC;QACF,IAAI,MAAM,CAAC,QAAQ,EAAE;YACnB,WAAW,CAAC,YAAY,GAAG;gBACzB,MAAM,CAAC,QAAQ,CAAC,aAAa;gBAC7B,MAAM,CAAC,QAAQ,CAAC,gBAAgB;gBAChC,MAAM,CAAC,QAAQ,CAAC,cAAc;gBAC9B,MAAM,CAAC,QAAQ,CAAC,iBAAiB;aAClC,CAAC;SACH;QACD,OAAO,WAAW,CAAC;KACpB;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,yBAAyB,GAAG,CAA+B,EAAK,EAAE,IAAc,EAAE,EAAE;IACxF,MAAM,aAAa,GAAG;QACpB,eAAe,EAAE,CAAC,QAAQ,EAAE,GAAG;QAC/B,2BAA2B;QAC3B,qCAAqC;QACrC,6BAA6B;QAC7B,IAAI;KACL,CAAC;IAEF,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE;QACnB,aAAa,CAAC,OAAO,CAAC,iBAAiB,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KACtF;IAED,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,aAAa,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;IAErD,uCAAuC;IACvC,MAAM,QAAQ,GAGR,EAAE,CAAC;IAET,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAE5D,OAAO,CAAC,IAAsB,EAAE,EAAE;QAChC,OAAO,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACpD,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACnC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,yBAAyB,CAAC,cAAc,EAAE;IACvD,0DAA0D;CAC3D,CAAC,CAAC;AAEH,MAAM,UAAU,eAAe,CAC7B,KAAsD,EACtD,EACE,SAAS,EACT,cAAc,EACd,QAAQ,EACR,SAAS,EACT,OAAO,GAOR;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAU,KAAK,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAqB,SAAS,CAAC,CAAC;IAE5D,KAAK,UAAU,SAAS;QACtB,wCAAwC;QACxC,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,CAAC,SAAS,EAAE;YACpC,IAAI,EAAE,CAAC;YACP,OAAO;SACR;QACD,IAAI;YACF,MAAM,IAAI,GAAG,gBAAgB,CAAC,KAAK,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;YAE7D,IAAI,IAAI,EAAE;gBACR,MAAM,WAAW,GAAgC,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;gBACpE,IAAI,WAAW,EAAE,IAAI,EAAE;oBACrB,SAAS,CAAC;wBACR,WAAW;qBACZ,CAAC,CAAC;iBACJ;aACF;SACF;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,OAAO,EAAE;gBACX,OAAO,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;aACjC;SACF;gBAAS;YACR,wCAAwC;YACxC,IAAI,QAAQ,KAAK,CAAC,EAAE;gBAClB,IAAI,EAAE,CAAC;gBACP,OAAO;aACR;YACD,MAAM,aAAa,GAAG,CAAC,QAAQ,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;YAChE,gEAAgE;YAChE,OAAO,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;gBAChC,SAAS,EAAE,CAAC;YACd,CAAC,EAAE,aAAa,CAAC,CAAC;SACnB;IACH,CAAC;IAED,SAAS,IAAI;QACX,SAAS,CAAC,OAAO,GAAG,KAAK,CAAC;QAC1B,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,SAAS,EAAE;YACb,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC;YACzB,SAAS,EAAE,CAAC;SACb;QAED,OAAO,GAAG,EAAE;YACV,IAAI,SAAS,EAAE;gBACb,IAAI,EAAE,CAAC;aACR;QACH,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;AAClB,CAAC","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"]}
|
package/ios/EXCamera/EXCamera.m
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
565
|
-
|
|
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
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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 (
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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
|
-
|
|
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(
|
|
719
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
770
|
-
|
|
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
|
|
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(
|
|
806
|
-
|
|
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(
|
|
847
|
-
|
|
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
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
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
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
|
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(
|
|
914
|
-
|
|
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(
|
|
927
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "13.7.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": "
|
|
45
|
+
"gitHead": "ee2c866ba3c7fbc35ff2a3e896041cf15d3bd7c5"
|
|
47
46
|
}
|
package/src/useWebQRScanner.ts
CHANGED
|
@@ -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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
120
|
-
|
|
141
|
+
if (isEnabled) {
|
|
142
|
+
stop();
|
|
143
|
+
}
|
|
121
144
|
};
|
|
122
|
-
}, []);
|
|
145
|
+
}, [isEnabled]);
|
|
123
146
|
}
|