getPipedProducersAlt function
- GetPipedProducersAltOptions options
Retrieves piped producers and signals new consumer transport for each retrieved producer.
Emits a getProducersPipedAlt event to the server using the provided nsock socket instance, islevel flag,
and parameters. The server responds with a list of producer IDs, and for each ID, this function calls
the signalNewConsumerTransport function in parameters to handle the new consumer transport.
options(GetPipedProducersAltOptions): The options for the operation, including the socket instance, level, and additional parameters.
Example:
final parameters = GetPipedProducersAltParameters(
member: 'memberId',
signalNewConsumerTransport: (nsock, remoteProducerId, islevel, parameters) async {
// Implementation for signaling new consumer transport
},
);
await getPipedProducersAlt(
GetPipedProducersAltOptions(
community: true,
nsock: socketInstance,
islevel: '1',
parameters: parameters,
),
);
Throws: Logs and rethrows any errors encountered during the operation.
Implementation
Future<void> getPipedProducersAlt(
GetPipedProducersAltOptions options,
) async {
try {
final nsock = options.nsock;
final islevel = options.islevel;
final parameters = options.parameters;
final member = parameters.member;
final signalNewConsumerTransport = parameters.signalNewConsumerTransport;
bool? community = options.community;
String emitEvent = 'getProducersPipedAlt';
if (community == true) {
emitEvent = 'getProducersAlt';
}
// Emit request to get piped producers
nsock.emitWithAck(
emitEvent,
{'islevel': islevel, 'member': member},
ack: (dynamic producerIds) async {
// Callback to handle the server response with producer IDs
if (producerIds is List && producerIds.isNotEmpty) {
for (final id in producerIds) {
String remoteProducerId;
TranslationMeta? translationMeta;
if (id is String) {
remoteProducerId = id;
} else if (id is Map<String, dynamic>) {
remoteProducerId = id['id'];
if (id['translationMeta'] != null) {
translationMeta =
TranslationMeta.fromMap(id['translationMeta']);
}
} else {
continue;
}
if (translationMeta != null) {
// Re-fetch parameters to get the latest state
final freshParams = parameters.getUpdatedAllParams();
final listenerTranslationPreferences =
freshParams.listenerTranslationPreferences;
final speakerTranslationStates =
freshParams.speakerTranslationStates;
final translationSubscriptions =
freshParams.translationSubscriptions;
final listenerTranslationOverrides =
freshParams.listenerTranslationOverrides;
final normalizedLang = translationMeta.language.toLowerCase();
// 1. SPEAKER-CONTROLLED (from metadata): Speaker set output language for everyone
final isSpeakerControlledFromMeta =
translationMeta.isSpeakerControlled == true;
// 2. Fallback: Check local state - this tells us WHICH language the speaker chose
final speakerState =
speakerTranslationStates?[translationMeta.speakerId];
final speakerStateOutputLanguage =
speakerState?['outputLanguage'] as String?;
final speakerStateEnabled =
speakerState?['enabled'] as bool? ?? false;
// For speaker-controlled translations, we should ONLY consume the language the speaker chose
// isSpeakerControlledFromMeta just tells us the speaker is in control mode
// isSpeakerControlledFromState tells us this specific language matches what the speaker chose
final isSpeakerControlledFromState = speakerStateEnabled &&
speakerStateOutputLanguage?.toLowerCase() == normalizedLang;
// If speaker-controlled mode but we don't have state yet OR language doesn't match, skip
// This prevents consuming ALL translations when speaker only chose ONE language
final shouldSkipBecauseWrongLanguage =
isSpeakerControlledFromMeta &&
speakerStateEnabled &&
speakerStateOutputLanguage?.toLowerCase() !=
normalizedLang;
// 3. LISTENER SUBSCRIPTION: Listener explicitly chose this language
final subscriptionKey =
'${translationMeta.speakerId}_$normalizedLang';
final isListenerSubscribed =
translationSubscriptions?.contains(subscriptionKey) ?? false;
// 4. CHECK LISTENER PREFERENCES (server-synced, includes global preference)
bool overrideBlocksConsumption = false;
bool shouldConsumeForOverride = false;
bool shouldConsumeForGlobal = false;
// Check per-speaker preference first (higher priority than global)
final perSpeakerPref = listenerTranslationPreferences
?.perSpeaker[translationMeta.speakerId];
final globalPref = listenerTranslationPreferences?.globalLanguage;
// Also check legacy overrides for backwards compatibility
final listenerOverride =
listenerTranslationOverrides?[translationMeta.speakerId];
if (perSpeakerPref != null) {
// Per-speaker preference takes highest priority
if (perSpeakerPref.isEmpty) {
overrideBlocksConsumption = true;
} else if (perSpeakerPref == normalizedLang) {
shouldConsumeForOverride = true;
} else {
overrideBlocksConsumption = true;
}
} else if (globalPref != null && globalPref.isNotEmpty) {
// Global preference: "I want to hear everyone in Twi"
if (globalPref.toLowerCase() == normalizedLang) {
shouldConsumeForGlobal = true;
} else {
overrideBlocksConsumption = true;
}
} else if (listenerOverride != null) {
// Legacy override support
final wantOriginal =
listenerOverride['wantOriginal'] as bool? ?? false;
final preferredLanguage =
listenerOverride['preferredLanguage'] as String?;
if (wantOriginal) {
overrideBlocksConsumption = true;
} else if (preferredLanguage != null &&
preferredLanguage.isNotEmpty) {
if (preferredLanguage.toLowerCase() == normalizedLang) {
shouldConsumeForOverride = true;
} else {
overrideBlocksConsumption = true;
}
}
}
// CRITICAL FIX: For speaker-controlled translations, we must verify the language matches
// what the speaker chose. If speakerState is not yet synced, we cannot blindly trust
// isSpeakerControlledFromMeta because multiple translation producers might exist.
// Only consume speaker-controlled if:
// 1. We have local speaker state confirming this exact language, OR
// 2. This is the ONLY translation for this speaker (no state conflict possible)
final shouldConsumeForSpeakerControlled =
isSpeakerControlledFromMeta &&
(!speakerStateEnabled ||
speakerStateOutputLanguage?.toLowerCase() ==
normalizedLang);
// If listener has NO preference (no global, no per-speaker), they should only consume
// speaker-controlled translations - NOT translations requested by other listeners
final hasNoPreference = perSpeakerPref == null &&
globalPref == null &&
listenerOverride == null;
final isListenerInitiated =
!isSpeakerControlledFromMeta && !isSpeakerControlledFromState;
// Block consumption if: listener has no preference AND this is listener-initiated
// (meaning another listener requested this, not the speaker or this listener)
final blockBecauseNotRelevant = hasNoPreference &&
isListenerInitiated &&
!isListenerSubscribed;
final shouldConsume = !overrideBlocksConsumption &&
!shouldSkipBecauseWrongLanguage &&
!blockBecauseNotRelevant &&
(shouldConsumeForOverride ||
shouldConsumeForGlobal ||
shouldConsumeForSpeakerControlled ||
isSpeakerControlledFromState ||
isListenerSubscribed);
if (!shouldConsume) {
continue;
}
await startConsumingTranslation(StartConsumingTranslationOptions(
nsock: nsock,
producerId: remoteProducerId,
islevel: islevel,
parameters: parameters,
translationMeta: translationMeta,
));
continue;
}
final signalOptions = SignalNewConsumerTransportOptions(
nsock: nsock,
remoteProducerId: remoteProducerId,
islevel: islevel,
parameters: parameters,
);
await signalNewConsumerTransport(signalOptions);
}
}
},
);
} catch (error) {
if (kDebugMode) {
print('Error getting piped producers: ${error.toString()}');
}
rethrow;
}
}