Skip to content

Commit

Permalink
feat: Add onStarted and onStopped events (#2273)
Browse files Browse the repository at this point in the history
* feat: Add `onStarted` and `onStopped` events

* Implement `onStart` for Android

* Update CameraSession.kt

* Update CameraSessionDelegate.swift
  • Loading branch information
mrousavy authored Dec 9, 2023
1 parent 9ef4a9a commit 4ee52d6
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ fun CameraView.invokeOnInitialized() {
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) {
Log.e(CameraView.TAG, "invokeOnError(...):")
error.printStackTrace()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,14 @@ class CameraView(context: Context) :
invokeOnInitialized()
}

override fun onStarted() {
invokeOnStarted()
}

override fun onStopped() {
invokeOnStopped()
}

override fun onCodeScanned(codes: List<Barcode>, scannerFrame: CodeScannerFrame) {
invokeOnCodeScanned(codes, scannerFrame)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class CameraViewManager : ViewGroupManager<CameraView>() {
MapBuilder.builder<String, Any>()
.put("cameraViewReady", MapBuilder.of("registrationName", "onViewReady"))
.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("cameraCodeScanned", MapBuilder.of("registrationName", "onCodeScanned"))
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ data class CameraConfiguration(
// Outputs & Session (Photo, Video, CodeScanner, HDR, Format)
val outputsChanged: Boolean,
// 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
get() = sidePropsChanged || outputsChanged || deviceChanged
Expand All @@ -98,10 +100,13 @@ data class CameraConfiguration(
left.zoom != right.zoom || left.videoStabilizationMode != right.videoStabilizationMode || left.isActive != right.isActive ||
left.exposure != right.exposure

val isActiveChanged = left?.isActive != right.isActive

return Difference(
deviceChanged,
outputsChanged,
sidePropsChanged
sidePropsChanged,
isActiveChanged
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,16 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
if (diff.deviceChanged) {
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) {
Log.e(TAG, "Failed to configure CameraSession! Error: ${error.message}, Config-Diff: $diff", error)
callback.onError(error)
Expand Down Expand Up @@ -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?
// If the Camera is not active, we don't do anything.
captureSession?.stopRepeating()
isRunning = false
return
}

Expand Down Expand Up @@ -621,6 +632,8 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
interface CameraSessionCallback {
fun onError(error: Throwable)
fun onInitialized()
fun onStarted()
fun onStopped()
fun onCodeScanned(codes: List<Barcode>, scannerFrame: CodeScannerFrame)
}
}
14 changes: 7 additions & 7 deletions package/example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ PODS:
- libwebp/sharpyuv (1.3.2)
- libwebp/webp (1.3.2):
- libwebp/sharpyuv
- MMKV (1.3.1):
- MMKVCore (~> 1.3.1)
- MMKVCore (1.3.1)
- MMKV (1.3.2):
- MMKVCore (~> 1.3.2)
- MMKVCore (1.3.2)
- RCT-Folly (2021.07.22.00):
- boost
- DoubleConversion
Expand Down Expand Up @@ -507,7 +507,7 @@ PODS:
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.10)
- SocketRocket (0.6.1)
- VisionCamera (3.6.11):
- VisionCamera (3.6.14):
- React
- React-callinvoker
- React-Core
Expand Down Expand Up @@ -698,8 +698,8 @@ SPEC CHECKSUMS:
hermes-engine: 10fbd3f62405c41ea07e71973ea61e1878d07322
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
MMKV: 5a07930c70c70b86cd87761a42c8f3836fb681d7
MMKVCore: e50135dbd33235b6ab390635991bab437ab873c0
MMKV: f21593c0af4b3f2a0ceb8f820f28bb639ea22bb7
MMKVCore: 31b4cb83f8266467eef20a35b6d78e409a11060d
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCTRequired: a2faf4bad4e438ca37b2040cb8f7799baa065c18
RCTTypeSafety: cb09f3e4747b6d18331a15eb05271de7441ca0b3
Expand Down Expand Up @@ -747,7 +747,7 @@ SPEC CHECKSUMS:
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
VisionCamera: b35fc51a521ce0a9b9da41d8b13127e3d414d195
VisionCamera: 3cf177fa91fa9fe04622071415032c5af618a5ac
Yoga: 8796b55dba14d7004f980b54bcc9833ee45b28ce

PODFILE CHECKSUM: 27f53791141a3303d814e09b55770336416ff4eb
Expand Down
20 changes: 19 additions & 1 deletion package/ios/CameraView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public final class CameraView: UIView, CameraSessionDelegate {
// events
@objc var onInitialized: RCTDirectEventBlock?
@objc var onError: RCTDirectEventBlock?
@objc var onStarted: RCTDirectEventBlock?
@objc var onStopped: RCTDirectEventBlock?
@objc var onViewReady: RCTDirectEventBlock?
@objc var onCodeScanned: RCTDirectEventBlock?
// zoom
Expand Down Expand Up @@ -283,7 +285,23 @@ public final class CameraView: UIView, CameraSessionDelegate {
guard let onInitialized = onInitialized else {
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) {
Expand Down
2 changes: 2 additions & 0 deletions package/ios/CameraViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ @interface RCT_EXTERN_REMAP_MODULE (CameraView, CameraViewManager, RCTViewManage
// Camera View Events
RCT_EXPORT_VIEW_PROPERTY(onError, 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);
// Code Scanner
RCT_EXPORT_VIEW_PROPERTY(codeScannerOptions, NSDictionary);
Expand Down
2 changes: 2 additions & 0 deletions package/ios/Core/CameraSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,10 @@ class CameraSession: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, AVC
// Start/Stop session
if configuration.isActive {
captureSession.startRunning()
delegate?.onCameraStarted()
} else {
captureSession.stopRunning()
delegate?.onCameraStopped()
}
}

Expand Down
8 changes: 8 additions & 0 deletions package/ios/Core/CameraSessionDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ protocol CameraSessionDelegate: AnyObject {
Called when the [CameraSession] successfully initializes
*/
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)
*/
Expand Down
14 changes: 14 additions & 0 deletions package/src/Camera.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type NativeCameraViewProps = Omit<CameraProps, 'device' | 'onInitialized' | 'onE
onInitialized?: (event: NativeSyntheticEvent<void>) => void
onError?: (event: NativeSyntheticEvent<OnErrorEvent>) => void
onCodeScanned?: (event: NativeSyntheticEvent<OnCodeScannedEvent>) => void
onStarted?: (event: NativeSyntheticEvent<void>) => void
onStopped?: (event: NativeSyntheticEvent<void>) => void
onViewReady: () => void
}
type NativeRecordVideoOptions = Omit<RecordVideoOptions, 'onRecordingError' | 'onRecordingFinished' | 'videoBitRate'> & {
Expand Down Expand Up @@ -89,6 +91,8 @@ export class Camera extends React.PureComponent<CameraProps, CameraState> {
super(props)
this.onViewReady = this.onViewReady.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.onCodeScanned = this.onCodeScanned.bind(this)
this.ref = React.createRef<RefType>()
Expand Down Expand Up @@ -416,6 +420,14 @@ export class Camera extends React.PureComponent<CameraProps, CameraState> {
private onInitialized(): void {
this.props.onInitialized?.()
}

private onStarted(): void {
this.props.onStarted?.()
}

private onStopped(): void {
this.props.onStopped?.()
}
//#endregion

private onCodeScanned(event: NativeSyntheticEvent<OnCodeScannedEvent>): void {
Expand Down Expand Up @@ -481,6 +493,8 @@ export class Camera extends React.PureComponent<CameraProps, CameraState> {
onViewReady={this.onViewReady}
onInitialized={this.onInitialized}
onCodeScanned={this.onCodeScanned}
onStarted={this.onStarted}
onStopped={this.onStopped}
onError={this.onError}
codeScannerOptions={codeScanner}
enableFrameProcessor={frameProcessor != null}
Expand Down
10 changes: 9 additions & 1 deletion package/src/CameraProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,17 @@ export interface CameraProps extends ViewProps {
*/
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
/**
* 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".
*
Expand Down

1 comment on commit 4ee52d6

@vercel
Copy link

@vercel vercel bot commented on 4ee52d6 Dec 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.