Merge pull request #370 from KryptKode/fix/fast-switch-mode-inconsistencies

fix inconsistencies when user switches camera mode fast
This commit is contained in:
Tibor Kaputa 2022-11-23 17:45:26 +01:00 committed by GitHub
commit 6e794d4cf7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 174 additions and 233 deletions

View File

@ -69,7 +69,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
implementation 'androidx.window:window:1.1.0-alpha03'
def camerax_version = '1.2.0-rc01'
def camerax_version = '1.2.0-beta01'
implementation "androidx.camera:camera-core:$camerax_version"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-video:$camerax_version"

View File

@ -6,11 +6,8 @@ import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.hardware.SensorManager
import android.hardware.camera2.CameraCharacteristics
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.MediaStore
import android.view.*
import android.widget.LinearLayout
@ -40,11 +37,11 @@ import com.simplemobiletools.camera.views.FocusCircleView
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.Release
import java.util.concurrent.TimeUnit
import kotlin.math.abs
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.layout_flash.*
import kotlinx.android.synthetic.main.layout_top.*
import java.util.concurrent.TimeUnit
import kotlin.math.abs
class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, CameraXPreviewListener {
private companion object {
@ -54,7 +51,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
private const val MIN_SWIPE_DISTANCE_X = 100
}
lateinit var mTimerHandler: Handler
private lateinit var defaultScene: Scene
private lateinit var flashModeScene: Scene
private lateinit var mOrientationEventListener: OrientationEventListener
@ -62,15 +58,26 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
private var mPreview: MyPreview? = null
private var mediaSizeToggleGroup: MaterialButtonToggleGroup? = null
private var mPreviewUri: Uri? = null
private var mIsInPhotoMode = true
private var mIsCameraAvailable = false
private var mIsHardwareShutterHandled = false
private var mCurrVideoRecTimer = 0
var mLastHandledOrientation = 0
private var mLastHandledOrientation = 0
private val tabSelectedListener = object : TabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
handleTogglePhotoVideo()
handlePermission(PERMISSION_RECORD_AUDIO) {
if (it) {
when (tab.position) {
VIDEO_MODE_INDEX -> mPreview?.initVideoMode()
PHOTO_MODE_INDEX -> mPreview?.initPhotoMode()
else -> throw IllegalStateException("Unsupported tab position ${tab.position}")
}
} else {
toast(R.string.no_audio_permissions)
selectPhotoTab()
if (isVideoCaptureIntent()) {
finish()
}
}
}
}
}
@ -79,7 +86,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
super.onCreate(savedInstanceState)
appLaunched(BuildConfig.APPLICATION_ID)
requestWindowFeature(Window.FEATURE_NO_TITLE)
initVariables()
tryInitCamera()
supportActionBar?.hide()
@ -107,6 +113,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
if (!triggerListener) {
removeTabListener()
}
camera_mode_tab.getTabAt(PHOTO_MODE_INDEX)?.select()
setTabListener()
}
@ -130,17 +137,13 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
override fun onResume() {
super.onResume()
if (hasStorageAndCameraPermissions()) {
resumeCameraItems()
setupPreviewImage(mIsInPhotoMode)
val isInPhotoMode = isInPhotoMode()
setupPreviewImage(isInPhotoMode)
mFocusCircleView.setStrokeColor(getProperPrimaryColor())
if (isVideoCaptureIntent() && mIsInPhotoMode) {
handleTogglePhotoVideo()
checkButtons()
}
toggleBottomButtons(enabled = true)
mOrientationEventListener.enable()
}
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
ensureTransparentNavigationBar()
}
@ -156,7 +159,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
return
}
hideTimer()
mOrientationEventListener.disable()
}
@ -172,18 +174,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
}
private fun initVariables() {
mIsInPhotoMode = if (isVideoCaptureIntent()) {
false
} else if (isImageCaptureIntent()) {
true
} else {
config.initPhotoMode
}
mIsCameraAvailable = false
mIsHardwareShutterHandled = false
mCurrVideoRecTimer = 0
mLastHandledOrientation = 0
config.lastUsedCamera = CameraCharacteristics.LENS_FACING_BACK.toString()
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
@ -218,19 +209,25 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
if (grantedCameraPermission) {
handleStoragePermission { grantedStoragePermission ->
if (grantedStoragePermission) {
if (mIsInPhotoMode) {
initializeCamera()
val isInPhotoMode = isInPhotoMode()
if (isInPhotoMode) {
initializeCamera(true)
} else {
handlePermission(PERMISSION_RECORD_AUDIO) { grantedRecordAudioPermission ->
if (grantedRecordAudioPermission) {
initializeCamera()
initializeCamera(false)
} else {
toast(R.string.no_audio_permissions)
togglePhotoVideoMode()
if (isThirdPartyIntent()) {
finish()
} else {
// re-initialize in photo mode
config.initPhotoMode = true
tryInitCamera()
}
}
}
}
} else {
toast(R.string.no_storage_permissions)
finish()
@ -243,6 +240,16 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
}
}
private fun isInPhotoMode(): Boolean {
return mPreview?.isInPhotoMode() ?: if (isVideoCaptureIntent()) {
false
} else if (isImageCaptureIntent()) {
true
} else {
config.initPhotoMode
}
}
private fun handleStoragePermission(callback: (granted: Boolean) -> Unit) {
if (isTiramisuPlus()) {
handlePermission(PERMISSION_READ_MEDIA_IMAGES) { grantedReadImages ->
@ -263,24 +270,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
private fun isVideoCaptureIntent(): Boolean = intent?.action == MediaStore.ACTION_VIDEO_CAPTURE
private fun checkImageCaptureIntent() {
if (isImageCaptureIntent()) {
hideIntentButtons()
val output = intent.extras?.get(MediaStore.EXTRA_OUTPUT)
if (output != null && output is Uri) {
mPreview?.setTargetUri(output)
}
}
}
private fun checkVideoCaptureIntent() {
if (isVideoCaptureIntent()) {
mIsInPhotoMode = false
hideIntentButtons()
shutter.setImageResource(R.drawable.ic_video_rec_vector)
}
}
private fun createToggleGroup(): MaterialButtonToggleGroup {
return MaterialButtonToggleGroup(this).apply {
isSingleSelection = true
@ -288,7 +277,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
}
}
private fun initializeCamera() {
private fun initializeCamera(isInPhotoMode: Boolean) {
setContentView(R.layout.activity_main)
initButtons()
initModeSwitcher()
@ -313,8 +302,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
WindowInsetsCompat.CONSUMED
}
checkVideoCaptureIntent()
if (mIsInPhotoMode) {
if (isInPhotoMode) {
selectPhotoTab()
} else {
selectVideoTab()
@ -327,25 +315,18 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
listener = this,
outputUri = outputUri,
isThirdPartyIntent = isThirdPartyIntent,
initInPhotoMode = mIsInPhotoMode,
initInPhotoMode = isInPhotoMode,
)
checkImageCaptureIntent()
mPreview?.setIsImageCaptureIntent(isImageCaptureIntent())
val imageDrawable = if (config.lastUsedCamera == CameraCharacteristics.LENS_FACING_BACK.toString()) {
R.drawable.ic_camera_front_vector
} else {
R.drawable.ic_camera_rear_vector
}
toggle_camera.setImageResource(imageDrawable)
mFocusCircleView = FocusCircleView(applicationContext)
mFocusCircleView = FocusCircleView(this)
view_holder.addView(mFocusCircleView)
mTimerHandler = Handler(Looper.getMainLooper())
setupPreviewImage(true)
initFlashModeTransitionNames()
if (isThirdPartyIntent) {
hideIntentButtons()
}
}
private fun initFlashModeTransitionNames() {
@ -357,9 +338,9 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
}
private fun initButtons() {
toggle_camera.setOnClickListener { toggleCamera() }
toggle_camera.setOnClickListener { mPreview!!.toggleFrontBackCamera() }
last_photo_video_preview.setOnClickListener { showLastMediaPreview() }
toggle_flash.setOnClickListener { toggleFlash() }
toggle_flash.setOnClickListener { mPreview!!.handleFlashlightClick() }
shutter.setOnClickListener { shutterPressed() }
settings.setShadowIcon(R.drawable.ic_settings_vector)
@ -382,7 +363,13 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
@SuppressLint("ClickableViewAccessibility")
private fun initModeSwitcher() {
val gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
val gestureDetector = GestureDetectorCompat(this, object : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent): Boolean {
// we have to return true here so ACTION_UP
// (and onFling) can be dispatched
return true
}
override fun onFling(event1: MotionEvent, event2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
// these can be null even if the docs say they cannot, getting event1.x in itself can cause crashes
try {
@ -430,11 +417,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
mPreview?.setFlashlightState(flashMode)
}
private fun toggleCamera() {
if (checkCameraAvailable()) {
mPreview!!.toggleFrontBackCamera()
}
}
private fun showLastMediaPreview() {
if (mPreviewUri != null) {
@ -443,24 +425,8 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
}
}
private fun toggleFlash() {
if (checkCameraAvailable()) {
if (mIsInPhotoMode) {
showFlashOptions(true)
} else {
mPreview?.toggleFlashlight()
}
}
}
private fun shutterPressed() {
if (checkCameraAvailable()) {
handleShutter()
}
}
private fun handleShutter() {
if (mIsInPhotoMode) {
if (isInPhotoMode()) {
toggleBottomButtons(enabled = false)
change_resolution.isEnabled = true
mPreview?.tryTakePicture()
@ -474,71 +440,15 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
startActivity(intent)
}
private fun handleTogglePhotoVideo() {
handlePermission(PERMISSION_RECORD_AUDIO) {
if (it) {
togglePhotoVideo()
} else {
toast(R.string.no_audio_permissions)
selectPhotoTab()
if (isVideoCaptureIntent()) {
finish()
}
}
}
}
private fun togglePhotoVideo() {
if (!checkCameraAvailable()) {
return
}
if (isVideoCaptureIntent()) {
mPreview?.initVideoMode()
}
mPreview?.setFlashlightState(FLASH_OFF)
hideTimer()
togglePhotoVideoMode()
checkButtons()
toggleBottomButtons(enabled = true)
}
private fun togglePhotoVideoMode() {
mIsInPhotoMode = !mIsInPhotoMode
config.initPhotoMode = mIsInPhotoMode
}
private fun checkButtons() {
if (mIsInPhotoMode) {
initPhotoMode()
} else {
tryInitVideoMode()
}
}
private fun initPhotoMode() {
override fun onInitPhotoMode() {
shutter.setImageResource(R.drawable.ic_shutter_animated)
mPreview?.initPhotoMode()
setupPreviewImage(true)
selectPhotoTab()
}
private fun tryInitVideoMode() {
try {
mPreview?.initVideoMode()
initVideoButtons()
} catch (e: Exception) {
if (!isVideoCaptureIntent()) {
toast(R.string.video_mode_error)
}
}
}
private fun initVideoButtons() {
override fun onInitVideoMode() {
shutter.setImageResource(R.drawable.ic_video_rec_animated)
setupPreviewImage(false)
mPreview?.checkFlashlight()
selectVideoTab()
}
@ -571,21 +481,8 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
}
}
private fun hideTimer() {
video_rec_curr_timer.text = 0.getFormattedDuration()
video_rec_curr_timer.beGone()
mCurrVideoRecTimer = 0
mTimerHandler.removeCallbacksAndMessages(null)
}
private fun resumeCameraItems() {
if (!mIsInPhotoMode) {
initVideoButtons()
}
}
private fun hasStorageAndCameraPermissions(): Boolean {
return if (mIsInPhotoMode) hasPhotoModePermissions() else hasVideoModePermissions()
return if (isInPhotoMode()) hasPhotoModePermissions() else hasVideoModePermissions()
}
private fun hasPhotoModePermissions(): Boolean {
@ -641,17 +538,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
private fun rotate(view: View, degrees: Int) = view.animate().rotation(degrees.toFloat()).start()
private fun checkCameraAvailable(): Boolean {
if (!mIsCameraAvailable) {
toast(R.string.camera_unavailable)
}
return mIsCameraAvailable
}
override fun setCameraAvailable(available: Boolean) {
mIsCameraAvailable = available
}
override fun setHasFrontAndBackCamera(hasFrontAndBack: Boolean) {
toggle_camera?.beVisibleIf(hasFrontAndBack)
}
@ -792,11 +678,11 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
isFrontCamera: Boolean,
onSelect: (index: Int, changed: Boolean) -> Unit
) {
top_options.removeView(mediaSizeToggleGroup)
val mediaSizeToggleGroup = createToggleGroup().apply {
mediaSizeToggleGroup = this
}
top_options.addView(mediaSizeToggleGroup)
val onItemClick = { clickedViewId: Int ->
@ -824,6 +710,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
val params = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
weight = 1f
}
return (layoutInflater.inflate(R.layout.layout_button, null) as MaterialButton).apply {
layoutParams = params
setShadowIcon(resolutionOption.imageDrawableResId)

View File

@ -35,10 +35,6 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getBoolean(FLIP_PHOTOS, true)
set(flipPhotos) = prefs.edit().putBoolean(FLIP_PHOTOS, flipPhotos).apply()
var lastUsedCamera: String
get() = prefs.getString(LAST_USED_CAMERA, "0")!!
set(cameraId) = prefs.edit().putString(LAST_USED_CAMERA, cameraId).apply()
var lastUsedCameraLens: Int
get() = prefs.getInt(LAST_USED_CAMERA_LENS, CameraSelector.LENS_FACING_BACK)
set(lens) = prefs.edit().putInt(LAST_USED_CAMERA_LENS, lens).apply()

View File

@ -23,7 +23,8 @@ class CameraXInitializer(private val activity: BaseSimpleActivity) {
mediaOutputHelper,
cameraErrorHandler,
listener,
initInPhotoMode,
isThirdPartyIntent = isThirdPartyIntent,
initInPhotoMode = initInPhotoMode,
)
}

View File

@ -4,6 +4,8 @@ import android.annotation.SuppressLint
import android.content.Context
import android.hardware.SensorManager
import android.hardware.display.DisplayManager
import android.os.Handler
import android.os.Looper
import android.util.Rational
import android.util.Size
import android.view.*
@ -19,6 +21,7 @@ import androidx.camera.view.PreviewView.ScaleType
import androidx.core.content.ContextCompat
import androidx.core.view.doOnLayout
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.window.layout.WindowMetricsCalculator
import com.bumptech.glide.load.ImageHeaderParser.UNKNOWN_ORIENTATION
@ -39,6 +42,7 @@ class CameraXPreview(
private val mediaOutputHelper: MediaOutputHelper,
private val cameraErrorHandler: CameraErrorHandler,
private val listener: CameraXPreviewListener,
private val isThirdPartyIntent: Boolean,
initInPhotoMode: Boolean,
) : MyPreview, DefaultLifecycleObserver {
@ -46,6 +50,7 @@ class CameraXPreview(
// Auto focus is 1/6 of the area.
private const val AF_SIZE = 1.0f / 6.0f
private const val AE_SIZE = AF_SIZE * 1.5f
private const val CAMERA_MODE_SWITCH_WAIT_TIME = 500L
}
private val config = activity.config
@ -80,6 +85,29 @@ class CameraXPreview(
}
}
}
private val startCameraHandler = Handler(Looper.getMainLooper())
private val photoModeRunnable = Runnable {
if (imageCapture == null) {
isPhotoCapture = true
if (!isThirdPartyIntent) { // we don't want to store the state for 3rd party intents
config.initPhotoMode = true
}
startCamera()
} else {
listener.onInitPhotoMode()
}
}
private val videoModeRunnable = Runnable {
if (videoCapture == null) {
isPhotoCapture = false
if (!isThirdPartyIntent) { // we don't want to store the state for 3rd party intents
config.initPhotoMode = false
}
startCamera()
} else {
listener.onInitVideoMode()
}
}
private var preview: Preview? = null
private var cameraProvider: ProcessCameraProvider? = null
@ -92,13 +120,11 @@ class CameraXPreview(
private var flashMode = FLASH_MODE_OFF
private var isPhotoCapture = initInPhotoMode
private var lastRotation = 0
private var lastCameraStartTime = 0L
init {
bindToLifeCycle()
mediaSoundHelper.loadSounds()
previewView.doOnLayout {
startCamera()
}
}
private fun bindToLifeCycle() {
@ -106,13 +132,12 @@ class CameraXPreview(
}
private fun startCamera(switching: Boolean = false) {
imageQualityManager.initSupportedQualities()
val cameraProviderFuture = ProcessCameraProvider.getInstance(activity)
val cameraProviderFuture = ProcessCameraProvider.getInstance(activity.applicationContext)
cameraProviderFuture.addListener({
try {
val provider = cameraProviderFuture.get()
cameraProvider = provider
imageQualityManager.initSupportedQualities()
videoQualityManager.initSupportedQualities(provider)
bindCameraUseCases()
setupCameraObservers()
@ -128,11 +153,11 @@ class CameraXPreview(
val resolution = if (isPhotoCapture) {
imageQualityManager.getUserSelectedResolution(cameraSelector).also {
displaySelectedResolution(it.toResolutionOption())
listener.displaySelectedResolution(it.toResolutionOption())
}
} else {
val selectedQuality = videoQualityManager.getUserSelectedQuality(cameraSelector).also {
displaySelectedResolution(it.toResolutionOption())
listener.displaySelectedResolution(it.toResolutionOption())
}
MySize(selectedQuality.width, selectedQuality.height)
}
@ -178,10 +203,6 @@ class CameraXPreview(
setFlashlightState(config.flashlightState)
}
private fun displaySelectedResolution(resolutionOption: ResolutionOption) {
listener.displaySelectedResolution(resolutionOption)
}
private fun getRotatedResolution(resolution: MySize, rotationDegrees: Int): Size {
return if (rotationDegrees == Surface.ROTATION_0 || rotationDegrees == Surface.ROTATION_180) {
Size(resolution.height, resolution.width)
@ -201,10 +222,12 @@ class CameraXPreview(
return if (isPhotoCapture) {
buildImageCapture(resolution, rotation).also {
imageCapture = it
videoCapture = null
}
} else {
buildVideoCapture().also {
videoCapture = it
imageCapture = null
}
}
}
@ -242,11 +265,16 @@ class CameraXPreview(
private fun setupCameraObservers() {
listener.setFlashAvailable(camera?.cameraInfo?.hasFlashUnit() ?: false)
listener.onChangeCamera(isFrontCameraInUse())
if (isPhotoCapture) {
listener.onInitPhotoMode()
} else {
listener.onInitVideoMode()
}
camera?.cameraInfo?.cameraState?.observe(activity) { cameraState ->
if (cameraState.error == null) {
when (cameraState.type) {
CameraState.Type.OPEN,
CameraState.Type.OPENING -> {
CameraState.Type.OPENING,
CameraState.Type.OPEN -> {
listener.setHasFrontAndBackCamera(hasFrontCamera() && hasBackCamera())
listener.setCameraAvailable(true)
}
@ -256,8 +284,10 @@ class CameraXPreview(
listener.setCameraAvailable(false)
}
}
cameraErrorHandler.handleCameraError(cameraState?.error)
} else {
listener.setCameraAvailable(false)
cameraErrorHandler.handleCameraError(cameraState.error)
}
}
}
@ -312,12 +342,21 @@ class CameraXPreview(
override fun onStart(owner: LifecycleOwner) {
orientationEventListener.enable()
previewView.doOnLayout {
if (owner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
startCamera()
}
}
}
override fun onStop(owner: LifecycleOwner) {
orientationEventListener.disable()
}
override fun isInPhotoMode(): Boolean {
return isPhotoCapture
}
override fun showChangeResolution() {
val selectedResolution = if (isPhotoCapture) {
imageQualityManager.getUserSelectedResolution(cameraSelector).toResolutionOption()
@ -377,7 +416,15 @@ class CameraXPreview(
startCamera(switching = true)
}
override fun toggleFlashlight() {
override fun handleFlashlightClick() {
if (isPhotoCapture) {
listener.showFlashOptions(true)
} else {
toggleFlashlight()
}
}
private fun toggleFlashlight() {
val newFlashMode = if (isPhotoCapture) {
when (flashMode) {
FLASH_MODE_OFF -> FLASH_MODE_ON
@ -396,17 +443,22 @@ class CameraXPreview(
}
override fun setFlashlightState(state: Int) {
var flashState = state
if (isPhotoCapture) {
camera?.cameraControl?.enableTorch(state == FLASH_ALWAYS_ON)
camera?.cameraControl?.enableTorch(flashState == FLASH_ALWAYS_ON)
} else {
camera?.cameraControl?.enableTorch(state == FLASH_ON || state == FLASH_ALWAYS_ON)
camera?.cameraControl?.enableTorch(flashState == FLASH_ON || flashState == FLASH_ALWAYS_ON)
// reset to the FLASH_ON for video capture
if (flashState == FLASH_ALWAYS_ON) {
flashState = FLASH_ON
}
val newFlashMode = state.toCameraXFlashMode()
}
val newFlashMode = flashState.toCameraXFlashMode()
flashMode = newFlashMode
imageCapture?.flashMode = newFlashMode
config.flashlightState = state
listener.onChangeFlashMode(state)
config.flashlightState = flashState
listener.onChangeFlashMode(flashState)
}
override fun tryTakePicture() {
@ -467,13 +519,23 @@ class CameraXPreview(
}
override fun initPhotoMode() {
isPhotoCapture = true
startCamera()
debounceChangeCameraMode(photoModeRunnable)
}
override fun initVideoMode() {
isPhotoCapture = false
startCamera()
debounceChangeCameraMode(videoModeRunnable)
}
private fun debounceChangeCameraMode(cameraModeRunnable: Runnable) {
val currentTime = System.currentTimeMillis()
if (currentTime - lastCameraStartTime > CAMERA_MODE_SWITCH_WAIT_TIME) {
cameraModeRunnable.run()
} else {
startCameraHandler.removeCallbacks(photoModeRunnable)
startCameraHandler.removeCallbacks(videoModeRunnable)
startCameraHandler.postDelayed(cameraModeRunnable, CAMERA_MODE_SWITCH_WAIT_TIME)
}
lastCameraStartTime = currentTime
}
override fun toggleRecording() {
@ -489,8 +551,7 @@ class CameraXPreview(
private fun startRecording() {
val videoCapture = videoCapture ?: throw IllegalStateException("Camera initialization failed.")
val mediaOutput = mediaOutputHelper.getVideoMediaOutput()
val recording = when (mediaOutput) {
val recording = when (val mediaOutput = mediaOutputHelper.getVideoMediaOutput()) {
is MediaOutput.FileDescriptorMediaOutput -> {
FileDescriptorOutputOptions.Builder(mediaOutput.fileDescriptor).build()
.let { videoCapture.output.prepareRecording(activity, it) }

View File

@ -5,7 +5,9 @@ import android.net.Uri
import com.simplemobiletools.camera.models.ResolutionOption
interface CameraXPreviewListener {
fun setCameraAvailable(available: Boolean)
fun onInitPhotoMode()
fun onInitVideoMode()
fun setCameraAvailable(available: Boolean) {}
fun setHasFrontAndBackCamera(hasFrontAndBack: Boolean)
fun setFlashAvailable(available: Boolean)
fun onChangeCamera(frontCamera: Boolean)

View File

@ -1,18 +1,14 @@
package com.simplemobiletools.camera.interfaces
import android.net.Uri
interface MyPreview {
fun setTargetUri(uri: Uri) = Unit
fun isInPhotoMode(): Boolean
fun setIsImageCaptureIntent(isImageCaptureIntent: Boolean) = Unit
fun setFlashlightState(state: Int) = Unit
fun setFlashlightState(state: Int)
fun toggleFrontBackCamera()
fun toggleFlashlight() = Unit
fun handleFlashlightClick()
fun tryTakePicture()
@ -22,7 +18,5 @@ interface MyPreview {
fun initVideoMode()
fun checkFlashlight() = Unit
fun showChangeResolution() = Unit
fun showChangeResolution()
}