Files
meeting-assistant/MeetingAssistant/Transcription/SpeechRecognitionPipelineHostedService.cs
T
codex c46c587e65
PR and Push Build/Test / build-and-test (push) Successful in 6m48s
Warm up pyannote and FunASR runtimes
2026-05-28 13:05:27 +02:00

124 lines
4.1 KiB
C#

using MeetingAssistant.LaunchProfiles;
namespace MeetingAssistant.Transcription;
public sealed class SpeechRecognitionPipelineHostedService : IHostedService
{
private readonly ISpeechRecognitionPipelineFactory pipelineFactory;
private readonly ILaunchProfileOptionsProvider? launchProfiles;
private readonly ILogger<SpeechRecognitionPipelineHostedService> logger;
private CancellationTokenSource? startupCancellation;
private Task? startupTask;
private List<ISpeechRecognitionPipeline> startupPipelines = [];
public SpeechRecognitionPipelineHostedService(
ISpeechRecognitionPipelineFactory pipelineFactory,
ILogger<SpeechRecognitionPipelineHostedService> logger)
: this(pipelineFactory, null, logger)
{
}
public SpeechRecognitionPipelineHostedService(
ISpeechRecognitionPipelineFactory pipelineFactory,
ILaunchProfileOptionsProvider? launchProfiles,
ILogger<SpeechRecognitionPipelineHostedService> logger)
{
this.pipelineFactory = pipelineFactory;
this.launchProfiles = launchProfiles;
this.logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
var cancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var pipelines = CreateStartupPipelines();
startupCancellation = cancellation;
startupPipelines = pipelines;
startupTask = Task.Run(
() => WarmUpPipelinesAsync(pipelines, cancellation.Token),
CancellationToken.None);
return Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
var cancellation = startupCancellation;
var task = startupTask;
var pipelines = startupPipelines;
if (cancellation is null || task is null)
{
return;
}
startupCancellation = null;
startupTask = null;
startupPipelines = [];
await cancellation.CancelAsync();
try
{
await task.WaitAsync(cancellationToken);
}
catch (OperationCanceledException)
{
}
finally
{
foreach (var pipeline in pipelines)
{
await pipeline.DisposeAsync();
}
cancellation.Dispose();
}
}
private List<ISpeechRecognitionPipeline> CreateStartupPipelines()
{
if (launchProfiles is null)
{
return [pipelineFactory.Create()];
}
return launchProfiles.GetProfiles()
.Where(ShouldWarmUpProfile)
.Select(profile => pipelineFactory.Create(profile.Name))
.ToList();
}
private static bool ShouldWarmUpProfile(LaunchProfile profile)
{
return profile.Name.Equals(
ConfigurationLaunchProfileOptionsProvider.DefaultProfileName,
StringComparison.OrdinalIgnoreCase) ||
profile.Options.Recording.TranscriptionProvider.Equals("funasr", StringComparison.OrdinalIgnoreCase);
}
private async Task WarmUpPipelinesAsync(
IReadOnlyList<ISpeechRecognitionPipeline> pipelines,
CancellationToken cancellationToken)
{
await Task.WhenAll(pipelines.Select(pipeline => WarmUpPipelineAsync(pipeline, cancellationToken)));
}
private async Task WarmUpPipelineAsync(
ISpeechRecognitionPipeline pipeline,
CancellationToken cancellationToken)
{
try
{
await pipeline.InitializeAsync(cancellationToken);
await pipeline.WaitUntilReadyAsync(cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
logger.LogInformation("Speech recognition pipeline warm-up was cancelled during application shutdown");
}
catch (Exception exception)
{
logger.LogError(
exception,
"Speech recognition pipeline warm-up failed; recording can still start and recognition will retry when audio is processed");
}
}
}