Skip to content

workspace sdk timeline providers

Andre Lafleur edited this page Apr 20, 2026 · 6 revisions

About timeline providers

Timeline providers add custom event markers to Security Desk's playback timeline. Use them when operators reviewing video also need related events, such as alarms, access activity, or analytics detections, in the same time range.

A timeline provider extension has two parts: a TimelineProviderBuilder component that you register with Workspace.Components, and a TimelineProvider instance that answers timeline queries and can push real-time updates. The example in this guide uses the public alarm sample and shows how to display active alarms as timeline markers and remove them when alarms are acknowledged.

How timeline providers work

Timeline providers follow a lifecycle managed by Security Desk:

  1. Registration: Your module creates a TimelineProviderBuilder, calls Initialize(Workspace), and registers it with Workspace.Components.

  2. Provider instantiation: When the timeline needs your provider, the system calls your builder's CreateProvider() method.

  3. Querying: When the user navigates the timeline, the system calls your Query() method with a time range. Your implementation queries historical data and calls InsertEvent() or InsertEvents() to add markers.

  4. Real-time updates: As SDK events occur, your event handlers call InsertEvent() or RemoveEvent() to update the timeline dynamically.

TimelineProviderBuilder does not expose an IsSupported method. If your provider only applies to certain content, inspect contentGroup.Current inside Query() and return without inserting events when the current content is not relevant.

Registering timeline providers

Register the builder in your module:

public class SampleModule : Module
{
    private AlarmTimelineProviderBuilder m_builder;

    public override void Load()
    {
        if (Workspace.ApplicationType == ApplicationType.SecurityDesk)
        {
            m_builder = new AlarmTimelineProviderBuilder();
            m_builder.Initialize(Workspace);
            Workspace.Components.Register(m_builder);
        }
    }

    public override void Unload()
    {
        if (m_builder != null)
        {
            Workspace.Components.Unregister(m_builder);
            m_builder = null;
        }
    }
}

The Initialize(Workspace) call is on the builder, not the provider. The builder's CreateProvider() method is called by the system when needed. Keep a module-level reference so Unload() can unregister the component.

Note

Timeline providers require a certificate file to load. See About Workspace SDK certificates for certificate requirements.

TimelineProviderBuilder

Create a timeline provider by inheriting from TimelineProviderBuilder:

public class AlarmTimelineProviderBuilder : TimelineProviderBuilder
{
    public override string Name => nameof(AlarmTimelineProviderBuilder);
    public override string Title => "Active Alarms";
    public override Guid UniqueId { get; } = new Guid("...");

    public override TimelineProvider CreateProvider()
    {
        return new AlarmTimelineProvider(Workspace);
    }
}

TimelineProviderBuilder members

Member Type Description
Name string Component name
Title string Display title in UI
Priority int Display order in the timeline provider list. Lower values appear first.
UniqueId Guid Unique identifier
CreateProvider() TimelineProvider Factory method for provider instances

TimelineProvider

The TimelineProvider class supplies timeline events. It has a single public entry point, Query(...), plus protected helper methods for managing the timeline cache and pushing updates.

public class AlarmTimelineProvider : TimelineProvider
{
    private readonly Workspace m_workspace;

    public AlarmTimelineProvider(Workspace workspace)
    {
        m_workspace = workspace;
        m_workspace.Sdk.AlarmAcknowledged += OnAlarmAcknowledged;
    }

    public override void Query(ContentGroup contentGroup, DateTime startTime, DateTime endTime)
    {
        Task.Run(async () =>
        {
            var query = (AlarmActivityQuery)m_workspace.Sdk.ReportManager.CreateReportQuery(ReportType.AlarmActivity);
            query.TriggeredTimeRange.SetTimeRange(startTime, endTime);
            query.States.Add(AlarmState.Active);
            query.States.Add(AlarmState.AknowledgeRequired);
            query.States.Add(AlarmState.SourceConditionInvestigating);

            QueryCompletedEventArgs result = await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null);

            var events = result.Data.AsEnumerable().Select(row =>
            {
                var alarmGuid = row.Field<Guid>(AlarmActivityQuery.AlarmColumnName);
                var instanceId = row.Field<int>(AlarmActivityQuery.InstanceIdColumnName);
                var triggerTime = row.Field<DateTime>(AlarmActivityQuery.TriggerTimeColumnName);

                return new AlarmTimelineEvent(alarmGuid, instanceId, triggerTime);
            });

            InsertEvents(events);
        });
    }

    private void OnAlarmAcknowledged(object sender, AlarmAcknowledgedEventArgs e)
    {
        foreach (AlarmTimelineEvent timelineEvent in GetEvents().OfType<AlarmTimelineEvent>().Where(t => t.AlarmGuid == e.AlarmGuid && t.InstanceId == e.InstanceId))
        {
            RemoveEvent(timelineEvent);
        }
    }
}

TimelineProvider members

Member Access Description
Query(ContentGroup, DateTime, DateTime) public abstract Query events for the current content and time range
InsertEvent(TimelineEvent) protected Add a single event to the timeline
InsertEvents(IEnumerable<TimelineEvent>) protected Add multiple events to the timeline
GetEvents() protected Retrieve the provider's cached events
RemoveEvent(TimelineEvent) protected Remove an event from the timeline
ClearCache() protected Clear all cached events

The base class also exposes OnQueryCompleted(), but the current Workspace timeline adapter and the public DAP alarm sample do not rely on it.

Timeline events

Create custom event types for display on the timeline by inheriting from TimelineEvent and implementing GetVisual:

public class AlarmTimelineEvent : TimelineEvent
{
    private static readonly Size s_size = new Size(16, 16);
    private static readonly BitmapImage s_image;

    static AlarmTimelineEvent()
    {
        s_image = EntityType.Alarm.GetIcon();
        s_image.Freeze();
    }

    public AlarmTimelineEvent(Guid alarmGuid, int instanceId, DateTime dateTime)
        : base(dateTime)
    {
        AlarmGuid = alarmGuid;
        InstanceId = instanceId;
    }

    public Guid AlarmGuid { get; }
    public int InstanceId { get; }

    public override TimelineVisual GetVisual(Rect constraint, double msPerPixel)
    {
        var drawingVisual = new DrawingVisual();
        DrawingContext drawingContext = drawingVisual.RenderOpen();

        drawingContext.DrawImage(s_image, new Rect(new Point(constraint.X, (constraint.Height - 16) / 2), s_size));

        drawingContext.Close();

        return new TimelineVisual(drawingVisual)
        {
            AlignmentY = AlignmentY.Center,
            AlignmentX = AlignmentX.Center
        };
    }
}

TimelineEvent

The TimelineEvent base class provides:

Member Type Description
Timestamp DateTime When the event occurred (required, set via constructor)
Priority int Display priority, higher values appear topmost (default: 0)
Method Description
GetVisual(Rect, double) Abstract method to create the visual representation

Add custom properties like AlarmGuid and InstanceId to your derived class.

The base TimelineEvent equality implementation uses Timestamp and Priority. If your event type can produce multiple distinct events with the same timestamp and priority, override Equals and GetHashCode() so the timeline cache can distinguish them.

See also

Platform SDK

Plugin SDK

Workspace SDK

Media SDK

Macro SDK

Web SDK

Media Gateway

Genetec Web Player

Clone this wiki locally