fix(core): complete voice joins on server accept
This commit is contained in:
@@ -642,13 +642,6 @@ impl ChanoraSession {
|
|||||||
if let Some(projection) = projection {
|
if let Some(projection) = projection {
|
||||||
self.emit_voice_state(projection).await;
|
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 {
|
if let Err(audio_err) = self.ensure_audio_running().await {
|
||||||
warn!(
|
warn!(
|
||||||
target: "chanora_core",
|
target: "chanora_core",
|
||||||
@@ -1241,6 +1234,18 @@ impl ChanoraSession {
|
|||||||
if let Some(pw) = requested_password {
|
if let Some(pw) = requested_password {
|
||||||
state.channel_passwords.insert(channel_id, pw);
|
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);
|
drop(guard);
|
||||||
// 2. Bring the audio engine up. Tolerate failure: the
|
// 2. Bring the audio engine up. Tolerate failure: the
|
||||||
// server-side channel move has ALREADY succeeded (step
|
// server-side channel move has ALREADY succeeded (step
|
||||||
@@ -1272,78 +1277,7 @@ impl ChanoraSession {
|
|||||||
// UI gains the channel + mute + PTT controls.
|
// UI gains the channel + mute + PTT controls.
|
||||||
let _ = self.events_tx.send(SessionEvent::AudioStopped);
|
let _ = self.events_tx.send(SessionEvent::AudioStopped);
|
||||||
}
|
}
|
||||||
// 3. Belt-and-braces: if the server replied Ok but never
|
info!(target: "chanora_core", channel_id, "voice_join accepted by server");
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user