From 413fd7b9a3a88eb455e7c2aa1edb39ce85302fb0 Mon Sep 17 00:00:00 2001 From: Emmanuel Atawodi Date: Sat, 11 Oct 2025 00:49:20 +0100 Subject: [PATCH 1/5] chore: remove android CI build --- .github/workflows/ci.yml | 58 ---------------------------------- example/src/test-framework.tsx | 48 +++++++++++++++------------- 2 files changed, 26 insertions(+), 80 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee892cb..3da44ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,64 +57,6 @@ jobs: - name: Build package run: yarn prepare - build-android: - runs-on: ubuntu-latest - - env: - TURBO_CACHE_DIR: .turbo/android - - steps: - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - name: Setup - uses: ./.github/actions/setup - - - name: Cache turborepo for Android - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 - with: - path: ${{ env.TURBO_CACHE_DIR }} - key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }} - restore-keys: | - ${{ runner.os }}-turborepo-android- - - - name: Check turborepo cache for Android - run: | - TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status") - - if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then - echo "turbo_cache_hit=1" >> $GITHUB_ENV - fi - - - name: Install JDK - if: env.turbo_cache_hit != 1 - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 - with: - distribution: 'zulu' - java-version: '17' - - - name: Finalize Android SDK - if: env.turbo_cache_hit != 1 - run: | - /bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null" - - - name: Cache Gradle - if: env.turbo_cache_hit != 1 - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 - with: - path: | - ~/.gradle/wrapper - ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Build example for Android - env: - JAVA_OPTS: "-XX:MaxHeapSize=6g" - run: | - yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" - build-ios: runs-on: macos-latest diff --git a/example/src/test-framework.tsx b/example/src/test-framework.tsx index a85a905..72340d2 100644 --- a/example/src/test-framework.tsx +++ b/example/src/test-framework.tsx @@ -1,4 +1,4 @@ -import React, {useState, useRef, useCallback, useEffect} from 'react'; +import * as React from 'react'; import {View, Text, Button, StyleSheet, ScrollView, Alert} from 'react-native'; import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'; import { @@ -38,23 +38,27 @@ type TestFrameworkProps = { }; export default function TestFramework({onBack}: TestFrameworkProps = {}) { - const [testSuites, setTestSuites] = useState([]); - const [isRunning, setIsRunning] = useState(false); - const cameraRef = useRef(null); + const [testSuites, setTestSuites] = React.useState([]); + const [isRunning, setIsRunning] = React.useState(false); + const cameraRef = React.useRef(null); const device = useCameraDevice('back'); const insets = useSafeAreaInsets(); // Test state - const [currentSourceId, setCurrentSourceId] = useState(null); - const [currentTrackId, setCurrentTrackId] = useState(null); + const [currentSourceId, setCurrentSourceId] = React.useState( + null + ); + const [currentTrackId, setCurrentTrackId] = React.useState( + null + ); // Frame processor for testing actual frame delivery const frameProcessor = useFrameProcessor((frame) => { processFrame(frame); }, []); - const updateTestResult = useCallback( + const updateTestResult = React.useCallback( (suiteName: string, testName: string, result: Partial) => { setTestSuites((prev) => prev.map((suite) => @@ -72,7 +76,7 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { [] ); - const runTest = useCallback( + const runTest = React.useCallback( async ( suiteName: string, testName: string, @@ -102,7 +106,7 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { ); // Initialize test suites - useEffect(() => { + React.useEffect(() => { setTestSuites([ { name: 'Core Integration', @@ -142,7 +146,7 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { }, []); // Test implementations - const testCreateVisionCameraSource = useCallback(async () => { + const testCreateVisionCameraSource = React.useCallback(async () => { const node = findNodeHandle(cameraRef.current); if (!node) throw new Error('Camera ref not available'); @@ -152,7 +156,7 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { setCurrentSourceId(result.__nativeSourceId); }, []); - const testCreateWebRTCTrack = useCallback(async () => { + const testCreateWebRTCTrack = React.useCallback(async () => { if (!currentSourceId) throw new Error('No source ID available'); const result = await createWebRTCTrack( @@ -169,7 +173,7 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { setCurrentTrackId(result.trackId); }, [currentSourceId]); - const testSetupFrameProcessor = useCallback(async () => { + const testSetupFrameProcessor = React.useCallback(async () => { if (!currentSourceId) throw new Error('No source ID available'); setSourceId(currentSourceId); @@ -183,7 +187,7 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { console.log('✅ Frame processor setup complete'); }, [currentSourceId]); - const testFrameDelivery = useCallback(async () => { + const testFrameDelivery = React.useCallback(async () => { if (!currentSourceId || !currentTrackId) { throw new Error('Source ID and Track ID required'); } @@ -219,7 +223,7 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { ); }, [currentSourceId, currentTrackId]); - const testDisposeResources = useCallback(async () => { + const testDisposeResources = React.useCallback(async () => { // Clear frame processor first clearSourceId(); @@ -233,7 +237,7 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { } }, [currentTrackId, currentSourceId]); - const testMultipleTrackCreationDisposal = useCallback(async () => { + const testMultipleTrackCreationDisposal = React.useCallback(async () => { if (!currentSourceId) throw new Error('No source ID available'); const trackIds: string[] = []; @@ -253,7 +257,7 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { } }, [currentSourceId]); - const testFrameDeliveryLatency = useCallback(async () => { + const testFrameDeliveryLatency = React.useCallback(async () => { if (!currentTrackId) throw new Error('No track ID available'); const startTime = Date.now(); @@ -267,7 +271,7 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { console.log(`Stats query latency: ${latency}ms`, stats); }, [currentTrackId]); - const testInvalidSourceId = useCallback(async () => { + const testInvalidSourceId = React.useCallback(async () => { const NativeVisionRTC = require('react-native-vision-rtc/src/NativeVisionRtc').default; @@ -286,7 +290,7 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { } }, []); - const runCoreIntegrationTests = useCallback(async () => { + const runCoreIntegrationTests = React.useCallback(async () => { await runTest( 'Core Integration', 'Create Vision Camera Source', @@ -317,7 +321,7 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { testDisposeResources, ]); - const runMemoryManagementTests = useCallback(async () => { + const runMemoryManagementTests = React.useCallback(async () => { // Recreate source for memory tests await runTest( 'Core Integration', @@ -342,7 +346,7 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { testDisposeResources, ]); - const runPerformanceTests = useCallback(async () => { + const runPerformanceTests = React.useCallback(async () => { // Recreate resources for performance tests await runTest( 'Core Integration', @@ -375,11 +379,11 @@ export default function TestFramework({onBack}: TestFrameworkProps = {}) { testDisposeResources, ]); - const runErrorHandlingTests = useCallback(async () => { + const runErrorHandlingTests = React.useCallback(async () => { await runTest('Error Handling', 'Invalid Source ID', testInvalidSourceId); }, [runTest, testInvalidSourceId]); - const runAllTests = useCallback(async () => { + const runAllTests = React.useCallback(async () => { if (isRunning) return; setIsRunning(true); From ac684d26278b7d2d19434d71d8a35e837806d843 Mon Sep 17 00:00:00 2001 From: Emmanuel Atawodi Date: Sat, 11 Oct 2025 15:04:24 +0100 Subject: [PATCH 2/5] chore: refine documentation --- README.md | 75 +++---------------------------------------------------- 1 file changed, 3 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 4bd5dfa..521334a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # react-native-vision-rtc -React Native library for streaming Vision Camera, ARKit, OpenCV, or ML frames over WebRTC. +VisionRTC fills the gap that react-native-webrtc doesn’t: it enables zero-copy streaming of custom native GPU frames (from Vision Camera, ARKit, ML, etc.) into WebRTC tracks without requiring per-project native code. + +It’s a plugin-based, type-safe, cross-platform pipeline for sources, processors (filters, ML, AR), and track management, while staying unopinionated about signaling and transport. > 🚧 This library is a work in progress. APIs, behaviors, and native implementations may change without notice. @@ -11,75 +13,4 @@ React Native library for streaming Vision Camera, ARKit, OpenCV, or ML frames ov The package has not been published to npm yet. Installation instructions will be added once the first release is available. -## Usage - - -```ts -import { - createVisionCameraSource, - createWebRTCTrack, - disposeTrack, - getStats, -} from 'react-native-vision-rtc'; - -async function demo(reactTag: number) { - const source = await createVisionCameraSource(reactTag); - const { trackId } = await createWebRTCTrack(source, { - fps: 30, - resolution: { width: 1280, height: 720 }, - }); - - const stats = (await getStats?.()) ?? null; - await disposeTrack(trackId); - return stats; -} - -// Example invocation: -// demo(findNodeHandle(cameraRef)); -``` - -### API - -- **Functions** - - `createVisionCameraSource(viewTag: number)`: Links a native camera-like view to the library and returns a source you can stream from. - - `createWebRTCTrack(source, opts?)`: Creates a WebRTC video track from a source. You can pass simple options like `fps` and `resolution`. - - `replaceTrack(senderId: string, nextTrackId: string)`: Swaps the video track used by an existing WebRTC sender. - - `pauseTrack(trackId: string)`: Temporarily stops sending frames for that track (does not destroy it). - - `resumeTrack(trackId: string)`: Restarts frame sending for a paused track. - - `setTrackConstraints(trackId: string, opts)`: Changes track settings on the fly (for example, fps or resolution). - - `disposeTrack(trackId: string)`: Frees native resources for that track. - - `getStats()`: Returns basic runtime stats like `fps` and `droppedFrames` (if supported on the platform). - -- **Component** - - `VisionRTCView`: A native view that can render a given `trackId`. - - Props: - - `trackId?: string | null` - - `style?: ViewStyle | ViewStyle[]` - -- **Types** - - `TrackOptions`: Options for tracks. Common fields: - - `fps?: number` (frames per second) - - `resolution?: { width: number; height: number }` - - `bitrate?: number` - - `colorSpace?: 'auto' | 'sRGB' | 'BT.709' | 'BT.2020'` - - `orientationMode?: 'auto' | 'fixed-0' | 'fixed-90' | 'fixed-180' | 'fixed-270'` - - `mode?: 'null-gpu' | 'null-cpu' | 'external'` - - `VisionRTCTrack`: `{ trackId: string }` returned when you create a track. - - `VisionCameraSource`: Source handle returned by `createVisionCameraSource`. - - `NativePixelSource`: Low-level source if you already have native pixels (platform-specific shapes). - - `Resolution`: `{ width: number; height: number }`. - - -## Contributing - -- [Development workflow](CONTRIBUTING.md#development-workflow) -- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request) -- [Code of conduct](CODE_OF_CONDUCT.md) - -## License - -MIT - ---- - Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob) From 45e815b7abc28e642523982fee77280b12423b72 Mon Sep 17 00:00:00 2001 From: Emmanuel Atawodi Date: Sat, 11 Oct 2025 21:06:56 +0100 Subject: [PATCH 3/5] chore: rearchitect library to be composable via plugins --- babel.config.js | 4 +- eslint.config.mjs | 2 +- example/babel.config.js | 10 +- example/index.js | 8 +- example/jest.config.js | 2 +- example/metro.config.js | 8 +- example/package.json | 12 +- example/react-native.config.js | 6 +- example/src/App.tsx | 82 +-- example/src/test-framework.tsx | 211 +++--- example/src/vision-camera-frame-processor.ts | 12 +- example/tsconfig.json | 20 + package.json | 5 +- packages/core/VisionRtc.podspec | 29 + packages/core/android/build.gradle | 78 +++ packages/core/android/gradle.properties | 5 + .../core/android/src/main/AndroidManifest.xml | 2 + .../java/com/visionrtc/GlNullGenerator.kt | 97 +++ .../java/com/visionrtc/VisionRtcModule.kt | 325 +++++++++ .../java/com/visionrtc/VisionRtcPackage.kt | 38 ++ .../com/visionrtc/VisionRtcViewManager.kt | 42 ++ packages/core/babel.config.js | 12 + packages/core/ios/NullGPUGenerator.swift | 124 ++++ packages/core/ios/VisionRTC+Spec.mm | 265 ++++++++ packages/core/ios/VisionRTCModule.swift | 634 ++++++++++++++++++ packages/core/ios/VisionRTCRegistryBridge.h | 14 + packages/core/ios/VisionRTCRegistryBridge.mm | 27 + .../core/ios/VisionRTCTrackRegistry.swift | 30 + packages/core/ios/VisionRTCViewManager.m | 51 ++ packages/core/package.json | 181 +++++ packages/core/src/NativeVisionRtc.ts | 60 ++ packages/core/src/__tests__/index.test.tsx | 1 + packages/core/src/index.ts | 296 ++++++++ packages/core/src/types.ts | 72 ++ packages/core/src/vision-rtc-view.tsx | 42 ++ packages/core/tsconfig.build.json | 15 + packages/core/tsconfig.json | 9 + packages/core/turbo.json | 41 ++ packages/react-native-vision-rtc/package.json | 74 ++ packages/react-native-vision-rtc/src/index.ts | 3 + .../tsconfig.build.json | 19 + .../react-native-vision-rtc/tsconfig.json | 14 + packages/source-visioncamera/package.json | 77 +++ packages/source-visioncamera/register.ts | 3 + packages/source-visioncamera/src/register.ts | 3 + .../source-visioncamera/tsconfig.build.json | 15 + packages/source-visioncamera/tsconfig.json | 10 + scripts/build-wrapper.cjs | 24 + src/NativeVisionRtc.ts | 14 +- src/__tests__/index.test.tsx | 2 +- src/index.ts | 24 +- src/types.ts | 22 +- src/vision-rtc-view.tsx | 10 +- tsconfig.json | 16 +- yarn.lock | 412 +++++++++++- 55 files changed, 3367 insertions(+), 247 deletions(-) create mode 100644 example/tsconfig.json create mode 100644 packages/core/VisionRtc.podspec create mode 100644 packages/core/android/build.gradle create mode 100644 packages/core/android/gradle.properties create mode 100644 packages/core/android/src/main/AndroidManifest.xml create mode 100644 packages/core/android/src/main/java/com/visionrtc/GlNullGenerator.kt create mode 100644 packages/core/android/src/main/java/com/visionrtc/VisionRtcModule.kt create mode 100644 packages/core/android/src/main/java/com/visionrtc/VisionRtcPackage.kt create mode 100644 packages/core/android/src/main/java/com/visionrtc/VisionRtcViewManager.kt create mode 100644 packages/core/babel.config.js create mode 100644 packages/core/ios/NullGPUGenerator.swift create mode 100644 packages/core/ios/VisionRTC+Spec.mm create mode 100644 packages/core/ios/VisionRTCModule.swift create mode 100644 packages/core/ios/VisionRTCRegistryBridge.h create mode 100644 packages/core/ios/VisionRTCRegistryBridge.mm create mode 100644 packages/core/ios/VisionRTCTrackRegistry.swift create mode 100644 packages/core/ios/VisionRTCViewManager.m create mode 100644 packages/core/package.json create mode 100644 packages/core/src/NativeVisionRtc.ts create mode 100644 packages/core/src/__tests__/index.test.tsx create mode 100644 packages/core/src/index.ts create mode 100644 packages/core/src/types.ts create mode 100644 packages/core/src/vision-rtc-view.tsx create mode 100644 packages/core/tsconfig.build.json create mode 100644 packages/core/tsconfig.json create mode 100644 packages/core/turbo.json create mode 100644 packages/react-native-vision-rtc/package.json create mode 100644 packages/react-native-vision-rtc/src/index.ts create mode 100644 packages/react-native-vision-rtc/tsconfig.build.json create mode 100644 packages/react-native-vision-rtc/tsconfig.json create mode 100644 packages/source-visioncamera/package.json create mode 100644 packages/source-visioncamera/register.ts create mode 100644 packages/source-visioncamera/src/register.ts create mode 100644 packages/source-visioncamera/tsconfig.build.json create mode 100644 packages/source-visioncamera/tsconfig.json create mode 100644 scripts/build-wrapper.cjs diff --git a/babel.config.js b/babel.config.js index 0c05fd6..e661e2b 100644 --- a/babel.config.js +++ b/babel.config.js @@ -2,11 +2,11 @@ module.exports = { overrides: [ { exclude: /\/node_modules\//, - presets: ['module:react-native-builder-bob/babel-preset'], + presets: ["module:react-native-builder-bob/babel-preset"], }, { include: /\/node_modules\//, - presets: ['module:@react-native/babel-preset'], + presets: ["module:@react-native/babel-preset"], }, ], }; diff --git a/eslint.config.mjs b/eslint.config.mjs index 16b00bb..bc68b07 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -24,6 +24,6 @@ export default defineConfig([ }, }, { - ignores: ['node_modules/', 'lib/'], + ignores: ['node_modules/', 'lib/', '**/lib/**', '**/dist/**'], }, ]); diff --git a/example/babel.config.js b/example/babel.config.js index 988c3fd..0acb147 100644 --- a/example/babel.config.js +++ b/example/babel.config.js @@ -1,12 +1,12 @@ -const path = require('path'); -const {getConfig} = require('react-native-builder-bob/babel-config'); -const pkg = require('../package.json'); +const path = require("path"); +const {getConfig} = require("react-native-builder-bob/babel-config"); +const pkg = require("../package.json"); -const root = path.resolve(__dirname, '..'); +const root = path.resolve(__dirname, ".."); module.exports = getConfig( { - presets: ['module:@react-native/babel-preset'], + presets: ["module:@react-native/babel-preset"], }, {root, pkg} ); diff --git a/example/index.js b/example/index.js index c0b5867..a75e6f0 100644 --- a/example/index.js +++ b/example/index.js @@ -1,7 +1,7 @@ -import {AppRegistry} from 'react-native'; -import {SafeAreaProvider} from 'react-native-safe-area-context'; -import App from './src/App'; -import {name as appName} from './app.json'; +import {AppRegistry} from "react-native"; +import {SafeAreaProvider} from "react-native-safe-area-context"; +import App from "./src/App"; +import {name as appName} from "./app.json"; const Root = () => ( diff --git a/example/jest.config.js b/example/jest.config.js index 8eb675e..edf1946 100644 --- a/example/jest.config.js +++ b/example/jest.config.js @@ -1,3 +1,3 @@ module.exports = { - preset: 'react-native', + preset: "react-native", }; diff --git a/example/metro.config.js b/example/metro.config.js index c33a19e..243666a 100644 --- a/example/metro.config.js +++ b/example/metro.config.js @@ -1,8 +1,8 @@ -const path = require('path'); -const {getDefaultConfig} = require('@react-native/metro-config'); -const {withMetroConfig} = require('react-native-monorepo-config'); +const path = require("path"); +const {getDefaultConfig} = require("@react-native/metro-config"); +const {withMetroConfig} = require("react-native-monorepo-config"); -const root = path.resolve(__dirname, '..'); +const root = path.resolve(__dirname, ".."); /** * Metro configuration diff --git a/example/package.json b/example/package.json index cf0cb39..9f1c943 100644 --- a/example/package.json +++ b/example/package.json @@ -7,7 +7,9 @@ "ios": "react-native run-ios", "start": "react-native start", "build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"", - "build:ios": "react-native build-ios --mode Debug" + "build:ios": "react-native build-ios --mode Debug", + "lint": "eslint \"src/**/*.{ts,tsx}\"", + "typecheck": "tsc --noEmit" }, "dependencies": { "@react-native/new-app-screen": "0.81.1", @@ -20,15 +22,21 @@ "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", "@babel/runtime": "^7.25.0", + "@eslint/compat": "^1.4.0", "@react-native-community/cli": "20.0.0", "@react-native-community/cli-platform-android": "20.0.0", "@react-native-community/cli-platform-ios": "20.0.0", "@react-native/babel-preset": "0.81.1", + "@react-native/eslint-config": "^0.82.0", "@react-native/metro-config": "0.81.1", "@react-native/typescript-config": "0.81.1", "@types/react": "^19.1.0", + "eslint": "^9.37.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", "react-native-builder-bob": "^0.40.13", - "react-native-monorepo-config": "^0.1.9" + "react-native-monorepo-config": "^0.1.9", + "typescript": "^5.9.3" }, "engines": { "node": ">=20" diff --git a/example/react-native.config.js b/example/react-native.config.js index 59d9698..9544d0e 100644 --- a/example/react-native.config.js +++ b/example/react-native.config.js @@ -1,5 +1,5 @@ -const path = require('path'); -const pkg = require('../package.json'); +const path = require("path"); +const pkg = require("../package.json"); module.exports = { project: { @@ -9,7 +9,7 @@ module.exports = { }, dependencies: { [pkg.name]: { - root: path.join(__dirname, '..'), + root: path.join(__dirname, ".."), platforms: { // Codegen script incorrectly fails without this // So we explicitly specify the platforms with empty object diff --git a/example/src/App.tsx b/example/src/App.tsx index b5d04e8..e0fead7 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,7 +1,7 @@ -import * as React from 'react'; -import {Button, Text, StyleSheet, View, findNodeHandle} from 'react-native'; -import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'; -import {Camera, useCameraDevice} from 'react-native-vision-camera'; +import * as React from "react"; +import {Button, Text, StyleSheet, View, findNodeHandle} from "react-native"; +import {SafeAreaView, useSafeAreaInsets} from "react-native-safe-area-context"; +import {Camera, useCameraDevice} from "react-native-vision-camera"; import { createVisionCameraSource, createWebRTCTrack, @@ -11,13 +11,13 @@ import { disposeTrack, getStats, VisionRTCView, -} from 'react-native-vision-rtc'; -import TestFramework from './test-framework'; +} from "react-native-vision-rtc"; +import TestFramework from "./test-framework"; export default function App() { const insets = useSafeAreaInsets(); const cameraRef = React.useRef(null); - const device = useCameraDevice('back'); + const device = useCameraDevice("back"); const [trackId, setTrackId] = React.useState(null); const [showTests, setShowTests] = React.useState(false); const [sourceId, setSourceId] = React.useState(null); @@ -28,16 +28,16 @@ export default function App() { } | null>(null); const [creating, setCreating] = React.useState(false); const [torch, setTorch] = React.useState(false); - const [facing, setFacing] = React.useState<'front' | 'back'>('back'); + const [facing, setFacing] = React.useState<"front" | "back">("back"); const [backpressure, setBackpressure] = React.useState< - 'drop-late' | 'latest-wins' | 'throttle' - >('drop-late'); + "drop-late" | "latest-wins" | "throttle" + >("drop-late"); const ensurePermissions = React.useCallback(async () => { const cam = await Camera.getCameraPermissionStatus(); - if (cam !== 'granted') { + if (cam !== "granted") { const res = await Camera.requestCameraPermission(); - if (res !== 'granted') throw new Error('Camera permission not granted'); + if (res !== "granted") throw new Error("Camera permission not granted"); } }, []); @@ -48,7 +48,7 @@ export default function App() { try { await ensurePermissions(); const node = findNodeHandle(cameraRef.current); - if (!node) throw new Error('Camera view not ready'); + if (!node) throw new Error("Camera view not ready"); const {__nativeSourceId} = await createVisionCameraSource(node); setSourceId(__nativeSourceId); const created = await createWebRTCTrack( @@ -67,7 +67,7 @@ export default function App() { await disposeTrack(newId); } catch {} } - console.error('Failed to start WebRTC track', err); + console.error("Failed to start WebRTC track", err); } finally { setCreating(false); } @@ -79,7 +79,7 @@ export default function App() { if (trackId) await disposeTrack(trackId); if (sourceId) await disposeSource(sourceId); } catch (e) { - console.warn('Failed to dispose track', e); + console.warn("Failed to dispose track", e); } finally { setTrackId(null); setSourceId(null); @@ -113,7 +113,7 @@ export default function App() { const onFlip = async () => { if (!sourceId) return; - const next = facing === 'back' ? 'front' : 'back'; + const next = facing === "back" ? "front" : "back"; setFacing(next); // Turning torch off when flipping avoids devices without torch (e.g., front) setTorch(false); @@ -129,19 +129,19 @@ export default function App() { const onFps = async (fps: number) => { if (!trackId) return; - console.log('onFps', fps); + console.log("onFps", fps); await updateTrack(trackId, {fps}); }; const onToggleBackpressure = async () => { - const order: Array<'drop-late' | 'latest-wins' | 'throttle'> = [ - 'drop-late', - 'latest-wins', - 'throttle', + const order: Array<"drop-late" | "latest-wins" | "throttle"> = [ + "drop-late", + "latest-wins", + "throttle", ]; const currentIndex = order.indexOf(backpressure); const idx = currentIndex === -1 ? 0 : (currentIndex + 1) % order.length; - const next = order[idx] as 'drop-late' | 'latest-wins' | 'throttle'; + const next = order[idx] as "drop-late" | "latest-wins" | "throttle"; setBackpressure(next); if (trackId) { await updateTrack(trackId, {backpressure: next}); @@ -153,7 +153,7 @@ export default function App() { } return ( - + {device && ( )} - + - {trackId ?? 'No track id'} + {trackId ?? "No track id"} @@ -187,7 +187,7 @@ export default function App() {