2022-10-26 12:19:03 +02:00

185 lines
5.5 KiB
TypeScript

import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
OnInit,
QueryList, ViewChildren
} from '@angular/core';
import { Subscription } from 'rxjs';
import { PanelEvent, PanelService } from '../../services/panel/panel.service';
import { animate, style, transition, trigger } from '@angular/animations';
import { Session, SpeechToTextEvent } from 'openvidu-browser';
import { CaptionModel } from '../../models/caption.model';
import { PanelSettingsOptions, PanelType } from '../../models/panel.model';
import { CaptionService } from '../../services/caption/caption.service';
import { OpenViduService } from '../../services/openvidu/openvidu.service';
import { ParticipantService } from '../../services/participant/participant.service';
/**
* @internal
*/
@Component({
selector: 'ov-captions',
templateUrl: './captions.component.html',
styleUrls: ['./captions.component.css'],
animations: [
trigger('captionAnimation', [
transition(':enter', [style({ opacity: 0 }), animate('50ms ease-in', style({ opacity: 1 }))])
// transition(':leave', [style({ opacity: 1 }), animate('10ms ease-out', style({ opacity: 0 }))])
])
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CaptionsComponent implements OnInit {
scrollContainer: QueryList<ElementRef>;
@ViewChildren('captionEventElement')
set captionEventRef(captionEventsRef: QueryList<ElementRef>) {
setTimeout(() => {
if (captionEventsRef) {
this.scrollContainer = captionEventsRef;
}
}, 0);
}
settingsPanelOpened: boolean;
captionEvents: CaptionModel[] = [];
session: Session;
private deleteTimeout: NodeJS.Timeout;
private DELETE_TIMEOUT = 5 * 1000;
private MAX_EVENTS_LIMIT = 3;
private captionLanguageSubscription: Subscription;
private captionLangSelected: { name: string; ISO: string };
private screenSizeSub: Subscription;
private panelTogglingSubscription: Subscription;
constructor(
private panelService: PanelService,
private openviduService: OpenViduService,
private participantService: ParticipantService,
private captionService: CaptionService,
private cd: ChangeDetectorRef
) {}
ngOnInit(): void {
this.captionLangSelected = this.captionService.getLangSelected();
this.session = this.openviduService.getWebcamSession();
this.subscribeToCaptionLanguage();
this.subscribeToPanelToggling();
this.subscribeToTranscription();
}
ngOnDestroy() {
if (this.screenSizeSub) this.screenSizeSub.unsubscribe();
if (this.panelTogglingSubscription) this.panelTogglingSubscription.unsubscribe();
this.session.off('speechToTextMessage');
this.captionEvents = [];
}
onSettingsCliked() {
this.panelService.togglePanel(PanelType.SETTINGS, PanelSettingsOptions.CAPTIONS);
}
private subscribeToTranscription() {
this.session.on('speechToTextMessage', (event: SpeechToTextEvent) => {
const { connectionId, data } = event.connection;
const nickname: string = this.participantService.getNicknameFromConnectionData(data);
const color = this.participantService.getRemoteParticipantByConnectionId(connectionId)?.colorProfile || '';
const caption: CaptionModel = {
connectionId,
nickname,
color,
text: event.text,
type: event.reason
};
this.updateCaption(caption);
this.cd.markForCheck();
});
}
private updateCaption(caption: CaptionModel): void {
let captionEventsCopy = [...this.captionEvents];
let eventsNumber = captionEventsCopy.length;
if (eventsNumber === this.MAX_EVENTS_LIMIT) {
captionEventsCopy.shift();
eventsNumber--;
}
if (eventsNumber === 0) {
captionEventsCopy.push(caption);
} else {
const lastCaption: CaptionModel = captionEventsCopy.slice(-1)[0];
const sameAuthorAsAbove: boolean = lastCaption.connectionId === caption.connectionId;
const allEventsAreSameSpeaker = captionEventsCopy.every((e) => e.connectionId === caption.connectionId);
if (sameAuthorAsAbove) {
if (lastCaption.type === 'recognized') {
if (eventsNumber === 2 && allEventsAreSameSpeaker) {
captionEventsCopy.shift();
clearInterval(this.deleteTimeout);
}
captionEventsCopy.push(caption);
this.deleteEventAfterDelay(this.DELETE_TIMEOUT);
} else {
lastCaption.text = caption.text;
lastCaption.type = caption.type;
}
} else {
// TODO: Test when STT is able to work with multiple streams
if (eventsNumber === 2) {
captionEventsCopy.shift();
clearInterval(this.deleteTimeout);
}
captionEventsCopy.push(caption);
this.deleteEventAfterDelay(this.DELETE_TIMEOUT);
}
}
this.captionEvents = [...captionEventsCopy];
this.scrollToBottom();
}
private deleteEventAfterDelay(timeout: number) {
this.deleteTimeout = setTimeout(() => {
this.captionEvents.shift();
this.cd.markForCheck();
}, timeout);
}
private subscribeToCaptionLanguage() {
this.captionLanguageSubscription = this.captionService.captionLangObs.subscribe((lang) => {
this.captionLangSelected = lang;
this.cd.markForCheck();
});
}
private subscribeToPanelToggling() {
this.panelTogglingSubscription = this.panelService.panelOpenedObs.subscribe((ev: PanelEvent) => {
this.settingsPanelOpened = ev.opened;
setTimeout(() => this.cd.markForCheck(), 300);
});
}
private scrollToBottom(): void {
setTimeout(() => {
try {
this.scrollContainer.forEach((el: ElementRef) => {
el.nativeElement.scroll({
top: this.scrollContainer.first.nativeElement.scrollHeight,
left: 0
// behavior: 'smooth'
});
});
} catch (err) {}
}, 20);
}
}