mirror of
https://github.com/SimpleMobileTools/Simple-Camera.git
synced 2025-02-09 16:08:41 +01:00
fix inconsistencies when user switches camera mode fast
- debounce switching by 500ms when toggling between video/photo capture - ensure there is only one source of truth for the current camera mode - this is the CameraXPreview - refactor the MainActivity to depend on the CameraXPreview for the camera mode - remove unused code in CameraXPreviewListener, MyPreview and in MainActivity
This commit is contained in:
parent
c858e5b908
commit
fc2296e2ae
@ -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
|
||||
@ -52,10 +49,8 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
|
||||
const val PHOTO_MODE_INDEX = 1
|
||||
const val VIDEO_MODE_INDEX = 0
|
||||
private const val MIN_SWIPE_DISTANCE_X = 100
|
||||
private const val DELAY_BETWEEN_MODE_SWITCH = 300L
|
||||
}
|
||||
|
||||
lateinit var mTimerHandler: Handler
|
||||
private lateinit var defaultScene: Scene
|
||||
private lateinit var flashModeScene: Scene
|
||||
private lateinit var mOrientationEventListener: OrientationEventListener
|
||||
@ -63,19 +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 val togglePhotoVideoRunnable = Runnable {
|
||||
handleTogglePhotoVideo()
|
||||
}
|
||||
private var mLastHandledOrientation = 0
|
||||
|
||||
private val tabSelectedListener = object : TabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||
camera_mode_tab.removeCallbacks(togglePhotoVideoRunnable)
|
||||
camera_mode_tab.postDelayed(togglePhotoVideoRunnable, DELAY_BETWEEN_MODE_SWITCH)
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,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()
|
||||
@ -135,14 +136,9 @@ 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()
|
||||
}
|
||||
@ -160,8 +156,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
|
||||
if (!hasStorageAndCameraPermissions() || isAskingPermissions) {
|
||||
return
|
||||
}
|
||||
|
||||
hideTimer()
|
||||
mOrientationEventListener.disable()
|
||||
}
|
||||
|
||||
@ -177,18 +171,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 {
|
||||
@ -223,16 +206,22 @@ 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()
|
||||
tryInitCamera()
|
||||
if (isThirdPartyIntent()) {
|
||||
finish()
|
||||
} else {
|
||||
// re-initialize in photo mode
|
||||
config.initPhotoMode = true
|
||||
tryInitCamera()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -248,6 +237,17 @@ 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 ->
|
||||
@ -268,24 +268,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
|
||||
@ -293,7 +275,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeCamera() {
|
||||
private fun initializeCamera(isInPhotoMode: Boolean) {
|
||||
setContentView(R.layout.activity_main)
|
||||
initButtons()
|
||||
initModeSwitcher()
|
||||
@ -318,8 +300,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
|
||||
checkVideoCaptureIntent()
|
||||
if (mIsInPhotoMode) {
|
||||
if (isInPhotoMode) {
|
||||
selectPhotoTab()
|
||||
} else {
|
||||
selectVideoTab()
|
||||
@ -332,25 +313,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() {
|
||||
@ -362,9 +336,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)
|
||||
@ -437,11 +411,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
|
||||
mPreview?.setFlashlightState(flashMode)
|
||||
}
|
||||
|
||||
private fun toggleCamera() {
|
||||
if (checkCameraAvailable()) {
|
||||
mPreview!!.toggleFrontBackCamera()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLastMediaPreview() {
|
||||
if (mPreviewUri != null) {
|
||||
@ -450,24 +419,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()
|
||||
@ -481,71 +434,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()
|
||||
}
|
||||
|
||||
@ -578,21 +475,9 @@ 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 {
|
||||
@ -648,17 +533,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)
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -23,7 +23,8 @@ class CameraXInitializer(private val activity: BaseSimpleActivity) {
|
||||
mediaOutputHelper,
|
||||
cameraErrorHandler,
|
||||
listener,
|
||||
initInPhotoMode,
|
||||
isThirdPartyIntent = isThirdPartyIntent,
|
||||
initInPhotoMode = initInPhotoMode,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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,15 +265,19 @@ 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.OPEN -> {
|
||||
listener.setHasFrontAndBackCamera(hasFrontCamera() && hasBackCamera())
|
||||
listener.setCameraAvailable(true)
|
||||
}
|
||||
CameraState.Type.OPENING,
|
||||
CameraState.Type.PENDING_OPEN,
|
||||
CameraState.Type.CLOSING,
|
||||
CameraState.Type.CLOSED -> {
|
||||
@ -315,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()
|
||||
@ -380,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
|
||||
@ -399,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() {
|
||||
@ -470,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() {
|
||||
@ -492,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) }
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user