forked from Manuel/meeting-assistant
fix(macos): preserve audio when transcription fails
This commit is contained in:
@@ -15,6 +15,34 @@ namespace MeetingAssistant.Tests;
|
||||
|
||||
public sealed class MacOsMeetingAudioSourceTests
|
||||
{
|
||||
[Fact]
|
||||
public void NativeMicrophoneHelperDeclaresMacOsPrivacyMetadata()
|
||||
{
|
||||
if (!OperatingSystem.IsMacOS())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var configuration = new DirectoryInfo(AppContext.BaseDirectory)
|
||||
.Parent?.Name ?? "Debug";
|
||||
var helperPath = Path.GetFullPath(Path.Combine(
|
||||
AppContext.BaseDirectory,
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"MeetingAssistant",
|
||||
"bin",
|
||||
configuration,
|
||||
"net10.0",
|
||||
"Native",
|
||||
"macos-meeting-audio-capture"));
|
||||
var helperImage = System.Text.Encoding.UTF8.GetString(File.ReadAllBytes(helperPath));
|
||||
|
||||
Assert.Contains("cloud.schweigert.meeting-assistant.audio-capture", helperImage, StringComparison.Ordinal);
|
||||
Assert.Contains("NSMicrophoneUsageDescription", helperImage, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MacOsMicrophoneStreamsNativePcmUsingRunFormat()
|
||||
{
|
||||
|
||||
@@ -1378,6 +1378,45 @@ public sealed class RecordingCoordinatorTests
|
||||
Assert.False(audioArchive.Deleted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StopQueuesAzureBacklogAndRetainsAudioWhenTranscriptionFails()
|
||||
{
|
||||
var audioSource = new ControlledAudioSource();
|
||||
var provider = new FailingAfterFirstAudioProvider();
|
||||
var audioArchive = new InMemoryRecordedAudioStore();
|
||||
var backlog = new InMemoryOfflineTranscriptionBacklog();
|
||||
var coordinator = new MeetingRecordingCoordinator(
|
||||
audioSource,
|
||||
new TestSpeechRecognitionPipelineFactory(provider),
|
||||
new InMemoryTranscriptStore(),
|
||||
new InMemoryMeetingNoteStore(),
|
||||
new CapturingMeetingNoteOpener(),
|
||||
new InMemoryMeetingArtifactStore(),
|
||||
audioArchive,
|
||||
new CapturingMeetingSummaryPipeline(),
|
||||
Options.Create(new MeetingAssistantOptions
|
||||
{
|
||||
Recording = new RecordingOptions
|
||||
{
|
||||
TranscriptionProvider = "azure-speech"
|
||||
}
|
||||
}),
|
||||
NullLogger<MeetingRecordingCoordinator>.Instance,
|
||||
offlineTranscriptionBacklog: backlog);
|
||||
|
||||
await coordinator.StartAsync(CancellationToken.None);
|
||||
await audioSource.WriteAsync(new AudioChunk([1, 0], 16000, 1), CancellationToken.None);
|
||||
await provider.WaitUntilFailureObservedAsync();
|
||||
|
||||
var stopped = await coordinator.StopAsync(CancellationToken.None);
|
||||
|
||||
Assert.False(stopped.IsRecording);
|
||||
var item = Assert.Single(backlog.Items);
|
||||
Assert.Equal("memory-recording.wav", item.AudioPath);
|
||||
Assert.True(audioArchive.Completed);
|
||||
Assert.False(audioArchive.Deleted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OfflineBacklogReplaysQueuedRecordingAndCompletesMeetingArtifacts()
|
||||
{
|
||||
@@ -4988,6 +5027,31 @@ public sealed class RecordingCoordinatorTests
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FailingAfterFirstAudioProvider : IStreamingTranscriptionProvider
|
||||
{
|
||||
private readonly TaskCompletionSource failureObserved =
|
||||
new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
public Task WaitUntilFailureObservedAsync()
|
||||
{
|
||||
return failureObserved.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<TranscriptionSegment> TranscribeAsync(
|
||||
IAsyncEnumerable<AudioChunk> audio,
|
||||
SpeechRecognitionPipelineOptions options,
|
||||
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
await foreach (var _ in audio.WithCancellation(cancellationToken))
|
||||
{
|
||||
failureObserved.TrySetResult();
|
||||
throw new InvalidOperationException("Configured transcription backend is unavailable.");
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] Samples(params short[] samples)
|
||||
{
|
||||
var bytes = new byte[samples.Length * sizeof(short)];
|
||||
@@ -4995,6 +5059,3 @@ public sealed class RecordingCoordinatorTests
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user