Fix flush on PTT release; monitoring priority tweaks (v1.0.5)
This commit is contained in:
@@ -65,6 +65,7 @@ class MainActivity : Activity(), SvxService.Listener {
|
||||
|
||||
private var connected = false
|
||||
private var monitoring = false
|
||||
private var priorityActive = false
|
||||
private var settings: ConnectionSettings = ConnectionSettings("", 5300, "", "", 11)
|
||||
|
||||
private var svc: SvxService? = null
|
||||
@@ -159,6 +160,7 @@ class MainActivity : Activity(), SvxService.Listener {
|
||||
}
|
||||
|
||||
override fun onPttUp() {
|
||||
Log.d("SVXRemote", "PTT up")
|
||||
svc?.stopTx()
|
||||
lblPttHint.text = getString(R.string.hold_to_talk)
|
||||
lblTxState.text =
|
||||
@@ -291,17 +293,32 @@ class MainActivity : Activity(), SvxService.Listener {
|
||||
|
||||
override fun onTalkerStart(tg: Long, callsign: String) {
|
||||
mainHandler.post {
|
||||
lblTalkerTitle.text = if (monitoring) {
|
||||
getString(R.string.talker_mon, callsign, tg)
|
||||
} else {
|
||||
getString(R.string.talker_now, callsign)
|
||||
val priority = monitoring && settings.groups.firstOrNull() == tg
|
||||
if (priority) {
|
||||
priorityActive = true
|
||||
}
|
||||
if (priority || !priorityActive) {
|
||||
svc?.selectGroup(tg)
|
||||
lblTalkerTitle.text = when {
|
||||
priority -> getString(R.string.talker_mon_priority, callsign, tg)
|
||||
monitoring -> getString(R.string.talker_mon, callsign, tg)
|
||||
else -> getString(R.string.talker_now, callsign)
|
||||
}
|
||||
lblTalkerTitle.setTextColor(
|
||||
if (priority) getColor(R.color.danger) else getColor(R.color.text_secondary)
|
||||
)
|
||||
lblTalker.text = callsign
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTalkerStop(tg: Long, callsign: String) {
|
||||
// последний говоривший остаётся на экране
|
||||
mainHandler.post {
|
||||
if (monitoring && settings.groups.firstOrNull() == tg) {
|
||||
priorityActive = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNodeJoined(callsign: String, tg: Long?) {
|
||||
@@ -416,6 +433,7 @@ class MainActivity : Activity(), SvxService.Listener {
|
||||
/** Включить/выключить режим мониторинга групп с переподключением. */
|
||||
private fun toggleMonitoring() {
|
||||
monitoring = !monitoring
|
||||
priorityActive = false
|
||||
updateMonitorButton()
|
||||
updatePttEnabled()
|
||||
if (connected) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import ru.ua1zbe.svxremote.audio.OpusEngine
|
||||
import ru.ua1zbe.svxremote.proto.MsgParser
|
||||
import ru.ua1zbe.svxremote.proto.ReflectorClient
|
||||
@@ -87,13 +88,22 @@ class SvxService : Service(), ReflectorClient.Callbacks {
|
||||
|
||||
fun isRunning(): Boolean = client.isRunning()
|
||||
|
||||
fun selectGroup(tg: Long) {
|
||||
client.selectGroup(tg)
|
||||
}
|
||||
|
||||
fun startTx() {
|
||||
engine.startCapture { packet -> client.sendAudio(packet) }
|
||||
}
|
||||
|
||||
fun stopTx() {
|
||||
Log.d("SVXRemote", "stopTx")
|
||||
// В фоне: сеть запрещена в главном потоке (NetworkOnMainThreadException),
|
||||
// а join потока захвата блокировал бы UI
|
||||
Thread {
|
||||
engine.stopCapture()
|
||||
client.sendAudioFlush()
|
||||
}.start()
|
||||
}
|
||||
|
||||
// ---------- Callbacks (фоновые потоки) ----------
|
||||
|
||||
@@ -308,14 +308,43 @@ class OpusEngine(
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
// Дренаж: отдаём пакеты, оставшиеся в кодере после остановки записи
|
||||
try {
|
||||
var outIdx = enc.dequeueOutputBuffer(encodeBufferInfo, 0)
|
||||
while (outIdx >= 0) {
|
||||
if (encodeBufferInfo.size > 0) {
|
||||
val outBuf = ByteBuffer.allocate(encodeBufferInfo.size)
|
||||
enc.getOutputBuffer(outIdx)?.also { src ->
|
||||
src.position(encodeBufferInfo.offset)
|
||||
src.limit(encodeBufferInfo.offset + encodeBufferInfo.size)
|
||||
outBuf.put(src)
|
||||
}
|
||||
onPacket(outBuf.array())
|
||||
}
|
||||
enc.releaseOutputBuffer(outIdx, false)
|
||||
outIdx = enc.dequeueOutputBuffer(encodeBufferInfo, 0)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
fun stopCapture() {
|
||||
if (!encoding && recThread == null) return
|
||||
encoding = false
|
||||
try {
|
||||
recorder?.stop()
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
// Ждём завершения потока захвата: он должен отправить последние пакеты,
|
||||
// чтобы flush (окончание передачи) ушёл после всего аудио
|
||||
val t = recThread
|
||||
recThread = null
|
||||
if (t != null && t.isAlive) {
|
||||
try {
|
||||
t.join(1000)
|
||||
} catch (_: InterruptedException) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
recorder?.release()
|
||||
} catch (_: Exception) {
|
||||
@@ -330,8 +359,6 @@ class OpusEngine(
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
encoder = null
|
||||
recThread?.interrupt()
|
||||
recThread = null
|
||||
onLevel(SRC_MIC, 0f)
|
||||
}
|
||||
|
||||
|
||||
@@ -153,12 +153,30 @@ class ReflectorClient(
|
||||
udpSend(sock, s, data)
|
||||
}
|
||||
|
||||
/** Переключить принимаемую группу на живом соединении. */
|
||||
fun selectGroup(tg: Long) {
|
||||
val sock = tcpSocket
|
||||
if (sock == null || !running.get()) {
|
||||
Log.d(TAG, "selectGroup tg=$tg skipped (sock=${sock != null} running=${running.get()})")
|
||||
return
|
||||
}
|
||||
try {
|
||||
Log.d(TAG, "selectGroup tg=$tg")
|
||||
sendTcp(sock, MsgBuilder.selectTg(tg))
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Конец передачи. */
|
||||
fun sendAudioFlush() {
|
||||
val sock = udpSocket
|
||||
val s = settings
|
||||
val id = clientId
|
||||
if (sock == null || s == null || id == 0) return
|
||||
if (sock == null || s == null || id == 0) {
|
||||
Log.w(TAG, "sendAudioFlush skipped (sock=${sock != null} s=${s != null} id=$id)")
|
||||
return
|
||||
}
|
||||
Log.d(TAG, "sendAudioFlush")
|
||||
udpSend(sock, s, MsgBuilder.udpFlush(id, nextSeq()))
|
||||
}
|
||||
|
||||
@@ -368,7 +386,7 @@ class ReflectorClient(
|
||||
sendTcp(socket, MsgBuilder.selectTg(s.talkGroup))
|
||||
sendTcp(socket, MsgBuilder.tgMonitor(listOf(s.talkGroup)))
|
||||
} else {
|
||||
sendTcp(socket, MsgBuilder.selectTg(0))
|
||||
sendTcp(socket, MsgBuilder.selectTg(monitor.first()))
|
||||
sendTcp(socket, MsgBuilder.tgMonitor(monitor))
|
||||
}
|
||||
openUdp(s)
|
||||
@@ -396,6 +414,7 @@ class ReflectorClient(
|
||||
|
||||
ReflectorProtocol.MSG_ERROR -> {
|
||||
val err = MsgParser.errorMessage(msg.fields)
|
||||
Log.w(TAG, "MSG_ERROR: $err")
|
||||
if (state < STATE_READY) {
|
||||
stopFatal(err.ifEmpty { "Access denied" })
|
||||
return false
|
||||
@@ -498,7 +517,7 @@ class ReflectorClient(
|
||||
val seq = r.u16()
|
||||
val payload = data.copyOfRange(6, data.size)
|
||||
when (type) {
|
||||
ReflectorProtocol.MSG_UDP_HEARTBEAT -> Unit
|
||||
ReflectorProtocol.MSG_UDP_HEARTBEAT -> Log.d(TAG, "UDP RX heartbeat")
|
||||
ReflectorProtocol.MSG_UDP_AUDIO -> {
|
||||
val audio = MsgParser.udpAudio(payload)
|
||||
if (audio != null && audio.isNotEmpty()) {
|
||||
@@ -512,7 +531,7 @@ class ReflectorClient(
|
||||
Log.d(TAG, "UDP RX flush")
|
||||
callbacks.onAudioFlush()
|
||||
}
|
||||
ReflectorProtocol.MSG_UDP_ALL_SAMPLES_FLUSHED -> Unit
|
||||
ReflectorProtocol.MSG_UDP_ALL_SAMPLES_FLUSHED -> Log.d(TAG, "UDP RX all-flushed seq=$seq")
|
||||
else -> Log.d(TAG, "UDP RX type=$type len=${data.size}")
|
||||
}
|
||||
}
|
||||
@@ -536,10 +555,16 @@ class ReflectorClient(
|
||||
}
|
||||
|
||||
private fun sendRawUdp(sock: DatagramSocket, addr: InetAddress, port: Int, data: ByteArray) {
|
||||
if (sock.isClosed) return
|
||||
if (sock.isClosed) {
|
||||
Log.w(TAG, "UDP TX skipped: socket closed")
|
||||
return
|
||||
}
|
||||
try {
|
||||
sock.send(DatagramPacket(data, data.size, addr, port))
|
||||
val typ = if (data.size >= 2) ((data[0].toInt() shl 8) or (data[1].toInt() and 0xFF)) else -1
|
||||
Log.d(TAG, "UDP TX type=$typ len=${data.size}")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "UDP send error: ${e.message}", e)
|
||||
callbacks.onError("UDP send: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<string name="btn_monitor_on">Мониторинг групп включен (нажмите, чтобы выключить)</string>
|
||||
<string name="status_monitoring">Мониторинг: %1$s</string>
|
||||
<string name="talker_mon">Говорит: %1$s · TG%2$d</string>
|
||||
<string name="talker_mon_priority">ПРИОРИТЕТ: %1$s · TG%2$d</string>
|
||||
<string name="status_big_disconnected">DISCONNECTED</string>
|
||||
<string name="status_big_connected">CONNECTED</string>
|
||||
<string name="status_big_talking">ПЕРЕДАЧА</string>
|
||||
|
||||
Reference in New Issue
Block a user