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.
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 |
┌─────────────────────────────────────────────────────────┐
│ acre Access Control │
│ (Windows Desktop App) │
├─────────────────────────────────────────────────────────┤
│ Plugin Host (MEF - Microsoft Extensibility Framework) │
├───────────────┬───────────────┬───────────────┬─────────┤
│ Your VMS │ Milestone │ Genetec │ FNVR │
│ Plugin │ Plugin │ Plugin │ Plugin │
├───────────────┴───────────────┴───────────────┴─────────┤
│ Video Management Systems │
└─────────────────────────────────────────────────────────┘
| 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 |
# 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
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
}
}
Create an MSI installer that:
Places your plugin assembly in the acre plugins folder:
%ProgramData%\Feenics\Plugins\
Registers any required COM components for your VMS SDK
Installs any VMS client dependencies
The acre Access Control native application discovers plugins using the Microsoft Extensibility Framework (MEF).
| 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 |
// Get the plugin folder path programmatically
string pluginPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
"Feenics",
"Plugins"
);
// Result: C:\ProgramData\Feenics\Plugins\
| Property | Type | Description |
|---|---|---|
PluginName |
string |
Display name shown to users in the UI |
PluginType |
PluginWindowType |
UI framework used: WinForms or WPF |
| 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 |
| Event | Description |
|---|---|
SaveUserCredentials |
Request the host to save VMS credentials |
VideoPlayerMouseUp |
User clicked on the video player |
public string PluginName => "My VMS Integration";
public PluginWindowType PluginType => PluginWindowType.WPF; // or WinForms
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;
}
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;
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);
}
}
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();
}
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);
}
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);
}
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);
}
public event EventHandler VideoPlayerMouseUp;
// In your player control initialization
private void InitializePlayerEvents()
{
_playerControl.MouseUp += (sender, args) =>
{
VideoPlayerMouseUp?.Invoke(this, EventArgs.Empty);
};
}
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);
}
}
| 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 |
[Export] attribute is present on the main class%ProgramData%\Feenics\Plugins\