diff --git a/app/build.gradle b/app/build.gradle
index a66fcaaf..fbc8af34 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -34,7 +34,7 @@ android {
buildTypes {
debug {
- applicationIdSuffix ".debug"
+ applicationIdSuffix ".debugcamerax"
}
release {
minifyEnabled true
@@ -65,4 +65,14 @@ dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:d1d5402388'
implementation 'androidx.documentfile:documentfile:1.0.1'
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.1.0-rc02'
+ 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"
}
diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml
index 37ff84c6..e7e9ee39 100644
--- a/app/src/debug/res/values/strings.xml
+++ b/app/src/debug/res/values/strings.xml
@@ -1,4 +1,5 @@
- Camera_debug
+
+ CameraX_debug
diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt
index adebc9d9..5f701f58 100644
--- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt
@@ -7,7 +7,12 @@ import android.net.Uri
import android.os.Bundle
import android.os.Handler
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 com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
@@ -16,17 +21,40 @@ import com.bumptech.glide.request.RequestOptions
import com.simplemobiletools.camera.BuildConfig
import com.simplemobiletools.camera.R
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.interfaces.MyPreview
-import com.simplemobiletools.camera.views.CameraPreview
import com.simplemobiletools.camera.views.FocusCircleView
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 kotlinx.android.synthetic.main.activity_main.*
+import java.util.concurrent.TimeUnit
+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 CAPTURE_ANIMATION_DURATION = 100L
@@ -68,7 +96,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
override fun onResume() {
super.onResume()
if (hasStorageAndCameraPermissions()) {
- mPreview?.onResumed()
resumeCameraItems()
setupPreviewImage(mIsInPhotoMode)
scheduleFadeOut()
@@ -97,14 +124,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
hideTimer()
mOrientationEventListener.disable()
-
- if (mPreview?.getCameraState() == STATE_PICTURE_TAKEN) {
- toast(R.string.photo_not_saved)
- }
-
- ensureBackgroundThread {
- mPreview?.onPaused()
- }
}
override fun onDestroy() {
@@ -201,8 +220,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
)
checkVideoCaptureIntent()
- mPreview = CameraPreview(this, camera_texture_view, mIsInPhotoMode)
- view_holder.addView(mPreview as ViewGroup)
+ mPreview = CameraXPreview(this, view_finder, this)
checkImageCaptureIntent()
mPreview?.setIsImageCaptureIntent(isImageCaptureIntent())
@@ -217,7 +235,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
mFadeHandler = Handler()
setupPreviewImage(true)
- val initialFlashlightState = FLASH_OFF
+ val initialFlashlightState = config.flashlightState
mPreview!!.setFlashlightState(initialFlashlightState)
updateFlashlightState(initialFlashlightState)
}
@@ -261,10 +279,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
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() {
if (checkCameraAvailable()) {
handleShutter()
@@ -283,19 +297,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() {
if (settings.alpha == 1f) {
val intent = Intent(applicationContext, SettingsActivity::class.java)
@@ -324,14 +325,13 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
}
if (mIsVideoCaptureIntent) {
- mPreview?.tryInitVideoMode()
+ mPreview?.initVideoMode()
}
mPreview?.setFlashlightState(FLASH_OFF)
hideTimer()
mIsInPhotoMode = !mIsInPhotoMode
config.initPhotoMode = mIsInPhotoMode
- showToggleCameraIfNeeded()
checkButtons()
toggleBottomButtons(false)
}
@@ -352,9 +352,10 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
}
private fun tryInitVideoMode() {
- if (mPreview?.initVideoMode() == true) {
+ try {
+ mPreview?.initVideoMode()
initVideoButtons()
- } else {
+ } catch (e: Exception) {
if (!mIsVideoCaptureIntent) {
toast(R.string.video_mode_error)
}
@@ -363,7 +364,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
private fun initVideoButtons() {
toggle_photo_video.setImageResource(R.drawable.ic_camera_vector)
- showToggleCameraIfNeeded()
shutter.setImageResource(R.drawable.ic_video_rec)
setupPreviewImage(false)
mPreview?.checkFlashlight()
@@ -378,6 +378,13 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
mPreviewUri = Uri.withAppendedPath(uri, lastMediaId.toString())
+ Log.e(TAG, "mPreviewUri= $mPreviewUri")
+
+ loadLastTakenMedia(mPreviewUri)
+ }
+
+ private fun loadLastTakenMedia(uri: Uri?) {
+ mPreviewUri = uri
runOnUiThread {
if (!isDestroyed) {
val options = RequestOptions()
@@ -385,7 +392,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
.diskCacheStrategy(DiskCacheStrategy.NONE)
Glide.with(this)
- .load(mPreviewUri)
+ .load(uri)
.apply(options)
.transition(DrawableTransitionOptions.withCrossFade())
.into(last_photo_video_preview)
@@ -447,7 +454,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
}
private fun resumeCameraItems() {
- showToggleCameraIfNeeded()
hideNavigationBarIcons()
if (!mIsInPhotoMode) {
@@ -455,10 +461,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 setupOrientationEventListener() {
@@ -505,7 +507,15 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
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) {
toggle_flash.beVisible()
} else {
@@ -515,8 +525,51 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
}
}
- fun setIsCameraAvailable(available: Boolean) {
- mIsCameraAvailable = available
+ override fun onChangeCamera(frontCamera: Boolean) {
+ 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)
+ }
+
+ override fun onVideoRecordingStarted() {
+ shutter.setImageResource(R.drawable.ic_video_stop)
+ toggle_camera.beInvisible()
+ video_rec_curr_timer.beVisible()
+ }
+
+ override fun onVideoRecordingStopped() {
+ shutter.setImageResource(R.drawable.ic_video_rec)
+ video_rec_curr_timer.text = 0.getFormattedDuration()
+ video_rec_curr_timer.beGone()
+ toggle_camera.beVisible()
+ }
+
+ override fun onVideoDurationChanged(durationNanos: Long) {
+ val seconds = TimeUnit.NANOSECONDS.toSeconds(durationNanos).toInt()
+ video_rec_curr_timer.text = seconds.getFormattedDuration()
+ }
+
+ override fun onFocusCamera(xPos: Float, yPos: Float) {
+ mFocusCircleView.drawFocusCircle(xPos, yPos)
}
fun setRecordingState(isRecording: Boolean) {
@@ -527,7 +580,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
showTimer()
} else {
shutter.setImageResource(R.drawable.ic_video_rec)
- showToggleCameraIfNeeded()
hideTimer()
}
}
@@ -548,6 +600,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
fun drawFocusCircle(x: Float, y: Float) = mFocusCircleView.drawFocusCircle(x, y)
override fun mediaSaved(path: String) {
+ Log.e(TAG, "mediaSaved: $path")
rescanPaths(arrayListOf(path)) {
setupPreviewImage(true)
Intent(BROADCAST_REFRESH_MEDIA).apply {
diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/extensions/CameraSelector.kt b/app/src/main/kotlin/com/simplemobiletools/camera/extensions/CameraSelector.kt
new file mode 100644
index 00000000..0b98bca4
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/camera/extensions/CameraSelector.kt
@@ -0,0 +1,11 @@
+package com.simplemobiletools.camera.extensions
+
+import androidx.camera.core.CameraSelector
+
+fun CameraSelector.toLensFacing(): Int {
+ return if (this == CameraSelector.DEFAULT_FRONT_CAMERA) {
+ CameraSelector.LENS_FACING_FRONT
+ } else {
+ CameraSelector.LENS_FACING_BACK
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/extensions/Int.kt b/app/src/main/kotlin/com/simplemobiletools/camera/extensions/Int.kt
new file mode 100644
index 00000000..4cf4caf7
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/camera/extensions/Int.kt
@@ -0,0 +1,35 @@
+package com.simplemobiletools.camera.extensions
+
+import androidx.camera.core.CameraSelector
+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")
+ }
+}
+
+fun Int.toCameraSelector(): CameraSelector {
+ return if (this == CameraSelector.LENS_FACING_FRONT) {
+ CameraSelector.DEFAULT_FRONT_CAMERA
+ } else {
+ CameraSelector.DEFAULT_BACK_CAMERA
+ }
+}
+
diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt
index ca317988..5ede2c12 100644
--- a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt
@@ -2,6 +2,7 @@ package com.simplemobiletools.camera.helpers
import android.content.Context
import android.os.Environment
+import androidx.camera.core.CameraSelector
import com.simplemobiletools.commons.helpers.BaseConfig
import java.io.File
@@ -37,6 +38,10 @@ class Config(context: Context) : BaseConfig(context) {
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()
+
var initPhotoMode: Boolean
get() = prefs.getBoolean(INIT_PHOTO_MODE, true)
set(initPhotoMode) = prefs.edit().putBoolean(INIT_PHOTO_MODE, initPhotoMode).apply()
diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Constants.kt
index b68dff71..0b543b7c 100644
--- a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Constants.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Constants.kt
@@ -10,6 +10,7 @@ const val SOUND = "sound"
const val VOLUME_BUTTONS_AS_SHUTTER = "volume_buttons_as_shutter"
const val FLIP_PHOTOS = "flip_photos"
const val LAST_USED_CAMERA = "last_used_camera_2"
+const val LAST_USED_CAMERA_LENS = "last_used_camera_lens"
const val FLASHLIGHT_STATE = "flashlight_state"
const val INIT_PHOTO_MODE = "init_photo_mode"
const val BACK_PHOTO_RESOLUTION_INDEX = "back_photo_resolution_index_2"
diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/MediaSoundHelper.kt b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/MediaSoundHelper.kt
new file mode 100644
index 00000000..abc2e71a
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/MediaSoundHelper.kt
@@ -0,0 +1,25 @@
+package com.simplemobiletools.camera.helpers
+
+import android.media.MediaActionSound
+
+class MediaSoundHelper {
+ private val mediaActionSound = MediaActionSound()
+
+ fun loadSounds() {
+ mediaActionSound.load(MediaActionSound.START_VIDEO_RECORDING)
+ mediaActionSound.load(MediaActionSound.STOP_VIDEO_RECORDING)
+ mediaActionSound.load(MediaActionSound.SHUTTER_CLICK)
+ }
+
+ fun playShutterSound() {
+ mediaActionSound.play(MediaActionSound.SHUTTER_CLICK)
+ }
+
+ fun playStartVideoRecordingSound() {
+ mediaActionSound.play(MediaActionSound.START_VIDEO_RECORDING)
+ }
+
+ fun playStopVideoRecordingSound() {
+ mediaActionSound.play(MediaActionSound.STOP_VIDEO_RECORDING)
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/PinchToZoomOnScaleGestureListener.kt b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/PinchToZoomOnScaleGestureListener.kt
new file mode 100644
index 00000000..3289e433
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/PinchToZoomOnScaleGestureListener.kt
@@ -0,0 +1,19 @@
+package com.simplemobiletools.camera.helpers
+
+import android.view.ScaleGestureDetector
+import androidx.camera.core.CameraControl
+import androidx.camera.core.CameraInfo
+
+class PinchToZoomOnScaleGestureListener(
+ private val cameraInfo: CameraInfo,
+ private val cameraControl: CameraControl,
+) : ScaleGestureDetector.SimpleOnScaleGestureListener() {
+ private val zoomCalculator = ZoomCalculator()
+
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
+ val zoomState = cameraInfo.zoomState.value ?: return false
+ val zoomRatio = zoomCalculator.calculateZoomRatio(zoomState, detector.scaleFactor)
+ cameraControl.setZoomRatio(zoomRatio)
+ return true
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/ZoomCalculator.kt b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/ZoomCalculator.kt
new file mode 100644
index 00000000..1e2aa294
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/ZoomCalculator.kt
@@ -0,0 +1,20 @@
+package com.simplemobiletools.camera.helpers
+
+import androidx.camera.core.ZoomState
+
+class ZoomCalculator {
+
+ fun calculateZoomRatio(zoomState: ZoomState, pinchToZoomScale: Float): Float {
+ val clampedRatio = zoomState.zoomRatio * speedUpZoomBy2X(pinchToZoomScale)
+ // Clamp the ratio with the zoom range.
+ return clampedRatio.coerceAtLeast(zoomState.minZoomRatio).coerceAtMost(zoomState.maxZoomRatio)
+ }
+
+ private fun speedUpZoomBy2X(scaleFactor: Float): Float {
+ return if (scaleFactor > 1f) {
+ 1.0f + (scaleFactor - 1.0f) * 2
+ } else {
+ 1.0f - (1.0f - scaleFactor) * 2
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt
new file mode 100644
index 00000000..580acecf
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt
@@ -0,0 +1,485 @@
+package com.simplemobiletools.camera.implementations
+
+import android.annotation.SuppressLint
+import android.content.ContentValues
+import android.content.Context
+import android.hardware.SensorManager
+import android.hardware.display.DisplayManager
+import android.net.Uri
+import android.os.Environment
+import android.provider.MediaStore
+import android.util.Log
+import android.view.Display
+import android.view.GestureDetector
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.MotionEvent
+import android.view.OrientationEventListener
+import android.view.ScaleGestureDetector
+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.toCameraSelector
+import com.simplemobiletools.camera.extensions.toCameraXFlashMode
+import com.simplemobiletools.camera.extensions.toLensFacing
+import com.simplemobiletools.camera.helpers.MediaSoundHelper
+import com.simplemobiletools.camera.helpers.PinchToZoomOnScaleGestureListener
+import com.simplemobiletools.camera.interfaces.MyPreview
+import com.simplemobiletools.commons.extensions.showErrorToast
+import com.simplemobiletools.commons.extensions.toast
+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 previewView: 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
+
+ // 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 val config = activity.config
+ private val contentResolver = activity.contentResolver
+ private val mainExecutor = activity.mainExecutor
+ private val displayManager = activity.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+ private val mediaSoundHelper = MediaSoundHelper()
+ 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 var preview: Preview? = null
+ private var cameraProvider: ProcessCameraProvider? = null
+ private var imageCapture: ImageCapture? = null
+ private var videoCapture: VideoCapture? = null
+ private var camera: Camera? = null
+ private var currentRecording: Recording? = null
+ private var recordingState: VideoRecordEvent? = null
+ private var cameraSelector = config.lastUsedCameraLens.toCameraSelector()
+ private var flashMode = config.flashlightState.toCameraXFlashMode()
+ private var isPhotoCapture = config.initPhotoMode
+
+ init {
+ bindToLifeCycle()
+ mediaSoundHelper.loadSounds()
+ previewView.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 = previewView.display.rotation
+
+ preview = buildPreview(aspectRatio, rotation)
+ val captureUseCase = getCaptureUseCase(aspectRatio, rotation)
+ cameraProvider.unbindAll()
+ camera = cameraProvider.bindToLifecycle(
+ activity,
+ cameraSelector,
+ preview,
+ captureUseCase,
+ )
+ preview?.setSurfaceProvider(previewView.surfaceProvider)
+ setupZoomAndFocus()
+ }
+
+ private fun setupCameraObservers() {
+ listener.setFlashAvailable(camera?.cameraInfo?.hasFlashUnit() ?: false)
+ listener.onChangeCamera(isFrontCameraInUse())
+
+ 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) {
+ cameraProvider?.unbind(videoCapture)
+ buildImageCapture(aspectRatio, rotation).also {
+ imageCapture = it
+ }
+ } else {
+ cameraProvider?.unbind(imageCapture)
+ buildVideoCapture().also {
+ videoCapture = it
+ }
+ }
+ }
+
+ private fun buildImageCapture(aspectRatio: Int, rotation: Int): ImageCapture {
+ return ImageCapture.Builder()
+ .setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY)
+ .setFlashMode(flashMode)
+ .setJpegQuality(config.photoQuality)
+ .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 {
+ val recorder = Recorder.Builder()
+ //TODO: user control for quality
+ .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
+ }
+
+ private fun hasBackCamera(): Boolean {
+ return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
+ }
+
+ private fun hasFrontCamera(): Boolean {
+ return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
+ }
+
+ private fun isFrontCameraInUse(): Boolean {
+ return cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ // source: https://stackoverflow.com/a/60095886/10552591
+ private fun setupZoomAndFocus() {
+ Log.i(TAG, "camera controller: ${previewView.controller}")
+ val scaleGesture = camera?.let { ScaleGestureDetector(activity, PinchToZoomOnScaleGestureListener(it.cameraInfo, it.cameraControl)) }
+ val gestureDetector = GestureDetector(activity, object : SimpleOnGestureListener() {
+ override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
+ return camera?.cameraInfo?.let {
+ val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)
+ val width = previewView.width.toFloat()
+ val height = previewView.height.toFloat()
+ Log.i(TAG, "onSingleTapConfirmed: width=$width,height=$height")
+ val factory = DisplayOrientedMeteringPointFactory(display, it, width, height)
+ val xPos = event.x
+ val yPos = event.y
+ val autoFocusPoint = factory.createPoint(xPos, yPos, AF_SIZE)
+ val autoExposurePoint = factory.createPoint(xPos, yPos, AE_SIZE)
+ val focusMeteringAction = FocusMeteringAction.Builder(autoFocusPoint, FocusMeteringAction.FLAG_AF)
+ .addPoint(autoExposurePoint, FocusMeteringAction.FLAG_AE)
+ .disableAutoCancel()
+ .build()
+ camera?.cameraControl?.startFocusAndMetering(focusMeteringAction)
+ listener.onFocusCamera(xPos, yPos)
+ Log.i(TAG, "start focus")
+ true
+ } ?: false
+ }
+ })
+ previewView.setOnTouchListener { _, event ->
+ Log.i(TAG, "setOnTouchListener: x=${event.x}, y=${event.y}")
+ gestureDetector.onTouchEvent(event)
+ scaleGesture?.onTouchEvent(event)
+ true
+ }
+ }
+
+ 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() {
+ val newCameraSelector = if (isFrontCameraInUse()) {
+ CameraSelector.DEFAULT_BACK_CAMERA
+ } else {
+ CameraSelector.DEFAULT_FRONT_CAMERA
+ }
+ cameraSelector = newCameraSelector
+ config.lastUsedCameraLens = newCameraSelector.toLensFacing()
+ 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 = config.flipPhotos
+ }
+
+ 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)
+ }
+ })
+ playShutterSoundIfEnabled()
+ }
+
+ 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 ")
+ recordingState = recordEvent
+ when (recordEvent) {
+ is VideoRecordEvent.Start -> {
+ playStartVideoRecordingSoundIfEnabled()
+ listener.onVideoRecordingStarted()
+ }
+
+ is VideoRecordEvent.Status -> {
+ listener.onVideoDurationChanged(recordEvent.recordingStats.recordedDurationNanos)
+ }
+
+ is VideoRecordEvent.Finalize -> {
+ playStopVideoRecordingSoundIfEnabled()
+ listener.onVideoRecordingStopped()
+ 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"
+ }
+ }
+
+ private fun playShutterSoundIfEnabled() {
+ if (config.isSoundEnabled) {
+ mediaSoundHelper.playShutterSound()
+ }
+ }
+
+ private fun playStartVideoRecordingSoundIfEnabled() {
+ if (config.isSoundEnabled) {
+ mediaSoundHelper.playStartVideoRecordingSound()
+ }
+ }
+
+ private fun playStopVideoRecordingSoundIfEnabled() {
+ if (config.isSoundEnabled) {
+ mediaSoundHelper.playStopVideoRecordingSound()
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt
new file mode 100644
index 00000000..b5758137
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt
@@ -0,0 +1,17 @@
+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)
+ fun onVideoRecordingStarted()
+ fun onVideoRecordingStopped()
+ fun onVideoDurationChanged(durationNanos: Long)
+ fun onFocusCamera(xPos: Float, yPos: Float)
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt b/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt
index f1437148..232b936f 100644
--- a/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt
@@ -3,17 +3,18 @@ package com.simplemobiletools.camera.interfaces
import android.net.Uri
interface MyPreview {
- fun onResumed()
- fun onPaused()
+ fun onResumed() = Unit
+
+ fun onPaused() = Unit
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()
@@ -25,11 +26,9 @@ interface MyPreview {
fun toggleRecording()
- fun tryInitVideoMode()
-
fun initPhotoMode()
- fun initVideoMode(): Boolean
+ fun initVideoMode()
- fun checkFlashlight()
+ fun checkFlashlight() = Unit
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/views/CameraPreview.kt b/app/src/main/kotlin/com/simplemobiletools/camera/views/CameraPreview.kt
index 5ffea60d..adf039ed 100644
--- a/app/src/main/kotlin/com/simplemobiletools/camera/views/CameraPreview.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/camera/views/CameraPreview.kt
@@ -96,7 +96,7 @@ class CameraPreview : ViewGroup, TextureView.SurfaceTextureListener, MyPreview {
private val mCameraToPreviewMatrix = Matrix()
private val mPreviewToCameraMatrix = Matrix()
private val mCameraOpenCloseLock = Semaphore(1)
- private val mMediaActionSound = MediaActionSound()
+ private val mediaSoundHelper = MediaSoundHelper()
private var mZoomRect: Rect? = null
constructor(context: Context) : super(context)
@@ -114,7 +114,7 @@ class CameraPreview : ViewGroup, TextureView.SurfaceTextureListener, MyPreview {
mUseFrontCamera = false
mIsInVideoMode = !initPhotoMode
- loadSounds()
+ mediaSoundHelper.loadSounds()
val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
@@ -182,12 +182,6 @@ class CameraPreview : ViewGroup, TextureView.SurfaceTextureListener, MyPreview {
}
}
- private fun loadSounds() {
- mMediaActionSound.load(MediaActionSound.START_VIDEO_RECORDING)
- mMediaActionSound.load(MediaActionSound.STOP_VIDEO_RECORDING)
- mMediaActionSound.load(MediaActionSound.SHUTTER_CLICK)
- }
-
@SuppressLint("MissingPermission")
private fun openCamera(width: Int, height: Int) {
try {
@@ -369,7 +363,7 @@ class CameraPreview : ViewGroup, TextureView.SurfaceTextureListener, MyPreview {
mIsFocusSupported = get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES)!!.size > 1
}
mActivity.setFlashAvailable(mIsFlashSupported)
- mActivity.updateCameraIcon(mUseFrontCamera)
+ mActivity.onChangeCamera(mUseFrontCamera)
return
}
} catch (e: Exception) {
@@ -429,21 +423,21 @@ class CameraPreview : ViewGroup, TextureView.SurfaceTextureListener, MyPreview {
mCameraOpenCloseLock.release()
mCameraDevice = cameraDevice
createCameraPreviewSession()
- mActivity.setIsCameraAvailable(true)
+ mActivity.setCameraAvailable(true)
}
override fun onDisconnected(cameraDevice: CameraDevice) {
mCameraOpenCloseLock.release()
cameraDevice.close()
mCameraDevice = null
- mActivity.setIsCameraAvailable(false)
+ mActivity.setCameraAvailable(false)
}
override fun onError(cameraDevice: CameraDevice, error: Int) {
mCameraOpenCloseLock.release()
cameraDevice.close()
mCameraDevice = null
- mActivity.setIsCameraAvailable(false)
+ mActivity.setCameraAvailable(false)
}
}
@@ -576,7 +570,7 @@ class CameraPreview : ViewGroup, TextureView.SurfaceTextureListener, MyPreview {
}
if (mActivity.config.isSoundEnabled) {
- mMediaActionSound.play(MediaActionSound.SHUTTER_CLICK)
+ mediaSoundHelper.playShutterSound()
}
mCameraState = STATE_PICTURE_TAKEN
@@ -824,7 +818,7 @@ class CameraPreview : ViewGroup, TextureView.SurfaceTextureListener, MyPreview {
closeCaptureSession()
setupMediaRecorder()
if (mActivity.config.isSoundEnabled) {
- mMediaActionSound.play(MediaActionSound.START_VIDEO_RECORDING)
+ mediaSoundHelper.playStartVideoRecordingSound()
}
try {
@@ -853,7 +847,7 @@ class CameraPreview : ViewGroup, TextureView.SurfaceTextureListener, MyPreview {
private fun stopRecording() {
mCameraState = STATE_STOPING_RECORDING
if (mActivity.config.isSoundEnabled) {
- mMediaActionSound.play(MediaActionSound.STOP_VIDEO_RECORDING)
+ mediaSoundHelper.playStopVideoRecordingSound()
}
mIsRecording = false
@@ -981,23 +975,18 @@ class CameraPreview : ViewGroup, TextureView.SurfaceTextureListener, MyPreview {
}
}
- override fun tryInitVideoMode() {
- initVideoMode()
- }
-
override fun initPhotoMode() {
mIsInVideoMode = false
closeCamera()
openCamera(mTextureView.width, mTextureView.height)
}
- override fun initVideoMode(): Boolean {
+ override fun initVideoMode() {
mLastFocusX = 0f
mLastFocusY = 0f
mIsInVideoMode = true
closeCamera()
openCamera(mTextureView.width, mTextureView.height)
- return true
}
override fun checkFlashlight() {
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 71ec8765..94059da6 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -5,10 +5,10 @@
android:layout_height="match_parent"
android:background="@android:color/black">
-
+