Java library for storage and playback of events along a timeline.
Author: Rohan Khayech
This library contains the Timeline
data structure, which allows events to be placed at a specified time along a timeline.
This allows objects (implementing the TimelineEvent
interface) to be added and retrieved from a chronological list, using their specified time as the index, rather than position.
The library also provides the TimelinePlayer
class, which allows playback of a timeline, triggering each event at it's specified time. TimelineEvent
is a functional interface that defines an event's behavior when triggered.
The library also supports contextual events and provides a listener interface for observing timeline changes and playback events.
A Timeline
can be created using it's constructor, taking in the type of event and the unit of time to use:
// Create a new timeline.
Timeline<TimelineEvent> timeline = new Timeline<>(TimeUnit.SECONDS);
Adding an event is then as easy as specifying the time to place it at and defining a lambda to be executed when the event is triggered:
// Add an event using lambda syntax.
timeline.addEvent(1, () -> {
System.out.println("Hello world.");
});
The timeline can then be played using a TimelinePlayer
object and calling play:
// Create a timeline player to handle playback of the timeline.
TimelinePlayer<TimelineEvent> player = new TimelinePlayer<>(timeline);
// Play the timeline.
player.play();
Playback will then trigger events at their specified times on the timeline.
See LICENSE.
Copyright © 2022 Rohan Khayech
A Timeline
can be constructed by specifying TimelineEvent
type parameter, representing the type of event to store, and the unit of time to use:
// Create a new timeline.
Timeline<TimelineEvent> timeline = new Timeline<>(TimeUnit.SECONDS);
An Event can then be added by specifying the timestamp to place it at, and then passing in an implementation of the TimelineEvent
interface:
// Add an event using an anonymous class.
timeline.addEvent(2, new TimelineEvent() {
@Override
public void trigger() {
System.out.println("This event is placed at two seconds, and will print when triggered.");
}
});
This can also be shortened to use lambda syntax as shown above in Getting Started.
To create a more complex or reusable event, create a class implementing TimelineEvent
. The example below shows an event that holds a message and prints it when triggered:
// Implement TimelineEvent for more complex events.
public class MessageEvent implements TimelineEvent {
private final String msg;
public MessageEvent(String msg) {
this.msg = msg;
}
@Override
public void trigger() {
System.out.println(msg);
}
@Override
public String toString() {
return "Print \"" + msg + "\"";
}
}
Events of this class may then be added to the timeline as follows:
// Add a custom event to the timeline.
MessageEvent msgEvent = new MessageEvent("Message");
timeline.addEvent(3, msgEvent);
The Timeline
data structure supports many of the same operations as other collections and lists.
Events can be retrieved from a timeline via their timestamp by calling the get
or getAll
methods:
// Retrieve the first event placed at the specified timestamp.
TimelineEvent event = timeline.get(1);
// Retrieve all events placed at the specified timestamp.
List<TimelineEvent> events = timeline.getAll(1);
Events can also be easily removed from a timeline by passing in a reference:
// Remove an event.
timeline.removeEvent(event);
It also features other operations such as inserting events and iteration. See the Timeline
class for more information.
Timelines can be played via the use of a TimelinePlayer
object. A timeline player can be initalised by passing in a Timeline
during construction:
// Create a timeline player to handle playback of the timeline.
TimelinePlayer<TimelineEvent> player = new TimelinePlayer<>(timeline);
Note: This starts a new thread for playback in the background, so they timeline player must be closed once it is no longer required.
Once a timeline player is created, the timeline can be played by calling the play
or start
methods:
// Play the timeline.
player.play();
// Play from the start of the timeline.
player.start();
Playback will then trigger events at their specified times on the timeline.
Playback can be paused by calling the pause
or stop
methods:
// Pause playback.
player.pause();
// Pause and return playhead to the start.
player.stop();
The player can also be set to play from any point along the timeline by calling the scrub
method:
// Scrub playback to 2 seconds along the timeline.
player.scrub(time);
Once the calling class is finished with the timeline player it is important to close it by calling the close
method.
// Close the player once finished.
player.close(); // Player can no longer be used.
This stops the playback thread, ensuring there are no memory leaks in the future.
Note: Once the timeline player is closed, it cannot be used again. If playback needs to be started again, a new
TimelinePlayer
object must be created.
The library provides two listener interfaces, one for observing modifications to a timeline, and one for observing playback events.
The TimelineListener
interface provides callbacks before and after any modifications to the timeline, and can be attached to a Timeline
as follows:
// Add a listener to the timeline to be notified before and after modification operations.
timeline.addListener(new TimelineListener<E>() {
@Override public void beforeTimelineChanged() {...}
@Override public void onTimelineChanged() {...}
@Override public void onEventAdded(long t, E event) {...}
@Override public void onEventInserted(long timestamp, long interval) {...}
@Override public void beforeEventModified(long timestamp, E event) {...}
@Override public void onEventModified(long timestamp, E event) {...}
@Override public void onEventShifted(long oldTimestamp, long newTimestamp, E event) {...}
@Override public void onEventRemoved(long timestamp, E event) {...}
@Override public void onDurationChanged(long oldDuration, long newDuration) {...}
@Override public void onTimelineCleared() {...}
});
The beforeTimelineChanged
callback can also throw an IllegalStateException
to prevent a modification operation if required, such as during playback.
The TLPlaybackListener
interface provides callbacks during playback of a timeline. It can be attached to a TimelinePlayer
as follows:
// Add a listener to the timeline player to be notified of playback events.
player.addListener(new TLPlaybackListener() {
@Override public void onPlayheadUpdated(long playhead) {...}
@Override public void onPlaybackStart() {...}
@Override public void onPlaybackPaused() {...}
@Override public void onPlaybackFinished() {...}
});
Listeners only need to implement the callbacks that are required for the use case. The default implementation of these listeners is to ignore the callback.
Certain events may require a reference to a context object, such as an audio device or platform application context, in order to execute their triggered action. Rather than storing this reference with each event, this library provides a ContextualTimelinePlayer
object, which injects the required context object to each event as they are triggered.
To use this feature, first create a Timeline
with an event type of ContextualTimelineEvent
(or subclass). The event itself must also specify the type of context object required:
// Create a new timeline of events that require context when triggered.
Timeline<
ContextualTimelineEvent<
Object // Context object type.
>
> cTimeline = new Timeline<>(TimeUnit.SECONDS);
Then construct a ContextualTimelinePlayer
with the same event type, and pass in the context object:
// Create a contextual timeline player and pass in the context.
Object context = new Object();
TimelinePlayer<ContextualTimelineEvent<Object>> cPlayer = new ContextualTimelinePlayer<>(cTimeline, context);
The player can be used in the same way as a standard timeline player. The context object will be passed to each event when it is triggered.
// Play the timeline.
cPlayer.play();
This library also provides a TimelineMap
data structure which, similar to the Map
interface in the standard library, only allows one event to be placed at each timestamp.
A timeline map can be created as follows:
// Create a new timeline that only allows one event at any specified time.
Timeline<TimelineEvent> timelineMap = new TimelineMap<>(TimeUnit.SECONDS);
The timeline map behaves the same way as a standard Timeline
, except that attempting to add an event at a timestamp where an event already exists will result in an exception.