feat: codec selection in settings (Opus / Opus Low / Codec2)
Some checks failed
Mirror to GitHub / mirror (push) Failing after 41s
Build Release Binaries / build-amd64 (push) Failing after 3m41s

- Settings UI: radio buttons for encode codec selection
- Persisted via SettingsRepository
- Passed through WzpEngine.startCall(profile=) → JNI → Rust CallStartConfig
- Decode always accepts all codecs (per-packet codec_id switch)
- 0 = Opus 24k (GOOD), 1 = Opus 6k (DEGRADED), 2 = Codec2 1.2k (CATASTROPHIC)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-07 10:50:01 +04:00
parent 00fa109f07
commit a8dc350a65
5 changed files with 55 additions and 5 deletions

View File

@@ -109,6 +109,10 @@ class CallViewModel : ViewModel(), WzpCallback {
private val _debugRecording = MutableStateFlow(false)
val debugRecording: StateFlow<Boolean> = _debugRecording.asStateFlow()
// 0 = Opus (GOOD), 1 = Opus Low (DEGRADED), 2 = Codec2 (CATASTROPHIC)
private val _codecChoice = MutableStateFlow(0)
val codecChoice: StateFlow<Int> = _codecChoice.asStateFlow()
/** True when a call just ended and debug report can be sent. */
private val _debugReportAvailable = MutableStateFlow(false)
val debugReportAvailable: StateFlow<Boolean> = _debugReportAvailable.asStateFlow()
@@ -164,6 +168,7 @@ class CallViewModel : ViewModel(), WzpCallback {
_seedHex.value = s.getOrCreateSeedHex()
_aecEnabled.value = s.loadAecEnabled()
_debugRecording.value = s.loadDebugRecording()
_codecChoice.value = s.loadCodecChoice()
_recentRooms.value = s.loadRecentRooms()
}
@@ -309,6 +314,11 @@ class CallViewModel : ViewModel(), WzpCallback {
settings?.saveDebugRecording(enabled)
}
fun setCodecChoice(choice: Int) {
_codecChoice.value = choice
settings?.saveCodecChoice(choice)
}
/**
* Resolve DNS hostname to IP address on the Kotlin/Android side,
* since Rust's DNS resolution may not work on Android.
@@ -406,7 +416,7 @@ class CallViewModel : ViewModel(), WzpCallback {
val seed = _seedHex.value
val name = _alias.value
Log.i(TAG, "startCall: resolved=$relay, alias=$name, calling engine.startCall")
val result = engine?.startCall(relay, room, seedHex = seed, alias = name) ?: -1
val result = engine?.startCall(relay, room, seedHex = seed, alias = name, profile = _codecChoice.value) ?: -1
Log.i(TAG, "startCall: engine returned $result")
// Only wire up notification callback after engine is running
CallService.onStopFromNotification = { stopCall() }

View File

@@ -1,5 +1,6 @@
package com.wzp.ui.settings
import androidx.compose.foundation.clickable
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
@@ -22,6 +23,7 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.RadioButton
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.IconButtonDefaults
@@ -241,6 +243,35 @@ fun SettingsScreen(
)
}
Spacer(modifier = Modifier.height(12.dp))
// Codec selection
val codecNames = listOf("Opus 24k (Best)", "Opus 6k (Low BW)", "Codec2 1.2k (Minimal)")
val currentCodec by viewModel.codecChoice.collectAsState()
Text("Encode Codec", style = MaterialTheme.typography.bodyMedium)
Text(
text = "Decode always accepts all codecs",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(4.dp))
codecNames.forEachIndexed { idx, name ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable { viewModel.setCodecChoice(idx) }
.padding(vertical = 4.dp)
) {
RadioButton(
selected = currentCodec == idx,
onClick = { viewModel.setCodecChoice(idx) }
)
Spacer(modifier = Modifier.width(8.dp))
Text(name, style = MaterialTheme.typography.bodyMedium)
}
}
Spacer(modifier = Modifier.height(24.dp))
Divider()
Spacer(modifier = Modifier.height(16.dp))