@octane-xplat/camera 0.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/dist/native/CameraView.android.js +165 -0
- package/dist/native/CameraView.ios.js +130 -0
- package/dist/native/camera-permission.mobile.js +45 -0
- package/dist/native/camera-session.ios.js +45 -0
- package/dist/native/cx.mobile.js +8 -0
- package/dist/native/index.js +2 -0
- package/dist/native/layout-child.js +53 -0
- package/dist/native/octane-client-build.json +8 -0
- package/dist/web/CameraView.web.tsrx.js +314 -0
- package/dist/web/index.js +2 -0
- package/dist/web/layout-child.web.js +29 -0
- package/dist/web/octane-client-build.json +8 -0
- package/package.json +61 -0
- package/platforms/android/AndroidManifest.xml +4 -0
- package/platforms/android/include.gradle +13 -0
- package/platforms/android/java/com/octanexplat/camera/CameraPreviewView.java +71 -0
- package/platforms/ios/Info.plist +8 -0
- package/src/CameraView.android.tsrx +125 -0
- package/src/CameraView.ios.tsrx +73 -0
- package/src/CameraView.mobile.ts +6 -0
- package/src/CameraView.web.tsrx +100 -0
- package/src/camera-permission.mobile.ts +66 -0
- package/src/camera-session.ios.ts +54 -0
- package/src/cx.mobile.ts +5 -0
- package/src/index.ts +4 -0
- package/src/index.web.ts +2 -0
- package/src/layout-child.ts +62 -0
- package/src/layout-child.web.ts +42 -0
- package/src/props.ts +62 -0
- package/types/index.d.ts +5 -0
- package/types/props.d.ts +47 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
+
import { useCallback, useEffect, useRef } from 'octane'
|
|
3
|
+
import type { CameraViewProps } from './props'
|
|
4
|
+
import { cameraHardwarePresent, ensureCameraPermission } from './camera-permission.mobile'
|
|
5
|
+
import { startCameraPreview } from './camera-session.ios'
|
|
6
|
+
import { applyEscapeProps, layoutChildProps, nativeAccessibilityRole, nativeAccessibilityState } from './layout-child'
|
|
7
|
+
import { cx } from './cx.mobile'
|
|
8
|
+
|
|
9
|
+
/** Live iOS preview backed by AVFoundation. */
|
|
10
|
+
export function CameraView(props: CameraViewProps) @{
|
|
11
|
+
const host = useRef<any>(null)
|
|
12
|
+
const active = props.active !== false
|
|
13
|
+
const facing = props.facing ?? 'back'
|
|
14
|
+
const bindHost = useCallback((view: any) => {
|
|
15
|
+
host.current = view
|
|
16
|
+
}, [])
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const view = host.current
|
|
20
|
+
if (!view || !active) return
|
|
21
|
+
let disposed = false
|
|
22
|
+
let stopPreview: (() => void) | undefined
|
|
23
|
+
const fail = (message: string) => props.onError?.({ message })
|
|
24
|
+
|
|
25
|
+
if (!cameraHardwarePresent()) {
|
|
26
|
+
fail('camera unsupported')
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
ensureCameraPermission().then((granted) => {
|
|
31
|
+
if (disposed) return
|
|
32
|
+
if (!granted) {
|
|
33
|
+
fail('camera permission denied')
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
stopPreview = startCameraPreview(view, facing, props.onReady)
|
|
39
|
+
} catch (error: any) {
|
|
40
|
+
fail(error?.message ?? 'camera error')
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return () => {
|
|
45
|
+
disposed = true
|
|
46
|
+
stopPreview?.()
|
|
47
|
+
}
|
|
48
|
+
}, [active, facing])
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const view = host.current
|
|
52
|
+
if (!view) return
|
|
53
|
+
applyEscapeProps(view.ios ?? view.nativeViewProtected, props)
|
|
54
|
+
props.bind?.({ native: view.ios ?? view.nativeViewProtected })
|
|
55
|
+
}, [props.bind, props.ios])
|
|
56
|
+
|
|
57
|
+
<gridlayout
|
|
58
|
+
ref={bindHost}
|
|
59
|
+
id={props.id}
|
|
60
|
+
rows="*"
|
|
61
|
+
columns="*"
|
|
62
|
+
className={cx('vx-stack', 'vx-cameraview', props.className)}
|
|
63
|
+
style={{ backgroundColor: '#000', ...props.style }}
|
|
64
|
+
{...layoutChildProps(props)}
|
|
65
|
+
accessible={props.accessible}
|
|
66
|
+
accessibilityLabel={props.accessibilityLabel}
|
|
67
|
+
accessibilityRole={nativeAccessibilityRole(props.accessibilityRole) as any}
|
|
68
|
+
accessibilityHint={props.accessibilityHint}
|
|
69
|
+
accessibilityValue={props.accessibilityValue}
|
|
70
|
+
accessibilityState={nativeAccessibilityState(props.accessibilityState) as any}
|
|
71
|
+
accessibilityLiveRegion={props.accessibilityLiveRegion as any}
|
|
72
|
+
/>
|
|
73
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { UniversalComponent } from 'octane/universal'
|
|
2
|
+
import type { CameraViewProps } from './props'
|
|
3
|
+
|
|
4
|
+
/** TypeScript fallback; the NativeScript resolver selects the .ios/.android
|
|
5
|
+
* implementation first at runtime. */
|
|
6
|
+
export declare const CameraView: UniversalComponent<CameraViewProps>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'octane'
|
|
2
|
+
import type { CameraViewProps } from './props'
|
|
3
|
+
import { applyEscapeProps, layoutChildProps } from './layout-child.web'
|
|
4
|
+
|
|
5
|
+
/** Live web preview backed by getUserMedia. */
|
|
6
|
+
export function CameraView(props: CameraViewProps) @{
|
|
7
|
+
const video = useRef<HTMLVideoElement | null>(null)
|
|
8
|
+
const facing = props.facing ?? 'back'
|
|
9
|
+
const active = props.active !== false
|
|
10
|
+
const readyFired = useRef(false)
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const element = video.current
|
|
14
|
+
if (!element || !active) return
|
|
15
|
+
const mediaDevices = navigator.mediaDevices
|
|
16
|
+
if (!mediaDevices?.getUserMedia) {
|
|
17
|
+
props.onError?.({ message: 'camera unsupported' })
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let stream: MediaStream | null = null
|
|
22
|
+
let disposed = false
|
|
23
|
+
readyFired.current = false
|
|
24
|
+
mediaDevices
|
|
25
|
+
.getUserMedia({
|
|
26
|
+
video: { facingMode: facing === 'front' ? 'user' : 'environment' },
|
|
27
|
+
audio: false,
|
|
28
|
+
})
|
|
29
|
+
.then((nextStream) => {
|
|
30
|
+
if (disposed) {
|
|
31
|
+
for (const track of nextStream.getTracks()) track.stop()
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
stream = nextStream
|
|
36
|
+
element.srcObject = nextStream
|
|
37
|
+
element.play()?.catch(() => {})
|
|
38
|
+
})
|
|
39
|
+
.catch((error: any) => {
|
|
40
|
+
props.onError?.({
|
|
41
|
+
message:
|
|
42
|
+
error?.name === 'NotAllowedError'
|
|
43
|
+
? 'camera permission denied'
|
|
44
|
+
: String(error?.name ?? error ?? 'camera error'),
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return () => {
|
|
49
|
+
disposed = true
|
|
50
|
+
element.srcObject = null
|
|
51
|
+
if (stream) for (const track of stream.getTracks()) track.stop()
|
|
52
|
+
}
|
|
53
|
+
}, [active, facing])
|
|
54
|
+
|
|
55
|
+
<div
|
|
56
|
+
id={props.id}
|
|
57
|
+
className={['vx-stack', 'vx-cameraview', props.className]}
|
|
58
|
+
{...layoutChildProps(props, {
|
|
59
|
+
position: 'relative',
|
|
60
|
+
overflow: 'hidden',
|
|
61
|
+
backgroundColor: '#000',
|
|
62
|
+
...props.style,
|
|
63
|
+
})}
|
|
64
|
+
role={props.accessibilityRole}
|
|
65
|
+
aria-label={props.accessibilityLabel}
|
|
66
|
+
aria-hidden={props.accessible === false ? true : undefined}
|
|
67
|
+
aria-description={props.accessibilityHint}
|
|
68
|
+
aria-valuetext={props.accessibilityValue}
|
|
69
|
+
aria-disabled={props.accessibilityState?.disabled}
|
|
70
|
+
aria-selected={props.accessibilityState?.selected}
|
|
71
|
+
aria-checked={props.accessibilityState?.checked}
|
|
72
|
+
aria-live={props.accessibilityLiveRegion === 'none' ? 'off' : props.accessibilityLiveRegion}
|
|
73
|
+
>
|
|
74
|
+
@if (active) {
|
|
75
|
+
<video
|
|
76
|
+
ref={(element: HTMLVideoElement | null) => {
|
|
77
|
+
video.current = element
|
|
78
|
+
if (!element) return
|
|
79
|
+
applyEscapeProps(element, props)
|
|
80
|
+
props.bind?.({ native: element })
|
|
81
|
+
}}
|
|
82
|
+
muted={true}
|
|
83
|
+
autoplay={true}
|
|
84
|
+
playsInline={true}
|
|
85
|
+
style={{
|
|
86
|
+
display: 'block',
|
|
87
|
+
width: '100%',
|
|
88
|
+
height: '100%',
|
|
89
|
+
objectFit: 'cover',
|
|
90
|
+
transform: facing === 'front' ? 'scaleX(-1)' : undefined,
|
|
91
|
+
}}
|
|
92
|
+
onPlaying={() => {
|
|
93
|
+
if (readyFired.current) return
|
|
94
|
+
readyFired.current = true
|
|
95
|
+
props.onReady?.()
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
}
|
|
99
|
+
</div>
|
|
100
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Application, isAndroid, isIOS } from '@nativescript/core'
|
|
2
|
+
|
|
3
|
+
let requestCode = 7719
|
|
4
|
+
|
|
5
|
+
export function cameraHardwarePresent(): boolean {
|
|
6
|
+
if (isIOS) {return typeof (globalThis as any).AVCaptureDevice !== 'undefined'}
|
|
7
|
+
if (!isAndroid) {return false}
|
|
8
|
+
try {
|
|
9
|
+
const context = Application.android?.context ?? Application.android?.startActivity
|
|
10
|
+
return context?.packageManager?.hasSystemFeature('android.hardware.camera.any') !== false
|
|
11
|
+
} catch {
|
|
12
|
+
return true
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function ensureCameraPermission(): Promise<boolean> {
|
|
17
|
+
try {
|
|
18
|
+
if (isIOS) {
|
|
19
|
+
const av = (globalThis as any).AVCaptureDevice
|
|
20
|
+
const mediaType = (globalThis as any).AVMediaTypeVideo ?? 'vide'
|
|
21
|
+
const status = av?.authorizationStatusForMediaType?.(mediaType)
|
|
22
|
+
if (status === 3) {return true}
|
|
23
|
+
if (status === 1 || status === 2) {return false}
|
|
24
|
+
return await new Promise<boolean>((resolve) =>
|
|
25
|
+
av.requestAccessForMediaTypeCompletionHandler(mediaType, resolve),
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (isAndroid) {
|
|
30
|
+
const permission = 'android.permission.CAMERA'
|
|
31
|
+
const activity = Application.android.foregroundActivity ?? Application.android.startActivity
|
|
32
|
+
if (
|
|
33
|
+
activity?.checkSelfPermission?.(permission) ===
|
|
34
|
+
android.content.pm.PackageManager.PERMISSION_GRANTED
|
|
35
|
+
) {return true}
|
|
36
|
+
|
|
37
|
+
if (!activity) {return false}
|
|
38
|
+
return await new Promise<boolean>((resolve) => {
|
|
39
|
+
const currentRequest = requestCode++
|
|
40
|
+
const onResult = (args: any) => {
|
|
41
|
+
if (args.requestCode !== currentRequest) {return}
|
|
42
|
+
Application.android.off(
|
|
43
|
+
Application.android.activityRequestPermissionsEvent,
|
|
44
|
+
onResult,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
resolve(
|
|
48
|
+
args.grantResults?.[0] ===
|
|
49
|
+
android.content.pm.PackageManager.PERMISSION_GRANTED,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Application.android.on(
|
|
54
|
+
Application.android.activityRequestPermissionsEvent,
|
|
55
|
+
onResult,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
activity.requestPermissions([permission], currentRequest)
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Attach an AVFoundation preview layer to a NativeScript UIView host. */
|
|
2
|
+
export function startCameraPreview(
|
|
3
|
+
view: any,
|
|
4
|
+
facing: 'front' | 'back',
|
|
5
|
+
onReady?: () => void,
|
|
6
|
+
): () => void {
|
|
7
|
+
const av = globalThis as any
|
|
8
|
+
const mediaType = av.AVMediaTypeVideo ?? 'vide'
|
|
9
|
+
const desiredPosition = facing === 'front'
|
|
10
|
+
? av.AVCaptureDevicePositionFront ?? 2
|
|
11
|
+
: av.AVCaptureDevicePositionBack ?? 1
|
|
12
|
+
|
|
13
|
+
const devices = av.AVCaptureDevice.devicesWithMediaType(mediaType)
|
|
14
|
+
let device: any = null
|
|
15
|
+
for (let index = 0; index < devices.count; index++) {
|
|
16
|
+
const candidate = devices.objectAtIndex(index)
|
|
17
|
+
if (candidate.position === desiredPosition) {
|
|
18
|
+
device = candidate
|
|
19
|
+
break
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!device) {throw new Error('camera unavailable')}
|
|
24
|
+
|
|
25
|
+
const session = av.AVCaptureSession.new()
|
|
26
|
+
const input = av.AVCaptureDeviceInput.deviceInputWithDeviceError(device)
|
|
27
|
+
if (!input || !session.canAddInput(input)) {throw new Error('camera input unavailable')}
|
|
28
|
+
session.addInput(input)
|
|
29
|
+
const layer = av.AVCaptureVideoPreviewLayer.layerWithSession(session)
|
|
30
|
+
layer.videoGravity = 'resizeAspectFill'
|
|
31
|
+
const connection = layer.connection
|
|
32
|
+
if (connection) {
|
|
33
|
+
connection.automaticallyAdjustsVideoMirroring = false
|
|
34
|
+
if (connection.isVideoMirroringSupported) {connection.isVideoMirrored = facing === 'front'}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const nativeView = view.ios ?? view.nativeViewProtected
|
|
38
|
+
if (!nativeView?.layer) {throw new Error('camera preview host unavailable')}
|
|
39
|
+
nativeView.layer.addSublayer(layer)
|
|
40
|
+
const updateFrame = () => {
|
|
41
|
+
layer.frame = nativeView.bounds
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
view.on?.('layoutChanged', updateFrame)
|
|
45
|
+
updateFrame()
|
|
46
|
+
session.startRunning()
|
|
47
|
+
onReady?.()
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
view.off?.('layoutChanged', updateFrame)
|
|
51
|
+
session.stopRunning()
|
|
52
|
+
layer.removeFromSuperlayer()
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/cx.mobile.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Keep this relative import extensionless so the NativeScript resolver picks
|
|
2
|
+
// the iOS or Android component for the current build target.
|
|
3
|
+
export { CameraView } from './CameraView'
|
|
4
|
+
export type { CameraAccessibilityRole, CameraViewHandle, CameraViewProps } from './props'
|
package/src/index.web.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { CameraViewProps } from './props'
|
|
2
|
+
import { isIOS } from '@nativescript/core'
|
|
3
|
+
|
|
4
|
+
/** NativeScript reads layout fields from a child view during parent layout. */
|
|
5
|
+
export function layoutChildProps(props: CameraViewProps): Record<string, any> {
|
|
6
|
+
const result: Record<string, any> = {}
|
|
7
|
+
for (const key of [
|
|
8
|
+
'row',
|
|
9
|
+
'col',
|
|
10
|
+
'rowSpan',
|
|
11
|
+
'colSpan',
|
|
12
|
+
'dock',
|
|
13
|
+
'left',
|
|
14
|
+
'top',
|
|
15
|
+
'flexGrow',
|
|
16
|
+
'flexShrink',
|
|
17
|
+
'alignSelf',
|
|
18
|
+
'order',
|
|
19
|
+
] as const) {
|
|
20
|
+
if (props[key] !== undefined) {result[key] = props[key]}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return result
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function applyEscapeProps(view: any, props: { ios?: any; android?: any }): void {
|
|
27
|
+
if (!view) {return}
|
|
28
|
+
const bag = isIOS ? props.ios : props.android
|
|
29
|
+
if (bag) {Object.assign(view, bag)}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const nativeRoles: Record<string, string> = {
|
|
33
|
+
button: 'button',
|
|
34
|
+
link: 'link',
|
|
35
|
+
search: 'search',
|
|
36
|
+
image: 'image',
|
|
37
|
+
heading: 'header',
|
|
38
|
+
adjustable: 'adjustable',
|
|
39
|
+
summary: 'summary',
|
|
40
|
+
text: 'text',
|
|
41
|
+
none: 'none',
|
|
42
|
+
progressbar: 'progressBar',
|
|
43
|
+
checkbox: 'checkbox',
|
|
44
|
+
switch: 'switch',
|
|
45
|
+
radio: 'radioButton',
|
|
46
|
+
spinbutton: 'spinButton',
|
|
47
|
+
tab: 'button',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function nativeAccessibilityRole(role?: string): string | undefined {
|
|
51
|
+
return role ? nativeRoles[role] : undefined
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function nativeAccessibilityState(state?: CameraViewProps['accessibilityState']):
|
|
55
|
+
| string
|
|
56
|
+
| undefined {
|
|
57
|
+
if (state?.disabled) {return 'disabled'}
|
|
58
|
+
if (state?.selected) {return 'selected'}
|
|
59
|
+
if (state?.checked === true) {return 'checked'}
|
|
60
|
+
if (state?.checked === false) {return 'unchecked'}
|
|
61
|
+
return undefined
|
|
62
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { CameraViewProps } from './props'
|
|
2
|
+
|
|
3
|
+
export function layoutChildProps(props: CameraViewProps, baseStyle: any = props.style) {
|
|
4
|
+
const style = { ...baseStyle }
|
|
5
|
+
if (props.row !== undefined) {
|
|
6
|
+
style.gridRow = `${props.row + 1} / span ${props.rowSpan ?? 1}`
|
|
7
|
+
} else if (props.rowSpan !== undefined) {
|
|
8
|
+
style.gridRow = `span ${props.rowSpan}`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (props.col !== undefined) {
|
|
12
|
+
style.gridColumn = `${props.col + 1} / span ${props.colSpan ?? 1}`
|
|
13
|
+
} else if (props.colSpan !== undefined) {
|
|
14
|
+
style.gridColumn = `span ${props.colSpan}`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (props.left !== undefined) {style.left = props.left}
|
|
18
|
+
if (props.top !== undefined) {style.top = props.top}
|
|
19
|
+
if (props.flexGrow !== undefined) {style.flexGrow = props.flexGrow}
|
|
20
|
+
if (props.flexShrink !== undefined) {style.flexShrink = props.flexShrink}
|
|
21
|
+
if (props.alignSelf !== undefined) {style.alignSelf = props.alignSelf}
|
|
22
|
+
if (props.order !== undefined) {style.order = props.order}
|
|
23
|
+
return { style }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function applyEscapeProps(element: any, props: { web?: any }): void {
|
|
27
|
+
if (!element || !props.web) {return}
|
|
28
|
+
for (const [key, value] of Object.entries(props.web)) {
|
|
29
|
+
if (key.includes('-')) {
|
|
30
|
+
if (value == null || value === false) {element.removeAttribute(key)}
|
|
31
|
+
else {element.setAttribute(key, value === true ? '' : String(value))}
|
|
32
|
+
} else if (key in element) {
|
|
33
|
+
try {
|
|
34
|
+
element[key] = value
|
|
35
|
+
} catch {
|
|
36
|
+
element.setAttribute(key, String(value))
|
|
37
|
+
}
|
|
38
|
+
} else if (value != null) {
|
|
39
|
+
element.setAttribute(key, String(value))
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/props.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type CameraAccessibilityRole =
|
|
2
|
+
| 'button'
|
|
3
|
+
| 'link'
|
|
4
|
+
| 'search'
|
|
5
|
+
| 'image'
|
|
6
|
+
| 'heading'
|
|
7
|
+
| 'adjustable'
|
|
8
|
+
| 'summary'
|
|
9
|
+
| 'text'
|
|
10
|
+
| 'none'
|
|
11
|
+
| 'progressbar'
|
|
12
|
+
| 'checkbox'
|
|
13
|
+
| 'switch'
|
|
14
|
+
| 'radio'
|
|
15
|
+
| 'spinbutton'
|
|
16
|
+
| 'tab'
|
|
17
|
+
|
|
18
|
+
export interface CameraViewHandle {
|
|
19
|
+
/** The native preview surface (`HTMLVideoElement` on web). */
|
|
20
|
+
native: any
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CameraViewProps {
|
|
24
|
+
className?: any
|
|
25
|
+
style?: any
|
|
26
|
+
id?: string
|
|
27
|
+
row?: number
|
|
28
|
+
col?: number
|
|
29
|
+
rowSpan?: number
|
|
30
|
+
colSpan?: number
|
|
31
|
+
dock?: 'left' | 'top' | 'right' | 'bottom'
|
|
32
|
+
left?: number
|
|
33
|
+
top?: number
|
|
34
|
+
flexGrow?: number
|
|
35
|
+
flexShrink?: number
|
|
36
|
+
alignSelf?: string
|
|
37
|
+
order?: number
|
|
38
|
+
accessible?: boolean
|
|
39
|
+
accessibilityLabel?: string
|
|
40
|
+
accessibilityRole?: CameraAccessibilityRole
|
|
41
|
+
accessibilityHint?: string
|
|
42
|
+
accessibilityValue?: string
|
|
43
|
+
accessibilityState?: {
|
|
44
|
+
disabled?: boolean
|
|
45
|
+
selected?: boolean
|
|
46
|
+
checked?: boolean
|
|
47
|
+
}
|
|
48
|
+
accessibilityLiveRegion?: 'none' | 'polite' | 'assertive'
|
|
49
|
+
/** Lens — defaults to the rear camera. */
|
|
50
|
+
facing?: 'front' | 'back'
|
|
51
|
+
/** Default true. False releases the preview session. */
|
|
52
|
+
active?: boolean
|
|
53
|
+
/** Fires after the preview surface starts. */
|
|
54
|
+
onReady?: () => void
|
|
55
|
+
onError?: (error: { message?: string }) => void
|
|
56
|
+
/** Native preview surface for platform-specific controls. */
|
|
57
|
+
bind?: (handle: CameraViewHandle) => void
|
|
58
|
+
/** Platform-specific properties applied to the preview surface. */
|
|
59
|
+
ios?: Record<string, any>
|
|
60
|
+
android?: Record<string, any>
|
|
61
|
+
web?: Record<string, any>
|
|
62
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { UniversalComponent } from 'octane/universal'
|
|
2
|
+
import type { CameraViewProps } from './props'
|
|
3
|
+
|
|
4
|
+
export declare const CameraView: UniversalComponent<CameraViewProps>
|
|
5
|
+
export type { CameraViewHandle, CameraViewProps, CameraAccessibilityRole } from './props'
|
package/types/props.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type CameraAccessibilityRole = 'button' | 'link' | 'search' | 'image' | 'heading' | 'adjustable' | 'summary' | 'text' | 'none' | 'progressbar' | 'checkbox' | 'switch' | 'radio' | 'spinbutton' | 'tab';
|
|
2
|
+
export interface CameraViewHandle {
|
|
3
|
+
/** The native preview surface (`HTMLVideoElement` on web). */
|
|
4
|
+
native: any;
|
|
5
|
+
}
|
|
6
|
+
export interface CameraViewProps {
|
|
7
|
+
className?: any;
|
|
8
|
+
style?: any;
|
|
9
|
+
id?: string;
|
|
10
|
+
row?: number;
|
|
11
|
+
col?: number;
|
|
12
|
+
rowSpan?: number;
|
|
13
|
+
colSpan?: number;
|
|
14
|
+
dock?: 'left' | 'top' | 'right' | 'bottom';
|
|
15
|
+
left?: number;
|
|
16
|
+
top?: number;
|
|
17
|
+
flexGrow?: number;
|
|
18
|
+
flexShrink?: number;
|
|
19
|
+
alignSelf?: string;
|
|
20
|
+
order?: number;
|
|
21
|
+
accessible?: boolean;
|
|
22
|
+
accessibilityLabel?: string;
|
|
23
|
+
accessibilityRole?: CameraAccessibilityRole;
|
|
24
|
+
accessibilityHint?: string;
|
|
25
|
+
accessibilityValue?: string;
|
|
26
|
+
accessibilityState?: {
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
selected?: boolean;
|
|
29
|
+
checked?: boolean;
|
|
30
|
+
};
|
|
31
|
+
accessibilityLiveRegion?: 'none' | 'polite' | 'assertive';
|
|
32
|
+
/** Lens — defaults to the rear camera. */
|
|
33
|
+
facing?: 'front' | 'back';
|
|
34
|
+
/** Default true. False releases the preview session. */
|
|
35
|
+
active?: boolean;
|
|
36
|
+
/** Fires after the preview surface starts. */
|
|
37
|
+
onReady?: () => void;
|
|
38
|
+
onError?: (error: {
|
|
39
|
+
message?: string;
|
|
40
|
+
}) => void;
|
|
41
|
+
/** Native preview surface for platform-specific controls. */
|
|
42
|
+
bind?: (handle: CameraViewHandle) => void;
|
|
43
|
+
/** Platform-specific properties applied to the preview surface. */
|
|
44
|
+
ios?: Record<string, any>;
|
|
45
|
+
android?: Record<string, any>;
|
|
46
|
+
web?: Record<string, any>;
|
|
47
|
+
}
|