#pragma warning restore VSTHRD103

    private async Task <Unit> StartProcess(StartFFmpegSession request, CancellationToken cancellationToken)
    {
        TimeSpan idleTimeout = await _configElementRepository
                               .GetValue <int>(ConfigElementKey.FFmpegSegmenterTimeout)
                               .Map(maybeTimeout => maybeTimeout.Match(i => TimeSpan.FromSeconds(i), () => TimeSpan.FromMinutes(1)));

        using IServiceScope scope = _serviceScopeFactory.CreateScope();
        HlsSessionWorker worker = scope.ServiceProvider.GetRequiredService <HlsSessionWorker>();

        _ffmpegSegmenterService.SessionWorkers.AddOrUpdate(request.ChannelNumber, _ => worker, (_, _) => worker);

        // fire and forget worker
        _ = worker.Run(request.ChannelNumber, idleTimeout, cancellationToken)
            .ContinueWith(
            _ => _ffmpegSegmenterService.SessionWorkers.TryRemove(
                request.ChannelNumber,
                out IHlsSessionWorker _),
            TaskScheduler.Default);

        string playlistFileName = Path.Combine(
            FileSystemLayout.TranscodeFolder,
            request.ChannelNumber,
            "live.m3u8");

        IConfigElementRepository repo = scope.ServiceProvider.GetRequiredService <IConfigElementRepository>();
        int initialSegmentCount       = await repo.GetValue <int>(ConfigElementKey.FFmpegInitialSegmentCount)
                                        .Map(maybeCount => maybeCount.Match(identity, () => 1));

        await WaitForPlaylistSegments(playlistFileName, initialSegmentCount, worker, cancellationToken);

        return(Unit.Default);
    }
    private Task <Validation <BaseError, Unit> > FolderMustBeEmpty(StartFFmpegSession request)
    {
        string folder = Path.Combine(FileSystemLayout.TranscodeFolder, request.ChannelNumber);

        _logger.LogDebug("Preparing transcode folder {Folder}", folder);

        _localFileSystem.EnsureFolderExists(folder);
        _localFileSystem.EmptyFolder(folder);

        return(Task.FromResult <Validation <BaseError, Unit> >(Unit.Default));
    }
    private Task <Validation <BaseError, Unit> > SessionMustBeInactive(StartFFmpegSession request)
    {
        var result = Optional(_ffmpegSegmenterService.SessionWorkers.TryAdd(request.ChannelNumber, null))
                     .Where(success => success)
                     .Map(_ => Unit.Default)
                     .ToValidation <BaseError>(new ChannelSessionAlreadyActive());

        if (result.IsFail && _ffmpegSegmenterService.SessionWorkers.TryGetValue(
                request.ChannelNumber,
                out IHlsSessionWorker worker))
        {
            worker?.Touch();
        }

        return(result.AsTask());
    }
    public Task <Either <BaseError, Unit> > Handle(StartFFmpegSession request, CancellationToken cancellationToken) =>
    Validate(request)
    .MapT(_ => StartProcess(request, cancellationToken))
    // this weirdness is needed to maintain the error type (.ToEitherAsync() just gives BaseError)
#pragma warning disable VSTHRD103
    .Bind(v => v.ToEither().MapLeft(seq => seq.Head()).MapAsync <BaseError, Task <Unit>, Unit>(identity));
 private Task <Validation <BaseError, Unit> > Validate(StartFFmpegSession request) =>
 SessionMustBeInactive(request)
 .BindT(_ => FolderMustBeEmpty(request));