bgp/ncd: владение conn через NCD-handle в senders_list; call audio route persist + incoming call sound
This commit is contained in:
1 parent
b11e77e4c0
commit
b483335c06
17 files changed
+185
-48
No files matched your search
@@ -35,6 +35,8 @@ class CallAudioRouter {
|
||||
|
||||
@Volatile var route = AudioRoute.SPEAKER
|
||||
private set
|
||||
@Volatile var preferredRoute = AudioRoute.SPEAKER
|
||||
private set
|
||||
@Volatile var headsetAvailable = false
|
||||
private set
|
||||
@Volatile var hasEarpiece = true
|
||||
@@ -71,7 +73,7 @@ class CallAudioRouter {
|
||||
bindBtHeadsetProfile()
|
||||
|
||||
hasEarpiece = findBuiltinEarpiece() != null
|
||||
route = AudioRoute.SPEAKER
|
||||
route = preferredRoute
|
||||
applyRoute(route)
|
||||
refresh()
|
||||
LogManager.addLog("INFO", "CallAudioRouter", "started (hasEarpiece=$hasEarpiece)")
|
||||
@@ -102,11 +104,18 @@ class CallAudioRouter {
|
||||
}
|
||||
if (route == r) return
|
||||
route = r
|
||||
preferredRoute = r
|
||||
applyRoute(r)
|
||||
LogManager.addLog("INFO", "CallAudioRouter", "route -> $r (headsetAvailable=$headsetAvailable)")
|
||||
onChanged?.invoke(route, headsetAvailable)
|
||||
}
|
||||
|
||||
/** Инжекция сохранённого источника аудио (из DataStore) перед стартом звонка. */
|
||||
fun setPreferredRoute(r: AudioRoute) {
|
||||
preferredRoute = r
|
||||
LogManager.addLog("INFO", "CallAudioRouter", "preferredRoute -> $r")
|
||||
}
|
||||
|
||||
/* ── применение маршрута ── */
|
||||
private fun applyRoute(r: AudioRoute) {
|
||||
try {
|
||||
@@ -200,6 +209,12 @@ class CallAudioRouter {
|
||||
applyRoute(route)
|
||||
LogManager.addLog("INFO", "CallAudioRouter", "headset lost, fallback route -> EARPIECE")
|
||||
}
|
||||
/* гарнитура появилась, а пользователь ранее выбрал её — восстанавливаем */
|
||||
if (now && !prev && preferredRoute == AudioRoute.HEADSET && route != AudioRoute.HEADSET) {
|
||||
route = AudioRoute.HEADSET
|
||||
applyRoute(route)
|
||||
LogManager.addLog("INFO", "CallAudioRouter", "headset available, restore preferred route -> HEADSET")
|
||||
}
|
||||
if (now != prev || route == AudioRoute.HEADSET) {
|
||||
onChanged?.invoke(route, now)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ data class NodeStatus(
|
||||
val bgpRtt: Int = 0,
|
||||
val bgpNodes: Int = 0
|
||||
) {
|
||||
val online: Boolean get() = (directPresence && directUp) || (indirectPresence && indirectUp)
|
||||
val online: Boolean get() = hasPresence
|
||||
val hasPresence: Boolean get() = directPresence || indirectPresence || bgpPresence
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ class ConfigProvider(private val context: Context) {
|
||||
private val serviceEnabledKey = booleanPreferencesKey("service_enabled")
|
||||
private val soundEnabledKey = booleanPreferencesKey("sound_enabled")
|
||||
private val mutedChannelsKey = stringSetPreferencesKey("muted_channels")
|
||||
private val callAudioRouteKey = stringPreferencesKey("call_audio_route")
|
||||
|
||||
private fun keysFilePath(): String {
|
||||
val dir = runBlocking { context.dataStore.data.first()[dbPath] } ?: context.filesDir.absolutePath
|
||||
@@ -247,6 +248,17 @@ class ConfigProvider(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Audio route for calls (запоминание источника аудио) ── */
|
||||
|
||||
fun getCallAudioRoute(): AudioRoute = runBlocking {
|
||||
val s = context.dataStore.data.first()[callAudioRouteKey]
|
||||
AudioRoute.entries.firstOrNull { it.name == s } ?: AudioRoute.SPEAKER
|
||||
}
|
||||
|
||||
suspend fun setCallAudioRoute(r: AudioRoute) {
|
||||
context.dataStore.edit { prefs -> prefs[callAudioRouteKey] = r.name }
|
||||
}
|
||||
|
||||
/* ── Server entries ── */
|
||||
|
||||
suspend fun detectInterfaces(saved: List<ServerEntry> = emptyList()): List<ServerEntry> {
|
||||
|
||||
+3
-11
@@ -141,17 +141,9 @@ private fun MemberListItem(
|
||||
) {
|
||||
val onlineColor = when {
|
||||
member.connecting -> Color(0xFFFFEB3B) // yellow: connecting
|
||||
member.directUp || member.indirectUp -> Color(0xFF4CAF50) // green: up
|
||||
member.directPresence || member.indirectPresence || member.bgpPresence -> Color(0xFFFFA726) // orange: presence but down
|
||||
member.online -> Color(0xFF4CAF50) // green: reachable (direct/indirect/relay)
|
||||
else -> Color(0xFF9E9E9E) // gray: offline
|
||||
}
|
||||
val connLabel = when {
|
||||
member.connecting -> "connecting"
|
||||
member.directUp -> "direct"
|
||||
member.indirectUp -> "indirect"
|
||||
member.directPresence || member.indirectPresence || member.bgpPresence -> "offline"
|
||||
else -> ""
|
||||
}
|
||||
val bgColor = if (isSelected) MaterialTheme.colorScheme.primaryContainer else Color.Transparent
|
||||
|
||||
Surface(
|
||||
@@ -213,14 +205,14 @@ private fun MemberListItem(
|
||||
else if (member.connecting) parts.add("connecting…")
|
||||
else if (member.directUp) parts.add("direct↑")
|
||||
else if (member.indirectUp) parts.add("indirect↑")
|
||||
else if (member.directPresence || member.indirectPresence || member.bgpPresence) parts.add("down")
|
||||
else if (member.online) parts.add("relay")
|
||||
if (member.rtt > 0) parts.add("${member.rtt}ms")
|
||||
if (parts.isNotEmpty()) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = parts.joinToString(" "),
|
||||
fontSize = 11.sp,
|
||||
color = if (member.isSelf || member.directUp || member.indirectUp)
|
||||
color = if (member.isSelf || member.online)
|
||||
Color(0xFF4CAF50) else Color.Gray
|
||||
)
|
||||
}
|
||||
|
||||
@@ -503,6 +503,8 @@ class ChatViewModel : ViewModel() {
|
||||
val st = _callState.value
|
||||
if (st != null && st.callId == callId) {
|
||||
LogManager.addLog("INFO", "VM", "CALL_ACCEPTED id=0x%016x".format(callId))
|
||||
callAudio.router.setPreferredRoute(
|
||||
ChatApplication.instance.configProvider.getCallAudioRoute())
|
||||
if (!callAudio.start(callId)) {
|
||||
LogManager.addLog("ERROR", "VM", "call audio engine start failed")
|
||||
NativeLib.callHangup(callId)
|
||||
@@ -629,6 +631,7 @@ class ChatViewModel : ViewModel() {
|
||||
|
||||
fun setAudioRoute(r: AudioRoute) {
|
||||
callAudio.router.setRoute(r)
|
||||
viewModelScope.launch { ChatApplication.instance.configProvider.setCallAudioRoute(r) }
|
||||
LogManager.addLog("INFO", "VM", "setAudioRoute -> $r")
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user