Keep recording startup independent of dictation words
PR and Push Build/Test / build-and-test (push) Failing after 8m52s

This commit is contained in:
DEEUSEW\DESCMANU
2026-06-02 13:27:10 +02:00
parent 0e9d525b63
commit 8a0b302f1d
8 changed files with 191 additions and 12 deletions
@@ -130,6 +130,8 @@ public sealed class RecordingOptions
public TimeSpan OpenMeetingNoteDelay { get; set; } = TimeSpan.FromSeconds(1);
public TimeSpan DictationWordsReadTimeout { get; set; } = TimeSpan.FromSeconds(2);
public string TemporaryRecordingsFolder { get; set; } = @"%LOCALAPPDATA%\MeetingAssistant\Recordings";
public RecordingInactivitySafeguardOptions InactivitySafeguard { get; set; } = new();
+1
View File
@@ -44,6 +44,7 @@ builder.Services.AddSingleton<IMeetingInactivityPromptService, WindowsMeetingIna
#else
builder.Services.AddSingleton<IMeetingInactivityPromptService, NoopMeetingInactivityPromptService>();
#endif
builder.Services.AddSingleton<IRecordingDictationWordProvider, RecordingDictationWordProvider>();
#if WINDOWS
builder.Services.AddSingleton<IActiveWindowScreenshotCapture, ActiveWindowScreenshotCapture>();
#else
@@ -24,7 +24,7 @@ public sealed class MeetingRecordingCoordinator
private readonly IMeetingSummaryPipeline summaryPipeline;
private readonly ISpeakerIdentificationService? speakerIdentificationService;
private readonly ISpeakerIdentityAttendeeCanonicalizer attendeeCanonicalizer;
private readonly IDictationWordStore dictationWordStore;
private readonly IRecordingDictationWordProvider dictationWordProvider;
private readonly ILaunchProfileOptionsProvider? launchProfiles;
private readonly IMeetingWorkflowEngine meetingWorkflowEngine;
private readonly IMeetingScreenshotService screenshotService;
@@ -52,7 +52,7 @@ public sealed class MeetingRecordingCoordinator
ILogger<MeetingRecordingCoordinator> logger,
IMeetingMetadataProvider? meetingMetadataProvider = null,
ISpeakerIdentificationService? speakerIdentificationService = null,
IDictationWordStore? dictationWordStore = null,
IRecordingDictationWordProvider? dictationWordProvider = null,
ISpeakerIdentityAttendeeCanonicalizer? attendeeCanonicalizer = null,
ILaunchProfileOptionsProvider? launchProfiles = null,
IMeetingWorkflowEngine? meetingWorkflowEngine = null,
@@ -70,7 +70,7 @@ public sealed class MeetingRecordingCoordinator
this.meetingMetadataProvider = meetingMetadataProvider ?? new NoopMeetingMetadataProvider();
this.recordedAudioStore = recordedAudioStore;
this.summaryPipeline = summaryPipeline;
this.dictationWordStore = dictationWordStore ?? new EmptyDictationWordStore();
this.dictationWordProvider = dictationWordProvider ?? EmptyRecordingDictationWordProvider.Instance;
this.speakerIdentificationService = speakerIdentificationService;
this.attendeeCanonicalizer = attendeeCanonicalizer ?? PassthroughSpeakerIdentityAttendeeCanonicalizer.Instance;
this.launchProfiles = launchProfiles;
@@ -1413,7 +1413,7 @@ public sealed class MeetingRecordingCoordinator
string? meetingNotePath,
CancellationToken cancellationToken)
{
var dictationWords = await dictationWordStore.ReadWordsAsync(runOptions, cancellationToken);
var dictationWords = await dictationWordProvider.ReadOptionalWordsAsync(runOptions, cancellationToken);
if (string.IsNullOrWhiteSpace(meetingNotePath))
{
return new SpeechRecognitionPipelineOptions(DictationWords: dictationWords);
@@ -1980,17 +1980,16 @@ public sealed class MeetingRecordingCoordinator
IReadOnlyDictionary<string, string> KnownSpeakerMappings);
}
private sealed class EmptyDictationWordStore : IDictationWordStore
private sealed class EmptyRecordingDictationWordProvider : IRecordingDictationWordProvider
{
public Task<IReadOnlyList<string>> ReadWordsAsync(CancellationToken cancellationToken)
public static readonly EmptyRecordingDictationWordProvider Instance = new();
public Task<IReadOnlyList<string>> ReadOptionalWordsAsync(
MeetingAssistantOptions runOptions,
CancellationToken cancellationToken)
{
return Task.FromResult<IReadOnlyList<string>>([]);
}
public Task<IReadOnlyList<string>> AddWordAsync(string word, CancellationToken cancellationToken)
{
return ReadWordsAsync(cancellationToken);
}
}
}
@@ -0,0 +1,86 @@
using MeetingAssistant.Transcription;
namespace MeetingAssistant.Recording;
public interface IRecordingDictationWordProvider
{
Task<IReadOnlyList<string>> ReadOptionalWordsAsync(
MeetingAssistantOptions runOptions,
CancellationToken cancellationToken);
}
public sealed class RecordingDictationWordProvider : IRecordingDictationWordProvider
{
private readonly IDictationWordStore dictationWordStore;
private readonly ILogger<RecordingDictationWordProvider> logger;
public RecordingDictationWordProvider(
IDictationWordStore dictationWordStore,
ILogger<RecordingDictationWordProvider> logger)
{
this.dictationWordStore = dictationWordStore;
this.logger = logger;
}
public async Task<IReadOnlyList<string>> ReadOptionalWordsAsync(
MeetingAssistantOptions runOptions,
CancellationToken cancellationToken)
{
var timeout = runOptions.Recording.DictationWordsReadTimeout;
if (timeout <= TimeSpan.Zero)
{
return await ReadWithoutFallbackTimeoutAsync(runOptions, cancellationToken);
}
try
{
using var readCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var readTask = dictationWordStore.ReadWordsAsync(runOptions, readCancellation.Token);
try
{
return await readTask.WaitAsync(timeout, cancellationToken);
}
catch (TimeoutException exception)
{
readCancellation.Cancel();
logger.LogWarning(
exception,
"Reading dictation words timed out after {Timeout}; recording will continue without dictation-word hints",
timeout);
return [];
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception exception)
{
logger.LogWarning(
exception,
"Could not read dictation words; recording will continue without dictation-word hints");
return [];
}
}
private async Task<IReadOnlyList<string>> ReadWithoutFallbackTimeoutAsync(
MeetingAssistantOptions runOptions,
CancellationToken cancellationToken)
{
try
{
return await dictationWordStore.ReadWordsAsync(runOptions, cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception exception)
{
logger.LogWarning(
exception,
"Could not read dictation words; recording will continue without dictation-word hints");
return [];
}
}
}
+1
View File
@@ -25,6 +25,7 @@
"BackgroundMetadataLookupTimeout": "00:01:00",
"MaxMetadataAttendeeImportCount": 30,
"OpenMeetingNoteDelay": "00:00:01",
"DictationWordsReadTimeout": "00:00:02",
"TemporaryRecordingsFolder": "%LOCALAPPDATA%\\MeetingAssistant\\Recordings",
"InactivitySafeguard": {
"Enabled": true,