# How FluidVoice's Meeting Transcription Service Detects and Transcribes Meetings

> FluidVoice's meeting transcription service uses MeetingTranscriptionService to validate audio, load ASR models, and process content for accurate transcriptions. Learn how it works.

- Repository: [ALTIC/FluidVoice](https://github.com/altic-dev/FluidVoice)
- Tags: how-to-guide
- Published: 2026-07-03

---

**FluidVoice's meeting transcription service coordinates the `MeetingTranscriptionService` class to validate audio files, initialize shared ASR models via `ASRService`, and process content either natively or in chunked segments to prevent memory overflow, ultimately aggregating results into a `TranscriptionResult` that drives the SwiftUI interface.**

The FluidVoice app (altic-dev/FluidVoice) enables local meeting transcription through a sophisticated service-oriented architecture. At the heart of this system lies the `MeetingTranscriptionService`, which orchestrates model loading, file validation, and audio processing to convert speech into searchable text. This article examines the exact mechanism by which the service detects meeting content and produces accurate transcriptions using on-device Whisper and Parakeet models.

## Architecture Overview

The transcription system relies on three primary components working in concert:

- **`MeetingTranscriptionService`** – The coordinator class that manages the end-to-end transcription lifecycle
- **`ASRService`** – A singleton that provides the underlying speech recognition engines (Whisper and Parakeet) and ensures models are loaded only once
- **`FileTranscriptionHistoryStore`** – Persists completed transcriptions for later retrieval and export

When a user initiates transcription from the **Meeting Transcription View**, the view instantiates `MeetingTranscriptionService` with the shared `ASRService` dependency, then delegates all processing logic to this service.

## The Transcription Workflow

### 1. Model Initialization

Before processing begins, the service ensures the ASR models are ready. The `initializeModels()` method calls `ASRService.ensureAsrReady()`, which downloads and loads the Whisper or Parakeet model exactly once and caches it for reuse across all subsequent transcriptions.

```swift
// In Sources/Fluid/Services/MeetingTranscriptionService.swift (lines 41-48)
func initializeModels() async throws {
    try await ASRService.shared.ensureAsrReady()
    // Model is now loaded and shared across all transcription tasks
}

```

This approach prevents redundant model loading and keeps memory usage predictable.

### 2. Input File Validation

The service validates file compatibility before processing. A static property `supportedFileExtensions` derives valid types from `AVURLAsset.audiovisualTypes()`, ensuring the system automatically honors OS-level supported audio and video formats.

```swift
// In Sources/Fluid/Services/MeetingTranscriptionService.swift (lines 73-82)
static let supportedFileExtensions: Set<String> = {
    let types = AVURLAsset.audiovisualTypes()
    return Set(types.compactMap { $0.preferredFileExtension })
}()

guard supportedFileExtensions.contains(url.pathExtension.lowercased()) else {
    throw TranscriptionError.fileNotSupported
}

```

Unsupported files trigger a `TranscriptionError.fileNotSupported` immediately, preventing wasted processing cycles.

### 3. Audio Duration Extraction

To enable progress reporting, the service extracts the total audio duration using `AVAsset.load(.duration)`. This value becomes the denominator for progress calculations during chunk processing.

```swift
// In Sources/Fluid/Services/MeetingTranscriptionService.swift (lines 96-101)
let asset = AVAsset(url: url)
let duration = try await asset.load(.duration)
self.totalDuration = CMTimeGetSeconds(duration)

```

### 4. Transcription Strategy Selection

The service chooses between two processing paths based on the provider's capabilities and file type:

- **Native File Transcription**: If the selected ASR provider advertises `prefersNativeFileTranscription` and the file is **not** a video container, the service passes the entire file path directly to the provider via `provider.transcribeFile(at:)`.

- **Chunked Processing**: If the file is a video container or the provider prefers chunked processing, the service reads the audio in approximately 20-minute segments, resamples each chunk to **16 kHz mono Float32**, and sends the raw samples to `provider.transcribe(samples)`.

The chunking logic, implemented in lines 254-329 of [`MeetingTranscriptionService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/MeetingTranscriptionService.swift), protects the app from memory overflow on very long recordings while maintaining transcription accuracy.

```swift
// Conceptual chunking implementation
while currentTime < totalDuration {
    let chunk = try await extractChunk(from: asset, 
                                       start: currentTime, 
                                       duration: 1200) // 20 minutes
    let resampled = try resampleTo16kHzMonoFloat32(chunk)
    let partialResult = try await provider.transcribe(samples: resampled)
    // Aggregate partial results...
}

```

### 5. Result Aggregation

After processing each chunk, the service accumulates text and confidence scores. Once all chunks complete, it concatenates the texts, computes an average confidence score, and constructs a `TranscriptionResult` containing:

- Raw transcribed text
- Average confidence score
- File duration
- Processing time
- Source file name
- Timestamp

This aggregation occurs in the result builder methods found in lines 40-53 of the service file.

### 6. State Management and Persistence

Throughout the process, the service publishes state changes via `@Published` properties:

- `isTranscribing`: Boolean indicating active processing
- `progress`: Double representing completion percentage (0.0 to 1.0)
- `currentStatus`: Human-readable status messages
- `error`: Any encountered errors

SwiftUI views bind directly to these properties, automatically updating progress bars and status indicators.

Upon completion, the service persists the `TranscriptionResult` to `FileTranscriptionHistoryStore` and provides export helpers:

```swift
// Export to plain text
try transcriptionService.exportToText(result, to: destinationURL)

// Export to JSON with metadata
try transcriptionService.exportToJSON(result, to: destinationURL)

```

These helpers are implemented in lines 99-124 of [`MeetingTranscriptionService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/MeetingTranscriptionService.swift).

### 7. Analytics and Error Handling

The service reports success and failure events to `AnalyticsService` with categorized tags, enabling the development team to monitor transcription reliability and model performance across different audio formats and durations.

## Integration Example

Below is a complete implementation pattern for consuming the service in a SwiftUI view:

```swift
import SwiftUI

struct MeetingTranscriptionView: View {
    @StateObject private var transcriptionService = MeetingTranscriptionService(asrService: ASRService.shared)
    
    var body: some View {
        VStack {
            // Progress indicator
            ProgressView(value: transcriptionService.progress)
                .opacity(transcriptionService.isTranscribing ? 1 : 0)
            
            // Status text
            Text(transcriptionService.currentStatus)
                .foregroundColor(.secondary)
            
            // Results display
            if let result = transcriptionService.result {
                ScrollView {
                    Text(result.text)
                }
                Button("Export") {
                    exportResult(result)
                }
            }
        }
        .onDrop(of: [.fileURL], perform: handleFileDrop)
    }
    
    func handleFileDrop(_ providers: [NSItemProvider]) -> Bool {
        // Handle dropped file URL...
        guard let provider = providers.first else { return false }
        
        provider.loadItem(forTypeIdentifier: "public.file-url", options: nil) { (urlData, error) in
            guard let urlData = urlData as? Data,
                  let url = URL(dataRepresentation: urlData, relativeTo: nil) else { return }
            
            Task {
                do {
                    let result = try await transcriptionService.transcribeFile(url)
                    print("Transcription completed: \(result.text)")
                } catch {
                    print("Transcription error: \(error)")
                }
            }
        }
        return true
    }
    
    func exportResult(_ result: TranscriptionResult) {
        let destination = FileManager.default.temporaryDirectory
            .appendingPathComponent("meeting_\(result.timestamp).txt")
        
        do {
            try transcriptionService.exportToText(result, to: destination)
        } catch {
            print("Export failed: \(error)")
        }
    }
}

```

## Summary

- **Model Efficiency**: `ASRService.ensureAsrReady()` loads Whisper/Parakeet models once and shares them across all transcription tasks, minimizing memory overhead.
- **Input Validation**: The service automatically supports all audio/video types recognized by `AVURLAsset.audiovisualTypes()`, rejecting unsupported formats immediately.
- **Adaptive Processing**: Files stream through either native file transcription or chunked processing (~20-minute segments resampled to 16 kHz mono Float32) based on provider capabilities and container type.
- **Progress Tracking**: `AVAsset.load(.duration)` enables accurate progress calculations bound to SwiftUI views via `@Published` properties.
- **Data Persistence**: Completed `TranscriptionResult` objects store in `FileTranscriptionHistoryStore` with export capabilities to both text and JSON formats.

## Frequently Asked Questions

### How does the service handle very large audio files without crashing?

The service implements a chunking strategy that processes audio in approximately 20-minute segments. When a file exceeds the threshold for native processing—or when the provider prefers chunked processing—the service reads the audio sequentially, resamples each chunk to 16 kHz mono Float32 format, and transcribes them individually before aggregating results. This prevents memory overflow while maintaining the full transcription context.

### What audio and video formats does FluidVoice support?

The service dynamically determines supported formats through `AVURLAsset.audiovisualTypes()`, which returns the system's current audio and video capabilities. This means FluidVoice automatically supports any format the underlying macOS/iOS AVFoundation framework recognizes, including MP3, WAV, AAC, M4A, MP4, and MOV, without requiring manual format lists that could become outdated.

### How does the UI update during transcription?

The `MeetingTranscriptionService` exposes four `@Published` properties—`isTranscribing`, `progress`, `currentStatus`, and `error`—that SwiftUI views observe automatically. When the service extracts audio duration, processes chunks, or encounters errors, it updates these properties on the main thread, causing the view to redraw progress bars, status messages, and error alerts in real time without manual refresh logic.

### Where are completed transcriptions stored?

Finished transcriptions persist in `FileTranscriptionHistoryStore`, which maintains a history of `TranscriptionResult` objects containing the transcribed text, confidence scores, processing metadata, and timestamps. Users can export these results to plain text files using `exportToText()` or to structured JSON using `exportToJSON()`, both of which include the full metadata captured during the transcription process.