Public Access
fix: recover from microphone disconnects
PR and Push Build/Test / build-and-test (push) Successful in 12m29s
PR and Push Build/Test / build-and-test (push) Successful in 12m29s
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
using MeetingAssistant.Recording;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace MeetingAssistant.Tests;
|
||||
|
||||
public sealed class MicrophoneAudioSourceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task CaptureMovesToNewlyResolvedMicrophoneWhenCurrentCaptureFails()
|
||||
{
|
||||
var captureSources = new SequenceMicrophoneCaptureSourceFactory(
|
||||
new FailingAfterChunkAudioSource(Pcm16(1_000)),
|
||||
new ActiveAudioSource(Pcm16(2_000)));
|
||||
var source = new MicrophoneAudioSource(
|
||||
captureSources,
|
||||
NullLogger<MicrophoneAudioSource>.Instance,
|
||||
TimeSpan.Zero);
|
||||
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
await using var chunks = source
|
||||
.CaptureAsync(new MeetingAssistantOptions(), cancellation.Token)
|
||||
.GetAsyncEnumerator(cancellation.Token);
|
||||
|
||||
Assert.True(await chunks.MoveNextAsync());
|
||||
Assert.Equal(1_000, BitConverter.ToInt16(chunks.Current.Pcm));
|
||||
Assert.True(await chunks.MoveNextAsync());
|
||||
Assert.Equal(2_000, BitConverter.ToInt16(chunks.Current.Pcm));
|
||||
Assert.Equal(2, captureSources.CaptureCreationCount);
|
||||
|
||||
await cancellation.CancelAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CaptureWaitsForMicrophoneToBecomeAvailable()
|
||||
{
|
||||
var captureSources = new InitiallyUnavailableMicrophoneCaptureSourceFactory(
|
||||
new ActiveAudioSource(Pcm16(3_000)));
|
||||
var source = new MicrophoneAudioSource(
|
||||
captureSources,
|
||||
NullLogger<MicrophoneAudioSource>.Instance,
|
||||
TimeSpan.Zero);
|
||||
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
await using var chunks = source
|
||||
.CaptureAsync(new MeetingAssistantOptions(), cancellation.Token)
|
||||
.GetAsyncEnumerator(cancellation.Token);
|
||||
|
||||
Assert.True(await chunks.MoveNextAsync());
|
||||
Assert.Equal(3_000, BitConverter.ToInt16(chunks.Current.Pcm));
|
||||
Assert.Equal(2, captureSources.CaptureCreationCount);
|
||||
|
||||
await cancellation.CancelAsync();
|
||||
}
|
||||
|
||||
private static byte[] Pcm16(short sample)
|
||||
{
|
||||
return BitConverter.GetBytes(sample);
|
||||
}
|
||||
|
||||
private sealed class SequenceMicrophoneCaptureSourceFactory(params IMeetingAudioSource[] sources)
|
||||
: IMicrophoneCaptureSourceFactory
|
||||
{
|
||||
private readonly Queue<IMeetingAudioSource> sources = new(sources);
|
||||
|
||||
public int CaptureCreationCount { get; private set; }
|
||||
|
||||
public IMeetingAudioSource CreateCapture(MeetingAssistantOptions options)
|
||||
{
|
||||
CaptureCreationCount++;
|
||||
return sources.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class InitiallyUnavailableMicrophoneCaptureSourceFactory(IMeetingAudioSource availableSource)
|
||||
: IMicrophoneCaptureSourceFactory
|
||||
{
|
||||
public int CaptureCreationCount { get; private set; }
|
||||
|
||||
public IMeetingAudioSource CreateCapture(MeetingAssistantOptions options)
|
||||
{
|
||||
CaptureCreationCount++;
|
||||
if (CaptureCreationCount == 1)
|
||||
{
|
||||
throw new InvalidOperationException("No microphone is currently available.");
|
||||
}
|
||||
|
||||
return availableSource;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FailingAfterChunkAudioSource(byte[] pcm) : IMeetingAudioSource
|
||||
{
|
||||
public async IAsyncEnumerable<AudioChunk> CaptureAsync(
|
||||
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Yield();
|
||||
yield return new AudioChunk(pcm, 16000, 1);
|
||||
throw new InvalidOperationException("The active microphone was disconnected.");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ActiveAudioSource(byte[] pcm) : IMeetingAudioSource
|
||||
{
|
||||
public async IAsyncEnumerable<AudioChunk> CaptureAsync(
|
||||
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
yield return new AudioChunk(pcm, 16000, 1);
|
||||
await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user