mirror of
https://github.com/SimpleMobileTools/Simple-Camera.git
synced 2025-02-07 23:18:56 +01:00
add initial camera-x implementation
- add CameraXPreview - basic support image capture - basic support for video capture - add CameraXPreviewListener to prevent coupling to MainActivity - support switching camera, flash light modes - modify MyPreview interface to add default implementation for methods not needed by the CameraXPreview
This commit is contained in:
parent
0cdf08b45f
commit
074351b88f
@ -9,12 +9,12 @@ if (keystorePropertiesFile.exists()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
compileSdkVersion 32
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.simplemobiletools.camera"
|
applicationId "com.simplemobiletools.camera"
|
||||||
minSdkVersion 29
|
minSdkVersion 29
|
||||||
targetSdkVersion 31
|
targetSdkVersion 32
|
||||||
versionCode 77
|
versionCode 77
|
||||||
versionName "5.3.1"
|
versionName "5.3.1"
|
||||||
setProperty("archivesBaseName", "camera")
|
setProperty("archivesBaseName", "camera")
|
||||||
@ -65,4 +65,14 @@ dependencies {
|
|||||||
implementation 'com.github.SimpleMobileTools:Simple-Commons:d5e1100f27'
|
implementation 'com.github.SimpleMobileTools:Simple-Commons:d5e1100f27'
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation "androidx.exifinterface:exifinterface:1.3.3"
|
implementation "androidx.exifinterface:exifinterface:1.3.3"
|
||||||
|
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
|
||||||
|
implementation 'androidx.window:window:1.1.0-alpha02'
|
||||||
|
|
||||||
|
def camerax_version = '1.2.0-alpha02'
|
||||||
|
implementation "androidx.camera:camera-core:$camerax_version"
|
||||||
|
implementation "androidx.camera:camera-camera2:$camerax_version"
|
||||||
|
implementation "androidx.camera:camera-video:$camerax_version"
|
||||||
|
implementation "androidx.camera:camera-extensions:$camerax_version"
|
||||||
|
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
||||||
|
implementation "androidx.camera:camera-view:$camerax_version"
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,12 @@ import android.net.Uri
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.view.*
|
import android.util.Log
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.OrientationEventListener
|
||||||
|
import android.view.View
|
||||||
|
import android.view.Window
|
||||||
|
import android.view.WindowManager
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
@ -16,17 +21,39 @@ import com.bumptech.glide.request.RequestOptions
|
|||||||
import com.simplemobiletools.camera.BuildConfig
|
import com.simplemobiletools.camera.BuildConfig
|
||||||
import com.simplemobiletools.camera.R
|
import com.simplemobiletools.camera.R
|
||||||
import com.simplemobiletools.camera.extensions.config
|
import com.simplemobiletools.camera.extensions.config
|
||||||
import com.simplemobiletools.camera.helpers.*
|
import com.simplemobiletools.camera.helpers.FLASH_OFF
|
||||||
|
import com.simplemobiletools.camera.helpers.FLASH_ON
|
||||||
|
import com.simplemobiletools.camera.helpers.ORIENT_LANDSCAPE_LEFT
|
||||||
|
import com.simplemobiletools.camera.helpers.ORIENT_LANDSCAPE_RIGHT
|
||||||
|
import com.simplemobiletools.camera.helpers.ORIENT_PORTRAIT
|
||||||
|
import com.simplemobiletools.camera.helpers.PhotoProcessor
|
||||||
|
import com.simplemobiletools.camera.implementations.CameraXPreview
|
||||||
|
import com.simplemobiletools.camera.implementations.CameraXPreviewListener
|
||||||
import com.simplemobiletools.camera.implementations.MyCameraImpl
|
import com.simplemobiletools.camera.implementations.MyCameraImpl
|
||||||
import com.simplemobiletools.camera.interfaces.MyPreview
|
import com.simplemobiletools.camera.interfaces.MyPreview
|
||||||
import com.simplemobiletools.camera.views.CameraPreview
|
|
||||||
import com.simplemobiletools.camera.views.FocusCircleView
|
import com.simplemobiletools.camera.views.FocusCircleView
|
||||||
import com.simplemobiletools.commons.extensions.*
|
import com.simplemobiletools.commons.extensions.*
|
||||||
import com.simplemobiletools.commons.helpers.*
|
import com.simplemobiletools.commons.helpers.BROADCAST_REFRESH_MEDIA
|
||||||
|
import com.simplemobiletools.commons.helpers.PERMISSION_CAMERA
|
||||||
|
import com.simplemobiletools.commons.helpers.PERMISSION_RECORD_AUDIO
|
||||||
|
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
|
||||||
|
import com.simplemobiletools.commons.helpers.REFRESH_PATH
|
||||||
import com.simplemobiletools.commons.models.Release
|
import com.simplemobiletools.commons.models.Release
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.btn_holder
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.capture_black_screen
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.change_resolution
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.last_photo_video_preview
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.settings
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.shutter
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.toggle_camera
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.toggle_flash
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.toggle_photo_video
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.video_rec_curr_timer
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.view_finder
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.view_holder
|
||||||
|
|
||||||
class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, CameraXPreviewListener {
|
||||||
|
private val TAG = "MainActivity"
|
||||||
private val FADE_DELAY = 5000L
|
private val FADE_DELAY = 5000L
|
||||||
private val CAPTURE_ANIMATION_DURATION = 100L
|
private val CAPTURE_ANIMATION_DURATION = 100L
|
||||||
|
|
||||||
@ -68,7 +95,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
if (hasStorageAndCameraPermissions()) {
|
if (hasStorageAndCameraPermissions()) {
|
||||||
mPreview?.onResumed()
|
|
||||||
resumeCameraItems()
|
resumeCameraItems()
|
||||||
setupPreviewImage(mIsInPhotoMode)
|
setupPreviewImage(mIsInPhotoMode)
|
||||||
scheduleFadeOut()
|
scheduleFadeOut()
|
||||||
@ -97,14 +123,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
|
|
||||||
hideTimer()
|
hideTimer()
|
||||||
mOrientationEventListener.disable()
|
mOrientationEventListener.disable()
|
||||||
|
|
||||||
if (mPreview?.getCameraState() == STATE_PICTURE_TAKEN) {
|
|
||||||
toast(R.string.photo_not_saved)
|
|
||||||
}
|
|
||||||
|
|
||||||
ensureBackgroundThread {
|
|
||||||
mPreview?.onPaused()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
@ -201,8 +219,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
)
|
)
|
||||||
|
|
||||||
checkVideoCaptureIntent()
|
checkVideoCaptureIntent()
|
||||||
mPreview = CameraPreview(this, camera_texture_view, mIsInPhotoMode)
|
mPreview = CameraXPreview(this, view_finder, this)
|
||||||
view_holder.addView(mPreview as ViewGroup)
|
|
||||||
checkImageCaptureIntent()
|
checkImageCaptureIntent()
|
||||||
mPreview?.setIsImageCaptureIntent(isImageCaptureIntent())
|
mPreview?.setIsImageCaptureIntent(isImageCaptureIntent())
|
||||||
|
|
||||||
@ -217,7 +234,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
mFadeHandler = Handler()
|
mFadeHandler = Handler()
|
||||||
setupPreviewImage(true)
|
setupPreviewImage(true)
|
||||||
|
|
||||||
val initialFlashlightState = FLASH_OFF
|
val initialFlashlightState = config.flashlightState
|
||||||
mPreview!!.setFlashlightState(initialFlashlightState)
|
mPreview!!.setFlashlightState(initialFlashlightState)
|
||||||
updateFlashlightState(initialFlashlightState)
|
updateFlashlightState(initialFlashlightState)
|
||||||
}
|
}
|
||||||
@ -261,10 +278,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
toggle_flash.setImageResource(flashDrawable)
|
toggle_flash.setImageResource(flashDrawable)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateCameraIcon(isUsingFrontCamera: Boolean) {
|
|
||||||
toggle_camera.setImageResource(if (isUsingFrontCamera) R.drawable.ic_camera_rear_vector else R.drawable.ic_camera_front_vector)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun shutterPressed() {
|
private fun shutterPressed() {
|
||||||
if (checkCameraAvailable()) {
|
if (checkCameraAvailable()) {
|
||||||
handleShutter()
|
handleShutter()
|
||||||
@ -283,19 +296,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleBottomButtons(hide: Boolean) {
|
|
||||||
runOnUiThread {
|
|
||||||
val alpha = if (hide) 0f else 1f
|
|
||||||
shutter.animate().alpha(alpha).start()
|
|
||||||
toggle_camera.animate().alpha(alpha).start()
|
|
||||||
toggle_flash.animate().alpha(alpha).start()
|
|
||||||
|
|
||||||
shutter.isClickable = !hide
|
|
||||||
toggle_camera.isClickable = !hide
|
|
||||||
toggle_flash.isClickable = !hide
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun launchSettings() {
|
private fun launchSettings() {
|
||||||
if (settings.alpha == 1f) {
|
if (settings.alpha == 1f) {
|
||||||
val intent = Intent(applicationContext, SettingsActivity::class.java)
|
val intent = Intent(applicationContext, SettingsActivity::class.java)
|
||||||
@ -324,14 +324,13 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mIsVideoCaptureIntent) {
|
if (mIsVideoCaptureIntent) {
|
||||||
mPreview?.tryInitVideoMode()
|
mPreview?.initVideoMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
mPreview?.setFlashlightState(FLASH_OFF)
|
mPreview?.setFlashlightState(FLASH_OFF)
|
||||||
hideTimer()
|
hideTimer()
|
||||||
mIsInPhotoMode = !mIsInPhotoMode
|
mIsInPhotoMode = !mIsInPhotoMode
|
||||||
config.initPhotoMode = mIsInPhotoMode
|
config.initPhotoMode = mIsInPhotoMode
|
||||||
showToggleCameraIfNeeded()
|
|
||||||
checkButtons()
|
checkButtons()
|
||||||
toggleBottomButtons(false)
|
toggleBottomButtons(false)
|
||||||
}
|
}
|
||||||
@ -352,9 +351,10 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun tryInitVideoMode() {
|
private fun tryInitVideoMode() {
|
||||||
if (mPreview?.initVideoMode() == true) {
|
try {
|
||||||
|
mPreview?.initVideoMode()
|
||||||
initVideoButtons()
|
initVideoButtons()
|
||||||
} else {
|
} catch (e: Exception) {
|
||||||
if (!mIsVideoCaptureIntent) {
|
if (!mIsVideoCaptureIntent) {
|
||||||
toast(R.string.video_mode_error)
|
toast(R.string.video_mode_error)
|
||||||
}
|
}
|
||||||
@ -363,7 +363,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
|
|
||||||
private fun initVideoButtons() {
|
private fun initVideoButtons() {
|
||||||
toggle_photo_video.setImageResource(R.drawable.ic_camera_vector)
|
toggle_photo_video.setImageResource(R.drawable.ic_camera_vector)
|
||||||
showToggleCameraIfNeeded()
|
|
||||||
shutter.setImageResource(R.drawable.ic_video_rec)
|
shutter.setImageResource(R.drawable.ic_video_rec)
|
||||||
setupPreviewImage(false)
|
setupPreviewImage(false)
|
||||||
mPreview?.checkFlashlight()
|
mPreview?.checkFlashlight()
|
||||||
@ -378,6 +377,13 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
|
|
||||||
mPreviewUri = Uri.withAppendedPath(uri, lastMediaId.toString())
|
mPreviewUri = Uri.withAppendedPath(uri, lastMediaId.toString())
|
||||||
|
|
||||||
|
Log.e(TAG, "mPreviewUri= $mPreviewUri")
|
||||||
|
|
||||||
|
loadLastTakenMedia(mPreviewUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadLastTakenMedia(uri: Uri?) {
|
||||||
|
mPreviewUri = uri
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
if (!isDestroyed) {
|
if (!isDestroyed) {
|
||||||
val options = RequestOptions()
|
val options = RequestOptions()
|
||||||
@ -385,7 +391,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
.load(mPreviewUri)
|
.load(uri)
|
||||||
.apply(options)
|
.apply(options)
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
.transition(DrawableTransitionOptions.withCrossFade())
|
||||||
.into(last_photo_video_preview)
|
.into(last_photo_video_preview)
|
||||||
@ -447,7 +453,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun resumeCameraItems() {
|
private fun resumeCameraItems() {
|
||||||
showToggleCameraIfNeeded()
|
|
||||||
hideNavigationBarIcons()
|
hideNavigationBarIcons()
|
||||||
|
|
||||||
if (!mIsInPhotoMode) {
|
if (!mIsInPhotoMode) {
|
||||||
@ -455,10 +460,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showToggleCameraIfNeeded() {
|
|
||||||
toggle_camera?.beInvisibleIf(mCameraImpl.getCountOfCameras() ?: 1 <= 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hasStorageAndCameraPermissions() = hasPermission(PERMISSION_WRITE_STORAGE) && hasPermission(PERMISSION_CAMERA)
|
private fun hasStorageAndCameraPermissions() = hasPermission(PERMISSION_WRITE_STORAGE) && hasPermission(PERMISSION_CAMERA)
|
||||||
|
|
||||||
private fun setupOrientationEventListener() {
|
private fun setupOrientationEventListener() {
|
||||||
@ -505,7 +506,15 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
return mIsCameraAvailable
|
return mIsCameraAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setFlashAvailable(available: Boolean) {
|
override fun setCameraAvailable(available: Boolean) {
|
||||||
|
mIsCameraAvailable = available
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setHasFrontAndBackCamera(hasFrontAndBack: Boolean) {
|
||||||
|
toggle_camera?.beVisibleIf(hasFrontAndBack)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setFlashAvailable(available: Boolean) {
|
||||||
if (available) {
|
if (available) {
|
||||||
toggle_flash.beVisible()
|
toggle_flash.beVisible()
|
||||||
} else {
|
} else {
|
||||||
@ -515,8 +524,29 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setIsCameraAvailable(available: Boolean) {
|
override fun onChangeCamera(frontCamera: Boolean) {
|
||||||
mIsCameraAvailable = available
|
toggle_camera.setImageResource(if (frontCamera) R.drawable.ic_camera_rear_vector else R.drawable.ic_camera_front_vector)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toggleBottomButtons(hide: Boolean) {
|
||||||
|
runOnUiThread {
|
||||||
|
val alpha = if (hide) 0f else 1f
|
||||||
|
shutter.animate().alpha(alpha).start()
|
||||||
|
toggle_camera.animate().alpha(alpha).start()
|
||||||
|
toggle_flash.animate().alpha(alpha).start()
|
||||||
|
|
||||||
|
shutter.isClickable = !hide
|
||||||
|
toggle_camera.isClickable = !hide
|
||||||
|
toggle_flash.isClickable = !hide
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMediaCaptured(uri: Uri) {
|
||||||
|
loadLastTakenMedia(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onChangeFlashMode(flashMode: Int) {
|
||||||
|
updateFlashlightState(flashMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setRecordingState(isRecording: Boolean) {
|
fun setRecordingState(isRecording: Boolean) {
|
||||||
@ -527,7 +557,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
showTimer()
|
showTimer()
|
||||||
} else {
|
} else {
|
||||||
shutter.setImageResource(R.drawable.ic_video_rec)
|
shutter.setImageResource(R.drawable.ic_video_rec)
|
||||||
showToggleCameraIfNeeded()
|
|
||||||
hideTimer()
|
hideTimer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -548,6 +577,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
|
|||||||
fun drawFocusCircle(x: Float, y: Float) = mFocusCircleView.drawFocusCircle(x, y)
|
fun drawFocusCircle(x: Float, y: Float) = mFocusCircleView.drawFocusCircle(x, y)
|
||||||
|
|
||||||
override fun mediaSaved(path: String) {
|
override fun mediaSaved(path: String) {
|
||||||
|
Log.e(TAG, "mediaSaved: $path")
|
||||||
rescanPaths(arrayListOf(path)) {
|
rescanPaths(arrayListOf(path)) {
|
||||||
setupPreviewImage(true)
|
setupPreviewImage(true)
|
||||||
Intent(BROADCAST_REFRESH_MEDIA).apply {
|
Intent(BROADCAST_REFRESH_MEDIA).apply {
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.simplemobiletools.camera.extensions
|
||||||
|
|
||||||
|
import androidx.camera.core.ImageCapture
|
||||||
|
import com.simplemobiletools.camera.helpers.FLASH_AUTO
|
||||||
|
import com.simplemobiletools.camera.helpers.FLASH_OFF
|
||||||
|
import com.simplemobiletools.camera.helpers.FLASH_ON
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
|
||||||
|
fun Int.toCameraXFlashMode(): Int {
|
||||||
|
return when (this) {
|
||||||
|
FLASH_ON -> ImageCapture.FLASH_MODE_ON
|
||||||
|
FLASH_OFF -> ImageCapture.FLASH_MODE_OFF
|
||||||
|
FLASH_AUTO -> ImageCapture.FLASH_MODE_AUTO
|
||||||
|
else -> throw IllegalArgumentException("Unknown mode: $this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.toAppFlashMode(): Int {
|
||||||
|
return when (this) {
|
||||||
|
ImageCapture.FLASH_MODE_ON -> FLASH_ON
|
||||||
|
ImageCapture.FLASH_MODE_OFF -> FLASH_OFF
|
||||||
|
ImageCapture.FLASH_MODE_AUTO -> FLASH_AUTO
|
||||||
|
else -> throw IllegalArgumentException("Unknown mode: $this")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,394 @@
|
|||||||
|
package com.simplemobiletools.camera.implementations
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.hardware.SensorManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.OrientationEventListener
|
||||||
|
import android.view.Surface
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.camera.core.*
|
||||||
|
import androidx.camera.core.ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
|
||||||
|
import androidx.camera.core.ImageCapture.FLASH_MODE_AUTO
|
||||||
|
import androidx.camera.core.ImageCapture.FLASH_MODE_OFF
|
||||||
|
import androidx.camera.core.ImageCapture.FLASH_MODE_ON
|
||||||
|
import androidx.camera.core.ImageCapture.Metadata
|
||||||
|
import androidx.camera.core.ImageCapture.OnImageSavedCallback
|
||||||
|
import androidx.camera.core.ImageCapture.OutputFileOptions
|
||||||
|
import androidx.camera.core.ImageCapture.OutputFileResults
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
|
import androidx.camera.video.MediaStoreOutputOptions
|
||||||
|
import androidx.camera.video.Quality
|
||||||
|
import androidx.camera.video.QualitySelector
|
||||||
|
import androidx.camera.video.Recorder
|
||||||
|
import androidx.camera.video.Recording
|
||||||
|
import androidx.camera.video.VideoCapture
|
||||||
|
import androidx.camera.video.VideoRecordEvent
|
||||||
|
import androidx.camera.view.PreviewView
|
||||||
|
import androidx.core.view.doOnLayout
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.window.layout.WindowMetricsCalculator
|
||||||
|
import com.bumptech.glide.load.ImageHeaderParser.UNKNOWN_ORIENTATION
|
||||||
|
import com.simplemobiletools.camera.R
|
||||||
|
import com.simplemobiletools.camera.extensions.config
|
||||||
|
import com.simplemobiletools.camera.extensions.toAppFlashMode
|
||||||
|
import com.simplemobiletools.camera.extensions.toCameraXFlashMode
|
||||||
|
import com.simplemobiletools.camera.interfaces.MyPreview
|
||||||
|
import com.simplemobiletools.commons.extensions.showErrorToast
|
||||||
|
import com.simplemobiletools.commons.extensions.toast
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
class CameraXPreview(
|
||||||
|
private val activity: AppCompatActivity,
|
||||||
|
private val viewFinder: PreviewView,
|
||||||
|
private val listener: CameraXPreviewListener,
|
||||||
|
) : MyPreview, DefaultLifecycleObserver {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "CameraXPreview"
|
||||||
|
private const val RATIO_4_3_VALUE = 4.0 / 3.0
|
||||||
|
private const val RATIO_16_9_VALUE = 16.0 / 9.0
|
||||||
|
}
|
||||||
|
|
||||||
|
private val config = activity.config
|
||||||
|
private val contentResolver = activity.contentResolver
|
||||||
|
private val mainExecutor = activity.mainExecutor
|
||||||
|
private val windowMetricsCalculator = WindowMetricsCalculator.getOrCreate()
|
||||||
|
private val orientationEventListener = object : OrientationEventListener(activity, SensorManager.SENSOR_DELAY_NORMAL) {
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
override fun onOrientationChanged(orientation: Int) {
|
||||||
|
if (orientation == UNKNOWN_ORIENTATION) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val rotation = when (orientation) {
|
||||||
|
in 45 until 135 -> Surface.ROTATION_270
|
||||||
|
in 135 until 225 -> Surface.ROTATION_180
|
||||||
|
in 225 until 315 -> Surface.ROTATION_90
|
||||||
|
else -> Surface.ROTATION_0
|
||||||
|
}
|
||||||
|
|
||||||
|
preview?.targetRotation = rotation
|
||||||
|
imageCapture?.targetRotation = rotation
|
||||||
|
videoCapture?.targetRotation = rotation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val hasBackCamera: Boolean
|
||||||
|
get() = cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
|
||||||
|
|
||||||
|
private val hasFrontCamera: Boolean
|
||||||
|
get() = cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
|
||||||
|
|
||||||
|
private val cameraCount: Int
|
||||||
|
get() = cameraProvider?.availableCameraInfos?.size ?: 0
|
||||||
|
|
||||||
|
private val frontCameraInUse: Boolean
|
||||||
|
get() = lensFacing == CameraSelector.DEFAULT_FRONT_CAMERA
|
||||||
|
|
||||||
|
private var preview: Preview? = null
|
||||||
|
private var cameraProvider: ProcessCameraProvider? = null
|
||||||
|
private var imageCapture: ImageCapture? = null
|
||||||
|
private var videoCapture: VideoCapture<Recorder>? = null
|
||||||
|
private var camera: Camera? = null
|
||||||
|
private var currentRecording: Recording? = null
|
||||||
|
private var recordingState: VideoRecordEvent? = null
|
||||||
|
private var lensFacing = CameraSelector.DEFAULT_BACK_CAMERA
|
||||||
|
private var flashMode = config.flashlightState.toCameraXFlashMode()
|
||||||
|
private var isPhotoCapture = config.initPhotoMode
|
||||||
|
|
||||||
|
init {
|
||||||
|
bindToLifeCycle()
|
||||||
|
viewFinder.doOnLayout {
|
||||||
|
startCamera()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindToLifeCycle() {
|
||||||
|
activity.lifecycle.addObserver(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startCamera() {
|
||||||
|
Log.i(TAG, "startCamera: ")
|
||||||
|
val cameraProviderFuture = ProcessCameraProvider.getInstance(activity)
|
||||||
|
cameraProviderFuture.addListener({
|
||||||
|
try {
|
||||||
|
cameraProvider = cameraProviderFuture.get()
|
||||||
|
bindCameraUseCases()
|
||||||
|
setupCameraObservers()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "startCamera: ", e)
|
||||||
|
activity.showErrorToast(activity.getString(R.string.camera_open_error))
|
||||||
|
}
|
||||||
|
}, mainExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindCameraUseCases() {
|
||||||
|
val cameraProvider = cameraProvider ?: throw IllegalStateException("Camera initialization failed.")
|
||||||
|
val metrics = windowMetricsCalculator.computeCurrentWindowMetrics(activity).bounds
|
||||||
|
val aspectRatio = aspectRatio(metrics.width(), metrics.height())
|
||||||
|
val rotation = viewFinder.display.rotation
|
||||||
|
|
||||||
|
preview = buildPreview(aspectRatio, rotation)
|
||||||
|
val captureUseCase = getCaptureUseCase(aspectRatio, rotation)
|
||||||
|
cameraProvider.unbindAll()
|
||||||
|
camera = cameraProvider.bindToLifecycle(
|
||||||
|
activity,
|
||||||
|
lensFacing,
|
||||||
|
preview,
|
||||||
|
captureUseCase,
|
||||||
|
)
|
||||||
|
preview?.setSurfaceProvider(viewFinder.surfaceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupCameraObservers() {
|
||||||
|
listener.setFlashAvailable(camera?.cameraInfo?.hasFlashUnit() ?: false)
|
||||||
|
listener.onChangeCamera(frontCameraInUse)
|
||||||
|
|
||||||
|
camera?.cameraInfo?.cameraState?.observe(activity) { cameraState ->
|
||||||
|
when (cameraState.type) {
|
||||||
|
CameraState.Type.OPEN,
|
||||||
|
CameraState.Type.OPENING -> {
|
||||||
|
listener.setHasFrontAndBackCamera(hasFrontCamera && hasBackCamera)
|
||||||
|
listener.setCameraAvailable(true)
|
||||||
|
}
|
||||||
|
CameraState.Type.PENDING_OPEN,
|
||||||
|
CameraState.Type.CLOSING,
|
||||||
|
CameraState.Type.CLOSED -> {
|
||||||
|
listener.setCameraAvailable(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle errors
|
||||||
|
cameraState.error?.let { error ->
|
||||||
|
listener.setCameraAvailable(false)
|
||||||
|
when (error.code) {
|
||||||
|
CameraState.ERROR_STREAM_CONFIG -> {
|
||||||
|
Log.e(TAG, "ERROR_STREAM_CONFIG")
|
||||||
|
// Make sure to setup the use cases properly
|
||||||
|
activity.toast(R.string.camera_unavailable)
|
||||||
|
}
|
||||||
|
CameraState.ERROR_CAMERA_IN_USE -> {
|
||||||
|
Log.e(TAG, "ERROR_CAMERA_IN_USE")
|
||||||
|
// Close the camera or ask user to close another camera app that's using the
|
||||||
|
// camera
|
||||||
|
activity.showErrorToast("Camera is in use by another app, please close")
|
||||||
|
}
|
||||||
|
CameraState.ERROR_MAX_CAMERAS_IN_USE -> {
|
||||||
|
Log.e(TAG, "ERROR_MAX_CAMERAS_IN_USE")
|
||||||
|
// Close another open camera in the app, or ask the user to close another
|
||||||
|
// camera app that's using the camera
|
||||||
|
activity.showErrorToast("Camera is in use by another app, please close")
|
||||||
|
}
|
||||||
|
CameraState.ERROR_OTHER_RECOVERABLE_ERROR -> {
|
||||||
|
Log.e(TAG, "ERROR_OTHER_RECOVERABLE_ERROR")
|
||||||
|
activity.toast(R.string.camera_open_error)
|
||||||
|
}
|
||||||
|
CameraState.ERROR_CAMERA_DISABLED -> {
|
||||||
|
Log.e(TAG, "ERROR_CAMERA_DISABLED")
|
||||||
|
// Ask the user to enable the device's cameras
|
||||||
|
activity.toast(R.string.camera_open_error)
|
||||||
|
}
|
||||||
|
CameraState.ERROR_CAMERA_FATAL_ERROR -> {
|
||||||
|
Log.e(TAG, "ERROR_CAMERA_FATAL_ERROR")
|
||||||
|
// Ask the user to reboot the device to restore camera function
|
||||||
|
activity.toast(R.string.camera_open_error)
|
||||||
|
}
|
||||||
|
CameraState.ERROR_DO_NOT_DISTURB_MODE_ENABLED -> {
|
||||||
|
// Ask the user to disable the "Do Not Disturb" mode, then reopen the camera
|
||||||
|
Log.e(TAG, "ERROR_DO_NOT_DISTURB_MODE_ENABLED")
|
||||||
|
activity.toast(R.string.camera_open_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCaptureUseCase(aspectRatio: Int, rotation: Int): UseCase {
|
||||||
|
return if (isPhotoCapture) {
|
||||||
|
buildImageCapture(aspectRatio, rotation).also {
|
||||||
|
imageCapture = it
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buildVideoCapture().also {
|
||||||
|
videoCapture = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildImageCapture(aspectRatio: Int, rotation: Int): ImageCapture {
|
||||||
|
return ImageCapture.Builder()
|
||||||
|
.setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY)
|
||||||
|
.setFlashMode(flashMode)
|
||||||
|
.setTargetAspectRatio(aspectRatio)
|
||||||
|
.setTargetRotation(rotation)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildPreview(aspectRatio: Int, rotation: Int): Preview {
|
||||||
|
return Preview.Builder()
|
||||||
|
.setTargetAspectRatio(aspectRatio)
|
||||||
|
.setTargetRotation(rotation)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildVideoCapture(): VideoCapture<Recorder> {
|
||||||
|
val recorder = Recorder.Builder()
|
||||||
|
.setQualitySelector(QualitySelector.from(Quality.FHD))
|
||||||
|
.build()
|
||||||
|
return VideoCapture.withOutput(recorder)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun aspectRatio(width: Int, height: Int): Int {
|
||||||
|
val previewRatio = max(width, height).toDouble() / min(width, height)
|
||||||
|
if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
|
||||||
|
return AspectRatio.RATIO_4_3
|
||||||
|
}
|
||||||
|
return AspectRatio.RATIO_16_9
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart(owner: LifecycleOwner) {
|
||||||
|
orientationEventListener.enable()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop(owner: LifecycleOwner) {
|
||||||
|
orientationEventListener.disable()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setTargetUri(uri: Uri) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showChangeResolutionDialog() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toggleFrontBackCamera() {
|
||||||
|
lensFacing = if (frontCameraInUse) {
|
||||||
|
CameraSelector.DEFAULT_BACK_CAMERA
|
||||||
|
} else {
|
||||||
|
CameraSelector.DEFAULT_FRONT_CAMERA
|
||||||
|
}
|
||||||
|
startCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toggleFlashlight() {
|
||||||
|
val newFlashMode = when (flashMode) {
|
||||||
|
FLASH_MODE_OFF -> FLASH_MODE_ON
|
||||||
|
FLASH_MODE_ON -> FLASH_MODE_AUTO
|
||||||
|
FLASH_MODE_AUTO -> FLASH_MODE_OFF
|
||||||
|
else -> throw IllegalArgumentException("Unknown mode: $flashMode")
|
||||||
|
}
|
||||||
|
|
||||||
|
flashMode = newFlashMode
|
||||||
|
imageCapture?.flashMode = newFlashMode
|
||||||
|
val appFlashMode = flashMode.toAppFlashMode()
|
||||||
|
config.flashlightState = appFlashMode
|
||||||
|
listener.onChangeFlashMode(appFlashMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tryTakePicture() {
|
||||||
|
Log.i(TAG, "captureImage: ")
|
||||||
|
val imageCapture = imageCapture ?: throw IllegalStateException("Camera initialization failed.")
|
||||||
|
|
||||||
|
val metadata = Metadata().apply {
|
||||||
|
isReversedHorizontal = frontCameraInUse
|
||||||
|
}
|
||||||
|
|
||||||
|
val contentValues = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, getRandomMediaName(true))
|
||||||
|
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
|
||||||
|
}
|
||||||
|
val contentUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||||
|
val outputOptions = OutputFileOptions.Builder(contentResolver, contentUri, contentValues)
|
||||||
|
.setMetadata(metadata)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
imageCapture.takePicture(outputOptions, mainExecutor, object : OnImageSavedCallback {
|
||||||
|
override fun onImageSaved(outputFileResults: OutputFileResults) {
|
||||||
|
listener.toggleBottomButtons(false)
|
||||||
|
listener.onMediaCaptured(outputFileResults.savedUri!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(exception: ImageCaptureException) {
|
||||||
|
listener.toggleBottomButtons(false)
|
||||||
|
activity.showErrorToast("Capture picture $exception")
|
||||||
|
Log.e(TAG, "Error", exception)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initPhotoMode() {
|
||||||
|
isPhotoCapture = true
|
||||||
|
startCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initVideoMode() {
|
||||||
|
isPhotoCapture = false
|
||||||
|
startCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toggleRecording() {
|
||||||
|
Log.d(TAG, "toggleRecording: currentRecording=$currentRecording, recordingState=$recordingState")
|
||||||
|
if (currentRecording == null || recordingState is VideoRecordEvent.Finalize) {
|
||||||
|
startRecording()
|
||||||
|
} else {
|
||||||
|
currentRecording?.stop()
|
||||||
|
currentRecording = null
|
||||||
|
Log.d(TAG, "Recording stopped")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
private fun startRecording() {
|
||||||
|
val videoCapture = videoCapture ?: throw IllegalStateException("Camera initialization failed.")
|
||||||
|
val contentValues = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, getRandomMediaName(false))
|
||||||
|
put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
|
||||||
|
}
|
||||||
|
val contentUri = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||||
|
val outputOptions = MediaStoreOutputOptions.Builder(contentResolver, contentUri)
|
||||||
|
.setContentValues(contentValues)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
currentRecording = videoCapture.output
|
||||||
|
.prepareRecording(activity, outputOptions)
|
||||||
|
.withAudioEnabled()
|
||||||
|
.start(mainExecutor) { recordEvent ->
|
||||||
|
Log.d(TAG, "recordEvent=$recordEvent ")
|
||||||
|
if (recordEvent !is VideoRecordEvent.Status) {
|
||||||
|
recordingState = recordEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recordEvent is VideoRecordEvent.Finalize) {
|
||||||
|
if (recordEvent.hasError()) {
|
||||||
|
// TODO: Handle errors
|
||||||
|
} else {
|
||||||
|
listener.onMediaCaptured(recordEvent.outputResults.outputUri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Recording started")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRandomMediaName(isPhoto: Boolean): String {
|
||||||
|
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
||||||
|
return if (isPhoto) {
|
||||||
|
"IMG_$timestamp"
|
||||||
|
} else {
|
||||||
|
"VID_$timestamp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.simplemobiletools.camera.implementations
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
|
||||||
|
interface CameraXPreviewListener {
|
||||||
|
fun setCameraAvailable(available: Boolean)
|
||||||
|
fun setHasFrontAndBackCamera(hasFrontAndBack:Boolean)
|
||||||
|
fun setFlashAvailable(available: Boolean)
|
||||||
|
fun onChangeCamera(frontCamera: Boolean)
|
||||||
|
fun toggleBottomButtons(hide:Boolean)
|
||||||
|
fun onMediaCaptured(uri: Uri)
|
||||||
|
fun onChangeFlashMode(flashMode: Int)
|
||||||
|
}
|
@ -3,17 +3,18 @@ package com.simplemobiletools.camera.interfaces
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
|
||||||
interface MyPreview {
|
interface MyPreview {
|
||||||
fun onResumed()
|
|
||||||
|
|
||||||
fun onPaused()
|
fun onResumed() = Unit
|
||||||
|
|
||||||
|
fun onPaused() = Unit
|
||||||
|
|
||||||
fun setTargetUri(uri: Uri)
|
fun setTargetUri(uri: Uri)
|
||||||
|
|
||||||
fun setIsImageCaptureIntent(isImageCaptureIntent: Boolean)
|
fun setIsImageCaptureIntent(isImageCaptureIntent: Boolean) = Unit
|
||||||
|
|
||||||
fun setFlashlightState(state: Int)
|
fun setFlashlightState(state: Int) = Unit
|
||||||
|
|
||||||
fun getCameraState(): Int
|
fun getCameraState(): Int = 0
|
||||||
|
|
||||||
fun showChangeResolutionDialog()
|
fun showChangeResolutionDialog()
|
||||||
|
|
||||||
@ -25,11 +26,9 @@ interface MyPreview {
|
|||||||
|
|
||||||
fun toggleRecording()
|
fun toggleRecording()
|
||||||
|
|
||||||
fun tryInitVideoMode()
|
|
||||||
|
|
||||||
fun initPhotoMode()
|
fun initPhotoMode()
|
||||||
|
|
||||||
fun initVideoMode(): Boolean
|
fun initVideoMode()
|
||||||
|
|
||||||
fun checkFlashlight()
|
fun checkFlashlight() = Unit
|
||||||
}
|
}
|
||||||
|
@ -369,7 +369,7 @@ class CameraPreview : ViewGroup, TextureView.SurfaceTextureListener, MyPreview {
|
|||||||
mIsFocusSupported = get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES)!!.size > 1
|
mIsFocusSupported = get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES)!!.size > 1
|
||||||
}
|
}
|
||||||
mActivity.setFlashAvailable(mIsFlashSupported)
|
mActivity.setFlashAvailable(mIsFlashSupported)
|
||||||
mActivity.updateCameraIcon(mUseFrontCamera)
|
mActivity.onChangeCamera(mUseFrontCamera)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -429,21 +429,21 @@ class CameraPreview : ViewGroup, TextureView.SurfaceTextureListener, MyPreview {
|
|||||||
mCameraOpenCloseLock.release()
|
mCameraOpenCloseLock.release()
|
||||||
mCameraDevice = cameraDevice
|
mCameraDevice = cameraDevice
|
||||||
createCameraPreviewSession()
|
createCameraPreviewSession()
|
||||||
mActivity.setIsCameraAvailable(true)
|
mActivity.setCameraAvailable(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDisconnected(cameraDevice: CameraDevice) {
|
override fun onDisconnected(cameraDevice: CameraDevice) {
|
||||||
mCameraOpenCloseLock.release()
|
mCameraOpenCloseLock.release()
|
||||||
cameraDevice.close()
|
cameraDevice.close()
|
||||||
mCameraDevice = null
|
mCameraDevice = null
|
||||||
mActivity.setIsCameraAvailable(false)
|
mActivity.setCameraAvailable(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(cameraDevice: CameraDevice, error: Int) {
|
override fun onError(cameraDevice: CameraDevice, error: Int) {
|
||||||
mCameraOpenCloseLock.release()
|
mCameraOpenCloseLock.release()
|
||||||
cameraDevice.close()
|
cameraDevice.close()
|
||||||
mCameraDevice = null
|
mCameraDevice = null
|
||||||
mActivity.setIsCameraAvailable(false)
|
mActivity.setCameraAvailable(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -981,23 +981,18 @@ class CameraPreview : ViewGroup, TextureView.SurfaceTextureListener, MyPreview {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tryInitVideoMode() {
|
|
||||||
initVideoMode()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initPhotoMode() {
|
override fun initPhotoMode() {
|
||||||
mIsInVideoMode = false
|
mIsInVideoMode = false
|
||||||
closeCamera()
|
closeCamera()
|
||||||
openCamera(mTextureView.width, mTextureView.height)
|
openCamera(mTextureView.width, mTextureView.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initVideoMode(): Boolean {
|
override fun initVideoMode() {
|
||||||
mLastFocusX = 0f
|
mLastFocusX = 0f
|
||||||
mLastFocusY = 0f
|
mLastFocusY = 0f
|
||||||
mIsInVideoMode = true
|
mIsInVideoMode = true
|
||||||
closeCamera()
|
closeCamera()
|
||||||
openCamera(mTextureView.width, mTextureView.height)
|
openCamera(mTextureView.width, mTextureView.height)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkFlashlight() {
|
override fun checkFlashlight() {
|
||||||
|
@ -5,10 +5,10 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@android:color/black">
|
android:background="@android:color/black">
|
||||||
|
|
||||||
<com.simplemobiletools.camera.views.AutoFitTextureView
|
<androidx.camera.view.PreviewView
|
||||||
android:id="@+id/camera_texture_view"
|
android:id="@+id/view_finder"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/capture_black_screen"
|
android:id="@+id/capture_black_screen"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user