Add support for day of month to the cron rules

The cron-style playing rules was missing day of month, which meant
that, for example, you couldn't specify things like "play this on
the 1st of each month"

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
Make sure track is playable when resuming playback

When resuming playback, make sure the current track is in a playlist
that is currently playable and if it's not, select a new track.  This
makes sure that date controlled playlists are properly obeyed during
long pauses.

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
Keep the internal playing state consistent

Set playing to false on end-of-stream before calling next/play.
Not doing this would cause play() to be called twice since next()
also called play() if playing is set to true.

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
select.poll timeout should be in ms

Not sure how it ever worked properly.

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
Turn the Signal class into a singleton

Let the Signal class behave as a singleton per signal, this allows
for more flexible usage without the reference to the first instance
for a specific signal number.

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
Update README with fancy ASCII art

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
Allow more than one parent for playable objects

This turns the playlist tree into a playlist graph.

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
Don't set next track until play is called

This allows the playlist to be populated after the player
is initialized.

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
Fix __str__ for playlists

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
Only poll the gstreamer bus if there is something on it

gst.bus.poll() sometimes hang for some reason, this seems
to be a work around it.

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
Only poll for the gstreamer messages we need

Reduce the amount of callbacks from the bus by only polling
for EOS (end-of-stream) and ERROR.

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
Better handling of empty/non-playable playlists

Let playlist.next() return None if the playlist is empty and
don't attempt to play if no file could be selected.

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
Add dynamic and controllable trigger support

This moves control of the existing triggers, GPIO and signal, to
the playlist and makes them configurable.  Triggers are assigned per
sub-playlist or in the DEFAULTS scope.  Multiple triggers are allowed
per playlist, but at least one is required (for the playlist itself or
in any of its parents) for the playlist to become active.

The syntax to define a trigger is
 trigger=<player_id> <type> <arguments>

The player id is a user defined string the identifies which player this
trigger belongs to.  Multiple triggers can be mapped to the same player,
this allows one player to be controlled by multiple triggers.

To configure a GPIO trigger
 trigger=default gpio path=/path/to/gpio/pin

And to configure a signal trigger
 trigger=default singal toggle=<signum> [next=<signum>]
where signum can be sigusr1, sigusr2

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
Add a random trigger

This trigger will randomly play a sound with configurable probability.

 trigger=<player> random delay=<seconds> dice=<roll>/<eyes>

Delay specifies how often the "dice" should be rolled.
Dice have two components, eyes are the total amount of eyes on the dice,
and roll specifies what counts as a win.  Everything upto and below
for <roll> counts as a win.

Examples

Will roll the dice once a second with a 1/6 probability of playing
 trigger=randomplayer delay=1 dice=1/6

Will roll the dice every 5 minutes with a 5/10 probability of playing
 trigger=randomplayer delay=300 dice=5/10

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
fix trigger_gpio

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>
Isolate each gstreamer instance to a separate process

Move each (relative) costly gstreamer player into separate processes,
since Python threads share the same execution context.  This allows
the gstream thread to run freely without the need to share execution
with the rest of the control code.  Control and state feedback
of each instance is performed over a pipe.

This also removes the need of a global watchdog as each gstreamer
instance will be monitored and restarted as needed.

Signed-off-by: Fredrik Lindberg <fli@shapeshifter.se>