Background
TeamPlayer is a Django-based streaming radio app with a twist. A while back it gained a feature called "shake things up" where, instead of dead silence, "DJ Ango" would play tracks from the TeamPlayer Library when no players had any queued songs. Initially this was implemented by creating a queue for DJ Ango and then filling it with random tracks. This worked but after I while I became annoyed by the "randomness" and so went about writing a few other implementations which I call "autofill strategies". These were function definitions and the autofill logic used an if/else
clause to select which function to call based on what was set in the Django settings.
Recently I got rid of the if/else
's and instead use setuptools entry points. This also allows for third parties to write "autofill plugins" for TeamPlayer. Here's how to do it.
As I said every autofill strategy is a Python function with the following signature:
def my_autofill_strategy(*, queryset, entries_needed, station):
This function should return a list
of teamplayer.models.LibraryItem
. The list should ideally have a length of entries_needed
but no longer, and the returned list should contain entries from the queryset
. The "should"s are emphasized because sometimes a particular strategy can't find enough entries from the queryset so it can either return a smaller list or return entries not in the queryset
or both. The station
argument is the teamplayer.models.Station
instance for which songs are being selected. This is (almost) always Station.main_station()
.
Idea
Regular terrestrial radio stations often play the same set of songs in rotation over and over again. This is one reason why I rarely listen to them. However I thought this would be an interesting (and easy) autofill strategy to write.
Implementation
Here's the idea: keep a (play)list of songs from the TeamPlayer Library for rotation, store it in a database table, and then write the autofill function to simply pick from that list. Here is the Django database model:
from django.db import models
from teamplayer.models import LibraryItem
class Song(models.Model):
song = models.OneToOneField(LibraryItem)
This table's rows just point to a LibraryItem
. We can use the Django admin site to maintain the list. So again the autofill function just points to entries from the list:
from .models import Song
def rotation_autofill(*, queryset, entries_needed, station):
songs = Song.objects.order_by('?')[:entries_needed]
songs = [i.song for i in songs]
return songs
Now all that we need is some logic to run the commercial breaks and station identification. Just kidding. Now all that is needed is to "package" our plugin.
Packaging
As I've said TeamPlayer now uses setuptools entry points to get autofill strategies. The entry point group name for autofill plugins is aptly called 'teamplayer.autofill_strategy'
. So in our setup.py
we register our function as such:
# setup.py
from setuptools import setup
setup(
name='mypackage',
...
entry_points={
'teamplayer.autofill_strategy': [
'rotation = mypackage.autofill:rotation_autofill',
]
}
)
Here the entry_points
argument to setup
defines the entry points. For this we declare the group teamplayer.autofill_strategy
and in that group we have a single entry point called rotation
. rotation
points to the rotation_autofill
function in the module mypackage.autofill
(using dots for the module and a colon for the member).
From there all you would need is to pip install
your app, add it to INSTALLED_APPS
(after TeamPlayer) and change the following setting:
TEAMPLAYER = {
'SHAKE_THINGS_UP': 10,
'AUTOFILL_STRATEGY': 'rotation',
}
The 'SHAKE_THINGS_UP'
setting tells TeamPlayer the (maximum) number of Library items to add to DJ Ango's queue at a time (0
to disable) and the AUTOFILL_STRATEGY
tells which autofill strategy plugin to load.
A (more) complete implementation of this example is here.