Add onVideoChunkReady callback #2

Merged
colonelpanic merged 1 commits from ivan/addOnVideoChunkReadyCallback into main 2024-02-01 19:43:07 -07:00
7 changed files with 440 additions and 410 deletions

View File

@ -11,6 +11,7 @@ import com.mrousavy.camera.core.CodeScannerFrame
import com.mrousavy.camera.core.UnknownCameraError import com.mrousavy.camera.core.UnknownCameraError
import com.mrousavy.camera.core.code import com.mrousavy.camera.core.code
import com.mrousavy.camera.types.CodeType import com.mrousavy.camera.types.CodeType
import java.io.File
fun CameraView.invokeOnInitialized() { fun CameraView.invokeOnInitialized() {
Log.i(CameraView.TAG, "invokeOnInitialized()") Log.i(CameraView.TAG, "invokeOnInitialized()")
@ -33,6 +34,15 @@ fun CameraView.invokeOnStopped() {
reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "cameraStopped", null) reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "cameraStopped", null)
} }
fun CameraView.invokeOnChunkReady(filepath: File, index: Int) {
Log.e(CameraView.TAG, "invokeOnError(...):")
val event = Arguments.createMap()
event.putInt("index", index)
event.putString("filepath", filepath.toString())
val reactContext = context as ReactContext
reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "onVideoChunkReady", event)
}
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

@ -25,6 +25,7 @@ import com.mrousavy.camera.types.Torch
import com.mrousavy.camera.types.VideoStabilizationMode import com.mrousavy.camera.types.VideoStabilizationMode
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File
// //
// TODOs for the CameraView which are currently too hard to implement either because of CameraX' limitations, or my brain capacity. // TODOs for the CameraView which are currently too hard to implement either because of CameraX' limitations, or my brain capacity.
@ -265,6 +266,10 @@ class CameraView(context: Context) :
invokeOnStopped() invokeOnStopped()
} }
override fun onVideoChunkReady(filepath: File, index: Int) {
invokeOnChunkReady(filepath, index)
}
override fun onCodeScanned(codes: List<Barcode>, scannerFrame: CodeScannerFrame) { override fun onCodeScanned(codes: List<Barcode>, scannerFrame: CodeScannerFrame) {
invokeOnCodeScanned(codes, scannerFrame) invokeOnCodeScanned(codes, scannerFrame)
} }

View File

@ -29,6 +29,7 @@ class CameraViewManager : ViewGroupManager<CameraView>() {
.put("cameraStopped", MapBuilder.of("registrationName", "onStopped")) .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"))
.put("onVideoChunkReady", MapBuilder.of("registrationName", "onVideoChunkReady"))
.build() .build()
override fun getName(): String = TAG override fun getName(): String = TAG

View File

@ -54,6 +54,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import java.io.File
class CameraSession(private val context: Context, private val cameraManager: CameraManager, private val callback: Callback) : class CameraSession(private val context: Context, private val cameraManager: CameraManager, private val callback: Callback) :
CameraManager.AvailabilityCallback(), CameraManager.AvailabilityCallback(),
@ -640,7 +641,8 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
orientation, orientation,
options, options,
callback, callback,
onError onError,
this.callback,
) )
recording.start() recording.start()
this.recording = recording this.recording = recording
@ -724,6 +726,7 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
fun onInitialized() fun onInitialized()
fun onStarted() fun onStarted()
fun onStopped() fun onStopped()
fun onVideoChunkReady(filepath: File, index: Int)
fun onCodeScanned(codes: List<Barcode>, scannerFrame: CodeScannerFrame) fun onCodeScanned(codes: List<Barcode>, scannerFrame: CodeScannerFrame)
} }
} }

View File

@ -13,12 +13,13 @@ import com.mrousavy.camera.types.RecordVideoOptions
import java.io.File import java.io.File
import java.nio.ByteBuffer import java.nio.ByteBuffer
class ChunkedRecordingManager(private val encoder: MediaCodec, private val outputDirectory: File, private val orientationHint: Int, private val iFrameInterval: Int) : class ChunkedRecordingManager(private val encoder: MediaCodec, private val outputDirectory: File, private val orientationHint: Int, private val iFrameInterval: Int, private val callbacks: CameraSession.Callback) :
MediaCodec.Callback() { MediaCodec.Callback() {
companion object { companion object {
private const val TAG = "ChunkedRecorder" private const val TAG = "ChunkedRecorder"
fun fromParams( fun fromParams(
callbacks: CameraSession.Callback,
size: Size, size: Size,
enableAudio: Boolean, enableAudio: Boolean,
fps: Int? = null, fps: Int? = null,
@ -57,7 +58,7 @@ class ChunkedRecordingManager(private val encoder: MediaCodec, private val outpu
// Create a MediaCodec encoder, and configure it with our format. Get a Surface // Create a MediaCodec encoder, and configure it with our format. Get a Surface
// we can use for input and wrap it with a class that handles the EGL work. // we can use for input and wrap it with a class that handles the EGL work.
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
return ChunkedRecordingManager(codec, outputDirectory, 0, iFrameInterval) return ChunkedRecordingManager(codec, outputDirectory, 0, iFrameInterval, callbacks)
} }
} }
@ -79,7 +80,7 @@ class ChunkedRecordingManager(private val encoder: MediaCodec, private val outpu
} }
// Muxer specific // Muxer specific
private class MuxerContext(val muxer: MediaMuxer, startTimeUs: Long, encodedFormat: MediaFormat) { private class MuxerContext(val muxer: MediaMuxer, val filepath: File, val chunkIndex: Int, startTimeUs: Long, encodedFormat: MediaFormat) {
val videoTrack: Int = muxer.addTrack(encodedFormat) val videoTrack: Int = muxer.addTrack(encodedFormat)
val startTimeUs: Long = startTimeUs val startTimeUs: Long = startTimeUs
@ -97,7 +98,10 @@ class ChunkedRecordingManager(private val encoder: MediaCodec, private val outpu
private var muxerContext: MuxerContext? = null private var muxerContext: MuxerContext? = null
private fun createNextMuxer(bufferInfo: BufferInfo) { private fun createNextMuxer(bufferInfo: BufferInfo) {
muxerContext?.finish() muxerContext?.let {
it.finish()
this.callbacks.onVideoChunkReady(it.filepath, it.chunkIndex)
}
chunkIndex++ chunkIndex++
val newFileName = "$chunkIndex.mp4" val newFileName = "$chunkIndex.mp4"
@ -109,7 +113,7 @@ class ChunkedRecordingManager(private val encoder: MediaCodec, private val outpu
) )
muxer.setOrientationHint(orientationHint) muxer.setOrientationHint(orientationHint)
muxerContext = MuxerContext( muxerContext = MuxerContext(
muxer, bufferInfo.presentationTimeUs, this.encodedFormat!! muxer, newOutputFile, chunkIndex, bufferInfo.presentationTimeUs, this.encodedFormat!!
) )
} }

View File

@ -24,7 +24,8 @@ class RecordingSession(
private val cameraOrientation: Orientation, private val cameraOrientation: Orientation,
private val options: RecordVideoOptions, private val options: RecordVideoOptions,
private val callback: (video: Video) -> Unit, private val callback: (video: Video) -> Unit,
private val onError: (error: CameraError) -> Unit private val onError: (error: CameraError) -> Unit,
private val allCallbacks: CameraSession.Callback,
) { ) {
companion object { companion object {
private const val TAG = "RecordingSession" private const val TAG = "RecordingSession"
@ -45,6 +46,7 @@ class RecordingSession(
private val bitRate = getBitRate() private val bitRate = getBitRate()
private val recorder = ChunkedRecordingManager.fromParams( private val recorder = ChunkedRecordingManager.fromParams(
allCallbacks,
size, size,
enableAudio, enableAudio,
fps, fps,

View File

@ -26,6 +26,10 @@ interface OnErrorEvent {
message: string message: string
cause?: ErrorWithCause cause?: ErrorWithCause
} }
interface OnVideoChunkReadyEvent {
filepath: string
index: int
}
type NativeCameraViewProps = Omit<CameraProps, 'device' | 'onInitialized' | 'onError' | 'frameProcessor' | 'codeScanner'> & { type NativeCameraViewProps = Omit<CameraProps, 'device' | 'onInitialized' | 'onError' | 'frameProcessor' | 'codeScanner'> & {
cameraId: string cameraId: string
enableFrameProcessor: boolean enableFrameProcessor: boolean
@ -35,6 +39,7 @@ type NativeCameraViewProps = Omit<CameraProps, 'device' | 'onInitialized' | 'onE
onCodeScanned?: (event: NativeSyntheticEvent<OnCodeScannedEvent>) => void onCodeScanned?: (event: NativeSyntheticEvent<OnCodeScannedEvent>) => void
onStarted?: (event: NativeSyntheticEvent<void>) => void onStarted?: (event: NativeSyntheticEvent<void>) => void
onStopped?: (event: NativeSyntheticEvent<void>) => void onStopped?: (event: NativeSyntheticEvent<void>) => void
onVideoChunkReady?: (event: NativeSyntheticEvent<OnVideoChunkReadyEvent>) => void
onViewReady: () => void onViewReady: () => void
} }
type NativeRecordVideoOptions = Omit<RecordVideoOptions, 'onRecordingError' | 'onRecordingFinished' | 'videoBitRate'> & { type NativeRecordVideoOptions = Omit<RecordVideoOptions, 'onRecordingError' | 'onRecordingFinished' | 'videoBitRate'> & {