openvidu-components: Added captions

pull/748/head
Carlos Santos 2022-10-24 17:08:32 +02:00
parent 370f7b015f
commit 2aec4b9fab
46 changed files with 751 additions and 1791 deletions

View File

@ -11,7 +11,7 @@
"@angular/platform-browser-dynamic": "13.3.9",
"@angular/router": "13.3.9",
"autolinker": "3.14.3",
"lorem-ipsum": "^2.0.8",
"openvidu-browser": "file:openvidu-browser-2.23.0.tgz",
"openvidu-browser": "2.23.0",
"rxjs": "7.5.6",
"tslib": "2.3.1",

View File

@ -1,78 +1,193 @@
.subtitles-container {
.captions-container {
/* padding: 5px; */
display: flex;
height: var(--ov-captions-height, 200px);
height: var(--ov-captions-height, 230px);
margin: 0px 10px;
}
.subtitles-offset {
height: var(--ov-captions-height, 200px);
.captions-offset {
height: var(--ov-captions-height, 230px);
width: 15%;
text-align: center;
}
.subtitles-main-container {
flex-grow: 1;
align-self: center;
margin-left: 15px;
max-height: var(--ov-captions-height, 200px);
width: 70%;
overflow: hidden;
.captions-offset-xl {
width: 25% !important;
}
/* .subtitles-offset + .subtitles-offset {
margin-left: 2%;
} */
#subtitle-settings-btn {
.captions-center-container {
flex-grow: 1;
align-self: center;
/* margin-left: 15px; */
max-height: var(--ov-captions-height, 230px);
width: 70%;
overflow: hidden;
height: var(--ov-captions-height, 230px);
padding: 0px 10vw 0px;
}
/*
* Screen XL
*/
.captions-center-container.screen-xl {
padding: 0px 14vw;
}
.captions-center-container.screen-xl .caption-event>span {
font-size: 22px;
}
.captions-center-container.screen-xl #speaker {
font-size: 16px;
}
/* captions div*/
.captions-center-container.screen-xl.events-one .caption-event {
max-height: 140px;
}
.captions-center-container.screen-xl.events-two .caption-event {
max-height: 69px;
}
.captions-center-container.screen-xl.events-three .caption-event {
max-height: 35px;
}
/*
* Screen MD
*/
.captions-center-container.screen-md .caption-event>span {
font-size: 20px;
}
.captions-center-container.screen-md #speaker {
font-size: 14px;
}
/* captions div*/
.captions-center-container.screen-md.events-one .caption-event {
max-height: 140px;
}
.captions-center-container.screen-md.events-two .caption-event {
max-height: 69px;
}
.captions-center-container.screen-md.events-three .caption-event {
max-height: 35px;
}
/*
* Screen SM
*/
.captions-center-container.screen-sm {
padding: 0px 2vw;
}
.captions-center-container.screen-sm .caption-event>span {
font-size: 20px;
}
.captions-center-container.screen-sm #speaker {
font-size: 12px;
}
/* captions div*/
.captions-center-container.screen-sm.events-one .caption-event {
max-height: 127px;
}
.captions-center-container.screen-sm.events-two .caption-event {
max-height: 64px;
}
.captions-center-container.screen-sm.events-three .caption-event {
max-height: 33px;
}
/*
* Screen XS
*/
.captions-center-container.screen-xs {
padding: 0px 2vw 0px;
}
.captions-center-container.screen-xs .caption-event>span {
font-size: 20px;
}
.captions-center-container.screen-xs #speaker {
font-size: 12px;
}
/* captions div*/
.captions-center-container.screen-xs.events-one .caption-event {
max-height: 130px;
}
.captions-center-container.screen-xs.events-two .caption-event {
max-height: 69px;
}
.captions-center-container.screen-xs.events-three .caption-event {
max-height: 35px;
}
.captions-center-container .going-to-disappear{
max-height: 30px !important;
}
#caption-settings-btn {
color: var(--ov-text-color);
background-color: var(--ov-secondary-color);
}
#subtitle-settings-icon {
#caption-settings-icon {
font-size: 15px;
height: 15px;
width: 15px;
padding-right: 5px;
}
#author {
margin-bottom: 3px;
#speaker {
margin-bottom: 2px;
font-weight: bold;
/* padding: 5px; */
margin-left: -5px;
width: fit-content;
}
#subtitle-text,
#author {
.captions-center-container .element {
margin: 8px 0px;
}
.caption-event {
/* background-color: beige; */
overflow: auto;
pointer-events: none;
}
.caption-text,
#speaker {
color: var(--ov-text-color);
font-family: "Roboto",arial,sans-serif
font-family: 'Roboto', arial, sans-serif;
}
.subtitles-main-container .element {
margin: 5px;
}
#subtitle-text {
.caption-text {
background-color: var(--ov-logo-background-color);
padding: 4.5px;
line-height: 1.6;
word-break: break-word;
}
.big-text {
font-size: 22px;
}
.medium-text {
font-size: 20px;
}
.small-text {
font-size: 18px;
}
.extra-small-text {
font-size: 16px;
}
.big-author {
font-size: 16px;
}
.medium-author {
font-size: 14px;
}
.small-author {
font-size: 12px;
::-webkit-scrollbar {
display: none;
}

View File

@ -1,36 +1,40 @@
<div class="subtitles-container" #captionsContainer>
<div *ngIf="screenSize !== 'sm' && screenSize !== 'xs' && !settingsPanelOpened" class="subtitles-offset">
<button (click)="onSettingsCliked()" id="subtitle-settings-btn" mat-flat-button>
<div class="captions-container" #captionsContainer>
<div
*ngIf="captionsContainer.offsetWidth >= 600 && !settingsPanelOpened"
class="captions-offset"
[ngClass]="{ 'captions-offset': captionsContainer.offsetWidth >= 1000 }"
>
<button (click)="onSettingsCliked()" id="caption-settings-btn" mat-flat-button>
<mat-icon id="subtitle-settings-icon">settings</mat-icon>
<span>English</span>
<span>{{ captionLangSelected.name }}</span>
</button>
</div>
<div class="subtitles-main-container" #textContainer>
<div class="element" *ngFor="let item of captionElements" #captionElement>
<p
id="author"
<div
class="captions-center-container"
[ngClass]="{
'big-author': captionsContainer.offsetWidth >= 1000 || screenSize === 'lg' || screenSize === 'xl',
'medium-author': (captionsContainer.offsetWidth >= 960 && captionsContainer.offsetWidth < 1000) || screenSize === 'md',
'small-author': captionsContainer.offsetWidth < 600 || screenSize === 'xs' || screenSize === 'sm'
'events-one': captionEvents.length === 1,
'events-two': captionEvents.length === 2,
'events-three': captionEvents.length === 3,
'screen-xl': captionsContainer.offsetWidth >= 1000,
'screen-md': captionsContainer.offsetWidth >= 960 && captionsContainer.offsetWidth < 1000,
'screen-sm': captionsContainer.offsetWidth >= 600 && captionsContainer.offsetWidth < 960,
'screen-xs': captionsContainer.offsetWidth < 600
}"
>
{{ item.author }} ({{item.text.length}})
<div class="element" *ngFor="let caption of captionEvents; let i = index" @captionAnimation>
<p id="speaker" [ngStyle]="{ color: caption.color }">
{{ caption.nickname }}
</p>
<span
id="subtitle-text"
[ngClass]="{
'big-text': captionsContainer.offsetWidth >= 1000 || screenSize === 'lg' || screenSize === 'xl',
'medium-text': (captionsContainer.offsetWidth >= 960 && captionsContainer.offsetWidth < 1000) || screenSize === 'md',
'small-text': (captionsContainer.offsetWidth >= 600 && captionsContainer.offsetWidth < 960) || screenSize === 'sm',
'extra-small-text': captionsContainer.offsetWidth < 600 || screenSize === 'xs'
}"
>
{{ item.text }}</span
<div
id="caption-event"
class="caption-event"
[ngClass]="{ 'going-to-disappear': i === 0 && captionEvents.length === MAX_EVENTS_LIMIT }"
#captionEventElement
>
<span id="caption-text" class="caption-text">{{ caption.text }}</span>
</div>
</div>
</div>
<div *ngIf="screenSize !== 'sm' && screenSize !== 'xs' && !settingsPanelOpened" class="subtitles-offset">
</div>
<div *ngIf="captionsContainer.offsetWidth >= 600 && !settingsPanelOpened" class="captions-offset"></div>
</div>

View File

@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SubtitlesComponent } from './subtitles.component';
import { CaptionsComponent } from './captions.component';
describe('SubtitlesComponent', () => {
let component: SubtitlesComponent;
let fixture: ComponentFixture<SubtitlesComponent>;
describe('CaptionsComponent', () => {
let component: CaptionsComponent;
let fixture: ComponentFixture<CaptionsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SubtitlesComponent ]
declarations: [ CaptionsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SubtitlesComponent);
fixture = TestBed.createComponent(CaptionsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@ -5,8 +5,8 @@
.container {
height: 100%;
}
.withSubtitles {
height: calc(100% - var(--ov-captions-height, 200px)) !important;
.withCaptions {
height: calc(100% - var(--ov-captions-height, 250px)) !important;
}
.layout {

View File

@ -1,4 +1,4 @@
<div class="container" [class.withSubtitles]="subtitlesEnabled">
<div class="container" [class.withCaptions]="captionsEnabled">
<div id="layout" class="layout" #layout>
<div *ngFor="let stream of localParticipant | streams" [ngClass]="{ OV_big: stream.videoEnlarged }" class="OT_root OT_publisher">
<ng-container *ngTemplateOutlet="streamTemplate; context: { $implicit: stream }"></ng-container>
@ -14,5 +14,5 @@
</div>
</div>
<ov-captions *ngIf="subtitlesEnabled" class="OV_ignored"></ov-captions>
<ov-captions *ngIf="captionsEnabled" class="OV_ignored"></ov-captions>
</div>

View File

@ -11,10 +11,10 @@ import {
ViewContainerRef
} from '@angular/core';
import { Subscription } from 'rxjs';
import { ParticipantService } from '../../services/participant/participant.service';
import { StreamDirective } from '../../directives/template/openvidu-angular.directive';
import { ParticipantAbstractModel } from '../../models/participant.model';
import { LayoutService } from '../../services/layout/layout.service';
import { StreamDirective } from '../../directives/template/openvidu-angular.directive';
import { ParticipantService } from '../../services/participant/participant.service';
/**
*
@ -82,11 +82,11 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
/**
* @ignore
*/
subtitlesEnabled = true;
captionsEnabled = true;
private localParticipantSubs: Subscription;
private remoteParticipantsSubs: Subscription;
private subtitlesSubs: Subscription;
private captionsSubs: Subscription;
/**
* @ignore
@ -95,7 +95,7 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
ngOnInit(): void {
this.subscribeToParticipants();
this.subscribeToSubtitles();
this.subscribeToCaptions();
}
ngAfterViewInit() {
@ -107,13 +107,13 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
this.remoteParticipants = [];
if (this.localParticipantSubs) this.localParticipantSubs.unsubscribe();
if (this.remoteParticipantsSubs) this.remoteParticipantsSubs.unsubscribe();
if (this.subtitlesSubs) this.subtitlesSubs.unsubscribe();
if (this.captionsSubs) this.captionsSubs.unsubscribe();
this.layoutService.clear();
}
private subscribeToSubtitles() {
this.subtitlesSubs = this.layoutService.subtitlesTogglingObs.subscribe((value: boolean) => {
this.subtitlesEnabled = value;
private subscribeToCaptions() {
this.captionsSubs = this.layoutService.captionsTogglingObs.subscribe((value: boolean) => {
this.captionsEnabled = value;
this.cd.markForCheck();
this.layoutService.update();
});

View File

@ -22,14 +22,14 @@
<div mat-line>{{ 'PANEL.SETTINGS.AUDIO' | translate }}</div>
</mat-list-option>
<mat-list-option
*ngIf="showSubtitles && false"
*ngIf="showCaptions"
class="option"
[selected]="selectedOption === settingsOptions.SUBTITLES"
[value]="settingsOptions.SUBTITLES"
id="subtitles-opt"
[selected]="selectedOption === settingsOptions.CAPTIONS"
[value]="settingsOptions.CAPTIONS"
id="captions-opt"
>
<mat-icon mat-list-icon>closed_caption</mat-icon>
<div mat-line>{{ 'PANEL.SETTINGS.SUBTITLE' | translate }}</div>
<div mat-line>{{ 'PANEL.SETTINGS.CAPTIONS' | translate }}</div>
</mat-list-option>
</mat-selection-list>
</div>
@ -47,7 +47,7 @@
</div>
<ov-video-devices-select *ngIf="selectedOption === settingsOptions.VIDEO"></ov-video-devices-select>
<ov-audio-devices-select *ngIf="selectedOption === settingsOptions.AUDIO"></ov-audio-devices-select>
<ov-subtitles-settings *ngIf="selectedOption === settingsOptions.SUBTITLES && showSubtitles && false"></ov-subtitles-settings>
<ov-captions-settings *ngIf="selectedOption === settingsOptions.CAPTIONS && showCaptions"></ov-captions-settings>
</div>
</div>
</div>

View File

@ -15,8 +15,8 @@ import { PanelEvent, PanelService } from '../../../services/panel/panel.service'
export class SettingsPanelComponent implements OnInit {
settingsOptions: typeof PanelSettingsOptions = PanelSettingsOptions;
selectedOption: PanelSettingsOptions = PanelSettingsOptions.GENERAL;
showSubtitles: boolean = true;
private subtitlesSubs: Subscription;
showCaptions: boolean = true;
private captionsSubs: Subscription;
panelSubscription: Subscription;
constructor(private panelService: PanelService, private libService: OpenViduAngularConfigService) {}
ngOnInit() {
@ -25,7 +25,7 @@ export class SettingsPanelComponent implements OnInit {
}
ngOnDestroy() {
if (this.subtitlesSubs) this.subtitlesSubs.unsubscribe();
if (this.captionsSubs) this.captionsSubs.unsubscribe();
}
close() {
@ -36,8 +36,8 @@ export class SettingsPanelComponent implements OnInit {
}
private subscribeToDirectives() {
this.subtitlesSubs = this.libService.subtitlesButtonObs.subscribe((value: boolean) => {
this.showSubtitles = value;
this.captionsSubs = this.libService.captionsButtonObs.subscribe((value: boolean) => {
this.showCaptions = value;
});
}

View File

@ -1,5 +1,7 @@
import {
ChangeDetectionStrategy, ChangeDetectorRef, Component,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild,
ElementRef,
EventEmitter,
@ -12,8 +14,12 @@ import {
} from '@angular/core';
import {
ConnectionEvent,
RecordingEvent, Session, SessionDisconnectedEvent, StreamEvent,
StreamPropertyChangedEvent, Subscriber
RecordingEvent,
Session,
SessionDisconnectedEvent,
StreamEvent,
StreamPropertyChangedEvent,
Subscriber
} from 'openvidu-browser';
import { ILogger } from '../../models/logger.model';
@ -26,6 +32,7 @@ import { SidenavMode } from '../../models/layout.model';
import { PanelType } from '../../models/panel.model';
import { Signal } from '../../models/signal.model';
import { ActionService } from '../../services/action/action.service';
import { CaptionService } from '../../services/caption/caption.service';
import { ChatService } from '../../services/chat/chat.service';
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
import { LayoutService } from '../../services/layout/layout.service';
@ -46,11 +53,7 @@ import { TranslateService } from '../../services/translate/translate.service';
selector: 'ov-session',
templateUrl: './session.component.html',
styleUrls: ['./session.component.css'],
animations: [
trigger('sessionAnimation', [
transition(':enter', [style({ opacity: 0 }), animate('400ms', style({ opacity: 1 }))]),
])
],
animations: [trigger('sessionAnimation', [transition(':enter', [style({ opacity: 0 }), animate('400ms', style({ opacity: 1 }))])])],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SessionComponent implements OnInit {
@ -77,6 +80,7 @@ export class SessionComponent implements OnInit {
protected layoutWidthSubscription: Subscription;
protected updateLayoutInterval: NodeJS.Timer;
private captionLanguageSubscription: Subscription;
protected log: ILogger;
@ -92,6 +96,7 @@ export class SessionComponent implements OnInit {
protected panelService: PanelService,
private recordingService: RecordingService,
private translateService: TranslateService,
private captionService: CaptionService,
private platformService: PlatformService,
private cd: ChangeDetectorRef
) {
@ -153,6 +158,7 @@ export class SessionComponent implements OnInit {
}
this.session = this.openviduService.getWebcamSession();
this.sessionScreen = this.openviduService.getScreenSession();
this.subscribeToCaptionLanguage();
this.subscribeToConnectionCreatedAndDestroyed();
this.subscribeToStreamCreated();
this.subscribeToStreamDestroyed();
@ -284,7 +290,7 @@ export class SessionComponent implements OnInit {
}
private subscribeToStreamCreated() {
this.session.on('streamCreated', (event: StreamEvent) => {
this.session.on('streamCreated', async (event: StreamEvent) => {
const connectionId = event.stream?.connection?.connectionId;
const data = event.stream?.connection?.data;
@ -293,14 +299,39 @@ export class SessionComponent implements OnInit {
const subscriber: Subscriber = this.session.subscribe(event.stream, undefined);
this.participantService.addRemoteConnection(connectionId, data, subscriber);
// this.oVSessionService.sendNicknameSignal(event.stream.connection);
try {
await this.session.subscribeToSpeechToText(event.stream, this.captionService.getLangSelected().ISO);
} catch (error) {
this.log.e('Error subscribing from STT: ', error);
}
}
});
}
private subscribeToStreamDestroyed() {
this.session.on('streamDestroyed', (event: StreamEvent) => {
this.session.on('streamDestroyed', async (event: StreamEvent) => {
const connectionId = event.stream.connection.connectionId;
this.participantService.removeConnectionByConnectionId(connectionId);
try {
await this.session.unsubscribeFromSpeechToText(event.stream);
} catch (error) {
this.log.e('Error unsubscribing from STT: ', error);
}
});
}
private subscribeToCaptionLanguage() {
this.captionLanguageSubscription = this.captionService.captionLangObs.subscribe(async (lang) => {
// 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);
for (const participant of this.participantService.getRemoteParticipants()) {
try {
await this.session.unsubscribeFromSpeechToText(participant.getCameraConnection().streamManager.stream);
await this.session.subscribeToSpeechToText(participant.getCameraConnection().streamManager.stream, lang.ISO);
} catch (error) {
this.log.e('Error re-subscribing to STT: ', error);
}
}
});
}

View File

@ -1,21 +1,18 @@
<mat-list>
<mat-list-item>
<div mat-line>{{ 'PANEL.SETTINGS.SUBTITLE' | translate }}</div>
<mat-slide-toggle (change)="toggleSubtitles()" [checked]="subtitlesEnabled" [disableRipple]="true"></mat-slide-toggle>
<div mat-line>{{ 'PANEL.SETTINGS.CAPTIONS' | translate }}</div>
<mat-slide-toggle (change)="toggleCaptions()" [checked]="captionsEnabled" [disableRipple]="true"></mat-slide-toggle>
</mat-list-item>
<mat-list-item>
<div mat-line>{{ 'PANEL.SETTINGS.LANGUAGE' | translate }}:</div>
<button mat-flat-button [matMenuTriggerFor]="menu" class="lang-button" [disabled]="true">
<span>{{langSelected}}</span>
<button mat-flat-button [matMenuTriggerFor]="menu" class="lang-button">
<span>{{ langSelected }}</span>
<mat-icon>expand_more</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let lang of languagesAvailable" (click)="onLangSelected(lang)">
<span>{{lang}}</span>
<span>{{ lang.name }}</span>
</button>
</mat-menu>
</mat-list-item>
</mat-list>

View File

@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CaptionsSettingComponent } from './captions.component';
describe('CaptionsSettingComponent', () => {
let component: CaptionsSettingComponent;
let fixture: ComponentFixture<CaptionsSettingComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CaptionsSettingComponent]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(CaptionsSettingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,47 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { CaptionService } from '../../../services/caption/caption.service';
import { LayoutService } from '../../../services/layout/layout.service';
/**
* @internal
*/
@Component({
selector: 'ov-captions-settings',
templateUrl: './captions.component.html',
styleUrls: ['./captions.component.css']
})
export class CaptionsSettingComponent implements OnInit, OnDestroy {
captionsEnabled: boolean;
languagesAvailable: { name: string; ISO: string }[] = [];
captionsSubscription: Subscription;
langSelected: string;
constructor(private layoutService: LayoutService, private captionService: CaptionService) {}
ngOnInit(): void {
this.subscribeToCaptions();
this.langSelected = this.captionService.getLangSelected().name;
this.languagesAvailable = this.captionService.getCaptionLanguages();
}
ngOnDestroy() {
if (this.captionsSubscription) this.captionsSubscription.unsubscribe();
}
onLangSelected(lang: { name: string; ISO: string }) {
this.langSelected = lang.name;
this.captionService.setLanguage(lang.ISO);
}
toggleCaptions() {
this.layoutService.toggleCaptions();
}
private subscribeToCaptions() {
this.captionsSubscription = this.layoutService.captionsTogglingObs.subscribe((value: boolean) => {
this.captionsEnabled = value;
// this.cd.markForCheck();
});
}
}

View File

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SubtitlesComponent } from './subtitles.component';
describe('SubtitlesComponent', () => {
let component: SubtitlesComponent;
let fixture: ComponentFixture<SubtitlesComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SubtitlesComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SubtitlesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,44 +0,0 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { LayoutService } from '../../../services/layout/layout.service';
/**
* @internal
*/
@Component({
selector: 'ov-subtitles-settings',
templateUrl: './subtitles.component.html',
styleUrls: ['./subtitles.component.css']
})
export class SubtitlesSettingComponent implements OnInit, OnDestroy {
subtitlesEnabled: boolean;
languagesAvailable = [];
subtitlesSubs: Subscription;
langSelected: string = 'English';
constructor(private layoutService: LayoutService) {}
ngOnInit(): void {
this.subscribeToSubtitles();
}
ngOnDestroy() {
if (this.subtitlesSubs) this.subtitlesSubs.unsubscribe();
}
onLangSelected(lang: string) {
this.langSelected = lang;
}
toggleSubtitles() {
this.layoutService.toggleSubtitles();
}
private subscribeToSubtitles() {
this.subtitlesSubs = this.layoutService.subtitlesTogglingObs.subscribe((value: boolean) => {
this.subtitlesEnabled = value;
// this.cd.markForCheck();
});
}
}

View File

@ -103,17 +103,17 @@
<span>{{ 'TOOLBAR.BACKGROUND' | translate }}</span>
</button>
<!-- Subtitles button -->
<!-- Captions button -->
<button
*ngIf="!isMinimal && showSubtitlesButton && false"
*ngIf="!isMinimal && showCaptionsButton"
[disabled]="isConnectionLost"
mat-menu-item
id="subtitles-btn"
(click)="toggleSubtitles()"
id="captions-btn"
(click)="toggleCaptions()"
>
<mat-icon>closed_caption</mat-icon>
<span *ngIf="subtitlesEnabled">{{ 'TOOLBAR.DISABLE_SUBTITLES' | translate }}</span>
<span *ngIf="!subtitlesEnabled">{{ 'TOOLBAR.ENABLE_SUBTITLES' | translate }}</span>
<span *ngIf="captionsEnabled">{{ 'TOOLBAR.DISABLE_CAPTIONS' | translate }}</span>
<span *ngIf="!captionsEnabled">{{ 'TOOLBAR.ENABLE_CAPTIONS' | translate }}</span>
</button>
<mat-divider class="divider" *ngIf="!isMinimal && showSettingsButton"></mat-divider>

View File

@ -308,12 +308,12 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
/**
* @ignore
*/
showSubtitlesButton: boolean = true;
showCaptionsButton: boolean = true;
/**
* @ignore
*/
subtitlesEnabled: boolean;
captionsEnabled: boolean;
/**
* @ignore
@ -362,7 +362,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private displaySessionNameSub: Subscription;
private screenSizeSub: Subscription;
private settingsButtonSub: Subscription;
private subtitlesSubs: Subscription;
private captionsSubs: Subscription;
private currentWindowHeight = window.innerHeight;
/**
@ -425,7 +425,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.subscribeToChatMessages();
this.subscribeToRecordingStatus();
this.subscribeToScreenSize();
this.subscribeToSubtitlesToggling();
this.subscribeToCaptionsToggling();
}
ngAfterViewInit() {
@ -453,7 +453,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
if (this.recordingSubscription) this.recordingSubscription.unsubscribe();
if (this.screenSizeSub) this.screenSizeSub.unsubscribe();
if (this.settingsButtonSub) this.settingsButtonSub.unsubscribe();
if (this.subtitlesSubs) this.subtitlesSubs.unsubscribe();
if (this.captionsSubs) this.captionsSubs.unsubscribe();
}
/**
@ -547,8 +547,8 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
/**
* @ignore
*/
toggleSubtitles() {
this.layoutService.toggleSubtitles();
toggleCaptions() {
this.layoutService.toggleCaptions();
}
/**
@ -697,8 +697,8 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.showSessionName = value;
this.cd.markForCheck();
});
this.subtitlesSubs = this.libService.subtitlesButtonObs.subscribe((value: boolean) => {
this.showSubtitlesButton = value;
this.captionsSubs = this.libService.captionsButtonObs.subscribe((value: boolean) => {
this.showCaptionsButton = value;
this.cd.markForCheck();
});
}
@ -710,9 +710,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
});
}
private subscribeToSubtitlesToggling() {
this.subtitlesSubs = this.layoutService.subtitlesTogglingObs.subscribe((value: boolean) => {
this.subtitlesEnabled = value;
private subscribeToCaptionsToggling() {
this.captionsSubs = this.layoutService.captionsTogglingObs.subscribe((value: boolean) => {
this.captionsEnabled = value;
this.cd.markForCheck();
});
}

View File

@ -27,5 +27,5 @@
/* Private css variables */
#call-container {
--ov-captions-height: 250px;
--ov-captions-height: 230px;
}

View File

@ -56,12 +56,14 @@ import { TranslateService } from '../../services/translate/translate.service';
* | :----------------------------: | :-------: | :---------------------------------------------: |
* | **minimal** | `boolean` | {@link MinimalDirective} |
* | **lang** | `string` | {@link LangDirective} |
* | **captionsLang** | `string` | {@link CaptionsLangDirective} |
* | **prejoin** | `boolean` | {@link PrejoinDirective} |
* | **participantName** | `string` | {@link ParticipantNameDirective} |
* | **videoMuted** | `boolean` | {@link VideoMutedDirective} |
* | **audioMuted** | `boolean` | {@link AudioMutedDirective} |
* | **toolbarScreenshareButton** | `boolean` | {@link ToolbarScreenshareButtonDirective} |
* | **toolbarFullscreenButton** | `boolean` | {@link ToolbarFullscreenButtonDirective} |
* | **toolbarCaptionsButton** | `boolean` | {@link ToolbarCaptionsButtonDirective} |
* | **toolbarBackgroundEffectsButton** | `boolean` | {@link ToolbarBackgroundEffectsButtonDirective} |
* | **toolbarLeaveButton** | `boolean` | {@link ToolbarLeaveButtonDirective} |
* | **toolbarChatPanelButton** | `boolean` | {@link ToolbarChatPanelButtonDirective} |
@ -457,8 +459,8 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
const publisher = await this.openviduService.initDefaultPublisher();
if (publisher) {
publisher.once('accessDenied', (e: any) => {
this.handlePublisherError(e);
publisher.once('accessDenied', async (e: any) => {
await this.handlePublisherError(e);
resolve();
});
publisher.once('accessAllowed', async () => {
@ -662,20 +664,20 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
this.onSessionCreated.emit(session);
}
private handlePublisherError(e: any): Promise<void> {
private async handlePublisherError(e: any): Promise<void> {
let message: string = '';
console.log('ERROR!', e);
if (e.name === OpenViduErrorName.DEVICE_ALREADY_IN_USE) {
this.log.w('Video device already in use. Disabling video device...');
// Allow access to the room with only mic if camera device is already in use
this.deviceSrv.disableVideoDevices();
return this.initwebcamPublisher();
return await this.initwebcamPublisher();
}
if (e.name === OpenViduErrorName.DEVICE_ACCESS_DENIED) {
message = this.translateService.translate('ERRORS.MEDIA_ACCESS');
this.deviceSrv.disableVideoDevices();
this.deviceSrv.disableAudioDevices();
return this.initwebcamPublisher();
return await this.initwebcamPublisher();
} else if (e.name === OpenViduErrorName.NO_INPUT_SOURCE_SET) {
message = this.translateService.translate('ERRORS.DEVICE_NOT_FOUND');
}

View File

@ -5,37 +5,39 @@ import { LogoDirective } from './internals.directive';
import { ParticipantPanelItemMuteButtonDirective } from './participant-panel-item.directive';
import { RecordingActivityRecordingErrorDirective, RecordingActivityRecordingsListDirective } from './recording-activity.directive';
import {
StreamDisplayParticipantNameDirective,
StreamDisplayAudioDetectionDirective,
StreamDisplayParticipantNameDirective,
StreamSettingsButtonDirective
} from './stream.directive';
import {
ToolbarScreenshareButtonDirective,
ToolbarActivitiesPanelButtonDirective,
ToolbarBackgroundEffectsButtonDirective,
ToolbarCaptionsButtonDirective,
ToolbarChatPanelButtonDirective,
ToolbarDisplayLogoDirective,
ToolbarDisplaySessionNameDirective,
ToolbarFullscreenButtonDirective,
ToolbarLeaveButtonDirective,
ToolbarParticipantsPanelButtonDirective,
ToolbarChatPanelButtonDirective,
ToolbarDisplaySessionNameDirective,
ToolbarDisplayLogoDirective,
ToolbarActivitiesPanelButtonDirective,
ToolbarBackgroundEffectsButtonDirective,
ToolbarRecordingButtonDirective,
ToolbarSettingsButtonDirective,
ToolbarCaptionsButtonDirective
ToolbarScreenshareButtonDirective,
ToolbarSettingsButtonDirective
} from './toolbar.directive';
import {
AudioMutedDirective,
CaptionsLangDirective,
LangDirective,
MinimalDirective,
PrejoinDirective,
VideoMutedDirective,
ParticipantNameDirective,
LangDirective
PrejoinDirective,
VideoMutedDirective
} from './videoconference.directive';
@NgModule({
declarations: [
MinimalDirective,
LangDirective,
CaptionsLangDirective,
PrejoinDirective,
VideoMutedDirective,
AudioMutedDirective,
@ -66,6 +68,7 @@ import {
exports: [
MinimalDirective,
LangDirective,
CaptionsLangDirective,
PrejoinDirective,
VideoMutedDirective,
AudioMutedDirective,

View File

@ -6,7 +6,7 @@ import { Directive, ElementRef, HostListener, Input } from '@angular/core';
* Load default OpenVidu logo if custom one is not exist
* @internal
*/
@Directive({
@Directive({
selector: 'img[ovLogo]'
})
export class LogoDirective {

View File

@ -1,4 +1,4 @@
import { AfterViewInit, Directive, ElementRef, HostListener, Input, OnDestroy } from '@angular/core';
import { AfterViewInit, Directive, ElementRef, Input, OnDestroy } from '@angular/core';
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
/**
@ -256,9 +256,6 @@ export class ToolbarBackgroundEffectsButtonDirective implements AfterViewInit, O
* And it also can be used in the {@link ToolbarComponent}.
* @example
* <ov-toolbar [captionsButton]="false"></ov-toolbar>
*
* TODO: Make it public when speech to text is integrated
* @internal
*/
@Directive({
selector: 'ov-videoconference[toolbarCaptionsButton], ov-toolbar[captionsButton]'
@ -298,8 +295,8 @@ export class ToolbarCaptionsButtonDirective implements AfterViewInit, OnDestroy
}
private update(value: boolean) {
if (this.libService.subtitlesButton.getValue() !== value) {
this.libService.subtitlesButton.next(value);
if (this.libService.captionsButton.getValue() !== value) {
this.libService.captionsButton.next(value);
}
}
}

View File

@ -1,7 +1,9 @@
import { Directive, Input, ElementRef, OnDestroy, OnInit } from '@angular/core';
import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core';
import { CaptionService } from '../../services/caption/caption.service';
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
import { TranslateService } from '../../services/translate/translate.service';
/**
* The **minimal** directive applies a minimal UI hiding all controls except for cam and mic.
*
@ -53,7 +55,7 @@ export class MinimalDirective implements OnDestroy {
}
/**
* The **lang** directive allows et the UI language to a default language.
* The **lang** directive allows set the UI language to a default language.
*
* It is only available for {@link VideoconferenceComponent}.
*
@ -113,6 +115,70 @@ export class LangDirective implements OnDestroy {
}
}
/**
* The **captions-lang** directive allows specify the language of room's members
*
* It is only available for {@link VideoconferenceComponent}.
*
* It must be a valid [BCP-47](https://tools.ietf.org/html/bcp47) language tag like "en-US" or "es-ES".
*
*
* **Default:** English `en-US`
*
* **Available:**
*
* * English: `en-US`
* * Spanish: `es-ES`
* * German: `de-DE`
* * French: `fr-FR`
* * Chinese: `zh-CN`
* * Hindi: `hi-IN`
* * Italian: `it-IT`
* * Japanese: `jp-JP`
* * Portuguese: `pt-PT`
*
* @example
* <ov-videoconference [captionsLang]="es-ES"></ov-videoconference>
*/
@Directive({
selector: 'ov-videoconference[captionsLang]'
})
export class CaptionsLangDirective implements OnDestroy {
/**
* @ignore
*/
@Input() set captionsLang(value: string) {
this.update(value);
}
/**
* @ignore
*/
constructor(public elementRef: ElementRef, private captionService: CaptionService) {}
/**
* @ignore
*/
ngOnDestroy(): void {
this.clear();
}
/**
* @ignore
*/
clear() {
this.update('en-US');
}
/**
* @ignore
*/
update(value: string) {
this.captionService.setLanguage(value);
}
}
/**
* The **participantName** directive sets the participant name. It can be useful for aplications which doesn't need the prejoin page.
*

View File

@ -34,8 +34,8 @@
"MORE_OPTIONS":"更多选项",
"FULLSCREEN":"全屏",
"EXIT_FULLSCREEN": "退出全屏",
"ENABLE_SUBTITLES": "启用字幕",
"DISABLE_SUBTITLES": "禁用字幕",
"ENABLE_CAPTIONS": "启用字幕",
"DISABLE_CAPTIONS": "禁用字幕",
"BACKGROUND":"背景效果",
"START_RECORDING": "开始录音",
"STOP_RECORDING": "停止录制",
@ -73,7 +73,7 @@
"VIDEO": "视频",
"AUDIO": "声音的",
"LANGUAGE": "语",
"SUBTITLE": "字幕"
"CAPTIONS": "字幕"
},
"BACKGROUND": {
"TITLE": "背景效果",

View File

@ -34,8 +34,8 @@
"MORE_OPTIONS": "Weitere Optionen",
"FULLSCREEN": "Vollbild",
"EXIT_FULLSCREEN": "Vollbildmodus beenden",
"ENABLE_SUBTITLES": "Untertitel aktivieren",
"DISABLE_SUBTITLES": "Untertitel deaktivieren",
"ENABLE_CAPTIONS": "Untertitel aktivieren",
"DISABLE_CAPTIONS": "Untertitel deaktivieren",
"BACKGROUND": "Hintergrund-Effekte",
"START_RECORDING": "Aufzeichnung starten",
"STOP_RECORDING": "Aufzeichnung stoppen",
@ -73,7 +73,7 @@
"VIDEO": "Video",
"AUDIO": "Audio",
"LANGUAGE": "Sprache",
"SUBTITLE": "Untertitel"
"CAPTIONS": "Untertitel"
},
"BACKGROUND": {
"TITLE": "Hintergrund-Effekte",

View File

@ -35,8 +35,8 @@
"MORE_OPTIONS": "More options",
"FULLSCREEN": "Fullscreen",
"EXIT_FULLSCREEN": "Exit fullscreen",
"ENABLE_SUBTITLES": "Enable subtitles",
"DISABLE_SUBTITLES": "Disable subtitles",
"ENABLE_CAPTIONS": "Enable captions",
"DISABLE_CAPTIONS": "Disable captions",
"BACKGROUND": "Background effects",
"START_RECORDING": "Start recording",
"STOP_RECORDING": "Stop recording",
@ -74,7 +74,7 @@
"VIDEO": "Video",
"AUDIO": "Audio",
"LANGUAGE": "Language",
"SUBTITLE": "Subtitles"
"CAPTIONS": "Captions"
},
"BACKGROUND": {
"TITLE": "Background effects",

View File

@ -34,8 +34,8 @@
"MORE_OPTIONS": "Más opciones",
"EXIT_FULLSCREEN": "Quitar pantalla completa",
"FULLSCREEN": "Pantalla completa",
"ENABLE_SUBTITLES": "Activar subtítulos",
"DISABLE_SUBTITLES": "Desactivar subtítulos",
"ENABLE_CAPTIONS": "Activar subtítulos",
"DISABLE_CAPTIONS": "Desactivar subtítulos",
"BACKGROUND": "Efectos de fondo",
"START_RECORDING": "Iniciar grabación",
"STOP_RECORDING": "Detener grabación",
@ -73,7 +73,7 @@
"VIDEO": "Video",
"AUDIO": "Audio",
"LANGUAGE": "Idioma",
"SUBTITLE": "Subtítulos"
"CAPTIONS": "Subtítulos"
},
"BACKGROUND": {
"TITLE": "Efectos de fondo",

View File

@ -34,8 +34,8 @@
"MORE_OPTIONS": "Plus d'options",
"FULLSCREEN": "Plein écran",
"EXIT_FULLSCREEN": "Quitter le plein écran",
"ENABLE_SUBTITLES": "Activer les sous-titres",
"DISABLE_SUBTITLES": "Désactiver les sous-titres",
"ENABLE_CAPTIONS": "Activer les sous-titres",
"DISABLE_CAPTIONS": "Désactiver les sous-titres",
"BACKGROUND": "Effets de fond",
"START_RECORDING": "démarrer l'enregistrement",
"STOP_RECORDING": "Arrêter l'enregistrement",
@ -73,7 +73,7 @@
"VIDEO": "Vidéo",
"AUDIO": "l'audio",
"LANGUAGE": "Langue",
"SUBTITLE": "Les sous-titres"
"CAPTIONS": "Les sous-titres"
},
"BACKGROUND": {
"TITLE": "Effets de fond",

View File

@ -34,8 +34,8 @@
"MORE_OPTIONS": "अधिक विकल्प",
"FULLSCREEN": "पूर्ण स्क्रीन",
"EXIT_FULLSCREEN": "पूर्ण स्क्रीन से बाहर निकलें",
"ENABLE_SUBTITLES": "उपशीर्षक सक्षम करें",
"DISABLE_SUBTITLES": "उपशीर्षक अक्षम करें",
"ENABLE_CAPTIONS": "उपशीर्षक सक्षम करें",
"DISABLE_CAPTIONS": "उपशीर्षक अक्षम करें",
"BACKGROUND": "पृष्ठभूमि प्रभाव",
"START_RECORDING": "रिकॉर्डिंग प्रारंभ करें",
"STOP_RECORDING": "रिकॉर्डिंग रोकें",
@ -73,7 +73,7 @@
"VIDEO": "वीडियो",
"AUDIO": "ऑडियो",
"LANGUAGE": "भाषा",
"SUBTITLE": "उपशीर्षक"
"CAPTIONS": "उपशीर्षक"
},
"BACKGROUND": {
"TITLE": "पृष्ठभूमि प्रभाव",

View File

@ -34,8 +34,8 @@
"MORE_OPTIONS": "Altre opzioni",
"FULLSCREEN": "Schermo intero",
"EXIT_FULLSCREEN": "Esci dallo schermo intero",
"ENABLE_SUBTITLES": "Abilita i sottotitoli",
"DISABLE_SUBTITLES": "Disabilita i sottotitoli",
"ENABLE_CAPTIONS": "Abilita i sottotitoli",
"DISABLE_CAPTIONS": "Disabilita i sottotitoli",
"BACKGROUND": "Effetti di sfondo",
"START_RECORDING": "Avvia registrazione",
"STOP_RECORDING": "Interrompi registrazione",
@ -73,7 +73,7 @@
"VIDEO": "video",
"AUDIO": "Audio",
"LANGUAGE":"Lingua",
"SUBTITLE": "Sottotitoli"
"CAPTIONS": "Sottotitoli"
},
"BACKGROUND": {
"TITLE": "Effetti di sfondo",

View File

@ -34,8 +34,8 @@
"MORE_OPTIONS": "その他のオプション",
"FULLSCREEN": "フルスクリーン",
"EXIT_FULLSCREEN": "フルスクリーンを終了する",
"ENABLE_SUBTITLES": "字幕を有効にする",
"DISABLE_SUBTITLES": "字幕を無効にする",
"ENABLE_CAPTIONS": "字幕を有効にする",
"DISABLE_CAPTIONS": "字幕を無効にする",
"BACKGROUND": "背景効果",
"START_RECORDING": "録画開始",
"STOP_RECORDING": "録画の停止",
@ -73,7 +73,7 @@
"VIDEO": "ビデオ",
"AUDIO": "オーディオ",
"LANGUAGE":"言語",
"SUBTITLE": "字幕"
"CAPTIONS": "字幕"
},
"BACKGROUND": {
"TITLE": "背景効果",

View File

@ -34,8 +34,8 @@
"MORE_OPTIONS": "Meer opties",
"FULLSCREEN": "Volledig scherm",
"EXIT_FULLSCREEN": "Volledig scherm verlaten",
"ENABLE_SUBTITLES": "Ondertiteling inschakelen",
"DISABLE_SUBTITLES": "Ondertiteling uitschakelen",
"ENABLE_CAPTIONS": "Ondertiteling inschakelen",
"DISABLE_CAPTIONS": "Ondertiteling uitschakelen",
"BACKGROUND": "Achtergrondeffecten",
"START_RECORDING": "Start opname",
"STOP_RECORDING": "Stop opname",
@ -73,7 +73,7 @@
"VIDEO": "Video",
"AUDIO": "Audio",
"LANGUAGE":"Taal",
"SUBTITLE": "Ondertitels"
"CAPTIONS": "Ondertitels"
},
"BACKGROUND": {
"TITLE": "Achtergrondeffecten",

View File

@ -34,8 +34,8 @@
"MORE_OPTIONS": "Mais opções",
"FULLSCREEN": "Tela cheia",
"EXIT_FULLSCREEN": "Sair da tela cheia",
"ENABLE_SUBTITLES": "Ativar legendas",
"DISABLE_SUBTITLES": "Desativar legendas",
"ENABLE_CAPTIONS": "Ativar legendas",
"DISABLE_CAPTIONS": "Desativar legendas",
"BACKGROUND": "Efeitos de fundo",
"START_RECORDING": "Iniciar_gravação",
"STOP_RECORDING": "Parar de gravar",
@ -73,7 +73,7 @@
"VIDEO": "Vídeo",
"AUDIO": "Áudio",
"LANGUAGE":"Linguagem",
"SUBTITLE": "Legendas"
"CAPTIONS": "Legendas"
},
"BACKGROUND": {
"TITLE": "Efeitos de fundo",

View File

@ -0,0 +1,10 @@
export interface CaptionModel {
connectionId: string;
nickname: string;
color: string;
type: 'recognizing' | 'recognized';
text: string;
}

View File

@ -11,5 +11,5 @@ export enum PanelSettingsOptions {
GENERAL = 'general',
AUDIO = 'audio',
VIDEO = 'video',
SUBTITLES = 'subtitles'
CAPTIONS = 'captions'
}

View File

@ -7,5 +7,6 @@ export enum Storage {
AUDIO_DEVICE = 'openviduCallAudioDevice',
AUDIO_MUTED = 'openviduCallAudioMuted',
VIDEO_MUTED = 'openviduCallVideoMuted',
LANG = "openviduCallLang"
LANG = 'openviduCallLang',
CAPTION_LANG = 'openviduCallCaptionLang'
}

View File

@ -1,66 +1,66 @@
import { OverlayContainer } from '@angular/cdk/overlay';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { OverlayContainer } from '@angular/cdk/overlay';
import { ToolbarComponent } from './components/toolbar/toolbar.component';
import { VideoComponent } from './components/video/video.component';
import { ChatPanelComponent } from './components/panel/chat-panel/chat-panel.component';
import { SessionComponent } from './components/session/session.component';
import { LayoutComponent } from './components/layout/layout.component';
import { StreamComponent } from './components/stream/stream.component';
import { DeleteDialogComponent } from './components/dialogs/delete-recording.component';
import { DialogTemplateComponent } from './components/dialogs/dialog.component';
import { RecordingDialogComponent } from './components/dialogs/recording-dialog.component';
import { DeleteDialogComponent } from './components/dialogs/delete-recording.component';
import { LayoutComponent } from './components/layout/layout.component';
import { ChatPanelComponent } from './components/panel/chat-panel/chat-panel.component';
import { SessionComponent } from './components/session/session.component';
import { StreamComponent } from './components/stream/stream.component';
import { ToolbarComponent } from './components/toolbar/toolbar.component';
import { VideoComponent } from './components/video/video.component';
import { LinkifyPipe } from './pipes/linkify.pipe';
import { TranslatePipe } from './pipes/translate.pipe';
import { StreamTypesEnabledPipe, ParticipantStreamsPipe } from './pipes/participant.pipe';
import { ParticipantStreamsPipe, StreamTypesEnabledPipe } from './pipes/participant.pipe';
import { DurationFromSecondsPipe, SearchByStringPropertyPipe, ThumbnailFromUrlPipe } from './pipes/recording.pipe';
import { TranslatePipe } from './pipes/translate.pipe';
import { OpenViduAngularConfig } from './config/openvidu-angular.config';
import { CdkOverlayContainer } from './config/custom-cdk-overlay';
import { DeviceService } from './services/device/device.service';
import { LoggerService } from './services/logger/logger.service';
import { PlatformService } from './services/platform/platform.service';
import { StorageService } from './services/storage/storage.service';
import { TokenService } from './services/token/token.service';
import { OpenViduAngularConfigService } from './services/config/openvidu-angular.config.service';
import { OpenViduService } from './services/openvidu/openvidu.service';
import { OpenViduAngularConfig } from './config/openvidu-angular.config';
import { ActionService } from './services/action/action.service';
import { ChatService } from './services/chat/chat.service';
import { OpenViduAngularConfigService } from './services/config/openvidu-angular.config.service';
import { DeviceService } from './services/device/device.service';
import { DocumentService } from './services/document/document.service';
import { LayoutService } from './services/layout/layout.service';
import { LoggerService } from './services/logger/logger.service';
import { OpenViduService } from './services/openvidu/openvidu.service';
import { PanelService } from './services/panel/panel.service';
import { ParticipantService } from './services/participant/participant.service';
import { PlatformService } from './services/platform/platform.service';
import { RecordingService } from './services/recording/recording.service';
import { StorageService } from './services/storage/storage.service';
import { TokenService } from './services/token/token.service';
import { AudioWaveComponent } from './components/audio-wave/audio-wave.component';
import { PanelComponent } from './components/panel/panel.component';
import { ParticipantPanelItemComponent } from './components/panel/participants-panel/participant-panel-item/participant-panel-item.component';
import { ParticipantsPanelComponent } from './components/panel/participants-panel/participants-panel/participants-panel.component';
import { VideoconferenceComponent } from './components/videoconference/videoconference.component';
import { PanelComponent } from './components/panel/panel.component';
import { AudioWaveComponent } from './components/audio-wave/audio-wave.component';
import { PreJoinComponent } from './components/pre-join/pre-join.component';
import { VideoconferenceComponent } from './components/videoconference/videoconference.component';
import { AvatarProfileComponent } from './components/avatar-profile/avatar-profile.component';
import { OpenViduAngularDirectiveModule } from './directives/template/openvidu-angular.directive.module';
import { ApiDirectiveModule } from './directives/api/api.directive.module';
import { BackgroundEffectsPanelComponent } from './components/panel/background-effects-panel/background-effects-panel.component';
import { SettingsPanelComponent } from './components/panel/settings-panel/settings-panel.component';
import { ActivitiesPanelComponent } from './components/panel/activities-panel/activities-panel.component';
import { RecordingActivityComponent } from './components/panel/activities-panel/recording-activity-panel/recording-activity.component';
import { AdminDashboardComponent } from './admin/dashboard/dashboard.component';
import { AdminLoginComponent } from './admin/login/login.component';
import { AppMaterialModule } from './openvidu-angular.material.module';
import { VideoDevicesComponent } from './components/settings/video-devices/video-devices.component';
import { AudioDevicesComponent } from './components/settings/audio-devices/audio-devices.component';
import { NicknameInputComponent } from './components/settings/nickname-input/nickname-input.component';
import { LangSelectorComponent } from './components/settings/lang-selector/lang-selector.component';
import { SubtitlesSettingComponent } from './components/settings/subtitles/subtitles.component';
import { AvatarProfileComponent } from './components/avatar-profile/avatar-profile.component';
import { CaptionsComponent } from './components/captions/captions.component';
import { ActivitiesPanelComponent } from './components/panel/activities-panel/activities-panel.component';
import { RecordingActivityComponent } from './components/panel/activities-panel/recording-activity-panel/recording-activity.component';
import { BackgroundEffectsPanelComponent } from './components/panel/background-effects-panel/background-effects-panel.component';
import { SettingsPanelComponent } from './components/panel/settings-panel/settings-panel.component';
import { AudioDevicesComponent } from './components/settings/audio-devices/audio-devices.component';
import { CaptionsSettingComponent } from './components/settings/captions/captions.component';
import { LangSelectorComponent } from './components/settings/lang-selector/lang-selector.component';
import { NicknameInputComponent } from './components/settings/nickname-input/nickname-input.component';
import { VideoDevicesComponent } from './components/settings/video-devices/video-devices.component';
import { CustomBreakPointsProvider, CustomLayoutExtensionDirective } from './config/custom-flexlayout-breakpoints';
import { ApiDirectiveModule } from './directives/api/api.directive.module';
import { OpenViduAngularDirectiveModule } from './directives/template/openvidu-angular.directive.module';
import { AppMaterialModule } from './openvidu-angular.material.module';
const publicComponents = [
AdminDashboardComponent,
@ -92,7 +92,7 @@ const privateComponents = [
NicknameInputComponent,
LangSelectorComponent,
RecordingActivityComponent,
SubtitlesSettingComponent
CaptionsSettingComponent
];
@NgModule({

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { CaptionService } from './caption.service';
describe('CaptionService', () => {
let service: CaptionService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CaptionService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { StorageService } from '../storage/storage.service';
/**
* @internal
*/
@Injectable({
providedIn: 'root'
})
export class CaptionService {
private langTitles = [
{ name: 'English', ISO: 'en-US' },
{ name: 'Español', ISO: 'es-ES' },
{ name: 'Deutsch', ISO: 'de-DE' },
{ name: 'Français', ISO: 'fr-FR' },
{ name: '中国', ISO: 'zh-CN' },
{ name: 'हिन्दी', ISO: 'hi-IN' },
{ name: 'Italiano', ISO: 'it-IT' },
{ name: 'やまと', ISO: 'jp-JP' },
{ name: 'Português', ISO: 'pt-PT' }
];
captionLangSelected: { name: string; ISO: string };
captionLangObs: Observable<{ name: string; ISO: string }>;
private _captionLangObs: Subject<{ name: string; ISO: string }> = new Subject();
constructor(private storageService: StorageService) {
this.captionLangObs = this._captionLangObs.asObservable();
}
setLanguage(lang: string) {
if (this.langTitles.some((l) => l.ISO === lang)) {
this.captionLangSelected = this.langTitles.find((l) => l.ISO === lang);
this._captionLangObs.next(this.captionLangSelected);
this.storageService.setCaptionLang(lang);
}
}
getLangSelected(): { name: string; ISO: string } {
return this.captionLangSelected || this.langTitles[0];
}
getCaptionLanguages(): { name: string; ISO: string }[] {
return this.langTitles;
}
}

View File

@ -28,8 +28,8 @@ export class OpenViduAngularConfigService {
fullscreenButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
fullscreenButtonObs: Observable<boolean>;
subtitlesButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
subtitlesButtonObs: Observable<boolean>;
captionsButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
captionsButtonObs: Observable<boolean>;
toolbarSettingsButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
toolbarSettingsButtonObs: Observable<boolean>;
@ -95,7 +95,7 @@ export class OpenViduAngularConfigService {
this.displayLogoObs = this.displayLogo.asObservable();
this.recordingButtonObs = this.recordingButton.asObservable();
this.toolbarSettingsButtonObs = this.toolbarSettingsButton.asObservable();
this.subtitlesButtonObs = this.subtitlesButton.asObservable();
this.captionsButtonObs = this.captionsButton.asObservable();
//Stream observables
this.displayParticipantNameObs = this.displayParticipantName.asObservable();
this.displayAudioDetectionObs = this.displayAudioDetection.asObservable();

View File

@ -13,17 +13,7 @@ export class DocumentService {
screenSizeObs: Observable<MediaChange[]>;
constructor(private media: MediaObserver) {
this.screenSizeObs= this.media.asObservable();
}
getHTMLElementByClassName(element: HTMLElement, className: string): HTMLElement {
while (!!element && element !== document.body) {
if (element.className.includes(className)) {
return element;
}
element = element.parentElement;
}
return null;
this.screenSizeObs = this.media.asObservable();
}
toggleFullscreen(elementId: string) {

View File

@ -1,7 +1,6 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { LayoutAlignment, LayoutClass, OpenViduLayout, OpenViduLayoutOptions } from '../../models/layout.model';
import { DocumentService } from '../document/document.service';
/**
* @internal
@ -10,26 +9,24 @@ import { DocumentService } from '../document/document.service';
providedIn: 'root'
})
export class LayoutService {
layoutContainer: HTMLElement | null = null;
layoutWidthObs: Observable<number>;
subtitlesTogglingObs: Observable<boolean>;
captionsTogglingObs: Observable<boolean>;
private layoutWidth: BehaviorSubject<number> = new BehaviorSubject(0);
private openviduLayout: OpenViduLayout;
private openviduLayoutOptions: OpenViduLayoutOptions;
private subtitlesToggling: BehaviorSubject<boolean> = new BehaviorSubject(false);
private captionsToggling: BehaviorSubject<boolean> = new BehaviorSubject(false);
constructor(private documentService: DocumentService) {
constructor() {
this.layoutWidthObs = this.layoutWidth.asObservable();
this.subtitlesTogglingObs = this.subtitlesToggling.asObservable();
this.captionsTogglingObs = this.captionsToggling.asObservable();
}
initialize(container: HTMLElement) {
this.layoutContainer = container;
this.openviduLayout = new OpenViduLayout();
this.openviduLayoutOptions = this.getOptions();
if(this.layoutContainer){
if (this.layoutContainer) {
this.openviduLayout.initLayoutContainer(this.layoutContainer, this.openviduLayoutOptions);
}
this.sendLayoutWidthEvent();
@ -66,8 +63,8 @@ export class LayoutService {
return options;
}
toggleSubtitles() {
this.subtitlesToggling.next(!this.subtitlesToggling.getValue());
toggleCaptions() {
this.captionsToggling.next(!this.captionsToggling.getValue());
}
update(timeout: number = null) {
@ -93,7 +90,7 @@ export class LayoutService {
}
private sendLayoutWidthEvent() {
const sidenavLayoutElement = this.documentService.getHTMLElementByClassName(
const sidenavLayoutElement = this.getHTMLElementByClassName(
this.openviduLayout?.getLayoutContainer(),
LayoutClass.SIDENAV_CONTAINER
);
@ -101,4 +98,14 @@ export class LayoutService {
this.layoutWidth.next(sidenavLayoutElement.clientWidth);
}
}
private getHTMLElementByClassName(element: HTMLElement | null, className: string): HTMLElement | null {
while (!!element && element !== document.body) {
if (element.className.includes(className)) {
return element;
}
element = element.parentElement;
}
return null;
}
}

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { ILogger } from '../../models/logger.model';
import { LoggerService } from '../logger/logger.service';
import { Storage } from '../../models/storage.model';
import { LoggerService } from '../logger/logger.service';
/**
* @internal
@ -62,6 +62,14 @@ export class StorageService {
return this.get(Storage.LANG);
}
setCaptionLang(lang: string){
this.set(Storage.CAPTION_LANG, lang);
}
getCaptionsLang(): string {
return this.get(Storage.CAPTION_LANG);
}
private set(key: string, item: any) {
const value = JSON.stringify({ item: item });
// this.log.d('Storing on localStorage "' + key + '" with value "' + value + '"');

View File

@ -1,12 +1,12 @@
import { Injectable } from '@angular/core';
import * as cn from '../../lang/cn.json';
import * as de from '../../lang/de.json';
import * as en from '../../lang/en.json';
import * as es from '../../lang/es.json';
import * as de from '../../lang/de.json';
import * as fr from '../../lang/fr.json';
import * as cn from '../../lang/cn.json';
import * as hi from '../../lang/hi.json';
import * as ja from '../../lang/ja.json';
import * as it from '../../lang/it.json';
import * as ja from '../../lang/ja.json';
import * as nl from '../../lang/nl.json';
import * as pt from '../../lang/pt.json';
import { StorageService } from '../storage/storage.service';
@ -36,7 +36,7 @@ export class TranslateService {
constructor(private storageService: StorageService) {
const iso = this.storageService.getLang() || 'en';
this.langSelected = this.langTitles.find((lang) => lang.ISO === iso);
this.langSelected = this.langTitles.find((lang) => lang.ISO === iso) || this.langTitles[0];
this.currentLang = this.availableLanguages[this.langSelected.ISO];
}