Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion app/lib/services/sockets/composite_transcription_socket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ class CompositeTranscriptionSocket implements IPureSocket {
final String? suggestedTranscriptType;
final String? sttProvider;

/// When true, calls to [send] route messages only to [primarySocket].
/// [secondarySocket] still receives forwarded transcript JSON via
/// [_forwardAsSuggestedTranscript] — only the raw [send] path is skipped.
///
/// Use this when a custom STT provider handles transcription so the Omi
/// backend never processes raw audio — preventing listening-minute consumption.
///
/// Note: [PureSocket] sets pingInterval=20s at connect time, so the secondary
/// WebSocket connection stays alive during silence without application-level
/// keep-alive messages.
final bool skipSendToSecondary;

PureSocketStatus _status = PureSocketStatus.notConnected;
IPureSocketListener? _listener;

Expand All @@ -26,6 +38,7 @@ class CompositeTranscriptionSocket implements IPureSocket {
required this.secondarySocket,
this.suggestedTranscriptType = 'suggested_transcript',
this.sttProvider,
this.skipSendToSecondary = false,
}) {
_primaryListener = _PrimarySocketListener(this);
_secondaryListener = _SecondarySocketListener(this);
Expand Down Expand Up @@ -143,7 +156,15 @@ class CompositeTranscriptionSocket implements IPureSocket {
return;
}
primarySocket.send(message);
secondarySocket.send(message);
// When skipSendToSecondary is set, messages passed to send() go only to
// the primary socket. The secondary socket still receives forwarded
// transcript JSON via _forwardAsSuggestedTranscript (which calls
// secondarySocket.send directly), so conversation processing continues.
// WebSocket-level keepalives (pingInterval=20s in PureSocket) keep the
// secondary connection alive during silence without audio data.
if (!skipSendToSecondary) {
secondarySocket.send(message);
}
}

void _onPrimaryMessage(dynamic message) {
Expand Down
5 changes: 5 additions & 0 deletions app/lib/services/sockets/transcription_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,11 @@ class TranscriptSocketServiceFactory {
primarySocket: primarySocket,
secondarySocket: secondaryService.socket,
sttProvider: sttProvider,
// Custom STT handles transcription — send only forwarded transcripts to
// the Omi backend, never raw audio. Without this, the backend transcribes
// the audio stream in parallel and counts listening minutes even though
// the custom provider is doing the actual transcription work.
skipSendToSecondary: true,
);
return TranscriptSegmentSocketService.withSocket(
sampleRate,
Expand Down