Make meeting lifecycle stateful

This commit is contained in:
2026-05-27 12:55:17 +02:00
parent d607b957bb
commit e85274829a
91 changed files with 4076 additions and 479 deletions
@@ -25,7 +25,9 @@ public sealed class AzureSpeechStreamingTranscriptionProvider : IStreamingTransc
SpeechRecognitionPipelineOptions pipelineOptions,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
{
await using var enumerator = audio.GetAsyncEnumerator(cancellationToken);
var enumerator = audio.GetAsyncEnumerator(cancellationToken);
try
{
if (!await enumerator.MoveNextAsync())
{
yield break;
@@ -94,6 +96,18 @@ public sealed class AzureSpeechStreamingTranscriptionProvider : IStreamingTransc
}
await pumpTask.WaitAsync(cancellationToken);
}
finally
{
try
{
await enumerator.DisposeAsync();
}
catch (NotSupportedException)
{
// Some diagnostic async enumerable sources expose DisposeAsync but throw after the stream has been read.
}
}
}
internal ConversationTranscriber CreateTranscriber(SpeechConfig speechConfig, AudioConfig audioConfig)
@@ -150,7 +164,7 @@ public sealed class AzureSpeechStreamingTranscriptionProvider : IStreamingTransc
$"Azure Speech key is not configured. Set AzureSpeech:Key or environment variable {azure.KeyEnv}.");
}
var speechConfig = SpeechConfig.FromEndpoint(GetEndpoint(azure), key);
var speechConfig = CreateSpeechConfig(azure, key);
var autoDetectLanguages = GetAutoDetectLanguages(azure);
if (autoDetectLanguages.Count == 0)
{
@@ -167,6 +181,13 @@ public sealed class AzureSpeechStreamingTranscriptionProvider : IStreamingTransc
speechConfig.SetProperty(
PropertyId.SpeechServiceResponse_DiarizeIntermediateResults,
azure.DiarizeIntermediateResults ? "true" : "false");
if (!string.IsNullOrWhiteSpace(azure.PostProcessingOption))
{
logger.LogWarning(
"Azure Speech post-processing option {PostProcessingOption} is configured but skipped because the live Azure backend uses ConversationTranscriber; Speech SDK post-processing is documented for SpeechRecognizer.",
azure.PostProcessingOption.Trim());
}
return speechConfig;
}
@@ -185,10 +206,17 @@ public sealed class AzureSpeechStreamingTranscriptionProvider : IStreamingTransc
}
internal static Uri GetEndpoint(AzureSpeechOptions options)
{
return string.IsNullOrWhiteSpace(options.Endpoint)
? throw new InvalidOperationException("Azure Speech endpoint must be configured.")
: new Uri(options.Endpoint);
}
internal static SpeechConfig CreateSpeechConfig(AzureSpeechOptions options, string key)
{
if (!string.IsNullOrWhiteSpace(options.Endpoint))
{
return new Uri(options.Endpoint);
return SpeechConfig.FromEndpoint(GetEndpoint(options), key);
}
if (string.IsNullOrWhiteSpace(options.Region))
@@ -196,7 +224,7 @@ public sealed class AzureSpeechStreamingTranscriptionProvider : IStreamingTransc
throw new InvalidOperationException("Azure Speech region or endpoint must be configured.");
}
return new Uri($"wss://{options.Region}.stt.speech.microsoft.com/speech/universal/v2");
return SpeechConfig.FromSubscription(key, options.Region.Trim());
}
internal static string NormalizeLanguageIdMode(string? value)