Public Access
124 lines
4.1 KiB
C#
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");
|
|
}
|
|
}
|
|
}
|