2026-05-07 11:13:00 -04:00
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using TeamsISO.Engine.Interop;
|
|
|
|
|
using TeamsISO.Engine.Pipeline;
|
|
|
|
|
|
|
|
|
|
namespace TeamsISO.Engine.Tests.Fakes;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// In-memory test double for <see cref="INdiInterop"/>. Tests configure source lists and frame
|
|
|
|
|
/// queues; the fake feeds those into engine code as if a real NDI runtime were present.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed class FakeNdiInterop : INdiInterop
|
|
|
|
|
{
|
|
|
|
|
public List<string> Sources { get; } = new();
|
|
|
|
|
public ConcurrentDictionary<string, ConcurrentQueue<RawFrame>> ReceiverFrames { get; } = new();
|
|
|
|
|
public ConcurrentDictionary<string, List<ProcessedFrame>> SentFrames { get; } = new();
|
|
|
|
|
public string RuntimeVersion { get; set; } = "6.0.0";
|
|
|
|
|
public Dictionary<string, int> ReceiverCreatedCount { get; } = new();
|
|
|
|
|
public Dictionary<string, int> SenderCreatedCount { get; } = new();
|
|
|
|
|
|
2026-05-07 23:48:49 -04:00
|
|
|
/// <summary>Last <c>groups</c> string seen by <see cref="CreateFinder"/>; null = default Public.</summary>
|
|
|
|
|
public string? LastFinderGroups { get; private set; }
|
|
|
|
|
/// <summary>Per-output <c>groups</c> string seen by <see cref="CreateSender"/>; null = default Public.</summary>
|
|
|
|
|
public Dictionary<string, string?> SenderGroups { get; } = new();
|
|
|
|
|
|
|
|
|
|
public NdiFindHandle CreateFinder(string? groups = null)
|
|
|
|
|
{
|
|
|
|
|
LastFinderGroups = groups;
|
|
|
|
|
return new FakeFindHandle();
|
|
|
|
|
}
|
2026-05-07 11:13:00 -04:00
|
|
|
public IReadOnlyList<string> GetCurrentSources(NdiFindHandle finder) => Sources.ToArray();
|
|
|
|
|
|
|
|
|
|
public NdiReceiverHandle CreateReceiver(string sourceFullName)
|
|
|
|
|
{
|
|
|
|
|
ReceiverCreatedCount[sourceFullName] = ReceiverCreatedCount.GetValueOrDefault(sourceFullName) + 1;
|
|
|
|
|
ReceiverFrames.GetOrAdd(sourceFullName, _ => new ConcurrentQueue<RawFrame>());
|
|
|
|
|
return new FakeReceiverHandle(sourceFullName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public RawFrame? CaptureFrame(NdiReceiverHandle receiver, int timeoutMs)
|
|
|
|
|
{
|
|
|
|
|
var key = ((FakeReceiverHandle)receiver).Source;
|
|
|
|
|
if (ReceiverFrames.TryGetValue(key, out var q) && q.TryDequeue(out var frame))
|
|
|
|
|
return frame;
|
|
|
|
|
return null; // simulate timeout
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-07 23:48:49 -04:00
|
|
|
public NdiSenderHandle CreateSender(string outputName, string? groups = null)
|
2026-05-07 11:13:00 -04:00
|
|
|
{
|
|
|
|
|
SenderCreatedCount[outputName] = SenderCreatedCount.GetValueOrDefault(outputName) + 1;
|
2026-05-07 23:48:49 -04:00
|
|
|
SenderGroups[outputName] = groups;
|
2026-05-07 11:13:00 -04:00
|
|
|
SentFrames.GetOrAdd(outputName, _ => new List<ProcessedFrame>());
|
|
|
|
|
return new FakeSenderHandle(outputName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SendFrame(NdiSenderHandle sender, ProcessedFrame frame)
|
|
|
|
|
{
|
|
|
|
|
var key = ((FakeSenderHandle)sender).Output;
|
|
|
|
|
SentFrames[key].Add(frame);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetRuntimeVersion() => RuntimeVersion;
|
|
|
|
|
|
|
|
|
|
private sealed class FakeFindHandle : NdiFindHandle
|
|
|
|
|
{
|
|
|
|
|
public override void Dispose() { }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private sealed class FakeReceiverHandle : NdiReceiverHandle
|
|
|
|
|
{
|
|
|
|
|
public string Source { get; }
|
|
|
|
|
public FakeReceiverHandle(string source) => Source = source;
|
|
|
|
|
public override void Dispose() { }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private sealed class FakeSenderHandle : NdiSenderHandle
|
|
|
|
|
{
|
|
|
|
|
public string Output { get; }
|
|
|
|
|
public FakeSenderHandle(string output) => Output = output;
|
|
|
|
|
public override void Dispose() { }
|
|
|
|
|
}
|
|
|
|
|
}
|