fix(ui,audio,ios,macos): eight P0 mobile fixes
User report from sideloaded iPhone build, in order of priority: #4 'Could not join channel: audio: audio backend: build_output_stream: The requested stream configuration is not supported by the device.' Cause: we forced cpal::BufferSize::Fixed(2048) on the output and input streams unconditionally on non-Linux. iOS CoreAudio RemoteIO units reject arbitrary buffer-size requests with that exact error. Windows WASAPI needs the pinning for shared-mode jitter, but macOS / iOS do not. Fix: cfg-gate Fixed(2048) to target_os = 'windows'; everywhere else use BufferSize::Default and let the platform HAL pick. crates/chanora_audio/src/engine.rs. #5 'Could not join channel: invariant violated: voice_in already taken' Cause: start_audio tore down the old engine BEFORE attempting to construct the new one, and consumed voice_in (an mpsc Receiver that can only be taken once) early. When the new engine failed mid-construction (e.g. because of #4 above) the session was left with: no audio engine, voice_in consumed, no way to retry without reconnect. The second voice_join attempt surfaced the invariant message. Fix: build the new engine BEFORE tearing down the old. Only swap state.audio if construction succeeded. crates/chanora_ core/src/lib.rs::ChanoraSession::start_audio. Additionally added a put_voice_in helper to the protocol adapter ( crates/chanora_protocol/src/adapter.rs) for a future broadcast-channel migration; the helper is unused on the immediate fix path but documents the intent. #3 'permission request would better on first open' Cause: AVAudioSession only triggers the mic-permission prompt the first time it tries to record. We never recorded until voice_join, so the prompt fired then. Fix iOS: AVAudioSession.sharedInstance().requestRecordPermission in AppDelegate.swift::application(_:didFinishLaunchingWithOptions:). Fix macOS: AVCaptureDevice.requestAccess(for: .audio) in macos/Runner/AppDelegate.swift::applicationDidFinishLaunching. Both run non-blocking; user can deny without crashing app launch, and voice_join then surfaces a clearer downstream error when the engine fails to open the input device. #1 + #2 'one-column upper takes too much space; Push to Talk button at bottom would be better' Layout rework for narrow-mode (single column, mobile shape): - Flipped the stacking order in main.dart so Voice Bar moves to the BOTTOM of the body and the channel tree (Expanded) fills above. Wide-mode (Row, >= 840 dp) layout unchanged. - Inside the Voice Bar on touch-only hosts, moved the on-screen Push to Talk button to be the LAST element of the Voice Bar (was Row 3). Order now: pill + mutes, mode badge + settings, level meter, stats line, release-tail caption, PTT button. The button is closest to the user's thumb when the Voice Bar is pinned to the bottom of a narrow-layout screen. #6 'remove right top debug badge' debugShowCheckedModeBanner: false on the MaterialApp. Release builds never showed it anyway; this only affects local dev / debug builds. #7 'what does the refresh button use for? nothing happened' Removed. The snapshot updates via BridgeEvent::SnapshotChanged are pushed from the bridge — a manual rust.snapshot() call was redundant. Now only the Diagnostics + Disconnect actions remain in the AppBar trailing row when connected. #8 'Bind Key related function should not be added to a mobile platform' widgets/voice_settings.dart: bind-key OutlinedButton is now #cfg'd out when Platform.isIOS || Platform.isAndroid. The release-tail slider stays because it still applies to the on-screen PTT button. Capability badge in voice_bar.dart also hidden on mobile (it would always show L0Focused which is redundant with the visible on-screen button). Tests + analyze: chanora_audio 34/0/0 on macOS, workspace 78/0/1 on Linux; flutter analyze clean (6 pre-existing Radio.groupValue infos). flutter build ios --release --no-codesign: 28.8 s clean (Runner.app 29.9 MB).
This commit is contained in:
@@ -347,16 +347,34 @@ impl AudioEngine {
|
||||
let out_format = out_cfg.sample_format();
|
||||
let dev_sample_rate = out_cfg.sample_rate().0;
|
||||
let dev_channels = out_cfg.channels() as usize;
|
||||
// Buffer-size rationale:
|
||||
// * Windows (WASAPI via cpal): the default period is
|
||||
// small enough to expose audio-thread scheduler
|
||||
// jitter on shared-mode endpoints. Pinning at 2048
|
||||
// frames (~46 ms @ 44.1 kHz) gives the Opus decode
|
||||
// callback enough headroom while still being well
|
||||
// under voice-chat latency tolerance.
|
||||
// * macOS (CoreAudio via cpal): the default period
|
||||
// is fine and the OS picks a HAL-friendly size.
|
||||
// * iOS (CoreAudio via cpal): RemoteIO units reject
|
||||
// arbitrary buffer-size requests and surface them
|
||||
// as `build_output_stream: The requested stream
|
||||
// configuration is not supported by the device`.
|
||||
// Must use BufferSize::Default.
|
||||
#[cfg(target_os = "windows")]
|
||||
let buffer_size = cpal::BufferSize::Fixed(2048);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let buffer_size = cpal::BufferSize::Default;
|
||||
let out_stream_cfg = cpal::StreamConfig {
|
||||
channels: out_cfg.channels(),
|
||||
sample_rate: out_cfg.sample_rate(),
|
||||
buffer_size: cpal::BufferSize::Fixed(2048),
|
||||
buffer_size,
|
||||
};
|
||||
info!(
|
||||
target: "chanora_audio",
|
||||
dev_sample_rate,
|
||||
dev_channels,
|
||||
buffer_size_frames = 2048,
|
||||
buffer_size = ?buffer_size,
|
||||
"output stream using device native config (no 48k force)"
|
||||
);
|
||||
|
||||
@@ -588,14 +606,23 @@ fn try_open_capture(
|
||||
let in_sample_rate = in_cfg.sample_rate().0;
|
||||
let in_channels = in_cfg.channels() as usize;
|
||||
let in_format = in_cfg.sample_format();
|
||||
// Same buffer-size rationale as the output stream — request a
|
||||
// ~46 ms period on the capture side to give the Opus encoder
|
||||
// realistic time to run inside the cpal callback without
|
||||
// overrunning. cpal carries over the device's negotiated rate /
|
||||
// channels / sample-format from `in_cfg` via the From impl, then
|
||||
// we override only the buffer size.
|
||||
// Buffer-size rationale (same shape as the output path):
|
||||
// * Windows: pin to 2048 frames to avoid the small-period
|
||||
// jitter of WASAPI shared mode.
|
||||
// * macOS / iOS: CoreAudio picks a HAL-friendly default;
|
||||
// iOS RemoteIO rejects arbitrary buffer-size requests.
|
||||
// * Linux: same SDL2-vs-cpal split as the output path; we
|
||||
// still use cpal for capture but leave Default since
|
||||
// PipeWire's ALSA shim works well there.
|
||||
let mut in_stream_cfg: cpal::StreamConfig = in_cfg.into();
|
||||
in_stream_cfg.buffer_size = cpal::BufferSize::Fixed(2048);
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
in_stream_cfg.buffer_size = cpal::BufferSize::Fixed(2048);
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
in_stream_cfg.buffer_size = cpal::BufferSize::Default;
|
||||
}
|
||||
|
||||
let opus_enc = OpusEncoder::new(
|
||||
OpusSampleRate::Hz48000,
|
||||
|
||||
@@ -332,6 +332,25 @@ impl ProtocolClient {
|
||||
self.voice_in_rx.lock().ok().and_then(|mut g| g.take())
|
||||
}
|
||||
|
||||
/// Put a previously-taken voice_in receiver back so a
|
||||
/// follow-up `take_voice_in()` succeeds. Used by the core's
|
||||
/// `start_audio` to recover from a failed
|
||||
/// `AudioEngine::start_with_gate` — without this a single
|
||||
/// engine-construction failure would permanently poison the
|
||||
/// voice channel and force a reconnect to fix.
|
||||
pub fn put_voice_in(&self, rx: mpsc::Receiver<InboundVoice>) {
|
||||
if let Ok(mut g) = self.voice_in_rx.lock() {
|
||||
// If a consumer is already in possession we drop the
|
||||
// duplicate rather than overwriting; this branch
|
||||
// should not be reachable in practice because the only
|
||||
// caller (start_audio) takes-then-puts inside the same
|
||||
// critical section.
|
||||
if g.is_none() {
|
||||
*g = Some(rx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Take the loss-notifier. Returns `None` if it has already been
|
||||
/// taken. The supervisor in `chanora_core` consumes this to
|
||||
/// drive auto-reconnect; nothing else should call it.
|
||||
|
||||
Reference in New Issue
Block a user