Skip to content

Commit

Permalink
Merge pull request #22 from equinoxventures/fix/android-torch-control
Browse files Browse the repository at this point in the history
Fix up torch control for android
  • Loading branch information
fahadhaque007 authored Jul 25, 2024
2 parents 4815832 + 13ec330 commit 830da77
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 4 deletions.
1 change: 1 addition & 0 deletions package/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.CAMERA" />
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ data class CameraConfiguration(
var fps: Int? = null,
var enableLowLightBoost: Boolean = false,
var torch: Torch = Torch.OFF,

var torchLevel: Double = 0.0,
var torchDelay: Double = 0.0,
var torchDuration: Double = 0.0,
var backgroundLevel: Double = 0.0,
var backgroundDelay: Double = 0.0,
var backgroundDuration: Double = 0.0,
var isTorchOn: Boolean = false,

var videoStabilizationMode: VideoStabilizationMode = VideoStabilizationMode.OFF,
var exposure: Double? = null,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import android.content.Context
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.media.AudioManager
import android.os.Build
import android.util.Log
import android.util.Range
import android.util.Size
import androidx.annotation.MainThread
import androidx.annotation.OptIn
import androidx.annotation.RequiresApi
import androidx.camera.core.Camera
import androidx.camera.core.CameraControl
import androidx.camera.core.CameraSelector
Expand Down Expand Up @@ -61,10 +63,29 @@ import com.mrousavy.camera.core.types.VideoStabilizationMode
import com.mrousavy.camera.core.utils.FileUtils
import com.mrousavy.camera.core.utils.runOnUiThread
import com.mrousavy.camera.frameprocessors.Frame
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.io.Closeable
import kotlin.math.roundToInt
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.time.Instant
import kotlin.time.Duration.Companion.milliseconds

data class RecordingTimestamps(
var actualRecordingStartedAt: Long? = null,
var actualTorchOnAt: Long? = null,
var actualTorchOffAt: Long? = null,
var actualRecordingEndedAt: Long? = null,
var requestTorchOnAt: Long? = null,
var requestTorchOffAt: Long? = null,
var actualBackgroundTorchOnAt: Long? = null,
var actualBackgroundTorchOffAt: Long? = null,
var requestBackgroundTorchOnAt: Long? = null,
var requestBackgroundTorchOffAt: Long? = null
)

class CameraSession(private val context: Context, private val callback: Callback) :
Closeable,
Expand Down Expand Up @@ -101,6 +122,7 @@ class CameraSession(private val context: Context, private val callback: Callback
// Threading
private val mainExecutor = ContextCompat.getMainExecutor(context)

private var recordingTimestamps = RecordingTimestamps()
init {
lifecycleRegistry.currentState = Lifecycle.State.CREATED
lifecycle.addObserver(object : LifecycleEventObserver {
Expand Down Expand Up @@ -526,6 +548,7 @@ class CameraSession(private val context: Context, private val callback: Callback
return enable
}

@RequiresApi(Build.VERSION_CODES.O)
@OptIn(ExperimentalPersistentRecording::class)
@SuppressLint("MissingPermission", "RestrictedApi")
fun startRecording(
Expand Down Expand Up @@ -556,7 +579,41 @@ class CameraSession(private val context: Context, private val callback: Callback
isRecordingCanceled = false
recording = pendingRecording.start(CameraQueues.cameraExecutor) { event ->
when (event) {
is VideoRecordEvent.Start -> Log.i(TAG, "Recording started!")
is VideoRecordEvent.Start -> {
Log.i(TAG, "Recording started!")

if (configuration?.backgroundDelay?.toInt()!! > 0) {
setTorchMode("off", torchLevelVal = configuration?.torchLevel)
}

val recordingStartTimestamp = Instant.now().epochSecond
recordingTimestamps.actualRecordingStartedAt = Instant.now().epochSecond

val torchDelay = configuration?.torchDelay?.toInt()?.milliseconds

val torchDelayInt = configuration?.torchDelay?.toInt()?.milliseconds
val torchDurationInt = configuration?.torchDuration?.toInt()?.milliseconds
val torchEnd = torchDurationInt?.let { torchDelayInt?.plus(it) }

if (configuration?.torchDuration?.toInt()!! > 0) {
CoroutineScope(Dispatchers.Main).launch {
if (torchDelay != null) {
delay(torchDelay)
recordingTimestamps.requestTorchOnAt = Instant.now().epochSecond
setTorchMode("on", torchLevelVal = configuration?.torchLevel)
recordingTimestamps.actualTorchOnAt = Instant.now().epochSecond
}
}
CoroutineScope(Dispatchers.Main).launch {
if (torchEnd != null) {
delay(torchEnd)
recordingTimestamps.requestTorchOffAt = Instant.now().epochSecond
setTorchMode("off", torchLevelVal = configuration?.torchLevel)
recordingTimestamps.actualTorchOffAt = Instant.now().epochSecond
}
}
}
}

is VideoRecordEvent.Resume -> Log.i(TAG, "Recording resumed!")

Expand Down Expand Up @@ -590,16 +647,49 @@ class CameraSession(private val context: Context, private val callback: Callback
val durationMs = event.recordingStats.recordedDurationNanos / 1_000_000
Log.i(TAG, "Successfully completed video recording! Captured ${durationMs.toDouble() / 1_000.0} seconds.")
val path = event.outputResults.outputUri.path ?: throw UnknownRecorderError(false, null)
val video = Video(path, durationMs, size)

val metadata = mapOf(
"actualRecordingStartedAt" to recordingTimestamps.actualRecordingStartedAt,
"actualTorchOnAt" to recordingTimestamps.actualTorchOnAt,
"actualTorchOffAt" to recordingTimestamps.actualTorchOffAt,
"actualRecordingEndedAt" to recordingTimestamps.actualRecordingEndedAt,
"requestTorchOnAt" to recordingTimestamps.requestTorchOnAt,
"requestTorchOffAt" to recordingTimestamps.requestTorchOffAt,
"requestBackgroundTorchOnAt" to recordingTimestamps.requestBackgroundTorchOnAt,
"requestBackgroundTorchOffAt" to recordingTimestamps.requestBackgroundTorchOffAt,
"actualBackgroundTorchOnAt" to recordingTimestamps.actualBackgroundTorchOnAt,
"actualBackgroundTorchOffAt" to recordingTimestamps.actualBackgroundTorchOffAt
)
val video = Video(path, durationMs, size, metadata)
callback(video)
}
}
}
}

private fun setTorchMode(torchMode: String, torchLevelVal: Double?) {
try {
when (torchMode.lowercase()) {
"on" -> {
camera?.cameraControl?.enableTorch(true)
Log.d("Torch", "Torch turned on. Level: ${torchLevelVal?.roundToInt()}")
}
"off" -> {
camera?.cameraControl?.enableTorch(false)
}
else -> {
// Handle any other modes like "auto"
println("Invalid torch mode: $torchMode")
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}

fun stopRecording() {
val recording = recording ?: throw NoRecordingInProgressError()

setTorchMode("off", 1.0)
recording.stop()
this.recording = null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package com.mrousavy.camera.core.types

import android.util.Size

data class Video(val path: String, val durationMs: Long, val size: Size)
data class Video(val path: String, val durationMs: Long, val size: Size, val metadata: Map<String, Long?>)
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ class CameraView(context: Context) :
var isActive = false
var torch: Torch = Torch.OFF
var zoom: Float = 1f // in "factor"
var torchLevel: Double = 0.0
var torchDelay: Double = 0.0
var torchDuration: Double = 0.0
var backgroundLevel: Double = 0.0
var backgroundDelay: Double = 0.0
var backgroundDuration: Double = 0.0
var exposure: Double = 1.0
var orientation: Orientation = Orientation.PORTRAIT
var androidPreviewViewType: PreviewViewType = PreviewViewType.SURFACE_VIEW
Expand Down Expand Up @@ -217,6 +223,13 @@ class CameraView(context: Context) :
config.torch = torch
config.exposure = exposure

config.torchLevel = torchLevel
config.torchDelay = torchDelay
config.torchDuration = torchDuration
config.backgroundLevel = backgroundLevel
config.backgroundDelay = backgroundDelay
config.backgroundDuration = backgroundDuration

// Zoom
config.zoom = zoom

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,36 @@ class CameraViewManager : ViewGroupManager<CameraView>() {
view.torch = Torch.OFF
}
}

@ReactProp(name = "torchLevel")
fun setTorchLevel(view: CameraView, torchLevel: Double) {
view.torchLevel = torchLevel
}

@ReactProp(name = "torchDelay")
fun setTorchDelay(view: CameraView, torchDelay: Double) {
view.torchDelay = torchDelay
}

@ReactProp(name = "torchDuration")
fun setTorchDuration(view: CameraView, torchDuration: Double) {
view.torchDuration = torchDuration
}

@ReactProp(name = "backgroundLevel")
fun setBackgroundLevel(view: CameraView, backgroundLevel: Double) {
view.backgroundLevel = backgroundLevel
}

@ReactProp(name = "backgroundDelay")
fun setBackgroundDelay(view: CameraView, backgroundDelay: Double) {
view.backgroundDelay = backgroundDelay
}

@ReactProp(name = "backgroundDuration")
fun setBackgroundDuration(view: CameraView, backgroundDuration: Double) {
view.backgroundDuration = backgroundDuration
}

@ReactProp(name = "zoom")
fun setZoom(view: CameraView, zoom: Double) {
Expand Down

0 comments on commit 830da77

Please sign in to comment.