Cron like digital signage scheduling
02 Sep 2012My first attempt to create a digital signage player in Google Chrome used a very simple scheduling strategy: define a list of content items to be played, along with the duration for each item, and play each item sequentially. However, this solution is not very flexible as it does not allow different programs during a day. Using a Cron-based scheduler is much more flexible.
Sequential scheduler
My first sequential scheduler used a very simple scheduling table with just two pieces of information: the item to be played, and the duration. The scheduler simply plays each item, keeping the playhead on an item for the duration assigned to that item. When it reaches the end, the playhead is put at the top:
Table 1: Sequential player
item | duration | playhead |
---|---|---|
Item A | 1 min | <- |
Item B | 3 min | ..<- |
Item C | 2 min | ….<- |
Using a scheduler such as this, does not allow you to have different programs for different times of the day, unless you define a different schedule table for each time of day, AND program the player to use different schedule tables throught the day (which would only introduce complexity to the player).
Cron-based scheduler
Cron is a time-based job scheduler originally designed for Unix systems, which uses a very simple syntax for defining the periodicity of a job. For example, to make JobA run every minute, you would enter the following into the cron table (crontab):
1 * * * * JobA
Cron uses six fields for defining a schedule:
min | hour | day of month | month | day of week | job |
---|---|---|---|---|---|
1 | * | * | * | * | JobA |
Each field can have an asterisk, that matches any possible value for that field; a range, e.g., 1–4, that match all values in the interval, a range and increment, e.g., 10–30/5 (every 5 values from 10 to 30), and lists of values, ranges, and ranges/increment (these are the main ones, at least.)
Cron-jobs and sequential playing
In a digital signage player, although a sequential scheduling is very limited, it is easy to understand and specify, and it maps well to our mental model of a player.
Using cron, although one can specify multiple jobs, they are independently scheduled by cron. This makes it hard to define a set of jobs that will play sequentially. It possible, but very time consuming and error-prone. For example to schedule three jobs corresponding to the items in Table 1, if we make a list of which item is being played on the first few minutes:
Start of Min | Item |
---|---|
0 | A |
1 | B |
2 | B |
3 | B |
4 | C |
5 | C |
6 | A |
7 | B |
8 | B |
9 | B |
10 | C |
11 | C |
We can see that each item plays in 6 min intervals, so a crontab like the following would play the items in sequence:
min | hour | day of month | month | day of week | job |
---|---|---|---|---|---|
0–59/6 | * | * | * | * | ItemA |
1–59/6 | * | * | * | * | ItemB |
4–59/6 | * | * | * | * | ItemC |
However, changing the duration of any item, means changing the whole cron table!
Cron-like scheduling
A better solution would be to combine both the sequential scheduler with a cron-scheduler.
Ideally, I should be able to schedule a set of items for a given period of time, using the same cron definition for all items, but make sure they will play sequentially.
To do this, we need to change how jobs are executed, i.e., we need to make our own cron scheduler.
This scheduler keeps a list of jobs to run, defined in the traditional cron job definition, with two differences. First, we add a duration field to the job specification which indicates for how long the job should run; second, we allow only one job to be executed at a time. The basic process to determine which job should run next is:
- Evaluate each job definition and determine the period of time from now to when the job should be executed (calculate the job timeout),
- Go through the list and pick the first job which has already timed out (it’s timeout is lower than a pre-defined threshold),
- Remove that job from its position in the list an put it at the end of list,
- Run that job,
- Wait for the duration specified in the running job
- Repeat
If no job has timed out, then just wait for a period of time equal to the minimum job timeout in the list and then restart the process.
With this scheduler, it’s easy to define a set of jobs that will run in sequence:
min | hour | day of month | month | day of week | job | duration |
---|---|---|---|---|---|---|
* | * | * | * | * | ItemA | 1 min |
* | * | * | * | * | ItemB | 3 min |
* | * | * | * | * | ItemC | 2 min |
This would be equivalent to our first sequential scheduler example. Notice that, we specified all jobs to run every minute, but this will actually not be the case since our jobs are now executed one at a time. So, when itemA finishes, itemB will already have timed out one minute ago, but it does not matter. Because we execute the first timed out job of the list, and we move executed jobs to the end, we guarantee that all jobs have a chance to run.
However, the nice part is we now can easily define different jobs to run at different times of the day:
min | hour | day of month | month | day of week | job | duration |
---|---|---|---|---|---|---|
* | 9–12 | * | * | * | ItemA | 1 min |
* | 9–12 | * | * | * | ItemB | 3 min |
* | 9–12 | * | * | * | ItemC | 2 min |
* | 13–17 | * | * | * | ItemD | 3 min |
* | 13–17 | * | * | * | ItemE | 2 min |
* | 18–20 | * | * | * | ItemF | 1 min |
* | 18–20 | * | * | * | ItemG | 3 min |
* | 18–20 | * | * | * | ItemH | 2 min |
This defines three content programming for three periods of the day:
period | jobs |
---|---|
9h to 12h | ItemA, ItemB, ItemC |
13h to 17h | ItemD, ItemE |
18h to 20h | ItemG, ItemH |
In each period, the jobs run in repeating sequence.
Implementing in Javascript
The cron-like scheduler is easy to implement in Javascript if you have a cron interpreter like cron.js by Stefan Liebenberg. cron.js is full cron scheduler, but we only need two functions from it: parsing cron job definitions, and telling us the timeout for each job. The implementation of the process is fairly easy so I’ll skip the code…