fix(core): complete voice joins on server accept
This commit is contained in:
@@ -642,13 +642,6 @@ impl ChanoraSession {
|
||||
if let Some(projection) = projection {
|
||||
self.emit_voice_state(projection).await;
|
||||
}
|
||||
// Bring the audio engine up so the user can immediately
|
||||
// hear other speakers + transmit on PTT. Tolerates
|
||||
// failure the same way voice_join does: server-side
|
||||
// we're in the channel regardless; if audio fails
|
||||
// (missing mic permission, no device), the UI will
|
||||
// still expose the controls and the user can resolve
|
||||
// the underlying issue.
|
||||
if let Err(audio_err) = self.ensure_audio_running().await {
|
||||
warn!(
|
||||
target: "chanora_core",
|
||||
@@ -1241,6 +1234,18 @@ impl ChanoraSession {
|
||||
if let Some(pw) = requested_password {
|
||||
state.channel_passwords.insert(channel_id, pw);
|
||||
}
|
||||
let epoch = state.join_state.authoritative.confirmed_epoch;
|
||||
let _ = channel_join::reduce(
|
||||
&mut state.join_state,
|
||||
ChannelJoinEvent::AuthoritativeSelfMove {
|
||||
channel: Some(JoinChannelId(channel_id)),
|
||||
epoch,
|
||||
source: AuthoritativeSource::LiveDelta,
|
||||
},
|
||||
);
|
||||
let projection = channel_join::project(&state.join_state);
|
||||
self.voice_selector.set_in_channel(true);
|
||||
self.emit_voice_state(projection).await;
|
||||
drop(guard);
|
||||
// 2. Bring the audio engine up. Tolerate failure: the
|
||||
// server-side channel move has ALREADY succeeded (step
|
||||
@@ -1272,78 +1277,7 @@ impl ChanoraSession {
|
||||
// UI gains the channel + mute + PTT controls.
|
||||
let _ = self.events_tx.send(SessionEvent::AudioStopped);
|
||||
}
|
||||
// 3. Belt-and-braces: if the server replied Ok but never
|
||||
// actually moved us (legacy server, command processed
|
||||
// but rolled back later, etc.) the snapshot poll catches
|
||||
// it. Keeps the move/confirm contract honest even when
|
||||
// upstream tsclientlib changes shape.
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_millis(1500);
|
||||
let confirmed = loop {
|
||||
if let Ok(snap) = self.snapshot().await {
|
||||
if let Some((my_id, my_channel)) = self.find_own_in(&snap).await {
|
||||
if my_channel == channel_id {
|
||||
info!(
|
||||
target: "chanora_core",
|
||||
client_id = my_id,
|
||||
channel_id,
|
||||
"voice_join confirmed by snapshot"
|
||||
);
|
||||
break true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if std::time::Instant::now() >= deadline {
|
||||
break false;
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(80)).await;
|
||||
};
|
||||
if !confirmed {
|
||||
let mut guard = self.inner.lock().await;
|
||||
if let Some(state) = guard.as_mut() {
|
||||
let _ = channel_join::reduce(
|
||||
&mut state.join_state,
|
||||
ChannelJoinEvent::JoinTimeout {
|
||||
key: pending_key,
|
||||
now: std::time::Instant::now(),
|
||||
},
|
||||
);
|
||||
let projection = channel_join::project(&state.join_state);
|
||||
let still_in_channel = projection.current_channel.is_some();
|
||||
self.voice_selector.set_in_channel(still_in_channel);
|
||||
self.emit_voice_state(projection).await;
|
||||
}
|
||||
return Err(CoreError::Protocol(
|
||||
chanora_protocol::ProtocolError::ServerRejected {
|
||||
// Use a sentinel "unknown" code (the canonical
|
||||
// TS3 error catalogue uses 0x0001 for `undefined`).
|
||||
code: 0x0001,
|
||||
message: "channel move did not take effect server-side within timeout"
|
||||
.to_string(),
|
||||
},
|
||||
));
|
||||
}
|
||||
let mut guard = self.inner.lock().await;
|
||||
if let Some(state) = guard.as_mut() {
|
||||
let epoch = state.join_state.authoritative.confirmed_epoch;
|
||||
let _ = channel_join::reduce(
|
||||
&mut state.join_state,
|
||||
ChannelJoinEvent::AuthoritativeSelfMove {
|
||||
channel: Some(JoinChannelId(channel_id)),
|
||||
epoch,
|
||||
source: AuthoritativeSource::Snapshot,
|
||||
},
|
||||
);
|
||||
}
|
||||
self.voice_selector.set_in_channel(true);
|
||||
let projection = {
|
||||
let guard = self.inner.lock().await;
|
||||
guard
|
||||
.as_ref()
|
||||
.map(|state| channel_join::project(&state.join_state))
|
||||
};
|
||||
if let Some(projection) = projection {
|
||||
self.emit_voice_state(projection).await;
|
||||
}
|
||||
info!(target: "chanora_core", channel_id, "voice_join accepted by server");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user