using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Windows.Threading; using ScreenRecorderLib; namespace Kniip.Recording; internal sealed record RecordingOptions(bool SystemAudio, bool Microphone, bool ShowCursor, bool HighlightClicks, int Fps) { public bool HasAudio => SystemAudio || Microphone; } /// Eén opname naar een tijdelijk MP4-bestand. Gebeurtenissen komen binnen op de UI-thread. internal sealed class RecordingSession : IDisposable { public static string TempFolder { get; } = Path.Combine(Path.GetTempPath(), "Kniip"); private readonly Dispatcher _dispatcher; private readonly RecordingRegion _region; private Recorder? _recorder; private bool _everRecorded; public event Action? Completed; /// Foutmelding en of de opname al gestart was (anders is opnieuw proberen zinvol). public event Action? Failed; public RecordingSession(RecordingRegion region, RecordingOptions options, Dispatcher dispatcher) { _region = region; Options = options; _dispatcher = dispatcher; OutputPath = Path.Combine(TempFolder, $"opname-{DateTime.Now:yyyyMMdd-HHmmss}-{Guid.NewGuid():N}.mp4"); } public RecordingOptions Options { get; } public string OutputPath { get; } /// Gekozen geluidsbron(nen) die niet bestaan op deze pc. public string? MissingAudio { get; private set; } public bool IsPaused => _recorder?.Status == RecorderStatus.Paused; public void Start() { Directory.CreateDirectory(TempFolder); _recorder = Recorder.CreateRecorder(BuildOptions()); _recorder.OnStatusChanged += (_, e) => { if (e.Status == RecorderStatus.Recording) { _everRecorded = true; } }; _recorder.OnRecordingComplete += (_, e) => _dispatcher.BeginInvoke(() => Completed?.Invoke(e.FilePath)); _recorder.OnRecordingFailed += (_, e) => { bool started = _everRecorded; _dispatcher.BeginInvoke(() => Failed?.Invoke(e.Error, started)); }; _recorder.Record(OutputPath); } /// Verwijdert achtergebleven opnames en klembordkopieën van meer dan een dag oud. public static void CleanupTempFolder() { try { if (!Directory.Exists(TempFolder)) { return; } DateTime limit = DateTime.Now.AddDays(-1); foreach (string file in Directory.EnumerateFiles(TempFolder, "*", SearchOption.AllDirectories)) { if (File.GetLastWriteTime(file) < limit) { File.Delete(file); } } foreach (string folder in Directory.EnumerateDirectories(TempFolder, "*", SearchOption.AllDirectories).Reverse()) { if (!Directory.EnumerateFileSystemEntries(folder).Any()) { Directory.Delete(folder); } } } catch (Exception) { // Opruimen is best effort. } } public void Pause() => _recorder?.Pause(); public void Resume() => _recorder?.Resume(); public void Stop() => _recorder?.Stop(); private RecorderOptions BuildOptions() { System.Drawing.Rectangle rect = _region.MonitorRelative; var source = new DisplayRecordingSource(_region.DeviceName) { SourceRect = new ScreenRect(rect.X, rect.Y, rect.Width, rect.Height), IsCursorCaptureEnabled = Options.ShowCursor }; var audioSources = new List(); var missing = new List(); if (Options.SystemAudio) { AddAudio(audioSources, missing, AudioDevices.DefaultPlaybackId, id => new LoopbackAudioSource(id), "systeemgeluid"); } if (Options.Microphone) { AddAudio(audioSources, missing, AudioDevices.DefaultMicrophoneId, id => new CaptureAudioSource(id), "microfoon"); } MissingAudio = missing.Count == 0 ? null : string.Join(" en ", missing); return new RecorderOptions { SourceOptions = new SourceOptions { RecordingSources = new List { source } }, OutputOptions = new OutputOptions { RecorderMode = RecorderMode.Video, OutputFrameSize = new ScreenSize(rect.Width, rect.Height), Stretch = StretchMode.Uniform }, AudioOptions = new AudioOptions { IsAudioEnabled = audioSources.Count > 0, AudioSources = audioSources }, MouseOptions = new MouseOptions { IsMousePointerEnabled = Options.ShowCursor, IsMouseClicksDetected = Options.HighlightClicks, MouseClickDetectionMode = MouseDetectionMode.Hook, MouseLeftClickDetectionColor = "#FFD400", MouseRightClickDetectionColor = "#00B4FF", MouseClickDetectionRadius = 22, MouseClickDetectionDuration = 200 }, VideoEncoderOptions = new VideoEncoderOptions { Encoder = new H264VideoEncoder { BitrateMode = H264BitrateControlMode.Quality, EncoderProfile = H264Profile.High }, Quality = 80, Framerate = Options.Fps, IsHardwareEncodingEnabled = true, IsMp4FastStartEnabled = true } }; } private static void AddAudio(List sources, List missing, string? deviceId, Func create, string label) { if (deviceId == null) { missing.Add(label); return; } sources.Add(create(deviceId)); } public void Dispose() { _recorder?.Dispose(); _recorder = null; } }