diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 049f8d3..26eaf3e 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -56,6 +56,7 @@ android {
}
buildFeatures {
compose = true
+ viewBinding = true
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a730ecb..68af5ff 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -9,6 +9,8 @@
+
+
+ android:screenOrientation="fullSensor" />
\ No newline at end of file
diff --git a/app/src/main/java/com/example/handycam/CameraControlActivity.kt b/app/src/main/java/com/example/handycam/CameraControlActivity.kt
index 8889fc7..21eb24f 100644
--- a/app/src/main/java/com/example/handycam/CameraControlActivity.kt
+++ b/app/src/main/java/com/example/handycam/CameraControlActivity.kt
@@ -10,11 +10,14 @@ import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.MotionEvent
+import android.view.ScaleGestureDetector
import android.view.Surface
import android.view.View
+import android.widget.ArrayAdapter
+import android.widget.Button
import android.widget.ImageButton
+import android.widget.SeekBar
import android.widget.Spinner
-import android.widget.ArrayAdapter
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
@@ -33,6 +36,14 @@ import java.util.concurrent.TimeUnit
private const val TAG = "CameraControlActivity"
+// Camera2 AWB mode constants (mirrors CaptureRequest.CONTROL_AWB_MODE_*)
+private const val AWB_AUTO = 1
+private const val AWB_DAYLIGHT = 2
+private const val AWB_INCANDESCENT = 3
+private const val AWB_FLUORESCENT = 4
+private const val AWB_CLOUDY = 8
+private const val AWB_SHADE = 9
+
class CameraControlActivity : AppCompatActivity() {
private lateinit var previewView: PreviewView
@@ -40,6 +51,8 @@ class CameraControlActivity : AppCompatActivity() {
private lateinit var cameraSpinner: Spinner
private lateinit var exposureLabel: TextView
private lateinit var focusRing: View
+ private lateinit var zoomSeekBar: SeekBar
+ private lateinit var zoomLabel: TextView
private var cameraManager: android.hardware.camera2.CameraManager? = null
private var cameraControl: androidx.camera.core.CameraControl? = null
@@ -48,11 +61,12 @@ class CameraControlActivity : AppCompatActivity() {
private var adjustingExposure = false
private var exposureStartY = 0f
private var lastExposureIndex = 0
-
- private var cameraReadyReceiver: BroadcastReceiver? = null
- private var useCameraX = false // Track which camera system is being used
+ private var currentLinearZoom = 0f
+ private var cameraReadyReceiver: BroadcastReceiver? = null
+ private var useCameraX = false
private lateinit var settingsManager: SettingsManager
+ private lateinit var scaleGestureDetector: ScaleGestureDetector
private val cameraPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
@@ -69,29 +83,11 @@ class CameraControlActivity : AppCompatActivity() {
settingsManager = SettingsManager.getInstance(this)
- // Setup observers for settings manager
settingsManager.isStreaming.observe(this) { streaming ->
- if (!streaming) {
- finish()
- }
+ if (!streaming) finish()
}
-
- settingsManager.torchEnabled.observe(this) { torchEnabled ->
- // Handle torch changes
- }
-
- settingsManager.autoFocus.observe(this) { autoFocus ->
- // Handle auto focus changes
- }
-
settingsManager.exposure.observe(this) { exposure ->
- runOnUiThread {
- exposureLabel.text = "Exposure: $exposure"
- }
- }
-
- settingsManager.camera.observe(this) { camera ->
- // Handle camera switching
+ exposureLabel.text = "EV: $exposure"
}
previewView = findViewById(R.id.previewView)
@@ -99,150 +95,160 @@ class CameraControlActivity : AppCompatActivity() {
cameraSpinner = findViewById(R.id.cameraSpinner)
exposureLabel = findViewById(R.id.exposureLabel)
focusRing = findViewById(R.id.focusRing)
+ zoomSeekBar = findViewById(R.id.zoomSeekBar)
+ zoomLabel = findViewById(R.id.zoomLabel)
try {
- cameraManager = getSystemService(android.content.Context.CAMERA_SERVICE) as android.hardware.camera2.CameraManager
+ cameraManager = getSystemService(Context.CAMERA_SERVICE) as android.hardware.camera2.CameraManager
} catch (_: Exception) {}
-
- // Check if streaming service is running to determine camera mode
+
val prefs = getSharedPreferences("handy_prefs", Context.MODE_PRIVATE)
val isStreaming = prefs.getBoolean("isStreaming", false)
- useCameraX = prefs.getBoolean("useAvc", false).not() // CameraX for MJPEG, Camera2 for AVC
-
+ useCameraX = !prefs.getBoolean("useAvc", false)
+
if (!isStreaming) {
Toast.makeText(this, "Please start streaming first", Toast.LENGTH_LONG).show()
finish()
return
}
+ setupFlashButton()
+ setupCameraSpinner()
+ setupZoomControls()
+ setupTouchGestures()
+
+ cameraReadyReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ runOnUiThread { updateCameraControls() }
+ }
+ }
+ registerReceiver(cameraReadyReceiver, IntentFilter("com.example.handycam.CAMERA_READY"), Context.RECEIVER_NOT_EXPORTED)
+
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+ cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
+ } else setupPreview()
+ }
+
+ private fun setupFlashButton() {
flashBtn.setOnClickListener {
val ctrl = SharedSurfaceProvider.cameraControl
val mgr = cameraManager
- if (ctrl == null) {
- // Try to toggle via CameraManager as a fallback
- val prefs = getSharedPreferences("handy_prefs", Context.MODE_PRIVATE)
- val prefCam = prefs.getString("camera", null)
- val camIdFallback = try {
- if (prefCam != null && mgr?.cameraIdList?.contains(prefCam) == true) prefCam else mgr?.cameraIdList?.getOrNull(cameraSpinner.selectedItemPosition)
- } catch (_: Exception) { null }
- if (camIdFallback != null) {
- lifecycleScope.launch(Dispatchers.Main) {
- try {
- val info = SharedSurfaceProvider.cameraInfo
- val currentTorch = info?.torchState?.value
- val enable = currentTorch != androidx.camera.core.TorchState.ON
- try {
- mgr?.setTorchMode(camIdFallback, enable)
- } catch (e: Exception) {
- Log.w(TAG, "CameraManager.setTorchMode failed", e)
- Toast.makeText(this@CameraControlActivity, "Torch toggle failed", Toast.LENGTH_SHORT).show()
- }
- } catch (e: Exception) {
- Log.w(TAG, "Torch toggle fallback failed", e)
- Toast.makeText(this@CameraControlActivity, "Torch toggle failed", Toast.LENGTH_SHORT).show()
- }
- }
- } else {
- Toast.makeText(this, "Camera not ready", Toast.LENGTH_SHORT).show()
- }
- } else {
- // toggle torch via CameraX control, with CameraManager fallback
- lifecycleScope.launch(Dispatchers.Main) {
- try {
- val info = SharedSurfaceProvider.cameraInfo ?: return@launch
- val torchState = info.torchState.value
- val enable = torchState != androidx.camera.core.TorchState.ON
- try {
- ctrl.enableTorch(enable)
- } catch (e: Exception) {
- // Some implementations return a ListenableFuture; attempt to call anyway
- try {
- val f = ctrl.javaClass.getMethod("enableTorch", Boolean::class.javaPrimitiveType).invoke(ctrl, enable)
- } catch (_: Exception) {}
- }
-
- // Also attempt CameraManager fallback to ensure hardware torch toggles on some devices
- try {
- val prefs = getSharedPreferences("handy_prefs", Context.MODE_PRIVATE)
- val prefCam = prefs.getString("camera", null)
- val camIdFallback = try { if (prefCam != null && mgr?.cameraIdList?.contains(prefCam) == true) prefCam else mgr?.cameraIdList?.getOrNull(cameraSpinner.selectedItemPosition) } catch (_: Exception) { null }
- if (camIdFallback != null) {
- try { mgr?.setTorchMode(camIdFallback, enable) } catch (_: Exception) {}
- }
- } catch (_: Exception) {}
- } catch (e: Exception) {
- Log.w(TAG, "Torch toggle failed", e)
- Toast.makeText(this@CameraControlActivity, "Torch toggle failed", Toast.LENGTH_SHORT).show()
+ lifecycleScope.launch(Dispatchers.Main) {
+ try {
+ val info = SharedSurfaceProvider.cameraInfo
+ val enable = info?.torchState?.value != androidx.camera.core.TorchState.ON
+ if (ctrl != null) {
+ ctrl.enableTorch(enable)
+ } else {
+ val prefs = getSharedPreferences("handy_prefs", Context.MODE_PRIVATE)
+ val prefCam = prefs.getString("camera", null)
+ val camId = try {
+ if (prefCam != null && mgr?.cameraIdList?.contains(prefCam) == true) prefCam
+ else mgr?.cameraIdList?.getOrNull(cameraSpinner.selectedItemPosition)
+ } catch (_: Exception) { null }
+ if (camId != null) mgr?.setTorchMode(camId, enable)
}
+ settingsManager.setTorchEnabled(enable)
+ updateFlashButton(enable)
+ } catch (e: Exception) {
+ Log.w(TAG, "Torch toggle failed", e)
+ Toast.makeText(this@CameraControlActivity, "Torch toggle failed", Toast.LENGTH_SHORT).show()
}
}
}
+ }
- // populate camera list into spinner
+ private fun setupCameraSpinner() {
try {
- val mgr = cameraManager
- if (mgr != null) {
- val ids = mgr.cameraIdList.toList()
- val labels = mutableListOf()
- for (id in ids) {
- try {
- val chars = mgr.getCameraCharacteristics(id)
- val facing = when (chars.get(android.hardware.camera2.CameraCharacteristics.LENS_FACING)) {
- android.hardware.camera2.CameraCharacteristics.LENS_FACING_FRONT -> "front"
- android.hardware.camera2.CameraCharacteristics.LENS_FACING_BACK -> "back"
- else -> "unknown"
- }
- labels.add("$id ($facing)")
- } catch (_: Exception) {
- labels.add(id)
+ val mgr = cameraManager ?: return
+ val ids = mgr.cameraIdList.toList()
+ val labels = ids.map { id ->
+ try {
+ val chars = mgr.getCameraCharacteristics(id)
+ val facing = when (chars.get(android.hardware.camera2.CameraCharacteristics.LENS_FACING)) {
+ android.hardware.camera2.CameraCharacteristics.LENS_FACING_FRONT -> "front"
+ android.hardware.camera2.CameraCharacteristics.LENS_FACING_BACK -> "back"
+ else -> "unknown"
}
- }
- val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, labels).also { it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) }
- cameraSpinner.adapter = adapter
- cameraSpinner.setSelection(0)
- cameraSpinner.setOnItemSelectedListener(object : android.widget.AdapterView.OnItemSelectedListener {
- override fun onItemSelected(parent: android.widget.AdapterView<*>, view: View?, position: Int, id: Long) {
- val camId = try { mgr.cameraIdList[position] } catch (_: Exception) { null }
- if (camId != null) {
- // Tell the service to switch camera
- val intent = Intent(this@CameraControlActivity, StreamService::class.java).apply {
- action = "com.example.handycam.ACTION_SET_CAMERA"
- putExtra("camera", camId)
- }
- startService(intent)
- }
- }
- override fun onNothingSelected(parent: android.widget.AdapterView<*>) {}
- })
+ "$id ($facing)"
+ } catch (_: Exception) { id }
+ }
+ val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, labels).also {
+ it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
+ cameraSpinner.adapter = adapter
+ cameraSpinner.setOnItemSelectedListener(object : android.widget.AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: android.widget.AdapterView<*>, view: View?, position: Int, id: Long) {
+ val camId = try { mgr.cameraIdList[position] } catch (_: Exception) { null } ?: return
+ startService(Intent(this@CameraControlActivity, StreamService::class.java).apply {
+ action = "com.example.handycam.ACTION_SET_CAMERA"
+ putExtra("camera", camId)
+ })
+ }
+ override fun onNothingSelected(parent: android.widget.AdapterView<*>) {}
+ })
} catch (_: Exception) {}
+ }
+
+ private fun setupZoomControls() {
+ zoomSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+ if (fromUser) applyZoom(progress / 100f)
+ }
+ override fun onStartTrackingTouch(seekBar: SeekBar) {}
+ override fun onStopTrackingTouch(seekBar: SeekBar) {}
+ })
+
+ scaleGestureDetector = ScaleGestureDetector(this, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
+ val info = SharedSurfaceProvider.cameraInfo
+ val maxZoom = info?.zoomState?.value?.maxZoomRatio ?: 8f
+ val minZoom = info?.zoomState?.value?.minZoomRatio ?: 1f
+ val currentZoom = minZoom + (maxZoom - minZoom) * currentLinearZoom
+ val newZoom = (currentZoom * detector.scaleFactor).coerceIn(minZoom, maxZoom)
+ val linear = ((newZoom - minZoom) / (maxZoom - minZoom)).coerceIn(0f, 1f)
+ applyZoom(linear)
+ return true
+ }
+ })
+ }
- // gestures: tap to focus, long-press then slide vertically to change exposure
+ private fun applyZoom(linearZoom: Float) {
+ currentLinearZoom = linearZoom
+ settingsManager.setZoom(linearZoom)
+ val info = SharedSurfaceProvider.cameraInfo
+ val zoomRatio = info?.zoomState?.value?.let { state ->
+ state.minZoomRatio + (state.maxZoomRatio - state.minZoomRatio) * linearZoom
+ } ?: (1f + 7f * linearZoom)
+ zoomLabel.text = String.format("%.1fx", zoomRatio)
+ }
+
+
+ private fun setupTouchGestures() {
val gd = object : android.view.GestureDetector.SimpleOnGestureListener() {
- override fun onSingleTapUp(e: android.view.MotionEvent): Boolean {
+ override fun onSingleTapUp(e: MotionEvent): Boolean {
performTapToFocus(e.x, e.y)
return true
}
-
- override fun onLongPress(e: android.view.MotionEvent) {
+ override fun onLongPress(e: MotionEvent) {
adjustingExposure = true
exposureStartY = e.y
- // snapshot current exposure index
lastExposureIndex = SharedSurfaceProvider.cameraInfo?.exposureState?.exposureCompensationIndex ?: 0
exposureLabel.visibility = View.VISIBLE
}
}
-
val gestureDetector = android.view.GestureDetector(this, gd)
previewView.setOnTouchListener { _, motion ->
- gestureDetector.onTouchEvent(motion)
+ scaleGestureDetector.onTouchEvent(motion)
+ if (!scaleGestureDetector.isInProgress) {
+ gestureDetector.onTouchEvent(motion)
+ }
when (motion.actionMasked) {
MotionEvent.ACTION_MOVE -> {
- if (adjustingExposure) {
+ if (adjustingExposure && !scaleGestureDetector.isInProgress) {
val dy = exposureStartY - motion.y
val height = previewView.height.coerceAtLeast(1)
- // map dy to exposure range
val range = SharedSurfaceProvider.cameraInfo?.exposureState?.exposureCompensationRange
if (range != null) {
val span = range.upper - range.lower
@@ -252,6 +258,7 @@ class CameraControlActivity : AppCompatActivity() {
try {
SharedSurfaceProvider.cameraControl?.setExposureCompensationIndex(target)
exposureLabel.text = "EV: $target"
+ settingsManager.setExposure(target)
} catch (e: Exception) {
Log.w(TAG, "Failed setting exposure index", e)
}
@@ -268,63 +275,41 @@ class CameraControlActivity : AppCompatActivity() {
}
true
}
-
- // Register receiver to know when camera is ready
- cameraReadyReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- Log.i(TAG, "Camera ready signal received")
- runOnUiThread {
- updateCameraControls()
- }
- }
- }
- registerReceiver(cameraReadyReceiver, IntentFilter("com.example.handycam.CAMERA_READY"), Context.RECEIVER_NOT_EXPORTED)
-
- // permission check
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
- cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
- } else setupPreview()
}
private fun setupPreview() {
- // For both modes, provide surface provider to the service
- // The service will handle Camera2 or CameraX appropriately
SharedSurfaceProvider.previewSurfaceProvider = previewView.surfaceProvider
-
- // Notify service to reconfigure with preview
- val intent = Intent(this, StreamService::class.java).apply {
+ startService(Intent(this, StreamService::class.java).apply {
action = "com.example.handycam.ACTION_SET_PREVIEW_SURFACE"
putExtra("surfaceToken", if (useCameraX) "camerax_preview" else "camera2_preview")
- }
- startService(intent)
-
- Log.i(TAG, "Setup preview surface provider for ${if (useCameraX) "CameraX" else "Camera2"} mode")
-
- // Update controls with current camera state
+ })
updateCameraControls()
}
-
+
private fun updateCameraControls() {
- // Update UI with current camera state from shared provider
SharedSurfaceProvider.cameraInfo?.let { info ->
- val torchState = info.torchState.value
- try {
- // set lightning bolt and tint it to indicate state
- flashBtn.setImageResource(R.drawable.ic_flash)
- val color = if (torchState == androidx.camera.core.TorchState.ON) android.graphics.Color.YELLOW else android.graphics.Color.WHITE
- flashBtn.imageTintList = android.content.res.ColorStateList.valueOf(color)
- } catch (_: Exception) {}
+ val torchOn = info.torchState.value == androidx.camera.core.TorchState.ON
+ updateFlashButton(torchOn)
val ev = info.exposureState.exposureCompensationIndex
exposureLabel.text = "EV: $ev"
+ info.zoomState.value?.let { zoomState ->
+ val linear = ((zoomState.zoomRatio - zoomState.minZoomRatio) /
+ (zoomState.maxZoomRatio - zoomState.minZoomRatio)).coerceIn(0f, 1f)
+ currentLinearZoom = linear
+ zoomSeekBar.progress = (linear * 100).toInt()
+ zoomLabel.text = String.format("%.1fx", zoomState.zoomRatio)
+ }
} ?: run {
- // If CameraInfo not available yet, try to infer flash availability from CameraManager
try {
val prefs = getSharedPreferences("handy_prefs", Context.MODE_PRIVATE)
val prefCam = prefs.getString("camera", null)
val mgr = cameraManager
- val camIdFallback = try { if (prefCam != null && mgr?.cameraIdList?.contains(prefCam) == true) prefCam else mgr?.cameraIdList?.getOrNull(cameraSpinner.selectedItemPosition) } catch (_: Exception) { null }
- if (camIdFallback != null) {
- val chars = mgr?.getCameraCharacteristics(camIdFallback)
+ val camId = try {
+ if (prefCam != null && mgr?.cameraIdList?.contains(prefCam) == true) prefCam
+ else mgr?.cameraIdList?.getOrNull(cameraSpinner.selectedItemPosition)
+ } catch (_: Exception) { null }
+ if (camId != null) {
+ val chars = mgr?.getCameraCharacteristics(camId)
val hasFlash = chars?.get(android.hardware.camera2.CameraCharacteristics.FLASH_INFO_AVAILABLE) == true
flashBtn.setImageResource(R.drawable.ic_flash)
flashBtn.imageTintList = android.content.res.ColorStateList.valueOf(android.graphics.Color.WHITE)
@@ -334,6 +319,14 @@ class CameraControlActivity : AppCompatActivity() {
}
}
+ private fun updateFlashButton(enabled: Boolean) {
+ try {
+ flashBtn.setImageResource(R.drawable.ic_flash)
+ val color = if (enabled) android.graphics.Color.YELLOW else android.graphics.Color.WHITE
+ flashBtn.imageTintList = android.content.res.ColorStateList.valueOf(color)
+ } catch (_: Exception) {}
+ }
+
private fun performTapToFocus(x: Float, y: Float) {
try {
val factory: MeteringPointFactory = previewView.meteringPointFactory
@@ -342,33 +335,20 @@ class CameraControlActivity : AppCompatActivity() {
.setAutoCancelDuration(3_000, TimeUnit.MILLISECONDS)
.build()
SharedSurfaceProvider.cameraControl?.startFocusAndMetering(action)
- // show focus ring at touch point
- try {
- val ring = focusRing
- val halfW = ring.width / 2f
- val halfH = ring.height / 2f
- // position: previewView's coordinate space -> parent FrameLayout
- val location = IntArray(2)
- previewView.getLocationOnScreen(location)
- val pvX = location[0]
- val pvY = location[1]
- // get parent location
- val parentLoc = IntArray(2)
- (ring.parent as View).getLocationOnScreen(parentLoc)
- val relX = x + pvX - parentLoc[0]
- val relY = y + pvY - parentLoc[1]
- ring.translationX = relX - halfW
- ring.translationY = relY - halfH
- ring.scaleX = 0.6f
- ring.scaleY = 0.6f
- ring.alpha = 1f
- ring.visibility = View.VISIBLE
- ring.animate().scaleX(1f).scaleY(1f).alpha(0f).setDuration(650).withEndAction {
- ring.visibility = View.GONE
- }.start()
- } catch (e: Exception) {
- Log.w(TAG, "Failed to show focus ring", e)
- }
+ val ring = focusRing
+ val halfW = ring.width / 2f
+ val halfH = ring.height / 2f
+ val location = IntArray(2)
+ previewView.getLocationOnScreen(location)
+ val parentLoc = IntArray(2)
+ (ring.parent as View).getLocationOnScreen(parentLoc)
+ ring.translationX = x + location[0] - parentLoc[0] - halfW
+ ring.translationY = y + location[1] - parentLoc[1] - halfH
+ ring.scaleX = 0.6f; ring.scaleY = 0.6f; ring.alpha = 1f
+ ring.visibility = View.VISIBLE
+ ring.animate().scaleX(1f).scaleY(1f).alpha(0f).setDuration(650).withEndAction {
+ ring.visibility = View.GONE
+ }.start()
} catch (e: Exception) {
Log.w(TAG, "Tap-to-focus failed", e)
}
@@ -376,23 +356,14 @@ class CameraControlActivity : AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
-
- // Clean up preview surface
- if (useCameraX) {
- SharedSurfaceProvider.previewSurfaceProvider = null
- } else {
- SharedSurfaceProvider.previewSurface = null
- }
-
- // Notify service to remove preview
+ if (useCameraX) SharedSurfaceProvider.previewSurfaceProvider = null
+ else SharedSurfaceProvider.previewSurface = null
try {
- val intent = Intent(this, StreamService::class.java).apply {
+ startService(Intent(this, StreamService::class.java).apply {
action = "com.example.handycam.ACTION_SET_PREVIEW_SURFACE"
putExtra("surfaceToken", null as String?)
- }
- startService(intent)
+ })
} catch (_: Exception) {}
-
try { cameraReadyReceiver?.let { unregisterReceiver(it) } } catch (_: Exception) {}
}
}
diff --git a/app/src/main/java/com/example/handycam/MainActivity.kt b/app/src/main/java/com/example/handycam/MainActivity.kt
index d1be805..fc93b50 100644
--- a/app/src/main/java/com/example/handycam/MainActivity.kt
+++ b/app/src/main/java/com/example/handycam/MainActivity.kt
@@ -1,28 +1,26 @@
package com.example.handycam
import android.Manifest
+import android.content.BroadcastReceiver
import android.os.Build
import android.content.Intent
import android.content.pm.PackageManager
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.os.Bundle
-import android.widget.Button
-import android.widget.EditText
-import android.widget.LinearLayout
-import android.widget.Switch
-import android.widget.TextView
+import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import android.content.Context
-import android.view.View
-import android.util.Size
+import com.google.android.material.tabs.TabLayout
import java.net.Inet4Address
import java.net.NetworkInterface
-import androidx.camera.core.CameraControl
+import java.util.Locale
+import com.example.handycam.databinding.ActivityMainBinding
private const val DEFAULT_PORT = 4747
+private const val PREFS_NAME = "handy_prefs"
class MainActivity : AppCompatActivity() {
@@ -45,23 +43,23 @@ class MainActivity : AppCompatActivity() {
}
private var isStreaming = false
- private var streamStateReceiver: android.content.BroadcastReceiver? = null
- private var httpsServerStateReceiver: android.content.BroadcastReceiver? = null
- private val PREFS = "handy_prefs"
- private var pendingStartBundle: android.os.Bundle? = null
+ private var streamStateReceiver: BroadcastReceiver? = null
+ private var httpsServerStateReceiver: BroadcastReceiver? = null
+ private var pendingStartBundle: Bundle? = null
private var isHttpsServerRunning = false
private var pagerAdapter: SettingsPagerAdapter? = null
private lateinit var settingsManager: SettingsManager
+ private lateinit var binding: ActivityMainBinding
+ private val cameraList = mutableListOf>()
private fun tryStartPendingIfPermsGranted() {
val b = pendingStartBundle ?: return
- // check permissions
val camGranted = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
val fgPerm = "android.permission.FOREGROUND_SERVICE_CAMERA"
val fgGranted = if (Build.VERSION.SDK_INT >= 34) {
ContextCompat.checkSelfPermission(this, fgPerm) == PackageManager.PERMISSION_GRANTED
} else true
- val notifGranted = if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
+ val notifGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
} else true
@@ -74,437 +72,299 @@ class MainActivity : AppCompatActivity() {
val jpeg = b.getInt("jpegQuality", 85)
val fps = b.getInt("fps", 50)
val useAvc = b.getBoolean("useAvc", false)
- try {
- startStreaming(host, port, width, height, camera, jpeg, fps, useAvc)
- isStreaming = true
- pendingStartBundle = null
- try {
- val btn = findViewById