namespace MeetingAssistant.Transcription; public sealed class SpeechRecognitionPipelineHostedService : IHostedService { private readonly ISpeechRecognitionPipelineFactory pipelineFactory; private readonly ILogger logger; private CancellationTokenSource? startupCancellation; private Task? startupTask; private ISpeechRecognitionPipeline? startupPipeline; public SpeechRecognitionPipelineHostedService( ISpeechRecognitionPipelineFactory pipelineFactory, ILogger logger) { this.pipelineFactory = pipelineFactory; this.logger = logger; } public Task StartAsync(CancellationToken cancellationToken) { startupCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); startupPipeline = pipelineFactory.Create(); startupTask = Task.Run( () => WarmUpPipelineAsync(startupPipeline, startupCancellation.Token), CancellationToken.None); return Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { var cancellation = startupCancellation; var task = startupTask; var pipeline = startupPipeline; if (cancellation is null || task is null || pipeline is null) { return; } startupCancellation = null; startupTask = null; startupPipeline = null; await cancellation.CancelAsync(); try { await task.WaitAsync(cancellationToken); } catch (OperationCanceledException) { } finally { await pipeline.DisposeAsync(); cancellation.Dispose(); } } 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"); } } }