Microphone and speaker are chosen separately, so a Bluetooth headset can
play in media quality while the phone's own microphone listens, instead of
the headset dropping into its call profile. Devices are kept by type and
address: Android hands out new ids whenever one reconnects, and a line that
lost its device comes back to it when it returns.
The microphone preset (voice recognition by default, or the platform's
call processing, unprocessed, ...) and whether voices play as media or as
a call are Android's own settings. Changing either reopens the open
streams in place. Communication mode, and the headset's call link that a
Bluetooth microphone needs, are held only while a stream needs them, and
are set up before that stream opens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Switching conversations dropped the message being written, and showed
the next one at the scroll position of the last.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Connect screen with an address or a bookmark, which can be added and
removed there.
- Server screen: the channel tree with each client's status, folding
channels, joining (asking for a password where needed) and
subscribing from a channel's sheet; private message, poke and volume
from a client's sheet.
- Chat: the server log, the channel and private conversations with
their history, unread marks, BBCode and clickable client, channel and
web links. Chat lines are the HTML the desktop shows, turned into
styled text.
- Voice bar: mute, deafen, away, and a hold-to-talk button in
push-to-talk mode. The microphone permission is asked for on connect.
- Settings: nickname, transmission mode, voice activation, noise
suppression, gain control and playback volume, in effect at once.
The session's state reaches Compose as immutable snapshots: the tree
at most every 30 ms, rebuilt only when channels or clients changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The session moves from the connection screen's ViewModel into an
app-wide SessionController, and a foreground service keeps the process
running while connected:
- Its notification shows the server and status and offers Mute, Deafen
and Disconnect.
- It declares microphone use only once the microphone is handed to the
connection, as Android requires that permission to be granted then.
- It holds a low-latency Wi-Fi lock and a CPU wake lock, since voice
goes out every 20 ms.
- When the system moves to another network, the session reconnects to
where it was, as its socket stays bound to the old one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
android/ builds core and ts3j from the same sources as the desktop's
Maven build, so the app shares all of the client's logic:
- audio: Oboe streams behind AudioCapture/AudioPlayback, with a lock-free
ring between the real-time callback and the blocking Java side, and
libopus 1.5.2 built with the NDK behind OpusCodec. Speakers are mixed
onto one stream. Capture uses the VoiceRecognition preset, leaving
noise suppression and gain to the client's own processing.
- app: a plain connection screen to try the port out - connect,
microphone, mute, deafen, channel chat and a log - until the real UI.
Targets Android 13 and up (minSdk 33) on arm64 and x86_64.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>