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.code
import com.mrousavy.camera.types.CodeType
import java.io.File
fun CameraView.invokeOnInitialized() {
Log.i(CameraView.TAG, "invokeOnInitialized()")
@ -33,6 +34,15 @@ fun CameraView.invokeOnStopped() {
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) {
Log.e(CameraView.TAG, "invokeOnError(...):")
error.printStackTrace()

View File

@ -25,6 +25,7 @@ import com.mrousavy.camera.types.Torch
import com.mrousavy.camera.types.VideoStabilizationMode
import kotlinx.coroutines.CoroutineScope
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.
@ -265,6 +266,10 @@ class CameraView(context: Context) :
invokeOnStopped()
}
override fun onVideoChunkReady(filepath: File, index: Int) {
invokeOnChunkReady(filepath, index)
}
override fun onCodeScanned(codes: List<Barcode>, scannerFrame: CodeScannerFrame) {
invokeOnCodeScanned(codes, scannerFrame)
}

View File

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

View File

@ -54,6 +54,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.File
class CameraSession(private val context: Context, private val cameraManager: CameraManager, private val callback: Callback) :
CameraManager.AvailabilityCallback(),
@ -640,7 +641,8 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
orientation,
options,
callback,
onError
onError,
this.callback,
)
recording.start()
this.recording = recording
@ -724,6 +726,7 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
fun onInitialized()
fun onStarted()
fun onStopped()
fun onVideoChunkReady(filepath: File, index: Int)
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.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() {
companion object {
private const val TAG = "ChunkedRecorder"
fun fromParams(
callbacks: CameraSession.Callback,
size: Size,
enableAudio: Boolean,
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
// 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)
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
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 startTimeUs: Long = startTimeUs
@ -97,7 +98,10 @@ class ChunkedRecordingManager(private val encoder: MediaCodec, private val outpu
private var muxerContext: MuxerContext? = null
private fun createNextMuxer(bufferInfo: BufferInfo) {
muxerContext?.finish()
muxerContext?.let {
it.finish()
this.callbacks.onVideoChunkReady(it.filepath, it.chunkIndex)
}
chunkIndex++
val newFileName = "$chunkIndex.mp4"
@ -109,7 +113,7 @@ class ChunkedRecordingManager(private val encoder: MediaCodec, private val outpu
)
muxer.setOrientationHint(orientationHint)
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 options: RecordVideoOptions,
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 {
private const val TAG = "RecordingSession"
@ -45,6 +46,7 @@ class RecordingSession(
private val bitRate = getBitRate()
private val recorder = ChunkedRecordingManager.fromParams(
allCallbacks,
size,
enableAudio,
fps,

View File

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