User-Friendly Content Scheduling in Digital Signage

Scheduling in digital signage is a unique experience.
Most digital signage software makes it difficult to understand what is currently playing on the screen or what will be displayed at a certain time in the future.
Some tools provide overwhelming calendar or timeline views with stacked items that have overlapping schedules or don't have a centralized view at all.
Others are showing real-time screenshots but they still don't clearly indicate the exact playlist currently on the screen. This is because only one content item is visible which could belong to different playlists.
In this article I describe my research project aimed at finding a user-friendly way to work with screen schedules.
I've developed a React application (live demo) with features for creating playlists, assigning screens to them, setting schedules for the playlists and viewing the screen schedule.
Playlists
For simplicity there is only one screen schedule entity: Playlist.
This project is a demo of the screen scheduling, not a fully production-ready app. Therefore I haven't implemented the feature to add content to playlists or the functionality to assign content items directly to the screen schedule.
Playlist have a name, priority, schedule and assigned screens.
I think everything is clear with the names and assigned screens. Let's start directly with schedules and priorities.
Playlist schedule settings
Scheduling is one of the key features in digital signage. Let's keep things simple and implement the most commonly used schedule settings.
At the moment a playlist can only have one schedule for all assigned screens.
However it's not difficult to create custom schedules for individual screens or screen groups.
You can set the date range, time range, and choose the weekdays when the playlist will appear. All schedule rules follow the AND logic not OR.
Simple enough yet flexible, isn't it?
Playlist priority is important
I believe that all digital signage software has a priority option.
Priority can be set on specific content or playlists. Usually it is a checkbox that marks specific content as the most important.
One drawback of the "highest priority checkbox" is that you can mark multiple items as the most important.
Content marked as a priority will take precedence over other content scheduled for the same time slot.
But what if you have two priority items scheduled for the same slot?
Then there are many ways software determines what is more important, except the most obvious and simple for the end user.
I think the best solution is to have different levels of priority similar to z-index in HTML.
I implemented a simple global playlist priority setting. This ensures that no playlists have the same priority and eliminates the need to manually manage priority values. You can simply drag and drop items to set their priorities visually.
The higher a playlist is on the list the higher priority it has. If two playlists have overlapping schedules on the same screen the one with higher priority will be displayed.
Having global priority settings is more convenient because you can easily determine which content is more important for you to display on screens.
This logic also allows having a fallback playlist for screens without needing to implement extra functionality. It is sufficient to set the lowest priority with an indefinite all-day schedule.
Screen schedule view
I truly believe that digital signage software should be the final step in content planning. Any changes made to schedules should not be drafts or tests; they should be planned actions.
My screen schedule view is not designed for making changes but for verifying that everything will be displayed as planned ensuring there are no empty gaps in the screen schedules.
I have a daily view for small screens and a weekly view for larger screens.
Monthly or larger timeframe views are not very usable due to the limited visibility of specific items unless you have a really large monitor.
Only one item per time slot
Displaying screen schedule items as events and appointments in most calendar apps is not the best idea.
In real life you can have simultaneous events and it's important to see when each starts and ends.
For digital signage scheduling it's not essential to know all the items scheduled for a specific time. It doesn't make sense to display all items in the schedule view that can be shown at a specific time because they are not displayed simultaneously.
What's more important is that there can be 5 or 10 overlapping playlists at the same time. It would clutter the interface if we displayed them all.
Even with overlapping schedules only one item will show on the screen following prioritization rules. We just need to know which exact item will be visible at a given time.
On my screen schedule only one specific item is displayed per time slot in the calendar.
Short content and empty gaps in the screen schedule
I've tried a lot of digital signage software and it's really hard to find empty gaps in screen schedules.
Almost every digital signage software solves this by having a fallback content option (default content item or playlist) to fill empty gaps in the schedules. However, there wasn't a convenient way to find and prevent these gaps from appearing on the screen schedules at all.
Most calendars interfaces are representing events related to hours layout. It makes sense because there can be overlapping events and it is unusual to have some extremely short events.
But if we make the height of schedule items relative to their length then the visibility of short items and empty slots will be poor. It's frustrating to have a 2-minute empty slot on the screen but it won't be visible in the overall calendar view. That's why I chose to set a minimum height for each slot that aligns better with our goals rather than sticking strictly to the timeline.
Now even a 1-minute empty slot or incorrectly set up playlist will be visible on the weekly or daily schedule interface.
How it works
Playlist Model
With PlaylistModel
I transform the object created by PlaylistBuilder
. In a real-world application PlaylistModel
can be used to convert JSON data provided by the backend.
One of the main tasks in the PlaylistModel
constructor is converting HH:mm
to minutes. This will be utilized later in the helper method isDisplayingAtMinute
which is used in DayScheduleBuilder
.
If a time range is not provided we set the maximum daily time range from 0 minutes to 1440.
constructor(playlist: Playlist) {
...
if(playlist.timeRange) {
const [startHours, startMinutes] = playlist.timeRange.start.split(":").map(Number)
const [endHours, endMinutes] = playlist.timeRange.end.split(":").map(Number)
this.minutesStartTime = (startHours * 60) + startMinutes
this.minutesEndTime = (endHours * 60) + endMinutes
} else {
this.minutesStartTime = 0
this.minutesEndTime = 1440
}
}
Day Schedule Builder
The primary logic for creating the schedule for a particular day resides in the DayScheduleBuilder
class.
First of all I sort all playlists for a specific day by priority. Here we benefit from global priority settings. It is really simple to set once and use everywhere.
private sortPlaylistsByPriority(): void {
this.playlists.sort((a, b) => b.priority - a.priority)
}
Then I retrieve the first matching playlist from the playlists array. You can refer to the PlaylistModel.ts
file to see the logic of the isDisplayingAtMinute
method, which is actually quite straightforward.
If there is no matching playlist it returns null. We will use this to identify and display empty time slots in the day's schedule on the UI.
private findPlaylistForMinuteSlot(minute: number): PlaylistModel | null {
return this.playlists.find(
playlist => playlist.isDisplayingAtMinute(minute)
) || null
}
In PlaylistModel
I convert the start and end displaying time of the playlist to minutes because that is the smallest time unit a user can choose in the playlist schedule settings. That's why I have 1440 (24 hours * 60 minutes) time slots in a day in the buildDaySlots
method.
It is possible to have any slots you want but I believe minute slots are the most flexible and not overwhelming to manage. I'm not sure if anyone sets schedules for digital signage down to seconds.
private buildDaySlots(): void {
const minutesInDay = 1440
this.daySlots = Array.from({ length: minutesInDay }, (_, minute) => ({
minute,
item: this.findPlaylistForMinuteSlot(minute),
}))
}
And finally we are just merging time slots with the same items that go sequentially.
private buildSchedule(): ScheduleItemModel[] {
const schedule: ScheduleItemModel[] = []
let lastItem: PlaylistModel | null | undefined
for (const { minute, item } of this.daySlots) {
if (item !== lastItem) {
schedule.push(this.createScheduleItem(minute, item))
lastItem = item
} else {
this.updateScheduleItemEndTime(schedule[schedule.length - 1], minute)
}
}
return schedule
}
You can check the entire code in the GitHub repository.
Day Schedule component
DaySchedule
component is responsible for rendering a schedule of items for a particular day based on the provided date and playlists.
The isDisplayingAtDate
method checks if the provided date is within the playlist's date range and if the weekday matches the playlist's scheduled weekdays.
export const DaySchedule = ({ date }: { date: Dayjs }) => {
const screen = useScreen()
const todayPlaylists = screen.playlists.filter((playlist) => playlist.isDisplayingAtDate(date))
const dayScheduleBuilder = new DayScheduleBuilder(todayPlaylists)
const schedule = dayScheduleBuilder.build()
return (
<div className="flex flex-col gap-2">
{ schedule.map((item, index) => (
<DayScheduleItem
key={ index }
item={ item }
/>
))}
</div>
)
}
Live demo
A live demo is available on signage.pages.dev. All data is stored in the browser's local storage.
Source code
https://github.com/514sid/digital-signage-schedule
I support the open-source community and believe that we can create a better future through sharing and supporting each other.
Source code, including the implemented interface design, is MIT-licensed. You are free to use anything from the repository without any restrictions.
You can show that this article and source code were useful to you by starring the repository.
If you have any questions or suggestions you can contact me in the comments or on Discord. My username is 514sid.
Subscribe to my newsletter
Read articles from 514sid directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
