Blog

Putting the audio in AudioRun

Posted 2026-08-30

The name AudioRun was chosen to highlight the most distinct feature of the app: the spoken announcements and solid audio handling. This blog post will give some background for the choices that have been made, and how they work.

You pick the stats

Every stat is its own toggle: distance, pace, elapsed time, heart rate, split times, the last interval’s time, distance and pace, interval count, elevation change. Cadence is separate — every 100 m, 400 m, every kilometer, on a timer, or both. Kilometer (or mile) splits are independent of the interval, so quiet 400 m repeats can still get split callouts.

AudioRun's Audio Announcements settings screen with per-stat toggles for distance, pace, elapsed time, heart rate, split times and interval stats, plus distance-interval choices from 100 m to 500 m; beside it the Spotify, Audible and Garmin logos over a forest trail.
Each stat is a toggle, the cadence is yours, and all of it has to live next to whatever else is using the speaker.

The sentence is assembled clause by clause, in the order you set. Anything that would be meaningless is dropped rather than spoken. No GPS on a treadmill means the distance clause would be “0 meters” every interval, so it stays quiet. A flat interval does not announce “climbed 0 meters”; elevation only exists once there is a whole meter to report. What is left comes out as one sentence:

“4 point 5 kilometers. In 25 minutes 10 seconds. Average pace 5 minutes 35 seconds per kilometer. Climbed 12 meters.”

Digits, colons and slashes never reach the speech engine. “5:35/km” comes out of TTS as “five colon thirty-five slash kay em”, which is useless, so every value is formatted for speech first. Related small thing: “in 25 minutes” only works as a follow-on to a distance clause. If distance is off, or GPS dropped it, the time clause switches to “elapsed time 25 minutes” and carries its own subject. The demo button on the config screen runs the same text builder as a live run. What you preview on the couch is the same string you will hear on the trail.

Three things want the speaker

State changes (“Run paused”), interval stats, and the AI coach all go through one priority queue.

State changes are HIGH. They interrupt whatever is playing and clear everything pending. If you have stopped at a crossing you want “Run paused” now, not after a twenty-second stats readout that no longer applies. Stats are MEDIUM and wait their turn. Coaching is LOW: it queues behind stats and gets a 500 ms gap before speaking, so a coach reply does not slam into the end of a stats sentence.

The queue also sends context back upstream. The coach is told what the last interval announcement said, so it does not repeat the pace you heard ten seconds earlier. The queue is capped. If the speech engine dies mid-run, the app will not save up a half-hour of stale callouts and dump them when it recovers.

Multiple audio sources

This is the bit most running apps get wrong, and one of the main features of AudioRun is about getting this right. AudioRun is designed to be used on long runs, and during that time, you might want to listen to music, an audiobook, or a podcast. AudioRun will handle this gracefully, and make sure that the spoken announcements are heard and not drowned out by other audio sources, or cause the audio streams to trip each other up.

Each announcement is tagged with Android’s USAGE_ASSISTANCE_NAVIGATION_GUIDANCE audio attributes and a speech content type — the same class as turn-by-turn directions, which is roughly what a stats callout is. Right before speaking, the app requests transient audio focus with MAY_DUCK: a few seconds, and whatever is already playing may keep going quieter underneath. Any music playing is attenuated to make room for the announcements, and then fades back in when the announcements are done.

At least, that's the intention. What the other app does with that attenuation signal is up to it. Spotify "ducks": the music dips, the announcement lands, the music comes back. Audible pauses instead, which is also correct (for audiobook playback, where you really don't want to miss a sentence).

Transient also matters because AudioRun is not the only thing talking on a run. My Garmin watch delivers its own spoken alerts through the phone. AudioRun only holds focus for the seconds it is actually speaking, never for the length of the run, so the watch gets a turn at the same ducking machinery. If AudioRun were to grab the audio focus for a whole session it would break this feature.

Focus is best-effort. If the request is refused, the announcement speaks anyway. Worst case, two things overlap for a moment. The alternative is a swallowed callout, and for an app whose interface is audio, silence is the one failure I want to avoid.

The reliability stuff

Most of this is invisible when it works.

“Run started” is often requested before the speech engine has finished initializing. It stays queued and plays when the engine is up, instead of disappearing. TTS engines can die quietly after a long stretch with the screen off. A failed speak call releases the queue’s speaking claim so the pipeline cannot wedge, and the next announcement retries. When you stop a run, the background service stays alive until the end-of-run summary has actually finished playing. Android is otherwise happy to kill it mid-sentence. And since the phone is in a pocket, GPS signal loss is announced out loud. That is the only way you would know why the distance callouts went quiet.

You can pick which installed TTS voice to use, and how fast it speaks. The audio features of the app have been tested thoroughly, and are expected to work reliably, but due to the nature of the Android ecosystem, there will inevitably be combinations of hardware, software and settings that will cause issues. If you encounter issues, please report them to me using the in-app feedback form, and include the following information: