How FluidVoice's Meeting Transcription Service Detects and Transcribes Meetings
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 lifecycleASRService– A singleton that provides the underlying speech recognition engines (Whisper and Parakeet) and ensures models are loaded only onceFileTranscriptionHistoryStore– 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.
// 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.
// 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.
// 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
prefersNativeFileTranscriptionand the file is not a video container, the service passes the entire file path directly to the provider viaprovider.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, protects the app from memory overflow on very long recordings while maintaining transcription accuracy.
// 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 processingprogress: Double representing completion percentage (0.0 to 1.0)currentStatus: Human-readable status messageserror: 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:
// 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.
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:
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@Publishedproperties. - Data Persistence: Completed
TranscriptionResultobjects store inFileTranscriptionHistoryStorewith 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →