@octane-xplat/camera 0.7.3 → 0.9.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/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # `@octane-xplat/camera`
2
+
3
+ Live camera preview for Octane xplat apps: `getUserMedia` video on web,
4
+ `AVCaptureSession` on iOS, CameraX on Android.
5
+
6
+ ```sh
7
+ pnpm add @octane-xplat/camera
8
+ ```
9
+
10
+ ```tsx
11
+ import { CameraView } from '@octane-xplat/camera'
12
+
13
+ ;<CameraView
14
+ facing="back" // 'front' | 'back', default 'back'
15
+ active={streaming} // default true — false releases the session
16
+ onReady={() => console.log('preview running')}
17
+ onError={(e) => console.log(e.message)}
18
+ bind={(h) => (handle = h)} // h.native is the preview surface
19
+ />
20
+ ```
21
+
22
+ This is the preview surface only — for one-shot still capture and library
23
+ picking use [`@octane-xplat/media`](../media/README.md). Permissions are
24
+ requested when the session starts; `onError` is how a denial shows up.
25
+ `bind` hands you the platform surface (`HTMLVideoElement` on web) for
26
+ controls the shared props don't cover, and the `ios`/`android`/`web`
27
+ escape props apply after the shared props.
28
+
29
+ Guide: [Building screens](../../docs/primitives.md) (leaf components);
30
+ component index: [`docs/components.md`](../../docs/components.md).
31
+ Exercised by [`CameraDemo`](../demos/src/CameraDemo.tsrx).
@@ -5,7 +5,7 @@ function cameraHardwarePresent() {
5
5
  if (isIOS) return typeof globalThis.AVCaptureDevice !== "undefined";
6
6
  if (!isAndroid) return false;
7
7
  try {
8
- return (Application.android?.context ?? Application.android?.startActivity)?.packageManager?.hasSystemFeature("android.hardware.camera.any") !== false;
8
+ return (Application.android?.context ?? Application.android?.startActivity)?.getPackageManager()?.hasSystemFeature("android.hardware.camera.any") !== false;
9
9
  } catch {
10
10
  return true;
11
11
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "buildId": "fcf4766e-6e75-4a8c-975d-f47323dec7a1",
3
+ "buildId": "43ccef1b-b61d-45ed-b9be-3e99093c9451",
4
4
  "mode": "production",
5
5
  "capabilities": {
6
6
  "independentHydration": false
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "buildId": "2cfced37-60f8-430f-9f32-7b16653af15c",
3
+ "buildId": "b4334c49-36f2-46b2-ba99-abac851b84f2",
4
4
  "mode": "production",
5
5
  "capabilities": {
6
6
  "independentHydration": false
package/package.json CHANGED
@@ -1,25 +1,25 @@
1
1
  {
2
2
  "name": "@octane-xplat/camera",
3
- "version": "0.7.3",
3
+ "version": "0.9.0",
4
4
  "description": "Live camera preview for Octane xplat — web getUserMedia + native capture (AVCapture / CameraX)",
5
5
  "keywords": [
6
- "octane",
7
- "xplat",
8
- "cross-platform",
6
+ "android",
9
7
  "camera",
10
8
  "camera-preview",
9
+ "cross-platform",
10
+ "ios",
11
+ "nativescript",
12
+ "octane",
11
13
  "photo",
12
14
  "video-capture",
13
- "nativescript",
14
- "ios",
15
- "android",
16
- "web"
15
+ "web",
16
+ "xplat"
17
17
  ],
18
- "license": "MIT",
19
18
  "homepage": "https://github.com/octane-xplat/octane-xplat/tree/main/packages/camera",
19
+ "license": "MIT",
20
20
  "repository": {
21
21
  "type": "git",
22
- "url": "git+https://github.com/octane-xplat/octane-xplat.git",
22
+ "url": "https://github.com/octane-xplat/octane-xplat",
23
23
  "directory": "packages/camera"
24
24
  },
25
25
  "files": [
@@ -29,12 +29,6 @@
29
29
  "platforms"
30
30
  ],
31
31
  "type": "module",
32
- "nativescript": {
33
- "platforms": {
34
- "ios": "7.0.0",
35
- "android": "7.0.0"
36
- }
37
- },
38
32
  "exports": {
39
33
  ".": {
40
34
  "types": "./types/index.d.ts",
@@ -47,10 +41,11 @@
47
41
  "access": "public"
48
42
  },
49
43
  "devDependencies": {
50
- "@nativescript-community/octane": "0.2.1",
44
+ "@nativescript-community/octane": "0.2.4",
51
45
  "@nativescript/core": "9.1.2",
52
46
  "@octanejs/vite-plugin": "0.1.61",
53
47
  "octane": "0.6.3",
48
+ "tsrx-typegen": "0.2.1",
54
49
  "typescript": "5.9.3",
55
50
  "vite": "8.3.0"
56
51
  },
@@ -70,7 +65,16 @@
70
65
  "optional": true
71
66
  }
72
67
  },
68
+ "nativescript": {
69
+ "platforms": {
70
+ "ios": "7.0.0",
71
+ "android": "7.0.0"
72
+ }
73
+ },
73
74
  "scripts": {
74
- "build": "vite build && vite build --mode native && tsc -p tsconfig.types.json && node ../../scripts/check-native-dist.mjs"
75
+ "build": "vite build && vite build --mode native && pnpm typegen && node ../../scripts/check-native-dist.mjs",
76
+ "typegen": "tsc -p tsconfig.types.json",
77
+ "test:packed": "node tests/packed-consumer.mjs",
78
+ "pack:check": "tsrx-typegen --pack-check"
75
79
  }
76
80
  }
@@ -3,11 +3,17 @@ import { Application, isAndroid, isIOS } from '@nativescript/core'
3
3
  let requestCode = 7719
4
4
 
5
5
  export function cameraHardwarePresent(): boolean {
6
- if (isIOS) {return typeof (globalThis as any).AVCaptureDevice !== 'undefined'}
7
- if (!isAndroid) {return false}
6
+ if (isIOS) {
7
+ return typeof (globalThis as any).AVCaptureDevice !== 'undefined'
8
+ }
9
+
10
+ if (!isAndroid) {
11
+ return false
12
+ }
13
+
8
14
  try {
9
15
  const context = Application.android?.context ?? Application.android?.startActivity
10
- return context?.packageManager?.hasSystemFeature('android.hardware.camera.any') !== false
16
+ return context?.getPackageManager()?.hasSystemFeature('android.hardware.camera.any') !== false
11
17
  } catch {
12
18
  return true
13
19
  }
@@ -19,8 +25,14 @@ export async function ensureCameraPermission(): Promise<boolean> {
19
25
  const av = (globalThis as any).AVCaptureDevice
20
26
  const mediaType = (globalThis as any).AVMediaTypeVideo ?? 'vide'
21
27
  const status = av?.authorizationStatusForMediaType?.(mediaType)
22
- if (status === 3) {return true}
23
- if (status === 1 || status === 2) {return false}
28
+ if (status === 3) {
29
+ return true
30
+ }
31
+
32
+ if (status === 1 || status === 2) {
33
+ return false
34
+ }
35
+
24
36
  return await new Promise<boolean>((resolve) =>
25
37
  av.requestAccessForMediaTypeCompletionHandler(mediaType, resolve),
26
38
  )
@@ -32,28 +44,27 @@ export async function ensureCameraPermission(): Promise<boolean> {
32
44
  if (
33
45
  activity?.checkSelfPermission?.(permission) ===
34
46
  android.content.pm.PackageManager.PERMISSION_GRANTED
35
- ) {return true}
47
+ ) {
48
+ return true
49
+ }
50
+
51
+ if (!activity) {
52
+ return false
53
+ }
36
54
 
37
- if (!activity) {return false}
38
55
  return await new Promise<boolean>((resolve) => {
39
56
  const currentRequest = requestCode++
40
57
  const onResult = (args: any) => {
41
- if (args.requestCode !== currentRequest) {return}
42
- Application.android.off(
43
- Application.android.activityRequestPermissionsEvent,
44
- onResult,
45
- )
58
+ if (args.requestCode !== currentRequest) {
59
+ return
60
+ }
61
+
62
+ Application.android.off(Application.android.activityRequestPermissionsEvent, onResult)
46
63
 
47
- resolve(
48
- args.grantResults?.[0] ===
49
- android.content.pm.PackageManager.PERMISSION_GRANTED,
50
- )
64
+ resolve(args.grantResults?.[0] === android.content.pm.PackageManager.PERMISSION_GRANTED)
51
65
  }
52
66
 
53
- Application.android.on(
54
- Application.android.activityRequestPermissionsEvent,
55
- onResult,
56
- )
67
+ Application.android.on(Application.android.activityRequestPermissionsEvent, onResult)
57
68
 
58
69
  activity.requestPermissions([permission], currentRequest)
59
70
  })
@@ -6,9 +6,10 @@ export function startCameraPreview(
6
6
  ): () => void {
7
7
  const av = globalThis as any
8
8
  const mediaType = av.AVMediaTypeVideo ?? 'vide'
9
- const desiredPosition = facing === 'front'
10
- ? av.AVCaptureDevicePositionFront ?? 2
11
- : av.AVCaptureDevicePositionBack ?? 1
9
+ const desiredPosition =
10
+ facing === 'front'
11
+ ? (av.AVCaptureDevicePositionFront ?? 2)
12
+ : (av.AVCaptureDevicePositionBack ?? 1)
12
13
 
13
14
  const devices = av.AVCaptureDevice.devicesWithMediaType(mediaType)
14
15
  let device: any = null
@@ -20,22 +21,32 @@ export function startCameraPreview(
20
21
  }
21
22
  }
22
23
 
23
- if (!device) {throw new Error('camera unavailable')}
24
+ if (!device) {
25
+ throw new Error('camera unavailable')
26
+ }
24
27
 
25
28
  const session = av.AVCaptureSession.new()
26
29
  const input = av.AVCaptureDeviceInput.deviceInputWithDeviceError(device)
27
- if (!input || !session.canAddInput(input)) {throw new Error('camera input unavailable')}
30
+ if (!input || !session.canAddInput(input)) {
31
+ throw new Error('camera input unavailable')
32
+ }
33
+
28
34
  session.addInput(input)
29
35
  const layer = av.AVCaptureVideoPreviewLayer.layerWithSession(session)
30
36
  layer.videoGravity = 'resizeAspectFill'
31
37
  const connection = layer.connection
32
38
  if (connection) {
33
39
  connection.automaticallyAdjustsVideoMirroring = false
34
- if (connection.isVideoMirroringSupported) {connection.isVideoMirrored = facing === 'front'}
40
+ if (connection.isVideoMirroringSupported) {
41
+ connection.isVideoMirrored = facing === 'front'
42
+ }
35
43
  }
36
44
 
37
45
  const nativeView = view.ios ?? view.nativeViewProtected
38
- if (!nativeView?.layer) {throw new Error('camera preview host unavailable')}
46
+ if (!nativeView?.layer) {
47
+ throw new Error('camera preview host unavailable')
48
+ }
49
+
39
50
  nativeView.layer.addSublayer(layer)
40
51
  const updateFrame = () => {
41
52
  layer.frame = nativeView.bounds
@@ -17,16 +17,23 @@ export function layoutChildProps(props: CameraViewProps): Record<string, any> {
17
17
  'alignSelf',
18
18
  'order',
19
19
  ] as const) {
20
- if (props[key] !== undefined) {result[key] = props[key]}
20
+ if (props[key] !== undefined) {
21
+ result[key] = props[key]
22
+ }
21
23
  }
22
24
 
23
25
  return result
24
26
  }
25
27
 
26
28
  export function applyEscapeProps(view: any, props: { ios?: any; android?: any }): void {
27
- if (!view) {return}
29
+ if (!view) {
30
+ return
31
+ }
32
+
28
33
  const bag = isIOS ? props.ios : props.android
29
- if (bag) {Object.assign(view, bag)}
34
+ if (bag) {
35
+ Object.assign(view, bag)
36
+ }
30
37
  }
31
38
 
32
39
  const nativeRoles: Record<string, string> = {
@@ -51,12 +58,24 @@ export function nativeAccessibilityRole(role?: string): string | undefined {
51
58
  return role ? nativeRoles[role] : undefined
52
59
  }
53
60
 
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
+ export function nativeAccessibilityState(
62
+ state?: CameraViewProps['accessibilityState'],
63
+ ): string | undefined {
64
+ if (state?.disabled) {
65
+ return 'disabled'
66
+ }
67
+
68
+ if (state?.selected) {
69
+ return 'selected'
70
+ }
71
+
72
+ if (state?.checked === true) {
73
+ return 'checked'
74
+ }
75
+
76
+ if (state?.checked === false) {
77
+ return 'unchecked'
78
+ }
79
+
61
80
  return undefined
62
81
  }
@@ -14,21 +14,45 @@ export function layoutChildProps(props: CameraViewProps, baseStyle: any = props.
14
14
  style.gridColumn = `span ${props.colSpan}`
15
15
  }
16
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}
17
+ if (props.left !== undefined) {
18
+ style.left = props.left
19
+ }
20
+
21
+ if (props.top !== undefined) {
22
+ style.top = props.top
23
+ }
24
+
25
+ if (props.flexGrow !== undefined) {
26
+ style.flexGrow = props.flexGrow
27
+ }
28
+
29
+ if (props.flexShrink !== undefined) {
30
+ style.flexShrink = props.flexShrink
31
+ }
32
+
33
+ if (props.alignSelf !== undefined) {
34
+ style.alignSelf = props.alignSelf
35
+ }
36
+
37
+ if (props.order !== undefined) {
38
+ style.order = props.order
39
+ }
40
+
23
41
  return { style }
24
42
  }
25
43
 
26
44
  export function applyEscapeProps(element: any, props: { web?: any }): void {
27
- if (!element || !props.web) {return}
45
+ if (!element || !props.web) {
46
+ return
47
+ }
48
+
28
49
  for (const [key, value] of Object.entries(props.web)) {
29
50
  if (key.includes('-')) {
30
- if (value == null || value === false) {element.removeAttribute(key)}
31
- else {element.setAttribute(key, value === true ? '' : String(value))}
51
+ if (value == null || value === false) {
52
+ element.removeAttribute(key)
53
+ } else {
54
+ element.setAttribute(key, value === true ? '' : String(value))
55
+ }
32
56
  } else if (key in element) {
33
57
  try {
34
58
  element[key] = value
package/types/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { UniversalComponent } from 'octane/universal'
2
- import type { CameraViewProps } from './props'
2
+ import type { CameraViewProps } from './props.js'
3
3
 
4
4
  export declare const CameraView: UniversalComponent<CameraViewProps>
5
- export type { CameraViewHandle, CameraViewProps, CameraAccessibilityRole } from './props'
5
+ export type { CameraViewHandle, CameraViewProps, CameraAccessibilityRole } from './props.js'