docs/codebase-analysis-v2
19
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5c3dd70bba |
fix(ios-audio): activate session before voice joins (#38)
* fix(ios-audio): add voice join session coordinator * fix(ios-audio): activate session before voice joins * docs(ios-audio): align activation lifecycle comments * fix(ios-audio): keep session active when already-in-channel The 'already in channel' server response (code 0x0302) is treated as a successful join by _onJoinChannel: the user stays in the channel and local state is updated to reflect the joined target. But the underlying voiceJoin call still raises BridgeError_ServerRejected, which the joinVoiceChannelWithIosAudioSession helper used to interpret as a join failure and deactivate the iOS audio session. Result: the UI shows the user as joined while the audio session is dead and capture/playback remain silent. Add an isJoinSuccess predicate to the ordering helper. When the predicate matches, the helper rethrows (so the caller can still run its success-on-already-joined branch) without deactivating the session. Wire _onJoinChannel to pass _isAlreadyInChannel as the predicate so the 0x0302 path keeps the session active. Adds two regression tests covering the success-on-rethrow and the predicate-false-still-deactivates paths. * docs(security): regenerate license inventories Cargo inventory: pick up chanora_resolver bump from 0.1.0 to 0.2.0-beta.1 so it matches the workspace; also adds a trailing newline so 'cargo about generate' is idempotent in CI license-drift checks. Flutter inventory: pick up flutter_local_notifications (+ platform interfaces) and timezone pulled in by the prior notification permission work. |
||
|
|
a0ff17b935 |
fix(voice,ios): scope AVAudioSession VoiceChat to call lifetime (#33)
Adopt a call-scoped VoIP audio session lifecycle so other apps' audio is not stopped while Chanora is idle and the in-call session does not get clobbered by media-server resets unrelated to voice. AppDelegate.swift - Set .ambient + .mixWithOthers as the idle baseline so the app does not hold a VoiceChat session when no call is active. - Switch to .playAndRecord + .voiceChat + .mixWithOthers + .duckOthers on demand via the new chanora/ios_audio_session MethodChannel, and revert to .ambient on deactivate. - Gate the media-services-reset rebuild on voiceSessionActive so a stray reset during idle no longer reactivates VoiceChat. ios_audio_session_controller.dart (new) - Thin Dart wrapper around chanora/ios_audio_session with activate/ deactivate; no-op on non-iOS; swallows PlatformException to keep audio start/stop resilient to platform-side races. main.dart - Activate the iOS audio session on BridgeEvent_AudioStarted, deactivate on BridgeEvent_AudioStopped, fire-and-forget via unawaited(). ios_voice_unit.rs - Add the 10 local bindings required by the render-callback closure preamble (wav_recorder_for_render, render_recorder_active, render_ref_len, render_ref_accum, cb_count, last_num_frames, num_frames_changes, callbacks_with_audio, callbacks_with_silence) so the iOS target compiles cleanly with the new lifecycle wiring. Tests - 5 unit tests in test/services/ios_audio_session_controller_test.dart cover activate/deactivate on iOS, no-op on non-iOS, and graceful PlatformException handling. Docs - SRS SRS-110 expanded to cover the call-scoped lifecycle invariant. - SysDes mobile-voice row updated to reflect the MethodChannel and .ambient idle baseline. - implementation-status-2026-05-28 voiceChat row flipped to done. Verification - flutter analyze: No issues found (2.8s) - flutter test: 195 passed / 2 skipped / 0 failed - cargo build -p chanora_audio --target aarch64-apple-ios: clean - cargo build -p chanora_audio (macOS host): clean Device QA matrix (Spotify-keeps-playing-while-idle, mix-during-call, revert-on-call-end, media-services-reset-during-idle) remains pending on physical hardware. |
||
|
|
a589ac953f |
fix(ios,macos): preserve Silero @_cdecl exports across Xcode Archive
The Apple CoreML Silero VAD backend resolves six @_cdecl Swift symbols
via dlsym(RTLD_DEFAULT) at runtime in the Rust audio crate. Local
flutter build paths preserved those symbols, but Xcode Archive (the
path used for TestFlight and App Store uploads) silently stripped them
through two independent mechanisms, causing Rust to fall back to
WebRTC VAD on every shipped build.
Both stripping mechanisms are now neutralised:
* ld dead-strip: OTHER_LDFLAGS now whitelists each of the six
chanora_silero_vad_* symbols via repeated `-Xlinker -exported_symbol`
pairs in ios/Flutter/{Release,Debug}.xcconfig and
macos/Flutter/Flutter-{Release,Debug}.xcconfig.
* install-time strip: STRIP_STYLE is set to `non-global` in the same
four xcconfigs so the post-link strip phase no longer drops exported
global text symbols from the Archive product. Cost: ~264 bytes per
binary; verified `xcrun strip` vs `xcrun strip -x` behaviour.
Self-test wired into both AppDelegates: at launch on a utility queue,
ChanoraSileroSelfTest resolves all six symbols through dlsym (the same
path the Rust runtime uses, not a direct call that would mask the bug
class) and exercises create → reset → process → destroy. Result is
logged via NSLog and surfaces in Console.app / idevicesyslog.
A post-link verify_silero_exports.sh build phase runs nm -gU on the
final Archive binary and fails the build if any of the six symbols are
missing. Empirically caught the original Archive regression that
flutter build --no-codesign did not.
CocoaPods bridge podspecs now emit a proper .dSYM via dsymutil so
TestFlight crash reports are symbolicated; Cargo.toml release profile
sets `debug = true` because dsymutil needs DWARF in the input dylib.
macOS chanora_bridge.podspec PATH inserts /opt/homebrew/opt/rustup/bin
ahead of /opt/homebrew/bin so rustup's cargo (which has the
x86_64-apple-darwin target installed) wins over the homebrew rust
formula that is aarch64-only.
iOS Podfile target renamed from `Runner` to `Chanora` to match the
Xcode target name shipped in the project (the workspace and scheme
already referenced Chanora; the Podfile mismatch produced lint
warnings during `pod install`).
ITSAppUsesNonExemptEncryption=false declared in both Info.plist files
so TestFlight and App Store Connect uploads skip the export-compliance
prompt; Chanora uses only platform-provided TLS.
.gitignore now covers Xcode archive bundles, IPA exports, dSYM
directories, the local macOS release zip, and agent/tooling state
directories so generated TestFlight artifacts no longer appear in
git status.
End-to-end verified by headless archive:
xcodebuild -workspace Runner.xcworkspace -scheme Runner \
-configuration Release -destination 'generic/platform=iOS' \
-archivePath /tmp/chanora.xcarchive archive CODE_SIGNING_ALLOWED=NO
nm -gU on the resulting .app/Chanora binary shows all six
chanora_silero_vad_* symbols present.
|
||
|
|
fe6e07353e | chore: restore product scaffold to rollback baseline | ||
|
|
6af4ecab0f | feat(voice): add iOS VAD runtime support | ||
|
|
7a59f5b9a1 | feat(ios,p0): iOS P0 platform, audio fixes, channel UX | ||
|
|
2735c55c97 |
diag(audio,ios): log actual VPIO + AVAudioSession state post-init (rc.8+71)
Per external review (helpful checklist from ChatGPT-style analysis pointing out we never verified that iOS actually accepted our preferred sample rate / channels / format): preferredSampleRate and preferredIOBufferDuration are HINTS, not guarantees. iOS may substitute its own values if the hardware can't satisfy our preference. If VPIO is running at 44.1 kHz Float32 stereo while our render callback writes 48 kHz Int16 mono into the buffer, the symptoms would match what user reports (broken playback, pitch shifted, severe distortion) and our previous diagnostics wouldn't catch it because they only sampled signal-level metrics. This commit adds two diagnostic emissions to verify: 1. AppDelegate.swift::activateAudioSession: after setActive succeeds, log the ACTUAL session state \u2014 category, mode, sampleRate, ioBufferDuration, current route (inputs + outputs), outputVolume. Lets us see whether iOS honoured our .default + .defaultToSpeaker setup and which physical route it picked at launch. 2. ios_voice_unit.rs::IosVoiceUnit::start: after unit.start() succeeds, log the actual OUTPUT and INPUT stream formats VPIO accepted (sample_rate, channels, sample_format, flags). If these differ from our requested 48 kHz Int16 mono, we have a format-substitution problem. Three possible outcomes from the next test: * Both diagnostics confirm 48 kHz Int16 mono on both buses and the session sampleRate=48000 -> format is correct; the playback breakage is somewhere else (e.g. AudioHandler jitter buffer behaviour, route binding, or hardware mixer). * Session sampleRate != 48000 -> we need to insert a sample rate converter or pin AVAudioSession's setPreferredSampleRate(48000) explicitly in Swift before setActive. * VPIO substituted Float32 for our Int16 request -> our render callback is writing i16 magnitudes into a Float32 buffer which would explain the distortion. Fix: write Float32 directly using data::Interleaved<f32> instead of i16. Build counter 70 -> 71. Pure diagnostic; no behavioural change. |
||
|
|
6e0bf21295 |
fix(audio,ios): route playback via media channel (.default + .defaultToSpeaker) (rc.8+70)
User report after the 8x boost commit ( |
||
|
|
c89acacc74 |
fix(ios,audio): pair VPIO with AVAudioSession mode .voiceChat (rc.8+67)
User report at +66: capture (mic -> remote) is clean, but local playback (remote -> speaker) is 'broken and poor', particularly for human voice. Musicbot audio (loud, near-continuous) plays correctly; human voice (peaks ~-6 dB, average ~-40 dB, classic 20 dB peak-to-average ratio) sounds gated out so most inter- phoneme content is unintelligible. Diagnostic at +66 (render callback peak sampling every 100 callbacks during a 60-second talker session) showed peak_stereo values in the 0.005-0.01 range with occasional 0.13-0.49 spikes \u2014 i.e. the signal is REAL and reaching the device, but VPIO's output-side voice processing chain is gating the average-level content. Root cause: AVAudioSession mode .default + VPIO is a mismatched pairing. Under .default mode the VPIO unit's internal AGC/NS thresholds are tuned wrong for telephony-style speech and treat quiet inter-phoneme content as noise to gate out. Fix: switch back to mode .voiceChat which is Apple's documented pair for VoiceProcessingIO. WebRTC's reference iOS audio device manager (chromium googlesource voice_processing_audio_unit.mm) also uses this pair. VPIO under .voiceChat tunes its processing chain for speech and passes quiet content through cleanly. The original 'speaker/receiver toggle is silent under .voiceChat' bug was caused by cpal's RemoteIO unit binding to a stale physical transducer at construction time, not by .voiceChat itself. After migrating to VPIO at commits 1-4 ( |
||
|
|
6a4dbad60e |
fix(ios,audio): use AVAudioSession mode .default + correct mono downmix
Two changes that together address the user-reported 'speaker selector not working' AND 'audio quality bad' symptoms on iPhone: 1. AppDelegate.swift: AVAudioSession mode .voiceChat -> .default .voiceChat binds the underlying AudioUnit's output element to a SINGLE physical transducer (the receiver/earpiece) at session- configure time. overrideOutputAudioPort updates AVAudioSession's route metadata so currentRoute.outputs reports Speaker, but the AudioUnit's output binding is stale and audio keeps routing to the original transducer. Net: tapping Speaker in the picker flipped the route in our log but produced no audible change. .voiceChat also enables iOS's telephony processing chain (forced mono output, aggressive AGC, heavy noise gating) which explains the 'garbled / watery / metallic' quality complaints. .default mode uses iOS's standard audio graph: stereo output, no AGC, no telephony post-processing, AudioUnit re-binds live when the route changes. Same mode Music.app and most non-telephony apps use. Trade-off: we lose iOS hardware AEC. If users report speakerphone echo we'll add software AEC (DEC-030). Category options unchanged \u2014 .allowBluetoothHFP + .allowBluetoothA2DP still permit BT headsets in both directions. 2. engine.rs::build_output_stream: mono device downmix fix The mixing path at dev_channels==1 previously wrote only the L channel of AudioHandler's stereo output into the single mono device channel and discarded R entirely. Anything panned right in the stereo voice mix was silently lost \u2014 on .voiceChat speakerphone (forced mono device) this manifested as quiet remote speakers being inaudible. Fix: when dev_channels==1, output = (L + R) * 0.5 instead of just L. The dev_channels>=2 branch is unchanged. With change #1 iOS will typically expose stereo so this branch is rarely hit, but the fix is correct for any genuinely-mono sink (some BT car-audio profiles, USB mono headsets). |
||
|
|
2f6cdd3ad4 |
Revert "diag(ios,audio): log AVAudioSession state + route changes around picker overrides"
This reverts commit
|
||
|
|
da631a2bef |
diag(ios,audio): log AVAudioSession state + route changes around picker overrides
Add structured NSLog instrumentation to AppDelegate.swift and debugPrint chains in voice_compact.dart::_AudioOutputPickerSheetState so we can correlate user picker taps with what iOS actually does to the route. Three diagnostic streams: * 'chanora.session[<tag>]' from Swift — full session snapshot (category, mode, sampleRate, ioBufferDuration, current route inputs+outputs, preferredInput) emitted on every setActive and every AVAudioSession.routeChangeNotification with the reason decoded (override / routeConfigurationChange / newDeviceAvailable / etc). * 'chanora.route[<tag>]' from Dart — current route's inputs+outputs emitted before/after every overrideOutputAudioPort or setPreferredInput call, plus a delayed re-check at +250 ms to detect silent reverts. * Existing 'chanora: ...' debugPrint lines from the picker now include the OK case (override returned, setPreferredInput returned) so we see a positive signal in the log when the API didn't throw. Used to root-cause the 'speaker selector not working' issue: the hypothesis is that cpal's RemoteIO AudioUnit reacts to its own format configuration notifications by triggering routeConfigurationChange that reverts our Dart-side override. The logs will confirm or deny this — if we see 'chanora.session[routeChange.override] out=Speaker' followed by 'chanora.session[routeChange.routeConfigurationChange] out=Receiver' within a few hundred ms, that's the smoking gun. Pure diagnostic commit. No behavioural change. Logs are NSLog + debugPrint so they appear in Xcode console / 'flutter logs' / the device log via Console.app or 'devicectl device log'. |
||
|
|
9413703372 |
fix(ios,audio): drop .defaultToSpeaker; speakerphone toggle now actually switches
User report: 'speaker change not work' \u2014 selecting Speaker or
iPhone receiver in the audio output picker had no audible effect.
Root cause:
AVAudioSession was configured with category options
[.defaultToSpeaker, .allowBluetoothHFP, .allowBluetoothA2DP] +
mode .voiceChat. The .defaultToSpeaker flag tells iOS 'this app's
baseline output route is the speakerphone, even though .voiceChat
mode would normally route to the receiver.'
When the user picked Speaker:
overrideOutputAudioPort(.speaker) <- already at speaker baseline; no-op
When the user picked iPhone receiver:
overrideOutputAudioPort(.none) <- removes speaker OVERRIDE,
restores baseline = .defaultToSpeaker
= speakerphone. Receiver
row silently mapped to speaker.
So both rows produced the same audible state. The picker UI changed
the selected radio but the route didn't actually move.
Fix:
1. AppDelegate.swift: drop .defaultToSpeaker from options. With
pure .voiceChat mode (no .defaultToSpeaker), the baseline is
the receiver/earpiece. overrideOutputAudioPort then works as
documented:
Default = receiver
overrideOutputAudioPort(.speaker) -> speakerphone
overrideOutputAudioPort(.none) -> back to receiver
BT/AirPods connected -> automatic
Wired headphones plugged in -> automatic
2. voice_compact.dart: replace 'catch (_) {/* ignore */}' silent
swallow with debugPrint logging of (a) the actual exception
and (b) which route was selected. So if iOS rejects an
override (e.g. wired headphones plugged in), we can see WHY
in the device log instead of a silent picker no-op.
3. _selectSpeaker also now sets preferredInput to the built-in
mic so input + output stay consistent. Previously the
speakerphone override could leave the mic still routed to a
previously-selected BT input \u2014 user hears self through
speaker but server hears nothing.
flutter build ios --release --no-codesign: 21.9 s, Runner.app
30.4 MB.
|
||
|
|
4ee2b3850a |
fix(ios): rename allowBluetooth -> allowBluetoothHFP (iOS 26 SDK)
Xcode warning on iOS SDK 26+: 'allowBluetooth' was deprecated in iOS 8.0: renamed to 'AVAudioSession.CategoryOptions.allowBluetoothHFP' The flag was renamed in iOS 8 (a decade ago) but the old name has been kept as a soft-deprecated alias. iOS 26 SDK finally emits the warning, and -Werror builds would fail on it. Same semantics: permit HFP-profile Bluetooth headsets as input + output. Kept .allowBluetoothA2DP alongside for higher-quality output-only A2DP devices. flutter build ios --release --no-codesign: 10.7 s, Runner.app 30.0 MB. |
||
|
|
0466000733 |
fix(ios): defer AVAudioSession.setActive(true) to didBecomeActive
From iPhone log:
chanora_flutter: AVAudioSession setup failed:
Error Domain=NSOSStatusErrorDomain Code=561017449
'Session activation failed'
Error code 561017449 = AVAudioSessionErrorCodeCannotStartPlaying
(ASCII '!cat' big-endian). iOS 17+ refuses setActive(true) calls
made before the app's scene is foregrounded: the audio policy
server denies the activation because the app is not yet considered
the foreground priority owner. didFinishLaunchingWithOptions runs
BEFORE the scene becomes .active, so synchronous activation there
hits this race on cold launch.
Symptom flow:
1. App cold-launch -> AppDelegate.didFinishLaunching fires
2. setActive(true) -> Error 561017449
3. Audio session is left inactive
4. cpal's later attempts to open RemoteIO see an inactive
session and reject with StreamConfigNotSupported
5. voice_join fails at ensure_audio_running
6. user sees the audio failure manifested as missing mute /
continuous / PTT buttons (now fixed in
|
||
|
|
7c0e5caa92 |
fix(ui,ios): first-tap on TextField now opens the keyboard
User report: 'every first time to tap to input box nothing
happened'. Symptom is iPhone-specific: the very first tap on any
of the Host / Nickname / Password text boxes in the connect form
fails to focus + open the keyboard. The second tap on the same
field works.
Two distinct causes, both addressed:
apps/chanora_flutter/lib/main.dart
The connect form lives inside a SingleChildScrollView. Flutter
on iOS has a long-standing issue (flutter#19027) where the
enclosing Scrollable's gesture-arena participant absorbs the
first tap as a possible scroll-intent, leaving the TextField
unfocused; the second tap reaches the field because the
scrollable has already declined to handle a drag.
Fix: _ConnectForm converted from StatelessWidget to
StatefulWidget so it can own FocusNodes for the three text
fields. Each TextField gains:
* focusNode: <its own FocusNode>
* onTap: () => focusNode.requestFocus()
— forces focus on tap-down regardless of arena outcome
* textInputAction: TextInputAction.next (host, nick) /
TextInputAction.done (password) for return-key flow
* autocorrect: false, enableSuggestions: false
— these are server-host / nickname / password fields, the
iOS auto-correct + suggestion bar is wrong for all three.
Also set keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior
.onDrag on the SingleChildScrollView so the keyboard hides
when the user starts scrolling the bookmark list below.
apps/chanora_flutter/ios/Runner/AppDelegate.swift
AVAudioSession.sharedInstance().requestRecordPermission was
fired synchronously from didFinishLaunchingWithOptions. The
permission alert can race with iOS's text-input subsystem
initialisation: if the alert appears before the keyboard
layer finishes wiring up, subsequent text-field focus
requests are dropped silently.
Fix: DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) to
defer the permission request until ~1 s after the app shell
is on screen. Long enough for iOS's text-input layer to fully
initialise; short enough that the user reads the prompt
before tapping a field.
flutter analyze: clean (6 pre-existing Radio.groupValue infos).
flutter build ios --release --no-codesign: 26.8 s clean
(Runner.app 29.8 MB).
|
||
|
|
d4c04b6a72 |
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). |
||
|
|
f3320715ea |
feat(audio,ios): AVAudioSession PlayAndRecord+voiceChat in AppDelegate
iOS AVAudioSession must be configured BEFORE Flutter starts its
audio pipeline; the canonical place is application(_:didFinishLaunching\
WithOptions:) in AppDelegate.swift. This commit:
apps/chanora_flutter/ios/Runner/AppDelegate.swift:
* import AVFoundation
* In application(_:didFinishLaunchingWithOptions:), call
AVAudioSession.sharedInstance().setCategory(.playAndRecord,
mode: .voiceChat,
options: [.defaultToSpeaker, .allowBluetooth, .allowBluetoothA2DP])
followed by setActive(true). Failures are NSLogged but do not
block app launch — cpal's CoreAudio backend will still come up
against the default iOS routing.
This shape:
* routes the receiver/speaker like a phone call (.playAndRecord +
.voiceChat),
* engages on-device AEC / NS where supported,
* defaults to speaker so users don't have to hold the phone to
their ear,
* permits Bluetooth headsets (AirPods et al. just work).
crates/chanora_audio/src/engine.rs:
* Replace the iOS engine-start placeholder log line ('binding
pending — Chanora iOS audio is documented-only for Beta') with
an honest acknowledgment that the AVAudioSession configuration
lives Swift-side. The Rust engine acknowledges the request, then
cpal opens its CoreAudio streams against the session.
iOS-only Rust code is #[cfg(target_os = "ios")]-gated so this commit
is no-op on every other platform.
SRS-197: iOS/macOS audio routing contract. DEC-025: iOS officially
in scope for P0 (Focused PTT only — Apple's sandbox model has no
global PTT analogue).
|
||
|
|
4d57d189c9 |
feat(flutter,macos,ios): scaffold platform Xcode projects via flutter create
Ran `flutter create --platforms=macos,ios --project-name=chanora_flutter
--org=app.chanora .` on the M1 Mac to generate the standard Flutter
platform-specific scaffolding (Runner.xcodeproj, Podfile, AppDelegate,
entitlements, etc.) for both macOS and iOS.
The cross-platform Dart source (lib/) and Rust workspace (crates/,
core/) carry the actual application logic; these scaffolds are
required only so flutter build macos / ios can resolve their Xcode
projects. No application code added.
macOS smoke-launch from the M1 Mac verified the bridge dylib load
path: after manually wrapping libchanora_bridge.dylib into a proper
chanora_bridge.framework bundle (FRB on macOS expects a framework,
not a plain dylib) and codesigning ad-hoc, the runner starts cleanly
through 'bridge initialised', 'identity store initialised', 'bookmark
store initialised' just like the Linux runner.
Following commits will:
* Wire the framework-bundling step into build glue (currently manual
install_name_tool + codesign).
* Replace the macOS PTT backend stub (crates/chanora_audio/src/
ptt_backends/macos.rs) with live IOHIDCheckAccess +
CGEventTapCreate so the descriptor advertises real L2/L3 capability
on a permission-granted box (SDD-085).
* Add the iOS AVAudioSession PlayAndRecord+voiceChat wiring.
* Add docs/verification/macos-p0-acceptance.md and ios-p0-acceptance.md.
|