feat: Add onStarted and onStopped events (#2273)

* feat: Add `onStarted` and `onStopped` events

* Implement `onStart` for Android

* Update CameraSession.kt

* Update CameraSessionDelegate.swift
This commit is contained in:
Marc Rousavy 2023-12-09 21:09:55 +03:00 committed by GitHub
parent 9ef4a9a210
commit 4ee52d6211
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 105 additions and 11 deletions

View File

@ -19,6 +19,20 @@ fun CameraView.invokeOnInitialized() {
reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "cameraInitialized", null) reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "cameraInitialized", null)
} }
fun CameraView.invokeOnStarted() {
Log.i(CameraView.TAG, "invokeOnStarted()")
val reactContext = context as ReactContext
reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "cameraStarted", null)
}
fun CameraView.invokeOnStopped() {
Log.i(CameraView.TAG, "invokeOnStopped()")
val reactContext = context as ReactContext
reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "cameraStopped", null)
}
fun CameraView.invokeOnError(error: Throwable) { fun CameraView.invokeOnError(error: Throwable) {
Log.e(CameraView.TAG, "invokeOnError(...):") Log.e(CameraView.TAG, "invokeOnError(...):")
error.printStackTrace() error.printStackTrace()

View File

@ -239,6 +239,14 @@ class CameraView(context: Context) :
invokeOnInitialized() invokeOnInitialized()
} }
override fun onStarted() {
invokeOnStarted()
}
override fun onStopped() {
invokeOnStopped()
}
override fun onCodeScanned(codes: List<Barcode>, scannerFrame: CodeScannerFrame) { override fun onCodeScanned(codes: List<Barcode>, scannerFrame: CodeScannerFrame) {
invokeOnCodeScanned(codes, scannerFrame) invokeOnCodeScanned(codes, scannerFrame)
} }

View File

@ -25,6 +25,8 @@ class CameraViewManager : ViewGroupManager<CameraView>() {
MapBuilder.builder<String, Any>() MapBuilder.builder<String, Any>()
.put("cameraViewReady", MapBuilder.of("registrationName", "onViewReady")) .put("cameraViewReady", MapBuilder.of("registrationName", "onViewReady"))
.put("cameraInitialized", MapBuilder.of("registrationName", "onInitialized")) .put("cameraInitialized", MapBuilder.of("registrationName", "onInitialized"))
.put("cameraStarted", MapBuilder.of("registrationName", "onStarted"))
.put("cameraStopped", MapBuilder.of("registrationName", "onStopped"))
.put("cameraError", MapBuilder.of("registrationName", "onError")) .put("cameraError", MapBuilder.of("registrationName", "onError"))
.put("cameraCodeScanned", MapBuilder.of("registrationName", "onCodeScanned")) .put("cameraCodeScanned", MapBuilder.of("registrationName", "onCodeScanned"))
.build() .build()

View File

@ -76,7 +76,9 @@ data class CameraConfiguration(
// Outputs & Session (Photo, Video, CodeScanner, HDR, Format) // Outputs & Session (Photo, Video, CodeScanner, HDR, Format)
val outputsChanged: Boolean, val outputsChanged: Boolean,
// Side-Props for CaptureRequest (fps, low-light-boost, torch, zoom, videoStabilization) // Side-Props for CaptureRequest (fps, low-light-boost, torch, zoom, videoStabilization)
val sidePropsChanged: Boolean val sidePropsChanged: Boolean,
// (isActive) changed
val isActiveChanged: Boolean
) { ) {
val hasAnyDifference: Boolean val hasAnyDifference: Boolean
get() = sidePropsChanged || outputsChanged || deviceChanged get() = sidePropsChanged || outputsChanged || deviceChanged
@ -98,10 +100,13 @@ data class CameraConfiguration(
left.zoom != right.zoom || left.videoStabilizationMode != right.videoStabilizationMode || left.isActive != right.isActive || left.zoom != right.zoom || left.videoStabilizationMode != right.videoStabilizationMode || left.isActive != right.isActive ||
left.exposure != right.exposure left.exposure != right.exposure
val isActiveChanged = left?.isActive != right.isActive
return Difference( return Difference(
deviceChanged, deviceChanged,
outputsChanged, outputsChanged,
sidePropsChanged sidePropsChanged,
isActiveChanged
) )
} }
} }

View File

@ -146,6 +146,16 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
if (diff.deviceChanged) { if (diff.deviceChanged) {
callback.onInitialized() callback.onInitialized()
} }
// Notify about Camera start/stop
if (diff.isActiveChanged) {
// TODO: Move that into the CaptureRequest callback to get actual first-frame arrive time?
if (config.isActive) {
callback.onStarted()
} else {
callback.onStopped()
}
}
} catch (error: Throwable) { } catch (error: Throwable) {
Log.e(TAG, "Failed to configure CameraSession! Error: ${error.message}, Config-Diff: $diff", error) Log.e(TAG, "Failed to configure CameraSession! Error: ${error.message}, Config-Diff: $diff", error)
callback.onError(error) callback.onError(error)
@ -367,6 +377,7 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
// TODO: Do we want to do stopRepeating() or entirely destroy the session? // TODO: Do we want to do stopRepeating() or entirely destroy the session?
// If the Camera is not active, we don't do anything. // If the Camera is not active, we don't do anything.
captureSession?.stopRepeating() captureSession?.stopRepeating()
isRunning = false
return return
} }
@ -621,6 +632,8 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
interface CameraSessionCallback { interface CameraSessionCallback {
fun onError(error: Throwable) fun onError(error: Throwable)
fun onInitialized() fun onInitialized()
fun onStarted()
fun onStopped()
fun onCodeScanned(codes: List<Barcode>, scannerFrame: CodeScannerFrame) fun onCodeScanned(codes: List<Barcode>, scannerFrame: CodeScannerFrame)
} }
} }

View File

@ -27,9 +27,9 @@ PODS:
- libwebp/sharpyuv (1.3.2) - libwebp/sharpyuv (1.3.2)
- libwebp/webp (1.3.2): - libwebp/webp (1.3.2):
- libwebp/sharpyuv - libwebp/sharpyuv
- MMKV (1.3.1): - MMKV (1.3.2):
- MMKVCore (~> 1.3.1) - MMKVCore (~> 1.3.2)
- MMKVCore (1.3.1) - MMKVCore (1.3.2)
- RCT-Folly (2021.07.22.00): - RCT-Folly (2021.07.22.00):
- boost - boost
- DoubleConversion - DoubleConversion
@ -507,7 +507,7 @@ PODS:
- libwebp (~> 1.0) - libwebp (~> 1.0)
- SDWebImage/Core (~> 5.10) - SDWebImage/Core (~> 5.10)
- SocketRocket (0.6.1) - SocketRocket (0.6.1)
- VisionCamera (3.6.11): - VisionCamera (3.6.14):
- React - React
- React-callinvoker - React-callinvoker
- React-Core - React-Core
@ -698,8 +698,8 @@ SPEC CHECKSUMS:
hermes-engine: 10fbd3f62405c41ea07e71973ea61e1878d07322 hermes-engine: 10fbd3f62405c41ea07e71973ea61e1878d07322
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
MMKV: 5a07930c70c70b86cd87761a42c8f3836fb681d7 MMKV: f21593c0af4b3f2a0ceb8f820f28bb639ea22bb7
MMKVCore: e50135dbd33235b6ab390635991bab437ab873c0 MMKVCore: 31b4cb83f8266467eef20a35b6d78e409a11060d
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCTRequired: a2faf4bad4e438ca37b2040cb8f7799baa065c18 RCTRequired: a2faf4bad4e438ca37b2040cb8f7799baa065c18
RCTTypeSafety: cb09f3e4747b6d18331a15eb05271de7441ca0b3 RCTTypeSafety: cb09f3e4747b6d18331a15eb05271de7441ca0b3
@ -747,7 +747,7 @@ SPEC CHECKSUMS:
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
VisionCamera: b35fc51a521ce0a9b9da41d8b13127e3d414d195 VisionCamera: 3cf177fa91fa9fe04622071415032c5af618a5ac
Yoga: 8796b55dba14d7004f980b54bcc9833ee45b28ce Yoga: 8796b55dba14d7004f980b54bcc9833ee45b28ce
PODFILE CHECKSUM: 27f53791141a3303d814e09b55770336416ff4eb PODFILE CHECKSUM: 27f53791141a3303d814e09b55770336416ff4eb

View File

@ -59,6 +59,8 @@ public final class CameraView: UIView, CameraSessionDelegate {
// events // events
@objc var onInitialized: RCTDirectEventBlock? @objc var onInitialized: RCTDirectEventBlock?
@objc var onError: RCTDirectEventBlock? @objc var onError: RCTDirectEventBlock?
@objc var onStarted: RCTDirectEventBlock?
@objc var onStopped: RCTDirectEventBlock?
@objc var onViewReady: RCTDirectEventBlock? @objc var onViewReady: RCTDirectEventBlock?
@objc var onCodeScanned: RCTDirectEventBlock? @objc var onCodeScanned: RCTDirectEventBlock?
// zoom // zoom
@ -283,7 +285,23 @@ public final class CameraView: UIView, CameraSessionDelegate {
guard let onInitialized = onInitialized else { guard let onInitialized = onInitialized else {
return return
} }
onInitialized([String: Any]()) onInitialized([:])
}
func onCameraStarted() {
ReactLogger.log(level: .info, message: "Camera started!")
guard let onStarted = onStarted else {
return
}
onStarted([:])
}
func onCameraStopped() {
ReactLogger.log(level: .info, message: "Camera stopped!")
guard let onStopped = onStopped else {
return
}
onStopped([:])
} }
func onFrame(sampleBuffer: CMSampleBuffer) { func onFrame(sampleBuffer: CMSampleBuffer) {

View File

@ -52,6 +52,8 @@ RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString);
// Camera View Events // Camera View Events
RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onInitialized, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onInitialized, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onStarted, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onStopped, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onViewReady, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onViewReady, RCTDirectEventBlock);
// Code Scanner // Code Scanner
RCT_EXPORT_VIEW_PROPERTY(codeScannerOptions, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(codeScannerOptions, NSDictionary);

View File

@ -249,8 +249,10 @@ class CameraSession: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, AVC
// Start/Stop session // Start/Stop session
if configuration.isActive { if configuration.isActive {
captureSession.startRunning() captureSession.startRunning()
delegate?.onCameraStarted()
} else { } else {
captureSession.stopRunning() captureSession.stopRunning()
delegate?.onCameraStopped()
} }
} }

View File

@ -21,6 +21,14 @@ protocol CameraSessionDelegate: AnyObject {
Called when the [CameraSession] successfully initializes Called when the [CameraSession] successfully initializes
*/ */
func onSessionInitialized() func onSessionInitialized()
/**
Called when the [CameraSession] starts streaming frames. (isActive=true)
*/
func onCameraStarted()
/**
Called when the [CameraSession] stopped streaming frames. (isActive=false)
*/
func onCameraStopped()
/** /**
Called for every frame (if video or frameProcessor is enabled) Called for every frame (if video or frameProcessor is enabled)
*/ */

View File

@ -33,6 +33,8 @@ type NativeCameraViewProps = Omit<CameraProps, 'device' | 'onInitialized' | 'onE
onInitialized?: (event: NativeSyntheticEvent<void>) => void onInitialized?: (event: NativeSyntheticEvent<void>) => void
onError?: (event: NativeSyntheticEvent<OnErrorEvent>) => void onError?: (event: NativeSyntheticEvent<OnErrorEvent>) => void
onCodeScanned?: (event: NativeSyntheticEvent<OnCodeScannedEvent>) => void onCodeScanned?: (event: NativeSyntheticEvent<OnCodeScannedEvent>) => void
onStarted?: (event: NativeSyntheticEvent<void>) => void
onStopped?: (event: NativeSyntheticEvent<void>) => void
onViewReady: () => void onViewReady: () => void
} }
type NativeRecordVideoOptions = Omit<RecordVideoOptions, 'onRecordingError' | 'onRecordingFinished' | 'videoBitRate'> & { type NativeRecordVideoOptions = Omit<RecordVideoOptions, 'onRecordingError' | 'onRecordingFinished' | 'videoBitRate'> & {
@ -89,6 +91,8 @@ export class Camera extends React.PureComponent<CameraProps, CameraState> {
super(props) super(props)
this.onViewReady = this.onViewReady.bind(this) this.onViewReady = this.onViewReady.bind(this)
this.onInitialized = this.onInitialized.bind(this) this.onInitialized = this.onInitialized.bind(this)
this.onStarted = this.onStarted.bind(this)
this.onStopped = this.onStopped.bind(this)
this.onError = this.onError.bind(this) this.onError = this.onError.bind(this)
this.onCodeScanned = this.onCodeScanned.bind(this) this.onCodeScanned = this.onCodeScanned.bind(this)
this.ref = React.createRef<RefType>() this.ref = React.createRef<RefType>()
@ -416,6 +420,14 @@ export class Camera extends React.PureComponent<CameraProps, CameraState> {
private onInitialized(): void { private onInitialized(): void {
this.props.onInitialized?.() this.props.onInitialized?.()
} }
private onStarted(): void {
this.props.onStarted?.()
}
private onStopped(): void {
this.props.onStopped?.()
}
//#endregion //#endregion
private onCodeScanned(event: NativeSyntheticEvent<OnCodeScannedEvent>): void { private onCodeScanned(event: NativeSyntheticEvent<OnCodeScannedEvent>): void {
@ -481,6 +493,8 @@ export class Camera extends React.PureComponent<CameraProps, CameraState> {
onViewReady={this.onViewReady} onViewReady={this.onViewReady}
onInitialized={this.onInitialized} onInitialized={this.onInitialized}
onCodeScanned={this.onCodeScanned} onCodeScanned={this.onCodeScanned}
onStarted={this.onStarted}
onStopped={this.onStopped}
onError={this.onError} onError={this.onError}
codeScannerOptions={codeScanner} codeScannerOptions={codeScanner}
enableFrameProcessor={frameProcessor != null} enableFrameProcessor={frameProcessor != null}

View File

@ -246,9 +246,17 @@ export interface CameraProps extends ViewProps {
*/ */
onError?: (error: CameraRuntimeError) => void onError?: (error: CameraRuntimeError) => void
/** /**
* Called when the camera was successfully initialized. * Called when the camera session was successfully initialized. This will get called everytime a new device is set.
*/ */
onInitialized?: () => void onInitialized?: () => void
/**
* Called when the camera started the session (`isActive={true}`)
*/
onStarted?: () => void
/**
* Called when the camera stopped the session (`isActive={false}`)
*/
onStopped?: () => void
/** /**
* A worklet which will be called for every frame the Camera "sees". * A worklet which will be called for every frame the Camera "sees".
* *