frontend: refactor captions button logic to improve state management and prevent concurrent toggles
This commit is contained in:
parent
7607f134a0
commit
2453ce2760
@ -11,10 +11,8 @@
|
||||
>
|
||||
<mat-icon class="material-symbols-outlined">subtitles</mat-icon>
|
||||
<span class="button-text">
|
||||
@if (captionsStatus() === 'DISABLED_WITH_WARNING') {
|
||||
@if (isCaptionsButtonDisabled()) {
|
||||
Live captions (disabled by admin)
|
||||
} @else if (isCaptionsTogglePending()) {
|
||||
{{ areCaptionsEnabledByUser() ? 'Disabling live captions...' : 'Enabling live captions...' }}
|
||||
} @else {
|
||||
{{ areCaptionsEnabledByUser() ? 'Disable live captions' : 'Enable live captions' }}
|
||||
}
|
||||
@ -30,16 +28,14 @@
|
||||
[disabledInteractive]="isCaptionsButtonDisabled()"
|
||||
[disableRipple]="true"
|
||||
[matTooltip]="
|
||||
captionsStatus() === 'DISABLED_WITH_WARNING'
|
||||
isCaptionsButtonDisabled()
|
||||
? 'Live captions are disabled by admin'
|
||||
: isCaptionsTogglePending()
|
||||
? (areCaptionsEnabledByUser() ? 'Disabling live captions...' : 'Enabling live captions...')
|
||||
: areCaptionsEnabledByUser()
|
||||
? 'Disable live captions'
|
||||
: 'Enable live captions'
|
||||
: areCaptionsEnabledByUser()
|
||||
? 'Disable live captions'
|
||||
: 'Enable live captions'
|
||||
"
|
||||
>
|
||||
@if (captionsStatus() === 'DISABLED_WITH_WARNING') {
|
||||
@if (isCaptionsButtonDisabled()) {
|
||||
<mat-icon>subtitles_off</mat-icon>
|
||||
} @else {
|
||||
<mat-icon>subtitles</mat-icon>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import { Component, computed, inject, signal } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
@ -34,8 +34,12 @@ export class MeetingToolbarExtraButtonsComponent {
|
||||
/** Whether to show the captions button (visible when not HIDDEN) */
|
||||
showCaptionsButton = computed(() => this.meetingContextService.meetingUI().showCaptionsControls);
|
||||
/** Whether captions button is disabled (true when DISABLED_WITH_WARNING) */
|
||||
// TODO: Apply disabled while an enable/disable request is in flight to prevent concurrent calls
|
||||
isCaptionsButtonDisabled = computed(() => this.meetingContextService.meetingUI().showCaptionsControlsDisabled);
|
||||
/**
|
||||
* True while an enable() or disable() call is in flight.
|
||||
* Use this to prevent concurrent toggle requests.
|
||||
*/
|
||||
isCaptionsTogglePending = signal<boolean>(false);
|
||||
/** Whether captions are currently enabled by the user */
|
||||
areCaptionsEnabledByUser = this.captionService.areCaptionsEnabledByUser;
|
||||
|
||||
@ -53,17 +57,27 @@ export class MeetingToolbarExtraButtonsComponent {
|
||||
}
|
||||
|
||||
async onCaptionsClick(): Promise<void> {
|
||||
if (this.isCaptionsTogglePending()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isCaptionsTogglePending.set(true);
|
||||
|
||||
try {
|
||||
// Don't allow toggling if captions are disabled at system level
|
||||
if (this.isCaptionsButtonDisabled()) {
|
||||
this.log.w('Captions are disabled at system level (MEET_CAPTIONS_ENABLED=false)');
|
||||
return;
|
||||
}
|
||||
|
||||
this.captionService.areCaptionsEnabledByUser()
|
||||
? await this.captionService.disable()
|
||||
: await this.captionService.enable();
|
||||
} catch (error) {
|
||||
this.log.e('Error toggling captions:', error);
|
||||
} finally {
|
||||
// Add a small delay before allowing another toggle to prevent rapid concurrent calls
|
||||
setTimeout(() => this.isCaptionsTogglePending.set(false), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Injectable, inject, signal } from '@angular/core';
|
||||
import { ILogger, LoggerService, ParticipantService, Room, TextStreamReader } from 'openvidu-components-angular';
|
||||
import { AiAssistantService } from '../../../shared/services/ai-assistant.service';
|
||||
import { Caption, CaptionsConfig } from '../models/captions.model';
|
||||
import { CustomParticipantModel } from '../models/custom-participant.model';
|
||||
import { AiAssistantService } from '../../../shared/services/ai-assistant.service';
|
||||
|
||||
/**
|
||||
* Service responsible for managing live transcription captions.
|
||||
@ -41,7 +41,6 @@ export class MeetingCaptionsService {
|
||||
private readonly _captions = signal<Caption[]>([]);
|
||||
private readonly _areCaptionsEnabledByUser = signal<boolean>(false);
|
||||
private readonly _captionsAgentId = signal<string | null>(null);
|
||||
private readonly _isCaptionsTogglePending = signal<boolean>(false);
|
||||
|
||||
/**
|
||||
* Current list of active captions
|
||||
@ -51,11 +50,6 @@ export class MeetingCaptionsService {
|
||||
* Whether captions are enabled by the user
|
||||
*/
|
||||
readonly areCaptionsEnabledByUser = this._areCaptionsEnabledByUser.asReadonly();
|
||||
/**
|
||||
* True while an enable() or disable() call is in flight.
|
||||
* Use this to prevent concurrent toggle requests.
|
||||
*/
|
||||
readonly isCaptionsTogglePending = this._isCaptionsTogglePending.asReadonly();
|
||||
|
||||
// Map to track expiration timeouts
|
||||
private expirationTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
@ -102,13 +96,6 @@ export class MeetingCaptionsService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isCaptionsTogglePending()) {
|
||||
this.logger.d('Captions toggle already in progress');
|
||||
return;
|
||||
}
|
||||
|
||||
this._isCaptionsTogglePending.set(true);
|
||||
|
||||
try {
|
||||
// Register the LiveKit transcription handler
|
||||
const agent = await this.aiAssistantService.createLiveCaptionsAssistant();
|
||||
@ -116,9 +103,8 @@ export class MeetingCaptionsService {
|
||||
this.room.registerTextStreamHandler('lk.transcription', this.handleTranscription.bind(this));
|
||||
this._areCaptionsEnabledByUser.set(true);
|
||||
this.logger.d('Captions enabled');
|
||||
} finally {
|
||||
// Add a small delay before allowing another toggle to prevent rapid concurrent calls
|
||||
setTimeout(() => this._isCaptionsTogglePending.set(false), 500);
|
||||
} catch (error) {
|
||||
this.logger.e('Error enabling captions:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,13 +118,6 @@ export class MeetingCaptionsService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isCaptionsTogglePending()) {
|
||||
this.logger.d('Captions toggle already in progress');
|
||||
return;
|
||||
}
|
||||
|
||||
this._isCaptionsTogglePending.set(true);
|
||||
|
||||
try {
|
||||
const agentId = this._captionsAgentId();
|
||||
|
||||
@ -155,9 +134,6 @@ export class MeetingCaptionsService {
|
||||
this.logger.d('Captions disabled');
|
||||
} catch (error) {
|
||||
this.logger.e('Error disabling captions:', error);
|
||||
} finally {
|
||||
// Add a small delay before allowing another toggle to prevent rapid concurrent calls
|
||||
setTimeout(() => this._isCaptionsTogglePending.set(false), 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user