mirror of https://github.com/OpenVidu/openvidu.git
openvidu-components: Improved algorithm for showing captions
parent
55c97c3d78
commit
b2426149c6
|
@ -1,11 +1,4 @@
|
||||||
import {
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, QueryList, ViewChildren } from '@angular/core';
|
||||||
ChangeDetectionStrategy,
|
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
ElementRef,
|
|
||||||
OnInit,
|
|
||||||
QueryList, ViewChildren
|
|
||||||
} from '@angular/core';
|
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { PanelEvent, PanelService } from '../../services/panel/panel.service';
|
import { PanelEvent, PanelService } from '../../services/panel/panel.service';
|
||||||
|
|
||||||
|
@ -50,10 +43,10 @@ export class CaptionsComponent implements OnInit {
|
||||||
|
|
||||||
session: Session;
|
session: Session;
|
||||||
|
|
||||||
private deleteTimeout: NodeJS.Timeout;
|
private deleteFirstTimeout: NodeJS.Timeout;
|
||||||
private deleteAllTimeout: NodeJS.Timeout;
|
private deleteAllTimeout: NodeJS.Timeout;
|
||||||
|
|
||||||
private DELETE_TIMEOUT = 5 * 1000;
|
private DELETE_TIMEOUT = 10 * 1000;
|
||||||
private MAX_EVENTS_LIMIT = 3;
|
private MAX_EVENTS_LIMIT = 3;
|
||||||
private captionLanguageSubscription: Subscription;
|
private captionLanguageSubscription: Subscription;
|
||||||
private captionLangSelected: { name: string; ISO: string };
|
private captionLangSelected: { name: string; ISO: string };
|
||||||
|
@ -85,16 +78,15 @@ export class CaptionsComponent implements OnInit {
|
||||||
|
|
||||||
async ngOnDestroy() {
|
async ngOnDestroy() {
|
||||||
this.captionService.setCaptionsEnabled(false);
|
this.captionService.setCaptionsEnabled(false);
|
||||||
|
if (this.screenSizeSub) this.screenSizeSub.unsubscribe();
|
||||||
|
if (this.panelTogglingSubscription) this.panelTogglingSubscription.unsubscribe();
|
||||||
|
this.session.off('speechToTextMessage');
|
||||||
|
this.captionEvents = [];
|
||||||
|
|
||||||
for (const p of this.participantService.getRemoteParticipants()) {
|
for (const p of this.participantService.getRemoteParticipants()) {
|
||||||
const stream = p.getCameraConnection().streamManager.stream;
|
const stream = p.getCameraConnection().streamManager.stream;
|
||||||
await this.session.unsubscribeFromSpeechToText(stream);
|
await this.session.unsubscribeFromSpeechToText(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.screenSizeSub) this.screenSizeSub.unsubscribe();
|
|
||||||
if (this.panelTogglingSubscription) this.panelTogglingSubscription.unsubscribe();
|
|
||||||
this.session.off('speechToTextMessage');
|
|
||||||
this.captionEvents = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSettingsCliked() {
|
onSettingsCliked() {
|
||||||
|
@ -125,47 +117,64 @@ export class CaptionsComponent implements OnInit {
|
||||||
let captionEventsCopy = [...this.captionEvents];
|
let captionEventsCopy = [...this.captionEvents];
|
||||||
let eventsNumber = captionEventsCopy.length;
|
let eventsNumber = captionEventsCopy.length;
|
||||||
|
|
||||||
if (eventsNumber === this.MAX_EVENTS_LIMIT) {
|
|
||||||
captionEventsCopy.shift();
|
|
||||||
eventsNumber--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventsNumber === 0) {
|
if (eventsNumber === 0) {
|
||||||
captionEventsCopy.push(caption);
|
captionEventsCopy.push(caption);
|
||||||
} else {
|
} else {
|
||||||
const lastCaption: CaptionModel = captionEventsCopy.slice(-1)[0];
|
const lastCaption: CaptionModel | undefined = captionEventsCopy[eventsNumber - 1];
|
||||||
const sameAuthorAsAbove: boolean = lastCaption.connectionId === caption.connectionId;
|
const sameSpeakerAsAbove: boolean = lastCaption.connectionId === caption.connectionId;
|
||||||
const allEventsAreSameSpeaker = captionEventsCopy.every((e) => e.connectionId === caption.connectionId);
|
const lastSpeakerHasStoppedTalking = lastCaption.type === 'recognized';
|
||||||
|
|
||||||
if (sameAuthorAsAbove) {
|
if (sameSpeakerAsAbove) {
|
||||||
if (lastCaption.type === 'recognized') {
|
if (lastSpeakerHasStoppedTalking) {
|
||||||
if (eventsNumber === 2 && allEventsAreSameSpeaker) {
|
// Add event if different from previous one
|
||||||
captionEventsCopy.shift();
|
if (caption.text !== lastCaption.text) {
|
||||||
clearInterval(this.deleteTimeout);
|
this.deleteFirstEventAfterDelay(this.DELETE_TIMEOUT);
|
||||||
|
captionEventsCopy.push(caption);
|
||||||
}
|
}
|
||||||
captionEventsCopy.push(caption);
|
|
||||||
this.deleteFirstEventAfterDelay(this.DELETE_TIMEOUT);
|
|
||||||
} else {
|
} else {
|
||||||
|
//Updating last 'recognizing' caption
|
||||||
lastCaption.text = caption.text;
|
lastCaption.text = caption.text;
|
||||||
lastCaption.type = caption.type;
|
lastCaption.type = caption.type;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: Test when STT is able to work with multiple streams
|
// Different speaker is talking
|
||||||
if (eventsNumber === 2) {
|
const speakerExists: boolean = captionEventsCopy.some((ev) => ev.connectionId === caption.connectionId);
|
||||||
captionEventsCopy.shift();
|
if (speakerExists) {
|
||||||
clearInterval(this.deleteTimeout);
|
// Speaker is already showing
|
||||||
|
if (lastSpeakerHasStoppedTalking) {
|
||||||
|
this.deleteFirstEventAfterDelay(this.DELETE_TIMEOUT);
|
||||||
|
captionEventsCopy.push(caption);
|
||||||
|
} else {
|
||||||
|
// There was an interruption. Last event is still being 'recognizing' (speaker is talking)
|
||||||
|
// Update last speaker event.
|
||||||
|
const lastSpeakerCaption = captionEventsCopy.find((cap) => cap.connectionId === caption.connectionId);
|
||||||
|
if (lastSpeakerCaption) {
|
||||||
|
if (lastSpeakerCaption.type === 'recognized') {
|
||||||
|
captionEventsCopy.push(caption);
|
||||||
|
} else {
|
||||||
|
lastSpeakerCaption.text = caption.text;
|
||||||
|
lastSpeakerCaption.type = caption.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.deleteFirstEventAfterDelay(this.DELETE_TIMEOUT);
|
||||||
|
captionEventsCopy.push(caption);
|
||||||
}
|
}
|
||||||
captionEventsCopy.push(caption);
|
|
||||||
this.deleteFirstEventAfterDelay(this.DELETE_TIMEOUT);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (captionEventsCopy.length === this.MAX_EVENTS_LIMIT) {
|
||||||
|
clearInterval(this.deleteFirstTimeout);
|
||||||
|
captionEventsCopy.shift();
|
||||||
|
}
|
||||||
|
|
||||||
this.captionEvents = [...captionEventsCopy];
|
this.captionEvents = [...captionEventsCopy];
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
private deleteFirstEventAfterDelay(timeout: number) {
|
private deleteFirstEventAfterDelay(timeout: number) {
|
||||||
this.deleteTimeout = setTimeout(() => {
|
this.deleteFirstTimeout = setTimeout(() => {
|
||||||
this.captionEvents.shift();
|
this.captionEvents.shift();
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
}, timeout);
|
}, timeout);
|
||||||
|
@ -195,9 +204,9 @@ export class CaptionsComponent implements OnInit {
|
||||||
private scrollToBottom(): void {
|
private scrollToBottom(): void {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
try {
|
try {
|
||||||
this.scrollContainer.forEach((el: ElementRef) => {
|
this.scrollContainer.forEach((el: ElementRef, index: number) => {
|
||||||
el.nativeElement.scroll({
|
el.nativeElement.scroll({
|
||||||
top: this.scrollContainer.first.nativeElement.scrollHeight,
|
top: this.scrollContainer.get(index)?.nativeElement.scrollHeight,
|
||||||
left: 0
|
left: 0
|
||||||
// behavior: 'smooth'
|
// behavior: 'smooth'
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue