feat: disable outgoing video when phone is near ear during calls
Uses the proximity sensor to detect when the phone is held to the ear and automatically pauses the camera/video track. Video resumes when the phone is moved away, but only if the user had video enabled. https://claude.ai/code/session_014espsysob7MrE8X8e75jFX
This commit is contained in:
@@ -24,6 +24,10 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.hardware.Sensor
|
||||||
|
import android.hardware.SensorEvent
|
||||||
|
import android.hardware.SensorEventListener
|
||||||
|
import android.hardware.SensorManager
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
import android.media.AudioDeviceInfo
|
import android.media.AudioDeviceInfo
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
@@ -62,6 +66,25 @@ class CallAudioManager(
|
|||||||
private val _isBluetoothAvailable = MutableStateFlow(false)
|
private val _isBluetoothAvailable = MutableStateFlow(false)
|
||||||
val isBluetoothAvailable: StateFlow<Boolean> = _isBluetoothAvailable.asStateFlow()
|
val isBluetoothAvailable: StateFlow<Boolean> = _isBluetoothAvailable.asStateFlow()
|
||||||
|
|
||||||
|
// Proximity sensor — detects when the phone is held near the ear
|
||||||
|
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as? SensorManager
|
||||||
|
private val proximitySensor: Sensor? = sensorManager?.getDefaultSensor(Sensor.TYPE_PROXIMITY)
|
||||||
|
private val _isNearEar = MutableStateFlow(false)
|
||||||
|
val isNearEar: StateFlow<Boolean> = _isNearEar.asStateFlow()
|
||||||
|
|
||||||
|
private val proximityListener =
|
||||||
|
object : SensorEventListener {
|
||||||
|
override fun onSensorChanged(event: SensorEvent) {
|
||||||
|
val maxRange = event.sensor.maximumRange
|
||||||
|
_isNearEar.value = event.values[0] < maxRange
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAccuracyChanged(
|
||||||
|
sensor: Sensor?,
|
||||||
|
accuracy: Int,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
fun startRinging() {
|
fun startRinging() {
|
||||||
startRingtone()
|
startRingtone()
|
||||||
startVibration()
|
startVibration()
|
||||||
@@ -137,11 +160,24 @@ class CallAudioManager(
|
|||||||
"amethyst:call_proximity",
|
"amethyst:call_proximity",
|
||||||
)
|
)
|
||||||
proximityWakeLock?.acquire(60 * 60 * 1000L)
|
proximityWakeLock?.acquire(60 * 60 * 1000L)
|
||||||
|
registerProximitySensor()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun releaseProximityWakeLock() {
|
fun releaseProximityWakeLock() {
|
||||||
proximityWakeLock?.let { if (it.isHeld) it.release() }
|
proximityWakeLock?.let { if (it.isHeld) it.release() }
|
||||||
proximityWakeLock = null
|
proximityWakeLock = null
|
||||||
|
unregisterProximitySensor()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerProximitySensor() {
|
||||||
|
proximitySensor?.let {
|
||||||
|
sensorManager?.registerListener(proximityListener, it, SensorManager.SENSOR_DELAY_NORMAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unregisterProximitySensor() {
|
||||||
|
sensorManager?.unregisterListener(proximityListener)
|
||||||
|
_isNearEar.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun release() {
|
fun release() {
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ class CallController(
|
|||||||
val audioRoute: StateFlow<AudioRoute> = audioManager.audioRoute
|
val audioRoute: StateFlow<AudioRoute> = audioManager.audioRoute
|
||||||
val isBluetoothAvailable: StateFlow<Boolean> = audioManager.isBluetoothAvailable
|
val isBluetoothAvailable: StateFlow<Boolean> = audioManager.isBluetoothAvailable
|
||||||
|
|
||||||
|
// Tracks whether video is paused because the phone is near the ear
|
||||||
|
private var videoPausedByProximity = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
callManager.onRenegotiationOfferReceived = { event -> onRenegotiationOfferReceived(event) }
|
callManager.onRenegotiationOfferReceived = { event -> onRenegotiationOfferReceived(event) }
|
||||||
|
|
||||||
@@ -126,6 +129,22 @@ class CallController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pause outgoing video when the phone is held near the ear
|
||||||
|
scope.launch {
|
||||||
|
audioManager.isNearEar.collect { nearEar ->
|
||||||
|
val session = webRtcSession ?: return@collect
|
||||||
|
if (nearEar && _isVideoEnabled.value && !videoPausedByProximity) {
|
||||||
|
videoPausedByProximity = true
|
||||||
|
session.setVideoEnabled(false)
|
||||||
|
session.stopCamera()
|
||||||
|
} else if (!nearEar && videoPausedByProximity) {
|
||||||
|
videoPausedByProximity = false
|
||||||
|
session.setVideoEnabled(true)
|
||||||
|
session.startCamera()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initiateCall(
|
fun initiateCall(
|
||||||
@@ -323,6 +342,7 @@ class CallController(
|
|||||||
_isAudioMuted.value = false
|
_isAudioMuted.value = false
|
||||||
_isVideoEnabled.value = false
|
_isVideoEnabled.value = false
|
||||||
_isRemoteVideoActive.value = false
|
_isRemoteVideoActive.value = false
|
||||||
|
videoPausedByProximity = false
|
||||||
webRtcSession?.dispose()
|
webRtcSession?.dispose()
|
||||||
webRtcSession = null
|
webRtcSession = null
|
||||||
remoteDescriptionSet.set(false)
|
remoteDescriptionSet.set(false)
|
||||||
|
|||||||
Reference in New Issue
Block a user