fix: auto codec, force-ping button, relay delete button
1. Auto codec: new "Auto" position on quality slider (JNI index 7). When selected, the engine uses the relay's chosen_profile from CallAnswer instead of the local preference. Slider now has 8 positions: Studio 64k → Auto → Codec2 1.2k. 2. Force ping: added refresh button (↻) in Manage Relays dialog header. Calls pingAllServers() to re-check all relays on demand. 3. Delete relay fix: the X button was inside a Surface(onClick=...) which swallowed the touch event. Replaced with a separate Surface that properly intercepts the click. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -485,6 +485,7 @@ fun InCallScreen(
|
||||
onSelect = { idx -> viewModel.selectServer(idx) },
|
||||
onDelete = { idx -> viewModel.removeServer(idx) },
|
||||
onAdd = { addr, label -> viewModel.addServer(addr, label) },
|
||||
onRefresh = { viewModel.pingAllServers() },
|
||||
onDismiss = { showManageRelays = false }
|
||||
)
|
||||
}
|
||||
@@ -513,6 +514,7 @@ private fun ManageRelaysDialog(
|
||||
onSelect: (Int) -> Unit,
|
||||
onDelete: (Int) -> Unit,
|
||||
onAdd: (String, String) -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
var addName by remember { mutableStateOf("") }
|
||||
@@ -528,14 +530,26 @@ private fun ManageRelaysDialog(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text("Manage Relays", color = Color.White, fontWeight = FontWeight.Bold)
|
||||
Surface(
|
||||
onClick = onDismiss,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = DarkSurface2,
|
||||
modifier = Modifier.size(32.dp)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Text("\u00D7", color = TextDim, fontSize = 18.sp)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
Surface(
|
||||
onClick = onRefresh,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = DarkSurface2,
|
||||
modifier = Modifier.size(32.dp)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Text("\u21BB", color = TextDim, fontSize = 16.sp)
|
||||
}
|
||||
}
|
||||
Surface(
|
||||
onClick = onDismiss,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = DarkSurface2,
|
||||
modifier = Modifier.size(32.dp)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Text("\u00D7", color = TextDim, fontSize = 18.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -590,13 +604,17 @@ private fun ManageRelaysDialog(
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
"\u00D7",
|
||||
color = TextDim,
|
||||
fontSize = 18.sp,
|
||||
modifier = Modifier.clickable { onDelete(idx) }
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Surface(
|
||||
onClick = { onDelete(idx) },
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = Color.Transparent,
|
||||
modifier = Modifier.size(32.dp)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Text("\u00D7", color = TextDim, fontSize = 18.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,19 +245,19 @@ fun SettingsScreen(
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// Quality selection — slider from best (studio 64k) to worst (codec2 1.2k)
|
||||
// Quality selection — slider from best (studio 64k) to worst (codec2 1.2k) + auto
|
||||
val qualityLabels = listOf(
|
||||
"Studio 64k", "Studio 48k", "Studio 32k", "Opus 24k",
|
||||
"Opus 6k", "Codec2 1.2k", "Codec2 3.2k"
|
||||
"Studio 64k", "Studio 48k", "Studio 32k", "Auto",
|
||||
"Opus 24k", "Opus 6k", "Codec2 3.2k", "Codec2 1.2k"
|
||||
)
|
||||
// Map slider position to JNI profile int:
|
||||
// 0=Studio64k(6), 1=Studio48k(5), 2=Studio32k(4), 3=Opus24k(0),
|
||||
// 4=Opus6k(1), 5=Codec2_1.2k(2), 6=Codec2_3.2k(3)
|
||||
val sliderToProfile = intArrayOf(6, 5, 4, 0, 1, 2, 3)
|
||||
val profileToSlider = mapOf(6 to 0, 5 to 1, 4 to 2, 0 to 3, 1 to 4, 2 to 5, 3 to 6)
|
||||
// 0=Studio64k(6), 1=Studio48k(5), 2=Studio32k(4), 3=Auto(7),
|
||||
// 4=Opus24k(0), 5=Opus6k(1), 6=Codec2_3.2k(3), 7=Codec2_1.2k(2)
|
||||
val sliderToProfile = intArrayOf(6, 5, 4, 7, 0, 1, 3, 2)
|
||||
val profileToSlider = mapOf(6 to 0, 5 to 1, 4 to 2, 7 to 3, 0 to 4, 1 to 5, 3 to 6, 2 to 7)
|
||||
val qualityColors = listOf(
|
||||
Color(0xFF22C55E), Color(0xFF4ADE80), Color(0xFF86EFAC), Color(0xFFA3E635),
|
||||
Color(0xFFFACC15), Color(0xFF991B1B), Color(0xFFE97320)
|
||||
Color(0xFFA3E635), Color(0xFFFACC15), Color(0xFFE97320), Color(0xFF991B1B)
|
||||
)
|
||||
val currentCodec by viewModel.codecChoice.collectAsState()
|
||||
val sliderPos = profileToSlider[currentCodec] ?: 3
|
||||
@@ -276,8 +276,8 @@ fun SettingsScreen(
|
||||
Slider(
|
||||
value = sliderPos.toFloat(),
|
||||
onValueChange = { viewModel.setCodecChoice(sliderToProfile[it.toInt()]) },
|
||||
valueRange = 0f..6f,
|
||||
steps = 5,
|
||||
valueRange = 0f..7f,
|
||||
steps = 6,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Row(
|
||||
|
||||
@@ -38,6 +38,8 @@ fn frame_samples_for(profile: &QualityProfile) -> usize {
|
||||
/// Configuration to start a call.
|
||||
pub struct CallStartConfig {
|
||||
pub profile: QualityProfile,
|
||||
/// When true, use the relay's chosen_profile from CallAnswer instead of local profile.
|
||||
pub auto_profile: bool,
|
||||
pub relay_addr: String,
|
||||
pub room: String,
|
||||
pub auth_token: Vec<u8>,
|
||||
@@ -49,6 +51,7 @@ impl Default for CallStartConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
profile: QualityProfile::GOOD,
|
||||
auto_profile: false,
|
||||
relay_addr: String::new(),
|
||||
room: String::new(),
|
||||
auth_token: Vec::new(),
|
||||
@@ -126,6 +129,7 @@ impl WzpEngine {
|
||||
let room = config.room.clone();
|
||||
let identity_seed = config.identity_seed;
|
||||
let profile = config.profile;
|
||||
let auto_profile = config.auto_profile;
|
||||
let alias = config.alias.clone();
|
||||
let state = self.state.clone();
|
||||
|
||||
@@ -134,7 +138,7 @@ impl WzpEngine {
|
||||
|
||||
let state_clone = state.clone();
|
||||
runtime.block_on(async move {
|
||||
if let Err(e) = run_call(relay_addr, &room, &identity_seed, profile, alias.as_deref(), state_clone).await
|
||||
if let Err(e) = run_call(relay_addr, &room, &identity_seed, profile, auto_profile, alias.as_deref(), state_clone).await
|
||||
{
|
||||
error!("call failed: {e}");
|
||||
}
|
||||
@@ -277,6 +281,7 @@ async fn run_call(
|
||||
room: &str,
|
||||
identity_seed: &[u8; 32],
|
||||
profile: QualityProfile,
|
||||
auto_profile: bool,
|
||||
alias: Option<&str>,
|
||||
state: Arc<EngineState>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
@@ -328,8 +333,8 @@ async fn run_call(
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("connection closed before CallAnswer"))?;
|
||||
|
||||
let relay_ephemeral_pub = match answer {
|
||||
SignalMessage::CallAnswer { ephemeral_pub, .. } => ephemeral_pub,
|
||||
let (relay_ephemeral_pub, chosen_profile) = match answer {
|
||||
SignalMessage::CallAnswer { ephemeral_pub, chosen_profile, .. } => (ephemeral_pub, chosen_profile),
|
||||
other => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"expected CallAnswer, got {:?}",
|
||||
@@ -338,8 +343,16 @@ async fn run_call(
|
||||
}
|
||||
};
|
||||
|
||||
// Auto mode: use the relay's chosen profile instead of the local preference
|
||||
let profile = if auto_profile {
|
||||
info!(chosen = ?chosen_profile.codec, "auto mode: using relay's chosen profile");
|
||||
chosen_profile
|
||||
} else {
|
||||
profile
|
||||
};
|
||||
|
||||
let _session = kx.derive_session(&relay_ephemeral_pub)?;
|
||||
info!("handshake complete, call active");
|
||||
info!(codec = ?profile.codec, "handshake complete, call active");
|
||||
|
||||
{
|
||||
let mut stats = state.stats.lock().unwrap();
|
||||
|
||||
@@ -21,6 +21,9 @@ unsafe fn handle_ref(handle: jlong) -> &'static mut EngineHandle {
|
||||
unsafe { &mut *(handle as *mut EngineHandle) }
|
||||
}
|
||||
|
||||
/// 7 = auto (use relay's chosen profile)
|
||||
const PROFILE_AUTO: jint = 7;
|
||||
|
||||
fn profile_from_int(value: jint) -> QualityProfile {
|
||||
match value {
|
||||
0 => QualityProfile::GOOD, // Opus 24k
|
||||
@@ -35,7 +38,7 @@ fn profile_from_int(value: jint) -> QualityProfile {
|
||||
4 => QualityProfile::STUDIO_32K, // Opus 32k
|
||||
5 => QualityProfile::STUDIO_48K, // Opus 48k
|
||||
6 => QualityProfile::STUDIO_64K, // Opus 64k
|
||||
_ => QualityProfile::GOOD,
|
||||
_ => QualityProfile::GOOD, // auto falls back to GOOD
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +125,7 @@ pub unsafe extern "system" fn Java_com_wzp_engine_WzpEngine_nativeStartCall(
|
||||
|
||||
let config = CallStartConfig {
|
||||
profile: profile_from_int(profile_j),
|
||||
auto_profile: profile_j == PROFILE_AUTO,
|
||||
relay_addr,
|
||||
room,
|
||||
auth_token: if token.is_empty() { Vec::new() } else { token.into_bytes() },
|
||||
|
||||
Reference in New Issue
Block a user