Browse Source

android radio: track independent manual PTT owners

master
evgeny 15 hours ago
parent
commit
1805b3dd27
  1. 15
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioAudioEngine.kt
  2. 6
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioOverlayService.kt
  3. 18
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioPttHolds.kt

15
tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioAudioEngine.kt

@ -72,6 +72,7 @@ class RadioAudioEngine {
@Volatile private var captureRunning = false
@Volatile private var playbackRunning = false
@Volatile private var requestedTalk = false
private val pttHolds = RadioPttHolds()
@Volatile private var talkRequestNs = 0L
@Volatile private var generation = 0L
private var audioGroup = 0L
@ -135,6 +136,7 @@ class RadioAudioEngine {
active = true
groupId = group
requestedTalk = false
pttHolds.cancel()
_talkingFlow.value = false
io.execute {
if (!active || generation != version) return@execute
@ -156,6 +158,7 @@ class RadioAudioEngine {
mainHandler.removeCallbacks(releaseTalk)
active = false
requestedTalk = false
pttHolds.cancel()
_talkingFlow.value = false
++generation
io.execute { releaseLocked() }
@ -513,7 +516,7 @@ class RadioAudioEngine {
restoreAudioMode()
}
fun talkBegin(): Boolean {
internal fun talkBegin(source: RadioPttSource = RadioPttSource.BUTTON): Boolean {
if (!active) return false
if (!MicrophonePermission.granted()) {
MicrophonePermission.request("PTT") {
@ -524,6 +527,8 @@ class RadioAudioEngine {
return false
}
mainHandler.removeCallbacks(releaseTalk)
pttHolds.press(source)
LogManager.addLog("DEBUG", "RadioAudio", "PTT hold source=$source transmitting=$requestedTalk group=$groupId")
if (requestedTalk) return true
talkRequestNs = SystemClock.elapsedRealtimeNanos()
requestedTalk = true
@ -540,15 +545,17 @@ class RadioAudioEngine {
}
/** Отпускание: 250мс передачи, повторное касание отменяет завершение. */
fun talkEnd() {
internal fun talkEnd(source: RadioPttSource = RadioPttSource.BUTTON) {
if (!pttHolds.release(source)) return
if (!active || !requestedTalk) return
mainHandler.removeCallbacks(releaseTalk)
mainHandler.postDelayed(releaseTalk, RELEASE_HOLD_MS)
LogManager.addLog("DEBUG", "RadioAudio", "PTT release scheduled +${RELEASE_HOLD_MS}ms")
LogManager.addLog("DEBUG", "RadioAudio", "PTT release source=$source scheduled +${RELEASE_HOLD_MS}ms")
}
/** Отмена без хвоста передачи при выключении/потере разрешений. */
fun cancelTalk() {
internal fun cancelTalk(source: RadioPttSource? = null) {
if (!pttHolds.cancel(source)) return
mainHandler.removeCallbacks(releaseTalk)
finishTalk()
}

6
tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioOverlayService.kt

@ -286,7 +286,7 @@ class RadioOverlayService : Service() {
private fun removeOverlay() {
val view = overlayView ?: return
talking = false
RadioAudioEngine.instance().cancelTalk()
RadioAudioEngine.instance().cancelTalk(RadioPttSource.OVERLAY)
try { windowManager?.removeView(view) } catch (e: Exception) {
LogManager.addLog("WARN", "RadioOverlay", "Overlay removal failed: $e")
}
@ -453,7 +453,7 @@ class RadioOverlayService : Service() {
private fun beginTalk() {
if (talking) return
if (!RadioAudioEngine.instance().talkBegin()) return
if (!RadioAudioEngine.instance().talkBegin(RadioPttSource.OVERLAY)) return
talking = true
updateButtonColor()
}
@ -461,7 +461,7 @@ class RadioOverlayService : Service() {
private fun endTalk() {
if (!talking) return
talking = false
RadioAudioEngine.instance().talkEnd()
RadioAudioEngine.instance().talkEnd(RadioPttSource.OVERLAY)
updateButtonColor()
}

18
tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioPttHolds.kt

@ -0,0 +1,18 @@
package com.utun.chat.data
/** Независимые владельцы ручной передачи. Все операции выполняются в главном потоке. */
internal enum class RadioPttSource { BUTTON, OVERLAY, VOLUME }
internal class RadioPttHolds {
private val held = mutableSetOf<RadioPttSource>()
fun press(source: RadioPttSource) { held.add(source) }
/** Завершать передачу можно только после отпускания последнего владельца. */
fun release(source: RadioPttSource): Boolean = held.remove(source) && held.isEmpty()
fun cancel(source: RadioPttSource? = null): Boolean {
if (source == null) held.clear() else held.remove(source)
return held.isEmpty()
}
}
Loading…
Cancel
Save