Merge pull request #370 from KryptKode/fix/fast-switch-mode-inconsistencies
fix inconsistencies when user switches camera mode fast
This commit is contained in:
commit
6e794d4cf7
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,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) }
|
||||
|
|
|
@ -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…
Reference in New Issue