Files
voice-cat/clients/apple
Talon 7033053184
Build and test / test (macos-latest) (push) Canceled after 0s
Build and test / test (ubuntu-24.04) (push) Canceled after 0s
Build and test / test (windows-latest) (push) Canceled after 0s
Build and test / apple-client (push) Canceled after 0s
fix(ios): keep the audio preset when speaker output is toggled
Speaker output decides where audio goes, not how it is captured, which is why
it sits beside the preset row rather than inside Advanced audio. It was
nonetheless moving the preset to Advanced, and the voice-chat preset was
clearing it back, so the two controls overwrote each other and a user who
wanted the speaker lost the preset that describes their capture setup.

SetForceSpeaker no longer touches the preset, and Load and SelectPreset no
longer clear the flag. A speakerIsExplicit default records that the user
actually chose, so the automatic value older installs inherited from the
voice-chat preset is dropped once rather than pinning a headset user to the
speaker, and the toggle is honoured from then on.

Apply issues the speaker port override again when the flag is set. Without it
the toggle barely did anything with a headset attached, because
DefaultToSpeaker only decides the route when nothing else is connected. With
the toggle off it passes None, so a route left alone still follows an HFP
headset.

The changed logic sits on AVAudioSession, which the net10.0 test project
cannot reference, so this carries no tests; the toggle needs device
verification under the voice-chat preset with a Bluetooth headset connected.
2026-09-25 18:48:08 +02:00
..

Apple clients

VoiceCat.Mac and VoiceCat.iOS are .NET 10 AppKit and UIKit clients over the shared managed core. The ReplayKit upload extension under native/apple/broadcast remains Swift because it runs under the extension memory limit and writes the versioned shared audio ring.

Build on macOS with Xcode and the pinned .NET workloads:

./scripts/build-native.ps1
./scripts/build-native-ios.sh
dotnet restore clients/apple/VoiceCat.Apple.slnx
dotnet build clients/apple/VoiceCat.Apple.slnx -c Debug --no-restore

Use publish-macos.sh --dry-run to validate a local ad-hoc macOS bundle. The dry-run build does not enable hardened runtime because ad-hoc signatures have no Team ID and cannot satisfy macOS library validation. The script normalizes nested signatures and installs the verified bundle at both VoiceCat.Mac/bin/Release/net10.0-macos27.0/osx-arm64/VoiceCat.app and VoiceCat.Mac/bin/Release/distribution/VoiceCat.app. Distribution builds remain hardened and require VOICECAT_CODESIGN_IDENTITY; optional notarization uses APPLE_ID, APPLE_TEAM_ID, and APPLE_APP_PASSWORD.

For a physical iOS device, use build-ios-device.sh and deploy-ios-device.sh. The host and ReplayKit extension require signing profiles with App Group group.me.iamtalon.voicecat. Hardware validation must cover VoiceOver, background and lock behavior, interruptions, route changes, Bluetooth, ReplayKit, and iOS 27 ScreenCaptureKit audio. For iOS voice stability, leave a call joined with the microphone active for at least 30 minutes and confirm speech stays clear and VC_AUDIO reports no growing feedDrops. While still joined, toggle Wi-Fi off and on, switch between Wi-Fi and cellular, and confirm the app stays open, reconnects, and restores the voice session. Repeat with mono, stereo, and voice processing.

The iOS remote-user manual gate must also cover Users → user detail → independent microphone and screen-audio gain/mute controls, microphone receive noise reduction, the no-active-stream state, and Private Chats → conversation → User/audio settings. Repeat the navigation with VoiceOver and disconnect the remote user while its detail and conversation views are open.

App Store builds use the same device builder with --configuration Release. Set VOICECAT_BUILD_NUMBER, VOICECAT_DISPLAY_VERSION, VOICECAT_DEVELOPMENT_TEAM, the host VOICECAT_CODESIGN_KEY/VOICECAT_CODESIGN_PROVISION pair, and the extension VOICECAT_BROADCAST_CODESIGN_KEY/VOICECAT_BROADCAST_CODESIGN_PROVISION pair. The host and extension profiles must both be App Store Connect profiles and include the shared App Group.

The complete TestFlight workflow is documented in docs/apple-ios-release.md. The shared ring contract is documented in docs/broadcast-ring-format.md.