Video Player Plug-In Development

The acre Access Control™ system offers an extensible architecture for Video Management System (VMS) vendors to integrate their video playback solutions with the native Windows desktop application.


Overview

What You Can Build

Using this extensible architecture, VMS integrators can:

Capability Description
Camera Import Automatically discover and import cameras from your VMS
Live Video Stream real-time video in the acre Access Control interface
Playback Review recorded video with full transport controls
PTZ Control Pan, tilt, and zoom compatible cameras
Event Linking Associate video clips with access control events
Snapshot Export Capture still images from video streams
Clip Export Extract video segments to files

Architecture Overview

┌─────────────────────────────────────────────────────────┐
│                acre Access Control                       │
│                (Windows Desktop App)                     │
├─────────────────────────────────────────────────────────┤
│    Plugin Host (MEF - Microsoft Extensibility Framework) │
├───────────────┬───────────────┬───────────────┬─────────┤
│   Your VMS    │   Milestone   │    Genetec    │  FNVR   │
│   Plugin      │    Plugin     │    Plugin     │ Plugin  │
├───────────────┴───────────────┴───────────────┴─────────┤
│              Video Management Systems                    │
└─────────────────────────────────────────────────────────┘

Getting Started

Prerequisites

Requirement Details
.NET Framework 4.7.2 or higher
Visual Studio 2019 or later recommended
NuGet Package Feenics.Keep.Windows.Contracts.VideoPlayer
NuGet Source https://nuget-secure.feenics.com/nuget

Step 1: Create the Project

# Create a new class library project
dotnet new classlib -n MyVmsPlugin -f net472

# Add the required NuGet package
dotnet add package Feenics.Keep.Windows.Contracts.VideoPlayer --source https://nuget.feenics.com/nuget

Step 2: Implement the Interface

Create a class that implements IVideoPlayerPlugin:

using System;
using System.ComponentModel.Composition;
using System.Threading.Tasks;
using Feenics.Keep.Windows.Contracts.VideoPlayer;

namespace MyVmsPlugin
{
    [Export(typeof(IVideoPlayerPlugin))]
    public class MyVmsVideoPlayer : IVideoPlayerPlugin
    {
        // Implementation details below
    }
}

Step 3: Deploy the Plugin

Create an MSI installer that:

  1. Places your plugin assembly in the acre plugins folder:

    %ProgramData%\Feenics\Plugins\
    
  2. Registers any required COM components for your VMS SDK

  3. Installs any VMS client dependencies


Plugin Discovery

The acre Access Control native application discovers plugins using the Microsoft Extensibility Framework (MEF).

Discovery Requirements

Requirement Description
Interface Must implement IVideoPlayerPlugin
Export Attribute Must have [Export(typeof(IVideoPlayerPlugin))]
Location Assembly must be in %ProgramData%\Feenics\Plugins\
Dependencies All dependencies must be resolvable

Plugin Folder Location

// Get the plugin folder path programmatically
string pluginPath = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
    "Feenics",
    "Plugins"
);
// Result: C:\ProgramData\Feenics\Plugins\

IVideoPlayerPlugin Interface Reference

Properties

Property Type Description
PluginName string Display name shown to users in the UI
PluginType PluginWindowType UI framework used: WinForms or WPF

Methods

Method Description
ImportCamerasAsync Discover and return available cameras from your VMS
SetUserCredentials Receive stored VMS credentials for the current user
ShowVideoPlayer Render the video player in the provided container
SetCameraAsync Select a camera as the video source
Live Begin streaming live video
Playback Begin video playback for a time range
PlaybackPosition Get current playback timecode
Seek Jump to a specific time position
Play Resume playback
Pause Pause playback
Rewind Decrease playback speed (repeated calls reverse)
Forward Increase playback speed
TakeSnapshot Capture current frame as JPEG image
ExportClip Export video segment to file
PanTilt Control camera pan/tilt position
Zoom Control camera zoom level

Events

Event Description
SaveUserCredentials Request the host to save VMS credentials
VideoPlayerMouseUp User clicked on the video player

Implementation Guide

Properties Implementation

public string PluginName => "My VMS Integration";

public PluginWindowType PluginType => PluginWindowType.WPF; // or WinForms

Camera Import

The ImportCamerasAsync method discovers cameras from your VMS and returns them for import into acre Access Control.

public async Task<Camera[]> ImportCamerasAsync(VmsLoginInfo loginInfo)
{
    // Connect to your VMS using provided credentials
    var vmsClient = new MyVmsClient(loginInfo.ServerAddress);
    await vmsClient.LoginAsync(loginInfo.Username, loginInfo.Password);
    
    // Discover cameras
    var vmsCameras = await vmsClient.GetCamerasAsync();
    
    // Convert to acre Camera objects
    var cameras = vmsCameras.Select(c => new Camera
    {
        Id = c.Guid.ToString(),
        Name = c.DisplayName,
        Description = c.Location,
        IsPtz = c.SupportsPtz
    }).ToArray();
    
    // Optionally save credentials for future sessions
    SaveUserCredentials?.Invoke(this, new SaveCredentialsEventArgs
    {
        ServerAddress = loginInfo.ServerAddress,
        Username = loginInfo.Username,
        EncryptedPassword = Encrypt(loginInfo.Password)
    });
    
    return cameras;
}

Credential Management

private VmsCredentials _savedCredentials;

public void SetUserCredentials(VmsCredentials credentials)
{
    // Called by host with previously saved credentials
    _savedCredentials = credentials;
}

// Event to request credential storage
public event EventHandler<SaveCredentialsEventArgs> SaveUserCredentials;

Video Player Initialization

private MyVmsPlayerControl _playerControl;

public void ShowVideoPlayer(object container)
{
    // For WPF plugins
    if (container is ContentControl wpfContainer)
    {
        _playerControl = new MyVmsPlayerControl();
        wpfContainer.Content = _playerControl;
    }
    
    // For WinForms plugins
    if (container is Panel winFormsContainer)
    {
        _playerControl = new MyVmsPlayerControl();
        _playerControl.Dock = DockStyle.Fill;
        winFormsContainer.Controls.Add(_playerControl);
    }
}

Camera Selection and Live View

private Camera _currentCamera;

public async Task SetCameraAsync(Camera camera)
{
    _currentCamera = camera;
    await _playerControl.ConnectToCameraAsync(camera.Id);
}

public void Live()
{
    if (_currentCamera == null)
        throw new InvalidOperationException("No camera selected");
        
    _playerControl.StartLiveStream();
}

Playback Controls

public void Playback(DateTime startTime, DateTime endTime)
{
    _playerControl.StartPlayback(startTime, endTime);
}

public DateTime PlaybackPosition()
{
    return _playerControl.CurrentPosition;
}

public void Seek(DateTime position)
{
    _playerControl.SeekTo(position);
}

public void Play()
{
    _playerControl.Resume();
}

public void Pause()
{
    _playerControl.Pause();
}

private int _playbackSpeed = 1;

public void Rewind()
{
    _playbackSpeed = Math.Max(_playbackSpeed - 1, -8); // Min -8x speed
    _playerControl.SetPlaybackSpeed(_playbackSpeed);
}

public void Forward()
{
    _playbackSpeed = Math.Min(_playbackSpeed + 1, 8); // Max 8x speed
    _playerControl.SetPlaybackSpeed(_playbackSpeed);
}

Export Functions

public byte[] TakeSnapshot()
{
    // Capture current frame and return as JPEG
    var bitmap = _playerControl.CaptureFrame();
    
    using (var stream = new MemoryStream())
    {
        bitmap.Save(stream, ImageFormat.Jpeg);
        return stream.ToArray();
    }
}

public void ExportClip(string filename, DateTime startTime, DateTime endTime)
{
    // Export video segment based on current playback range
    _playerControl.ExportVideo(filename, startTime, endTime);
}

PTZ Controls

public void PanTilt(PanTiltValue value)
{
    if (!_currentCamera.IsPtz)
        return;
        
    // Value ranges from -1.0 to 1.0 for both pan and tilt
    _playerControl.MovePtz(value.Pan, value.Tilt);
}

public void Zoom(double zoomLevel)
{
    if (!_currentCamera.IsPtz)
        return;
        
    // Zoom level typically 0.0 (wide) to 1.0 (telephoto)
    _playerControl.SetZoom(zoomLevel);
}

Mouse Event Handling

public event EventHandler VideoPlayerMouseUp;

// In your player control initialization
private void InitializePlayerEvents()
{
    _playerControl.MouseUp += (sender, args) =>
    {
        VideoPlayerMouseUp?.Invoke(this, EventArgs.Empty);
    };
}

Complete Example

using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Controls;
using Feenics.Keep.Windows.Contracts.VideoPlayer;

namespace MyVmsPlugin
{
    [Export(typeof(IVideoPlayerPlugin))]
    public class MyVmsVideoPlayer : IVideoPlayerPlugin
    {
        private MyVmsPlayerControl _player;
        private Camera _camera;
        private int _speed = 1;

        public string PluginName => "My VMS Plugin";
        public PluginWindowType PluginType => PluginWindowType.WPF;

        public event EventHandler<SaveCredentialsEventArgs> SaveUserCredentials;
        public event EventHandler VideoPlayerMouseUp;

        public async Task<Camera[]> ImportCamerasAsync(VmsLoginInfo loginInfo)
        {
            // Connect and discover cameras
            var client = await MyVmsClient.ConnectAsync(
                loginInfo.ServerAddress, 
                loginInfo.Username, 
                loginInfo.Password);
            
            return (await client.GetCamerasAsync())
                .Select(c => new Camera { Id = c.Id, Name = c.Name })
                .ToArray();
        }

        public void SetUserCredentials(VmsCredentials credentials)
        {
            // Store for auto-reconnect
        }

        public void ShowVideoPlayer(object container)
        {
            _player = new MyVmsPlayerControl();
            ((ContentControl)container).Content = _player;
        }

        public async Task SetCameraAsync(Camera camera)
        {
            _camera = camera;
            await _player.SelectCameraAsync(camera.Id);
        }

        public void Live() => _player.GoLive();
        public void Playback(DateTime start, DateTime end) => _player.Playback(start, end);
        public DateTime PlaybackPosition() => _player.Position;
        public void Seek(DateTime position) => _player.Seek(position);
        public void Play() => _player.Play();
        public void Pause() => _player.Pause();
        
        public void Rewind()
        {
            _speed = Math.Max(-8, _speed - 1);
            _player.Speed = _speed;
        }
        
        public void Forward()
        {
            _speed = Math.Min(8, _speed + 1);
            _player.Speed = _speed;
        }

        public byte[] TakeSnapshot() => _player.CaptureJpeg();
        
        public void ExportClip(string filename, DateTime start, DateTime end) 
            => _player.Export(filename, start, end);
            
        public void PanTilt(PanTiltValue value) => _player.Ptz(value.Pan, value.Tilt);
        public void Zoom(double level) => _player.Zoom(level);
    }
}

Best Practices

Practice Recommendation
Error Handling Wrap VMS SDK calls in try-catch (the more the better); log errors for diagnostics; document where they can be found
Threading Use async/await for network operations; marshal UI updates to main thread
Resource Cleanup Implement IDisposable; release VMS connections and video streams
Credentials Never store passwords in plain text; use the host’s secure storage
Testing Test with multiple cameras and concurrent video streams; document camera count limitations
Logging Include comprehensive logging for troubleshooting installations

Deployment Checklist

  • Plugin assembly compiles against correct .NET Framework version
  • [Export] attribute is present on the main class
  • All dependencies are included or installed by MSI
  • COM components are registered (if required by VMS SDK)
  • Plugin installs to %ProgramData%\Feenics\Plugins\
  • Tested on clean Windows installation
  • Tested with acre Access Control current version

See Also