Merge branch 'ov-components-stt-reconnection'

pull/765/head
Carlos Santos 2022-11-22 16:54:20 +01:00
commit dbd2e4a506
18 changed files with 209 additions and 81 deletions

View File

@ -26,6 +26,22 @@
padding: 0px 10vw 0px; padding: 0px 10vw 0px;
} }
.error-container {
display: grid;
text-align: center;
color: var(--ov-text-color);
font-size: 18px;
}
mat-spinner {
position: relative;
top: 35%;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
/* /*
* Screen XL * Screen XL

View File

@ -10,6 +10,7 @@
</button> </button>
</div> </div>
<div <div
*ngIf="isSttReady"
class="captions-center-container" class="captions-center-container"
[ngClass]="{ [ngClass]="{
'events-one': captionEvents.length === 1, 'events-one': captionEvents.length === 1,
@ -35,6 +36,11 @@
</div> </div>
</div> </div>
</div> </div>
<div *ngIf="!isSttReady" class="captions-center-container error-container">
<mat-spinner [diameter]="20"></mat-spinner>
<span>{{'ERRORS.SST_CONNECTION' | translate}}</span>
</div>
<div *ngIf="captionsContainer.offsetWidth >= 600 && !settingsPanelOpened" class="captions-offset"></div> <div *ngIf="captionsContainer.offsetWidth >= 600 && !settingsPanelOpened" class="captions-offset"></div>
</div> </div>

View File

@ -42,6 +42,7 @@ export class CaptionsComponent implements OnInit {
captionEvents: CaptionModel[] = []; captionEvents: CaptionModel[] = [];
session: Session; session: Session;
isSttReady: boolean = true;
private deleteFirstTimeout: NodeJS.Timeout; private deleteFirstTimeout: NodeJS.Timeout;
private deleteAllTimeout: NodeJS.Timeout; private deleteAllTimeout: NodeJS.Timeout;
@ -52,6 +53,8 @@ export class CaptionsComponent implements OnInit {
private captionLangSelected: { name: string; ISO: string }; private captionLangSelected: { name: string; ISO: string };
private screenSizeSub: Subscription; private screenSizeSub: Subscription;
private panelTogglingSubscription: Subscription; private panelTogglingSubscription: Subscription;
private sttStatusSubscription: Subscription;
constructor( constructor(
private panelService: PanelService, private panelService: PanelService,
@ -62,14 +65,12 @@ export class CaptionsComponent implements OnInit {
) {} ) {}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
this.subscribeToSTTStatus();
this.captionService.setCaptionsEnabled(true); this.captionService.setCaptionsEnabled(true);
this.captionLangSelected = this.captionService.getLangSelected(); this.captionLangSelected = this.captionService.getLangSelected();
this.session = this.openviduService.getWebcamSession(); this.session = this.openviduService.getWebcamSession();
for (const p of this.participantService.getRemoteParticipants()) { await this.openviduService.subscribeRemotesToSTT(this.captionLangSelected.ISO);
const stream = p.getCameraConnection().streamManager.stream;
await this.session.subscribeToSpeechToText(stream, this.captionLangSelected.ISO);
}
this.subscribeToCaptionLanguage(); this.subscribeToCaptionLanguage();
this.subscribeToPanelToggling(); this.subscribeToPanelToggling();
@ -77,16 +78,14 @@ export class CaptionsComponent implements OnInit {
} }
async ngOnDestroy() { async ngOnDestroy() {
await this.openviduService.unsubscribeRemotesFromSTT();
this.captionService.setCaptionsEnabled(false); this.captionService.setCaptionsEnabled(false);
if (this.screenSizeSub) this.screenSizeSub.unsubscribe(); if (this.screenSizeSub) this.screenSizeSub.unsubscribe();
if (this.panelTogglingSubscription) this.panelTogglingSubscription.unsubscribe(); if (this.panelTogglingSubscription) this.panelTogglingSubscription.unsubscribe();
if(this.sttStatusSubscription) this.sttStatusSubscription.unsubscribe();
this.session.off('speechToTextMessage'); this.session.off('speechToTextMessage');
this.captionEvents = []; this.captionEvents = [];
for (const p of this.participantService.getRemoteParticipants()) {
const stream = p.getCameraConnection().streamManager.stream;
await this.session.unsubscribeFromSpeechToText(stream);
}
} }
onSettingsCliked() { onSettingsCliked() {
@ -95,6 +94,7 @@ export class CaptionsComponent implements OnInit {
private subscribeToTranscription() { private subscribeToTranscription() {
this.session.on('speechToTextMessage', (event: SpeechToTextEvent) => { this.session.on('speechToTextMessage', (event: SpeechToTextEvent) => {
if(!!event.text) {
clearInterval(this.deleteAllTimeout); clearInterval(this.deleteAllTimeout);
const { connectionId, data } = event.connection; const { connectionId, data } = event.connection;
const nickname: string = this.participantService.getNicknameFromConnectionData(data); const nickname: string = this.participantService.getNicknameFromConnectionData(data);
@ -111,6 +111,7 @@ export class CaptionsComponent implements OnInit {
// Delete all events when there are no more events for a period of time // Delete all events when there are no more events for a period of time
this.deleteAllEventsAfterDelay(this.DELETE_TIMEOUT); this.deleteAllEventsAfterDelay(this.DELETE_TIMEOUT);
this.cd.markForCheck(); this.cd.markForCheck();
}
}); });
} }
private updateCaption(caption: CaptionModel): void { private updateCaption(caption: CaptionModel): void {
@ -187,6 +188,13 @@ export class CaptionsComponent implements OnInit {
}, timeout); }, timeout);
} }
private subscribeToSTTStatus() {
this.sttStatusSubscription = this.openviduService.isSttReadyObs.subscribe((ready: boolean) => {
this.isSttReady = ready;
this.cd.markForCheck();
});
}
private subscribeToCaptionLanguage() { private subscribeToCaptionLanguage() {
this.captionLanguageSubscription = this.captionService.captionLangObs.subscribe((lang) => { this.captionLanguageSubscription = this.captionService.captionLangObs.subscribe((lang) => {
this.captionLangSelected = lang; this.captionLangSelected = lang;

View File

@ -16,6 +16,7 @@ import {
import { import {
ConnectionEvent, ConnectionEvent,
ExceptionEvent, ExceptionEvent,
ExceptionEventName,
RecordingEvent, RecordingEvent,
Session, Session,
SessionDisconnectedEvent, SessionDisconnectedEvent,
@ -84,6 +85,7 @@ export class SessionComponent implements OnInit, OnDestroy {
protected layoutWidthSubscription: Subscription; protected layoutWidthSubscription: Subscription;
protected updateLayoutInterval: NodeJS.Timer; protected updateLayoutInterval: NodeJS.Timer;
// private sttReconnectionInterval: NodeJS.Timer;
private captionLanguageSubscription: Subscription; private captionLanguageSubscription: Subscription;
protected log: ILogger; protected log: ILogger;
@ -277,8 +279,15 @@ export class SessionComponent implements OnInit, OnDestroy {
} }
private subscribeToOpenViduException() { private subscribeToOpenViduException() {
this.session.on('exception', (event: ExceptionEvent) => { this.session.on('exception', async (event: ExceptionEvent) => {
if (event.name === ExceptionEventName.SPEECH_TO_TEXT_DISCONNECTED) {
this.log.w(event.name, event.message);
this.openviduService.setSTTReady(false);
// Try to re-subscribe to STT
await this.openviduService.subscribeRemotesToSTT(this.captionService.getLangSelected().ISO);
} else {
this.log.e(event.name, event.message); this.log.e(event.name, event.message);
}
}); });
} }
@ -319,18 +328,23 @@ export class SessionComponent implements OnInit, OnDestroy {
const data = event.stream?.connection?.data; const data = event.stream?.connection?.data;
const isCameraType: boolean = this.participantService.getTypeConnectionData(data) === VideoType.CAMERA; const isCameraType: boolean = this.participantService.getTypeConnectionData(data) === VideoType.CAMERA;
const isRemoteConnection: boolean = !this.openviduService.isMyOwnConnection(connectionId); const isRemoteConnection: boolean = !this.openviduService.isMyOwnConnection(connectionId);
const lang = this.captionService.getLangSelected().ISO;
if (isRemoteConnection) { if (isRemoteConnection) {
const subscriber: Subscriber = this.session.subscribe(event.stream, undefined); const subscriber: Subscriber = this.session.subscribe(event.stream, undefined);
this.participantService.addRemoteConnection(connectionId, data, subscriber); this.participantService.addRemoteConnection(connectionId, data, subscriber);
// this.oVSessionService.sendNicknameSignal(event.stream.connection); // this.oVSessionService.sendNicknameSignal(event.stream.connection);
if (this.captionService.areCaptionsEnabled() && isCameraType) { if (this.openviduService.isSttReady() && this.captionService.areCaptionsEnabled() && isCameraType) {
// Only subscribe to STT when stream is CAMERA type and it is a remote stream // Only subscribe to STT when is ready and stream is CAMERA type and it is a remote stream
try { try {
await this.session.subscribeToSpeechToText(event.stream, this.captionService.getLangSelected().ISO); await this.openviduService.subscribeStreamToStt(event.stream, lang);
} catch (error) { } catch (error) {
this.log.e('Error subscribing from STT: ', error); this.log.e('Error subscribing from STT: ', error);
// I assume the only reason of an STT error is a STT crash.
// It must be subscribed to all remotes again
// await this.openviduService.unsubscribeRemotesFromSTT();
await this.openviduService.subscribeRemotesToSTT(lang);
} }
} }
} }
@ -345,15 +359,13 @@ export class SessionComponent implements OnInit, OnDestroy {
const isCameraType: boolean = this.participantService.getTypeConnectionData(data) === VideoType.CAMERA; const isCameraType: boolean = this.participantService.getTypeConnectionData(data) === VideoType.CAMERA;
this.participantService.removeConnectionByConnectionId(connectionId); this.participantService.removeConnectionByConnectionId(connectionId);
if (isRemoteConnection) { if (this.openviduService.isSttReady() && this.captionService.areCaptionsEnabled() && isRemoteConnection && isCameraType) {
if (this.captionService.areCaptionsEnabled() && isCameraType) {
try { try {
await this.session.unsubscribeFromSpeechToText(event.stream); await this.session.unsubscribeFromSpeechToText(event.stream);
} catch (error) { } catch (error) {
this.log.e('Error unsubscribing from STT: ', error); this.log.e('Error unsubscribing from STT: ', error);
} }
} }
}
}); });
} }
@ -362,17 +374,8 @@ export class SessionComponent implements OnInit, OnDestroy {
if (this.captionService.areCaptionsEnabled()) { if (this.captionService.areCaptionsEnabled()) {
// Unsubscribe all streams from speech to text and re-subscribe with new language // Unsubscribe all streams from speech to text and re-subscribe with new language
this.log.d('Re-subscribe from STT because of language changed to ', lang.ISO); this.log.d('Re-subscribe from STT because of language changed to ', lang.ISO);
for (const participant of this.participantService.getRemoteParticipants()) { await this.openviduService.unsubscribeRemotesFromSTT();
const streamManager = participant.getCameraConnection()?.streamManager; await this.openviduService.subscribeRemotesToSTT(lang.ISO);
if (!!streamManager?.stream) {
try {
await this.session.unsubscribeFromSpeechToText(streamManager.stream);
await this.session.subscribeToSpeechToText(streamManager.stream, lang.ISO);
} catch (error) {
this.log.e('Error re-subscribing to STT: ', error);
}
}
}
} }
}); });
} }

View File

@ -15,7 +15,7 @@
<div id="lang-section"> <div id="lang-section">
<mat-list-item> <mat-list-item>
<div mat-line>{{ 'PANEL.SETTINGS.LANGUAGE' | translate }}</div> <div mat-line>{{ 'PANEL.SETTINGS.LANGUAGE' | translate }}</div>
<button mat-flat-button [matMenuTriggerFor]="menu" class="lang-button"> <button mat-flat-button [matMenuTriggerFor]="menu" [disabled]="!isSttReady" class="lang-button">
<span>{{ langSelected }}</span> <span>{{ langSelected }}</span>
<mat-icon>expand_more</mat-icon> <mat-icon>expand_more</mat-icon>
</button> </button>

View File

@ -13,25 +13,30 @@ import { OpenViduService } from '../../../services/openvidu/openvidu.service';
styleUrls: ['./captions.component.css'] styleUrls: ['./captions.component.css']
}) })
export class CaptionsSettingComponent implements OnInit, OnDestroy { export class CaptionsSettingComponent implements OnInit, OnDestroy {
isSttReady: boolean = true;
captionsEnabled: boolean; captionsEnabled: boolean;
languagesAvailable: { name: string; ISO: string }[] = []; languagesAvailable: { name: string; ISO: string }[] = [];
captionsSubscription: Subscription;
langSelected: string; langSelected: string;
isOpenViduPro: boolean = false; isOpenViduPro: boolean = false;
private captionsStatusSubs: Subscription;
private sttStatusSubs: Subscription;
constructor(private layoutService: LayoutService, private captionService: CaptionService, private openviduService: OpenViduService) {} constructor(private layoutService: LayoutService, private captionService: CaptionService, private openviduService: OpenViduService) {}
ngOnInit(): void { ngOnInit(): void {
this.isOpenViduPro = this.openviduService.isOpenViduPro(); this.isOpenViduPro = this.openviduService.isOpenViduPro();
if (this.isOpenViduPro) { if (this.isOpenViduPro) {
this.subscribeToCaptions(); this.subscribeToSttStatus();
this.subscribeToCaptionsStatus();
this.langSelected = this.captionService.getLangSelected().name; this.langSelected = this.captionService.getLangSelected().name;
this.languagesAvailable = this.captionService.getCaptionLanguages(); this.languagesAvailable = this.captionService.getCaptionLanguages();
} }
} }
ngOnDestroy() { ngOnDestroy() {
if (this.captionsSubscription) this.captionsSubscription.unsubscribe(); if (this.captionsStatusSubs) this.captionsStatusSubs.unsubscribe();
if (this.sttStatusSubs) this.sttStatusSubs.unsubscribe();
} }
onLangSelected(lang: { name: string; ISO: string }) { onLangSelected(lang: { name: string; ISO: string }) {
@ -43,8 +48,14 @@ export class CaptionsSettingComponent implements OnInit, OnDestroy {
this.layoutService.toggleCaptions(); this.layoutService.toggleCaptions();
} }
private subscribeToCaptions() { private subscribeToSttStatus(){
this.captionsSubscription = this.layoutService.captionsTogglingObs.subscribe((value: boolean) => { this.sttStatusSubs = this.openviduService.isSttReadyObs.subscribe((ready: boolean) => {
this.isSttReady = ready;
});
}
private subscribeToCaptionsStatus() {
this.captionsStatusSubs = this.layoutService.captionsTogglingObs.subscribe((value: boolean) => {
this.captionsEnabled = value; this.captionsEnabled = value;
// this.cd.markForCheck(); // this.cd.markForCheck();
}); });

View File

@ -112,6 +112,7 @@
"SCREEN_SHARING": "分享屏幕出错", "SCREEN_SHARING": "分享屏幕出错",
"SCREEN_SUPPORT": "您的浏览器不支持屏幕共享", "SCREEN_SUPPORT": "您的浏览器不支持屏幕共享",
"MEDIA_ACCESS": "不允许访问媒体设备", "MEDIA_ACCESS": "不允许访问媒体设备",
"DEVICE_NOT_FOUND": "没有找到视频或音频设备 请至少连接一个" "DEVICE_NOT_FOUND": "没有找到视频或音频设备 请至少连接一个",
"SST_CONNECTION": "连接丢失。正在重新连接到语音到文本服务"
} }
} }

View File

@ -112,6 +112,7 @@
"SCREEN_SHARING": "Fehler beim Teilen des Bildschirms", "SCREEN_SHARING": "Fehler beim Teilen des Bildschirms",
"SCREEN_SUPPORT": "Ihr Browser unterstützt keine Bildschirmfreigabe", "SCREEN_SUPPORT": "Ihr Browser unterstützt keine Bildschirmfreigabe",
"MEDIA_ACCESS": "Der Zugriff auf Mediengeräte war nicht erlaubt.", "MEDIA_ACCESS": "Der Zugriff auf Mediengeräte war nicht erlaubt.",
"DEVICE_NOT_FOUND": "Es wurden keine Video- oder Audiogeräte gefunden. Bitte schließen Sie mindestens eines an." "DEVICE_NOT_FOUND": "Es wurden keine Video- oder Audiogeräte gefunden. Bitte schließen Sie mindestens eines an.",
"SST_CONNECTION": "Verbindung verloren. Wiederverbindung zum Sprach zu Text Service..."
} }
} }

View File

@ -113,6 +113,7 @@
"SCREEN_SHARING": "Error sharing screen", "SCREEN_SHARING": "Error sharing screen",
"SCREEN_SUPPORT": "Your browser does not support screen sharing", "SCREEN_SUPPORT": "Your browser does not support screen sharing",
"MEDIA_ACCESS": "Access to media devices was not allowed.", "MEDIA_ACCESS": "Access to media devices was not allowed.",
"DEVICE_NOT_FOUND": "No video or audio devices have been found. Please, connect at least one." "DEVICE_NOT_FOUND": "No video or audio devices have been found. Please, connect at least one.",
"SST_CONNECTION": "Connection lost. Reconnecting to the speech to text service ..."
} }
} }

View File

@ -112,6 +112,7 @@
"SCREEN_SHARING": "Hubo un error compartiendo pantalla", "SCREEN_SHARING": "Hubo un error compartiendo pantalla",
"SCREEN_SUPPORT": "Tu navegador no soporta la pantalla compartida", "SCREEN_SUPPORT": "Tu navegador no soporta la pantalla compartida",
"MEDIA_ACCESS": "No se ha podido acceder a tus dispositivos", "MEDIA_ACCESS": "No se ha podido acceder a tus dispositivos",
"DEVICE_NOT_FOUND": "No se han encontrado dispositivos de audio o video. Por favor, conecta al menos uno." "DEVICE_NOT_FOUND": "No se han encontrado dispositivos de audio o video. Por favor, conecta al menos uno.",
"SST_CONNECTION": "Conexión perdida. Reconectando al servicio de transcripción a texto..."
} }
} }

View File

@ -112,6 +112,7 @@
"SCREEN_SHARING": "Erreur de partage d'écran", "SCREEN_SHARING": "Erreur de partage d'écran",
"SCREEN_SUPPORT": "Votre navigateur ne prend pas en charge le partage d'écran", "SCREEN_SUPPORT": "Votre navigateur ne prend pas en charge le partage d'écran",
"MEDIA_ACCESS": "L'accès aux périphériques médias n'a pas été autorisé", "MEDIA_ACCESS": "L'accès aux périphériques médias n'a pas été autorisé",
"DEVICE_NOT_FOUND": "Aucun périphérique vidéo ou audio n'a été trouvé. Veuillez en connecter au moins un." "DEVICE_NOT_FOUND": "Aucun périphérique vidéo ou audio n'a été trouvé. Veuillez en connecter au moins un.",
"SST_CONNECTION": "Connexion perdue. Reconnexion au service de reconnaissance vocale..."
} }
} }

View File

@ -112,6 +112,7 @@
"SCREEN_SHARING": "स्क्रीन साझा करने में त्रुटि", "SCREEN_SHARING": "स्क्रीन साझा करने में त्रुटि",
"SCREEN_SUPPORT": "आपका ब्राउज़र स्क्रीन साझाकरण का समर्थन नहीं करता", "SCREEN_SUPPORT": "आपका ब्राउज़र स्क्रीन साझाकरण का समर्थन नहीं करता",
"MEDIA_ACCESS": "मीडिया उपकरणों तक पहुंच की अनुमति नहीं थी।", "MEDIA_ACCESS": "मीडिया उपकरणों तक पहुंच की अनुमति नहीं थी।",
"DEVICE_NOT_FOUND": "कोई वीडियो या ऑडियो डिवाइस नहीं मिला। कृपया, कम से कम एक कनेक्ट करें।" "DEVICE_NOT_FOUND": "कोई वीडियो या ऑडियो डिवाइस नहीं मिला। कृपया, कम से कम एक कनेक्ट करें।",
"SST_CONNECTION": "खोया तार। लेख सेवा से लिप्यंतरण से पुन: कनेक्ट हो रहा है..."
} }
} }

View File

@ -112,6 +112,7 @@
"SCREEN_SHARING": "Errore nella condivisione dello schermo", "SCREEN_SHARING": "Errore nella condivisione dello schermo",
"SCREEN_SUPPORT": "Il browser non supporta la condivisione dello schermo", "SCREEN_SUPPORT": "Il browser non supporta la condivisione dello schermo",
"MEDIA_ACCESS": "L'accesso ai dispositivi multimediali non è stato consentito", "MEDIA_ACCESS": "L'accesso ai dispositivi multimediali non è stato consentito",
"DEVICE_NOT_FOUND": "Non sono stati trovati dispositivi video o audio. Si prega di collegarne almeno uno" "DEVICE_NOT_FOUND": "Non sono stati trovati dispositivi video o audio. Si prega di collegarne almeno uno",
"SST_CONNECTION": "Connessione persa. Riconnessione al servizio di conversione testo da audio in corso..."
} }
} }

View File

@ -112,6 +112,7 @@
"SCREEN_SHARING": "画面共有にエラーが発生しました", "SCREEN_SHARING": "画面共有にエラーが発生しました",
"SCREEN_SUPPORT": "お使いのブラウザは画面共有に対応していません", "SCREEN_SUPPORT": "お使いのブラウザは画面共有に対応していません",
"MEDIA_ACCESS": "メディアデバイスへのアクセスが許可されませんでした", "MEDIA_ACCESS": "メディアデバイスへのアクセスが許可されませんでした",
"DEVICE_NOT_FOUND": "ビデオまたはオーディオデバイスが見つかりませんでした 最低1台は接続してください" "DEVICE_NOT_FOUND": "ビデオまたはオーディオデバイスが見つかりませんでした 最低1台は接続してください",
"SST_CONNECTION": "接続が失われました。音声からテキストへの変換サービスに再接続しています"
} }
} }

View File

@ -112,6 +112,7 @@
"SCREEN_SHARING": "Fout bij het delen van het scherm", "SCREEN_SHARING": "Fout bij het delen van het scherm",
"SCREEN_SUPPORT": "Uw browser ondersteunt het delen van schermen niet", "SCREEN_SUPPORT": "Uw browser ondersteunt het delen van schermen niet",
"MEDIA_ACCESS": "Toegang tot media-apparaten was niet toegestaan.", "MEDIA_ACCESS": "Toegang tot media-apparaten was niet toegestaan.",
"DEVICE_NOT_FOUND": "Er zijn geen video- of audioapparaten gevonden. Sluit er alstublieft ten minste één aan." "DEVICE_NOT_FOUND": "Er zijn geen video- of audioapparaten gevonden. Sluit er alstublieft ten minste één aan.",
"SST_CONNECTION": "Verbinding verbroken. Opnieuw verbinden met de spraak-naar-tekstservice..."
} }
} }

View File

@ -112,6 +112,7 @@
"SCREEN_SHARING": "ecrã_partilha de erros", "SCREEN_SHARING": "ecrã_partilha de erros",
"SCREEN_SUPPORT": "O seu browser não suporta a partilha de ecrãs", "SCREEN_SUPPORT": "O seu browser não suporta a partilha de ecrãs",
"MEDIA_ACCESS": "Não foi permitido o acesso a dispositivos de media", "MEDIA_ACCESS": "Não foi permitido o acesso a dispositivos de media",
"DEVICE_NOT_FOUND": "Nenhum dispositivo de vídeo ou áudio foi encontrado. Por favor, ligue pelo menos um" "DEVICE_NOT_FOUND": "Nenhum dispositivo de vídeo ou áudio foi encontrado. Por favor, ligue pelo menos um",
"SST_CONNECTION": "Conexão perdida. Reconectando ao serviço de texto de voz..."
} }
} }

View File

@ -22,7 +22,7 @@ export class CaptionService {
]; ];
captionLangSelected: { name: string; ISO: string } = { name: 'English', ISO: 'en-US' }; captionLangSelected: { name: string; ISO: string } = { name: 'English', ISO: 'en-US' };
captionLangObs: Observable<{ name: string; ISO: string }>; captionLangObs: Observable<{ name: string; ISO: string }>;
private _captionLangObs: Subject<{ name: string; ISO: string }> = new Subject(); private _captionLang: Subject<{ name: string; ISO: string }> = new Subject();
private captionsEnabled: boolean = false; private captionsEnabled: boolean = false;
constructor(private storageService: StorageService) { constructor(private storageService: StorageService) {
@ -33,7 +33,7 @@ export class CaptionService {
} else { } else {
this.captionLangSelected = this.langs[0]; this.captionLangSelected = this.langs[0];
} }
this.captionLangObs = this._captionLangObs.asObservable(); this.captionLangObs = this._captionLang.asObservable();
} }
setCaptionsEnabled(value: boolean) { setCaptionsEnabled(value: boolean) {
@ -49,7 +49,7 @@ export class CaptionService {
if (!!newLang && newLang.ISO !== this.captionLangSelected.ISO) { if (!!newLang && newLang.ISO !== this.captionLangSelected.ISO) {
this.captionLangSelected = newLang; this.captionLangSelected = newLang;
this.storageService.setCaptionLang(lang); this.storageService.setCaptionLang(lang);
this._captionLangObs.next(this.captionLangSelected); this._captionLang.next(this.captionLangSelected);
} }
} }

View File

@ -7,11 +7,13 @@ import {
Publisher, Publisher,
PublisherProperties, PublisherProperties,
Session, Session,
SignalOptions SignalOptions,
Stream
} from 'openvidu-browser'; } from 'openvidu-browser';
import { LoggerService } from '../logger/logger.service'; import { LoggerService } from '../logger/logger.service';
import { BehaviorSubject, Observable } from 'rxjs';
import { CameraType } from '../../models/device.model'; import { CameraType } from '../../models/device.model';
import { ILogger } from '../../models/logger.model'; import { ILogger } from '../../models/logger.model';
import { OpenViduEdition } from '../../models/openvidu.model'; import { OpenViduEdition } from '../../models/openvidu.model';
@ -26,6 +28,7 @@ import { PlatformService } from '../platform/platform.service';
providedIn: 'root' providedIn: 'root'
}) })
export class OpenViduService { export class OpenViduService {
isSttReadyObs: Observable<boolean>;
private ovEdition: OpenViduEdition; private ovEdition: OpenViduEdition;
private webcamToken = ''; private webcamToken = '';
private screenToken = ''; private screenToken = '';
@ -35,6 +38,9 @@ export class OpenViduService {
protected screenSession: Session; protected screenSession: Session;
protected videoSource = undefined; protected videoSource = undefined;
protected audioSource = undefined; protected audioSource = undefined;
private STT_TIMEOUT_MS = 2 * 1000;
private sttReconnectionTimeout: NodeJS.Timeout;
private _isSttReady: BehaviorSubject<boolean> = new BehaviorSubject(true);
protected log: ILogger; protected log: ILogger;
/** /**
@ -48,6 +54,7 @@ export class OpenViduService {
protected deviceService: DeviceService protected deviceService: DeviceService
) { ) {
this.log = this.loggerSrv.get('OpenViduService'); this.log = this.loggerSrv.get('OpenViduService');
this.isSttReadyObs = this._isSttReady.asObservable();
} }
/** /**
@ -170,6 +177,25 @@ export class OpenViduService {
return !!this.screenSession.capabilities; return !!this.screenSession.capabilities;
} }
/**
* @internal
* Whether the STT service is ready or not
* This will be `false` when the app receives a SPEECH_TO_TEXT_DISCONNECTED exception
* and it cannot subscribe to STT
*/
isSttReady(): boolean {
return this._isSttReady.getValue();
}
/**
* @internal
*/
setSTTReady(value: boolean): void {
if (this._isSttReady.getValue() !== value) {
this._isSttReady.next(value);
}
}
/** /**
* @internal * @internal
*/ */
@ -210,7 +236,7 @@ export class OpenViduService {
* @internal * @internal
* Initialize a publisher checking devices saved on storage or if participant have devices available. * Initialize a publisher checking devices saved on storage or if participant have devices available.
*/ */
async initDefaultPublisher(): Promise<Publisher> { async initDefaultPublisher(): Promise<Publisher | undefined> {
const hasVideoDevices = this.deviceService.hasVideoDeviceAvailable(); const hasVideoDevices = this.deviceService.hasVideoDeviceAvailable();
const hasAudioDevices = this.deviceService.hasAudioDeviceAvailable(); const hasAudioDevices = this.deviceService.hasAudioDeviceAvailable();
const isVideoActive = !this.deviceService.isVideoMuted(); const isVideoActive = !this.deviceService.isVideoMuted();
@ -487,19 +513,67 @@ export class OpenViduService {
} }
} }
// private destroyPublisher(publisher: Publisher): void { /**
// if (!!publisher) { * @internal
// if (publisher.stream.getWebRtcPeer()) { * Subscribe all `CAMERA` stream types to speech-to-text
// publisher.stream.disposeWebRtcPeer(); * It will retry the subscription each `STT_TIMEOUT_MS`
// } *
// publisher.stream.disposeMediaStream(); * @param lang The language of the Stream's audio track.
// if (publisher.id === this.participantService.getMyCameraPublisher().id) { */
// this.participantService.setMyCameraPublisher(publisher); async subscribeRemotesToSTT(lang: string): Promise<void> {
// } else if (publisher.id === this.participantService.getMyScreenPublisher().id) { const remoteParticipants = this.participantService.getRemoteParticipants();
// this.participantService.setMyScreenPublisher(publisher); let successNumber = 0;
// }
// } for (const p of remoteParticipants) {
// } const stream = p.getCameraConnection()?.streamManager?.stream;
if (stream) {
try {
await this.subscribeStreamToStt(stream, lang);
successNumber++;
} catch (error) {
this.log.e(`Error subscribing ${stream.streamId} to STT:`, error);
break;
}
}
}
this.setSTTReady(successNumber === remoteParticipants.length);
if (!this.isSttReady()) {
this.log.w('STT is not ready. Retrying subscription...');
this.sttReconnectionTimeout = setTimeout(this.subscribeRemotesToSTT.bind(this, lang), this.STT_TIMEOUT_MS);
}
}
/**
* @internal
* Subscribe a stream to speech-to-text
* @param stream
* @param lang
*/
async subscribeStreamToStt(stream: Stream, lang: string): Promise<void> {
await this.getWebcamSession().subscribeToSpeechToText(stream, lang);
this.log.d(`Subscribed stream ${stream.streamId} to STT with ${lang} language.`);
}
/**
* @internal
* Unsubscribe to all `CAMERA` stream types to speech-to-text if STT is up(ready)
*/
async unsubscribeRemotesFromSTT(): Promise<void> {
clearTimeout(this.sttReconnectionTimeout);
if (this.isSttReady()) {
for (const p of this.participantService.getRemoteParticipants()) {
const stream = p.getCameraConnection().streamManager.stream;
if (stream) {
try {
await this.getWebcamSession().unsubscribeFromSpeechToText(stream);
} catch (error) {
this.log.e(`Error unsubscribing ${stream.streamId} from STT:`, error);
}
}
}
}
}
private async createMediaStream(pp: PublisherProperties): Promise<MediaStream> { private async createMediaStream(pp: PublisherProperties): Promise<MediaStream> {
let mediaStream: MediaStream; let mediaStream: MediaStream;