Android: a webradio stream alarm

In the morning, I want to be woken up by a webradio, not by the same dummy alarm (or audio file) again and again.

Because I care about freedom and privacy, I try not to use apps from the Play Store1. I usually find everything I need from the F-Droid repository: IceCat, OpenKeychain, Password Store, OsmAnd, Plumble, VLC… There's plenty of alarm apps in the F-Droid repository, but none that offer to use an internet stream. ServeStream claims to support alarm clocks, but I couldn't get it to work.

Shortly before giving up I realised I didn't need an alarm app, since VLC can already play webradios extremely well from a playlist. I just need a way to "open" the playlist file with VLC at a predetermined time. So I looked for "scheduled tasks" and found crond. As the name implies, it will run whatever you put in /data/crontab. So I have to figure out how to run stuff from the command line.

Opening a file from the CLI

(The commands below can be tested from any terminal-like app, or from an adb shell.)

That's the relatively easy part, Android has a am binary that's essentially an interface to its ActivityManager.

am start -a android.intent.action.VIEW \
         -d file:///sdcard/foo.pls \
         -t audio/wav \
         -p org.videolan.vlc

The value of -t isn't the right content type, but it's only needed for VLC to figure out what it should do with the opened file (in this case, play the audio).

If -p is omitted, Android will prompt a "open with" popup, not what we want in a non-interactive use case.

Okay, now that it works, we can use it to play the radio with crond, but there's still several things that can go wrong:

Setting music volume from the CLI

This is where it gets tricky. There is a simple approach, that uses input keyevent to simulate pressing the volume-up or volume-down buttons:

input keyevent 24 # volume-up
input keyevent 25 # volume-down

This method sucks for many reasons. First, it's quite slow (every call takes a solid 2 seconds to complete), you can't set the volume at a specific level and you can't control which volume is being changed. If no music is currently playing, these calls will change the ringtone volume. So, play the music first, at whatever volume, then fiddle with slowly changing its volume. It works, but it's messy and doesn't make for a good alarm clock.

Thankfully there is anohter, messier approach: service call. But it requires knowing much of the internals of whatever version of Android you're running. Thanks to this gist, I finally got it working (on my device, if you run a different version of Android then it will not work!):

service call audio 3 i32 3 i32 10

Okay, so call the third function of the audio service, which implements the IAudioService interface, defined here for Android 6.0.1. Let's look at the prototype:

void setStreamVolume(int streamType, int index,
                     int flags, String callingPackage);

Putting it all together

% cat >/sdcard/ <<EOT
service call audio 3 i32 3 i32 10
am start -a android.intent.action.VIEW \
         -d file:///sdcard/foo.pls \
         -t audio/wav \
         -p org.videolan.vlc
% echo "59 6 * * * sh /sdcard/" >/data/crontab

Because cron is fairly flexible, it's possible to setup many alarms at different times based on, for example, the day of the week. Read crontab(5) for more info.

UPDATE 9/11: sometimes, the phone will randomly decide not to wake up for the alarm. This is a known issue. A workaround is to set up a silent alarm in the clock app at the same time as the cron job. It will force the phone to wake up.

Possible improvements

Of course, the cleanest solution would be to write an app, but that's a hornet's nest I don't want to get into at the moment.

  1. Yes, I know, I am still using Gmail. Sadly, self-hosting email requires a huge amount of work and maintenance time, and you still have no control over other people's SMTP servers ; email privacy is a huge can of worms. You can always use my OpenPGP public key. ↩︎