Ask an Atheist Stream Reflector (2012 Version)

Problem

Not really a problem, but KLAY recently upgraded it's internet streaming solution to one that has ads and is way^50 better quality. It's also ad supported, and it uses a technology that is a bit harder to stream.

Previously, I connected to KLAY's stream, transcoded it to MP3, and streamed it to our icecast server entirely with VLC. The new version uses abst streaming, which looks like HTTP streaming but wasn't something I could simply suss out with VLC.

Eventually, I'll create my own stream either from the studio or using a relatively clear AM receiver piped into a quiet computer (think RasPi), but as there's no internet in the studio and I've no place to put a receiver, scraping the KLAY stream is all we've got.

UPDATE 12/21/2012: For some reason, when I went to start the reflector this last Sunday, the filesystem had been completely borked. No idea why, but the fact that I've started documenting things certainly helped get me back online sooner rather than later.

There are changes, since I discovered that the KLAY stream will play commercials over the live programming.

Solution

So, I've decided to create a small virtual machine that runs a browser and lets me pipe the audio to the streaming server.

Packages Used

  • Chromium
  • Some form of Click-to-Flash
  • alsa tools
  • Flash Plugin
  • wmctrl
  • xdotool
  • Some scripting

OS Stuff

I used SLiM to boot directly into a user, and as I tend to do on small systems or projects, I used Window Maker as my window manager. VM runs Arch Linux, as usual.

Update 12/21/2012: I dropped SLiM in favor of booting into X right from systemd using this from the Arch Linux wiki.

Chromium

The first time out, we took advantage of the fact that the stream started as soon as the page was loaded. But since we found out the stream provider plays commercials over the stream, I installed a click-to-flash plugin to prevent flash video from playing1). This meant that we had to click around on the screen to get the flash to load. Fortunately, this also allowed us to turn the volume up on the flash app.

It also meant that if the browser shutdown incorrectly, it would push the “Restore” dialog and throw the coordinates off. To avoid that, we blow away the Chromium config directory and restore from a tar'ed backup on each chromium start.

chromewatch.pl is a little more complex than it started out as. It still uses a lot of sleep()'s and usleep()'s where we can't really watch for things, though.

ALSA Tools

ALSA provides the snd-aloop module, which provides a loopback soundcard. It has two devices. Data sent to one device is sent right back out the other device. Given that the Xen virtual machine has no sound card defined, this soundcard will be the only one in the system, and used by default. Hooray for that.

  • In Device: hw:Loopback,0,0
  • Out Device: hw:Loopback,1,0

Flash Plugin

Chromium doesn't come with flash, so the flash plugin is installed. Both flash plugins are pretty flaky, so some scripting is required, see below.

DarkIce

DarkIce takes audio from a sound card and streams it to the server. Easy peasy.

Here's the config file:

[general]
duration = 0
bufferSecs = 5

[input]
device = hw:Loopback,1,0
#device = pcm.makemono
sampleRate = 44100
bitsPerSample = 16
channel = 2

[icecast2-0]
bitrateMode = cbr
format = mp3
bitrate = 64
quality = 0.5
server = media.askanatheist.tv
port = 8000
channel = 1

password = PASSWORD.GOES.HERE

mountPoint = aaalive.mp3
name = Ask an Atheist Live
description = Ask an Atheist Live Stream
url = http://askanatheist.tv
genre =  Live Radio
public = yes

I might play around with the quality setting in the future.

Scripting

Flash Watching

Flash in Linux is flaky. Just in testing I managed to get it to crash a few times. I wanted it to be able to recover if the plugin died. Simplest way was to watch the state of the sound card, and if it wasn't open, kill off the browser and relaunch.

chromewach.pl

#!/usr/bin/perl
 
# Chromium Streamer 0.2
#  Sam Mulvey <sam@askanatheist.tv> for Ask an Atheist
#  GPLv3, I guess.  Just share the wealth, man.
 
use Time::HiRes qw/usleep/;
use File::Path;
 
 
$watchfile = "/proc/asound/card0/pcm0p/sub0/hw_params";
$wmctrl    = "/usr/bin/wmctrl";
$xdotool   = "/usr/bin/xdotool";
$chromium  = "/usr/bin/chromium";
$killall   = "/usr/bin/killall";
 
$klay_url  = "http://p.freestreams.com/?pid=513.";
$x_loc     = 199;
$y_loc     = 207;
$c_delay   = 5000;
$window_id = 0;
 
 
$config_dir   = "/home/sam/.config";
$restore_file = "/home/sam/klay_reflect/chromium.tgz";
 
$SIG{CHLD} = 'IGNORE';
$ENV{'DISPLAY'} = ":0.0";
 
$|++;
 
for (;;) {
 
        open(WATCH, $watchfile);
        chomp($check = <WATCH>);
        close(WATCH);
 
        if ($check eq "closed") {
 
                print "\nSound card not in use.  Restarting!\n\n";
 
					 # Launch Chromium
                unless (fork()) {
 
								rmtree($config_dir."/chromium");
								system("tar -C $config_dir -zxf $restore_file");
								usleep(500);
                        `$killall chromium`;
 
								exec("$chromium $klay_url");
                }
 
 
					 # Move Things Around
					 unless (fork()) {
 
 
 
						# Get Window ID;
						$window_id = 0;
						do {
 
							chomp(@LIST = `$wmctrl -l`);
							foreach (@LIST) {
								(@F) = split(" ", $_, 2);
								$window_id = $F[0] if ($F[1] =~ m/Chromium/i);
 
							}
 
							usleep(500);
 
						} until ($window_id =~ 'x');
 
						sleep 5;
 
						print "WINDOW ID: $window_id\n";
 
						# Maximize!
						`$wmctrl -iR $window_id`;
						`$wmctrl -ir $window_id -b add,maximized_vert,maximized_horz`;
 
 
						# findclient: 10485795
						# x:199 y:207 screen:0 window:10485795
						`$xdotool mousemove $x_loc $y_loc click --repeat 2 --delay $c_delay 1`;
 
						exit 0;
 
					 }
 
 
                sleep 60;
                print "Continuing to watch.";
 
 
        } else {
                print ".";
        }
 
        sleep 2;
 
}

There's a gap between the ad and the actual audio, leading to a possible race where chromewatch.pl checks the card status in the gap, but in testing I haven't run into it. However, we've extended the wait period to after the ad play, so it's unlikely that the race will appear. For more notes on the operation of this script, see Chromium.

Start Up

At that point, all that's left is an .xinitrc script that calls all the right stuff:

/usr/bin/urxvt -name ChromeWatch -title ChromeWatch -e /home/sam/klay_reflect/chromewatch.pl 
&
/usr/bin/urxvt -name darkicerunner -title DarkIce -e /usr/bin/darkice -c /home/sam/klay_reflect/darkice.cfg &
exec wmaker

Timing

Cron on the dom0 starts and stops as necessary:

# AAA Sunday Schedule (+30 mins each side)
30 14 * * sun /usr/sbin/xl create /etc/xen/noauto/reflector.cfg
30 17 * * sun /usr/sbin/xl shutdown reflector

Screenshot

Why the hell not?

In Use

Old Version

Here is the script:

klay-reflector

cvlc -Idummy  mms://vista.streamguys.com/klay1?MSWMExt=.asf \
	--sout "#transcode{acodec=mp3,ab=64k,samplerate=44100,channels=2}:std{access=shout{mp3=1},bitrate=64k,mux=raw,dst=user:pass@127.0.0.1:8000/aaalive.mp3}" \
	--sout-shout-name "Ask an Atheist Live!"  \
	--sout-shout-description "Ask an Atheist live on the radio!" \
	--sout-shout-bitrate 64 \
	--sout-shout-channels 2 \
	--sout-shout-samplerate 44100 \
	--sout-shout-url http://askanatheist.tv 1>>/home/deadstream/var/klay.log
 2>&1 &
 
echo $! > /home/deadstream/var/klay.pid
1)
Becky's Idea!