feat(ringtones): let a music app play the alarm

"Choose from a music app…" in the sound picker browses any app that shares
its library through Android's MediaBrowserService and stores the choice as a
clockula://media URI in the existing ringtone field — no schema change, no
new permission, no internet. The music app streams with its own.

At ring time the app is asked to play through its media session. Every wait
is bounded and any failure falls back to the ordinary alarm sound: the app
won't connect or start, stalls or buffers too long, or the media stream
might not be heard (headphones or a classic Bluetooth device connected, Do
Not Disturb muting media). The ramp drives the media stream and the user's
media volume is restored when the last media ring ends.

RingAudioPlayer.start no longer suspends: vibration starts up front, the
timer collector is not held up, and a generation token keeps late work from
reviving a stopped ring.
This commit is contained in:
2026-10-02 20:44:58 +02:00
parent 767b9ed72d
commit f37c5f2f84
28 changed files with 1653 additions and 46 deletions
+11
View File
@@ -171,6 +171,17 @@
<string name="ringtone_unavailable">Unavailable \u2014 the default alarm sound will be used</string>
<string name="ringtone_unknown">Unknown sound</string>
<string name="ringtone_access_may_lapse">Clockula may lose access to this file later</string>
<string name="ringtone_pick_media_app">Choose from a music app\u2026</string>
<string name="ringtone_pick_media_app_summary">The app plays your alarm</string>
<string name="media_picker_title">Music apps</string>
<string name="media_picker_explainer">The chosen app plays the alarm itself. If it can\u2019t \u2014 not installed, logged out or offline \u2014 or if headphones or a Bluetooth device are connected, Clockula rings with the default alarm sound instead.</string>
<string name="media_picker_no_apps">No app on this device shares its music library with other apps.</string>
<string name="media_picker_refused">%1$s doesn\u2019t share its library with other apps.</string>
<string name="media_picker_empty">Nothing here.</string>
<string name="media_picker_resume">Resume where you left off</string>
<string name="media_picker_resume_summary">Whatever %1$s played last</string>
<string name="media_picker_use">Use %1$s</string>
<string name="media_resume_title">Resume in %1$s</string>
<!-- M7: the stopwatch tab -->
<string name="stopwatch_start">Start</string>
<string name="stopwatch_pause">Pause</string>