Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ class AgoraServicePackage : ReactPackage {
}

override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(AgoraServiceManager(reactContext))
return listOf(AgoraServiceManager(reactContext), CameraCapabilitiesModule(reactContext))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.reactnativeagoraexampleexpo

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.os.Build
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod

/** Demo-only Camera2 metadata bridge. Does not open or take ownership of a camera. */
class CameraCapabilitiesModule(private val context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
override fun getName() = "CameraCapabilities"

@ReactMethod
fun getRearCameraZoomRanges(promise: Promise) {
if (context.checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
promise.reject("CAMERA_PERMISSION", "Grant camera permission before querying cameras")
return
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
promise.reject("ZOOM_RANGE_UNAVAILABLE", "Camera2 zoom ratio range requires Android 11 or later")
return
}
try {
val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
val cameras = Arguments.createArray()
for (cameraId in manager.cameraIdList) {
val characteristics = manager.getCameraCharacteristics(cameraId)
if (characteristics.get(CameraCharacteristics.LENS_FACING) != CameraCharacteristics.LENS_FACING_BACK) continue
val range = characteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE) ?: continue
val camera = Arguments.createMap()
camera.putString("cameraId", cameraId)
camera.putDouble("minZoom", range.lower.toDouble())
camera.putDouble("maxZoom", range.upper.toDouble())
cameras.pushMap(camera)
}
promise.resolve(cameras)
} catch (error: Exception) {
promise.reject("CAMERA_QUERY_FAILED", "Could not read Camera2 zoom ranges", error)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Android ultra-wide / Pixel zoom Demo

Build the Expo Android app from this branch with a valid Agora App ID in
`examples/expo/src/config/agora.config.ts`. Open **Advanced → CameraCapturerConfiguration**.
Grant camera and microphone permission. A channel/token is not needed for local preview.

Tap **Query capability and start ultra-wide preview**. The Demo always tests the rear camera:

1. Call `queryCameraFocalLengthCapability`. If the returned entries advertise rear
`CameraFocalLengthUltraWide`, configure it using `cameraDirection`, then start preview.
The status displays `CameraFocalLengthApplied (5)` when confirmed by the SDK.
`CameraFocalLengthFallbackToDefault (6)` is displayed as a failure, not success.
If neither event arrives, the status remains waiting; do not treat it as passed.
2. If the SDK does not advertise rear ultra-wide, the Demo-only Android bridge reads
Camera2 `CONTROL_ZOOM_RATIO_RANGE` for the exposed rear logical cameras (Android 11+).
A camera must actually report a minimum below 1x. Configure that exact `cameraId`
without also specifying `cameraDirection`, start preview, then call
`setCameraZoomFactor` once capture is ready. Use the native minimum, bounded by
the SDK's 0.5x minimum. This path does **not** wait for `onLocalVideoEvent`.
Use **Compare: 1x** and **Apply native minimum** to check the wider field of view.

The status and event log show the selected path, camera ID, native ranges and SDK
return code. Return 0 means the request was accepted; verify the actual picture.
Devices without a reported sub-1x range, Android versions below 11, missing
permission and API errors display an unsupported/error result instead of guessing 0.5x.

Tap **Stop ultra-wide demo** before another attempt or before using the existing
manual camera configuration and channel join controls. Stop/release invalidates any
outstanding native query so leaving the screen cannot restart capture.

## Required device verification

- A non-Pixel Android device advertising rear ultra-wide: expect event 5, no event 6,
and a visibly wider preview. Record device/OS, capability and callback logs.
- Pixel: if SDK reports no rear ultra-wide but Camera2 reports a sub-1x range,
verify the native-query path, `setCameraZoomFactor` return code and 1x/sub-1x
pictures. No focal-length callback is required.
- A device without sub-1x support: expect an explicit unsupported result, no
unsupported zoom request. Also test permission denial and Stop during query.
- Run both React Native architectures. The bridge is part of the Expo Demo only;
it does not change the public RN SDK API or require an Iris change.
Loading
Loading