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/platform-browser-dynamic": "13.3.9",
"@angular/router": "13.3.9", "@angular/router": "13.3.9",
"autolinker": "3.14.3", "autolinker": "3.14.3",
"lorem-ipsum": "^2.0.8", "openvidu-browser": "file:openvidu-browser-2.23.0.tgz",
"openvidu-browser": "2.23.0", "openvidu-browser": "2.23.0",
"rxjs": "7.5.6", "rxjs": "7.5.6",
"tslib": "2.3.1", "tslib": "2.3.1",

View File

@ -1,78 +1,193 @@
.subtitles-container { .captions-container {
/* padding: 5px; */ /* padding: 5px; */
display: flex; display: flex;
height: var(--ov-captions-height, 200px); height: var(--ov-captions-height, 230px);
margin: 0px 10px; margin: 0px 10px;
} }
.subtitles-offset { .captions-offset {
height: var(--ov-captions-height, 200px); height: var(--ov-captions-height, 230px);
width: 15%; width: 15%;
text-align: center; text-align: center;
} }
.subtitles-main-container { .captions-offset-xl {
flex-grow: 1; width: 25% !important;
align-self: center;
margin-left: 15px;
max-height: var(--ov-captions-height, 200px);
width: 70%;
overflow: hidden;
} }
/* .subtitles-offset + .subtitles-offset { .captions-center-container {
margin-left: 2%; flex-grow: 1;
} */ align-self: center;
#subtitle-settings-btn { /* 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); color: var(--ov-text-color);
background-color: var(--ov-secondary-color); background-color: var(--ov-secondary-color);
} }
#subtitle-settings-icon { #caption-settings-icon {
font-size: 15px; font-size: 15px;
height: 15px; height: 15px;
width: 15px; width: 15px;
padding-right: 5px; padding-right: 5px;
} }
#author { #speaker {
margin-bottom: 3px; margin-bottom: 2px;
font-weight: bold; font-weight: bold;
/* padding: 5px; */
margin-left: -5px;
width: fit-content;
} }
#subtitle-text, .captions-center-container .element {
#author { margin: 8px 0px;
}
.caption-event {
/* background-color: beige; */
overflow: auto;
pointer-events: none;
}
.caption-text,
#speaker {
color: var(--ov-text-color); color: var(--ov-text-color);
font-family: "Roboto",arial,sans-serif font-family: 'Roboto', arial, sans-serif;
} }
.subtitles-main-container .element { .caption-text {
margin: 5px;
}
#subtitle-text {
background-color: var(--ov-logo-background-color); background-color: var(--ov-logo-background-color);
padding: 4.5px; padding: 4.5px;
line-height: 1.6; line-height: 1.6;
word-break: break-word; 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; ::-webkit-scrollbar {
} display: none;
.medium-author {
font-size: 14px;
}
.small-author {
font-size: 12px;
} }

View File

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

View File

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

View File

@ -5,8 +5,8 @@
.container { .container {
height: 100%; height: 100%;
} }
.withSubtitles { .withCaptions {
height: calc(100% - var(--ov-captions-height, 200px)) !important; height: calc(100% - var(--ov-captions-height, 250px)) !important;
} }
.layout { .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 id="layout" class="layout" #layout>
<div *ngFor="let stream of localParticipant | streams" [ngClass]="{ OV_big: stream.videoEnlarged }" class="OT_root OT_publisher"> <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> <ng-container *ngTemplateOutlet="streamTemplate; context: { $implicit: stream }"></ng-container>
@ -14,5 +14,5 @@
</div> </div>
</div> </div>
<ov-captions *ngIf="subtitlesEnabled" class="OV_ignored"></ov-captions> <ov-captions *ngIf="captionsEnabled" class="OV_ignored"></ov-captions>
</div> </div>

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
import { import {
ChangeDetectionStrategy, ChangeDetectorRef, Component, ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild, ContentChild,
ElementRef, ElementRef,
EventEmitter, EventEmitter,
@ -12,8 +14,12 @@ import {
} from '@angular/core'; } from '@angular/core';
import { import {
ConnectionEvent, ConnectionEvent,
RecordingEvent, Session, SessionDisconnectedEvent, StreamEvent, RecordingEvent,
StreamPropertyChangedEvent, Subscriber Session,
SessionDisconnectedEvent,
StreamEvent,
StreamPropertyChangedEvent,
Subscriber
} from 'openvidu-browser'; } from 'openvidu-browser';
import { ILogger } from '../../models/logger.model'; import { ILogger } from '../../models/logger.model';
@ -26,6 +32,7 @@ import { SidenavMode } from '../../models/layout.model';
import { PanelType } from '../../models/panel.model'; import { PanelType } from '../../models/panel.model';
import { Signal } from '../../models/signal.model'; import { Signal } from '../../models/signal.model';
import { ActionService } from '../../services/action/action.service'; import { ActionService } from '../../services/action/action.service';
import { CaptionService } from '../../services/caption/caption.service';
import { ChatService } from '../../services/chat/chat.service'; import { ChatService } from '../../services/chat/chat.service';
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service'; import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
import { LayoutService } from '../../services/layout/layout.service'; import { LayoutService } from '../../services/layout/layout.service';
@ -46,11 +53,7 @@ import { TranslateService } from '../../services/translate/translate.service';
selector: 'ov-session', selector: 'ov-session',
templateUrl: './session.component.html', templateUrl: './session.component.html',
styleUrls: ['./session.component.css'], styleUrls: ['./session.component.css'],
animations: [ animations: [trigger('sessionAnimation', [transition(':enter', [style({ opacity: 0 }), animate('400ms', style({ opacity: 1 }))])])],
trigger('sessionAnimation', [
transition(':enter', [style({ opacity: 0 }), animate('400ms', style({ opacity: 1 }))]),
])
],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class SessionComponent implements OnInit { export class SessionComponent implements OnInit {
@ -77,6 +80,7 @@ export class SessionComponent implements OnInit {
protected layoutWidthSubscription: Subscription; protected layoutWidthSubscription: Subscription;
protected updateLayoutInterval: NodeJS.Timer; protected updateLayoutInterval: NodeJS.Timer;
private captionLanguageSubscription: Subscription;
protected log: ILogger; protected log: ILogger;
@ -92,6 +96,7 @@ export class SessionComponent implements OnInit {
protected panelService: PanelService, protected panelService: PanelService,
private recordingService: RecordingService, private recordingService: RecordingService,
private translateService: TranslateService, private translateService: TranslateService,
private captionService: CaptionService,
private platformService: PlatformService, private platformService: PlatformService,
private cd: ChangeDetectorRef private cd: ChangeDetectorRef
) { ) {
@ -153,6 +158,7 @@ export class SessionComponent implements OnInit {
} }
this.session = this.openviduService.getWebcamSession(); this.session = this.openviduService.getWebcamSession();
this.sessionScreen = this.openviduService.getScreenSession(); this.sessionScreen = this.openviduService.getScreenSession();
this.subscribeToCaptionLanguage();
this.subscribeToConnectionCreatedAndDestroyed(); this.subscribeToConnectionCreatedAndDestroyed();
this.subscribeToStreamCreated(); this.subscribeToStreamCreated();
this.subscribeToStreamDestroyed(); this.subscribeToStreamDestroyed();
@ -284,7 +290,7 @@ export class SessionComponent implements OnInit {
} }
private subscribeToStreamCreated() { private subscribeToStreamCreated() {
this.session.on('streamCreated', (event: StreamEvent) => { this.session.on('streamCreated', async (event: StreamEvent) => {
const connectionId = event.stream?.connection?.connectionId; const connectionId = event.stream?.connection?.connectionId;
const data = event.stream?.connection?.data; const data = event.stream?.connection?.data;
@ -293,14 +299,39 @@ export class SessionComponent implements OnInit {
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);
try {
await this.session.subscribeToSpeechToText(event.stream, this.captionService.getLangSelected().ISO);
} catch (error) {
this.log.e('Error subscribing from STT: ', error);
}
} }
}); });
} }
private subscribeToStreamDestroyed() { private subscribeToStreamDestroyed() {
this.session.on('streamDestroyed', (event: StreamEvent) => { this.session.on('streamDestroyed', async (event: StreamEvent) => {
const connectionId = event.stream.connection.connectionId; const connectionId = event.stream.connection.connectionId;
this.participantService.removeConnectionByConnectionId(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>
<mat-list-item> <mat-list-item>
<div mat-line>{{ 'PANEL.SETTINGS.SUBTITLE' | translate }}</div> <div mat-line>{{ 'PANEL.SETTINGS.CAPTIONS' | translate }}</div>
<mat-slide-toggle (change)="toggleSubtitles()" [checked]="subtitlesEnabled" [disableRipple]="true"></mat-slide-toggle> <mat-slide-toggle (change)="toggleCaptions()" [checked]="captionsEnabled" [disableRipple]="true"></mat-slide-toggle>
</mat-list-item> </mat-list-item>
<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" [disabled]="true"> <button mat-flat-button [matMenuTriggerFor]="menu" class="lang-button">
<span>{{langSelected}}</span> <span>{{ langSelected }}</span>
<mat-icon>expand_more</mat-icon> <mat-icon>expand_more</mat-icon>
</button> </button>
<mat-menu #menu="matMenu"> <mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let lang of languagesAvailable" (click)="onLangSelected(lang)"> <button mat-menu-item *ngFor="let lang of languagesAvailable" (click)="onLangSelected(lang)">
<span>{{lang}}</span> <span>{{ lang.name }}</span>
</button> </button>
</mat-menu> </mat-menu>
</mat-list-item> </mat-list-item>
</mat-list> </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> <span>{{ 'TOOLBAR.BACKGROUND' | translate }}</span>
</button> </button>
<!-- Subtitles button --> <!-- Captions button -->
<button <button
*ngIf="!isMinimal && showSubtitlesButton && false" *ngIf="!isMinimal && showCaptionsButton"
[disabled]="isConnectionLost" [disabled]="isConnectionLost"
mat-menu-item mat-menu-item
id="subtitles-btn" id="captions-btn"
(click)="toggleSubtitles()" (click)="toggleCaptions()"
> >
<mat-icon>closed_caption</mat-icon> <mat-icon>closed_caption</mat-icon>
<span *ngIf="subtitlesEnabled">{{ 'TOOLBAR.DISABLE_SUBTITLES' | translate }}</span> <span *ngIf="captionsEnabled">{{ 'TOOLBAR.DISABLE_CAPTIONS' | translate }}</span>
<span *ngIf="!subtitlesEnabled">{{ 'TOOLBAR.ENABLE_SUBTITLES' | translate }}</span> <span *ngIf="!captionsEnabled">{{ 'TOOLBAR.ENABLE_CAPTIONS' | translate }}</span>
</button> </button>
<mat-divider class="divider" *ngIf="!isMinimal && showSettingsButton"></mat-divider> <mat-divider class="divider" *ngIf="!isMinimal && showSettingsButton"></mat-divider>

View File

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

View File

@ -27,5 +27,5 @@
/* Private css variables */ /* Private css variables */
#call-container { #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} | * | **minimal** | `boolean` | {@link MinimalDirective} |
* | **lang** | `string` | {@link LangDirective} | * | **lang** | `string` | {@link LangDirective} |
* | **captionsLang** | `string` | {@link CaptionsLangDirective} |
* | **prejoin** | `boolean` | {@link PrejoinDirective} | * | **prejoin** | `boolean` | {@link PrejoinDirective} |
* | **participantName** | `string` | {@link ParticipantNameDirective} | * | **participantName** | `string` | {@link ParticipantNameDirective} |
* | **videoMuted** | `boolean` | {@link VideoMutedDirective} | * | **videoMuted** | `boolean` | {@link VideoMutedDirective} |
* | **audioMuted** | `boolean` | {@link AudioMutedDirective} | * | **audioMuted** | `boolean` | {@link AudioMutedDirective} |
* | **toolbarScreenshareButton** | `boolean` | {@link ToolbarScreenshareButtonDirective} | * | **toolbarScreenshareButton** | `boolean` | {@link ToolbarScreenshareButtonDirective} |
* | **toolbarFullscreenButton** | `boolean` | {@link ToolbarFullscreenButtonDirective} | * | **toolbarFullscreenButton** | `boolean` | {@link ToolbarFullscreenButtonDirective} |
* | **toolbarCaptionsButton** | `boolean` | {@link ToolbarCaptionsButtonDirective} |
* | **toolbarBackgroundEffectsButton** | `boolean` | {@link ToolbarBackgroundEffectsButtonDirective} | * | **toolbarBackgroundEffectsButton** | `boolean` | {@link ToolbarBackgroundEffectsButtonDirective} |
* | **toolbarLeaveButton** | `boolean` | {@link ToolbarLeaveButtonDirective} | * | **toolbarLeaveButton** | `boolean` | {@link ToolbarLeaveButtonDirective} |
* | **toolbarChatPanelButton** | `boolean` | {@link ToolbarChatPanelButtonDirective} | * | **toolbarChatPanelButton** | `boolean` | {@link ToolbarChatPanelButtonDirective} |
@ -457,8 +459,8 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
const publisher = await this.openviduService.initDefaultPublisher(); const publisher = await this.openviduService.initDefaultPublisher();
if (publisher) { if (publisher) {
publisher.once('accessDenied', (e: any) => { publisher.once('accessDenied', async (e: any) => {
this.handlePublisherError(e); await this.handlePublisherError(e);
resolve(); resolve();
}); });
publisher.once('accessAllowed', async () => { publisher.once('accessAllowed', async () => {
@ -662,20 +664,20 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
this.onSessionCreated.emit(session); this.onSessionCreated.emit(session);
} }
private handlePublisherError(e: any): Promise<void> { private async handlePublisherError(e: any): Promise<void> {
let message: string = ''; let message: string = '';
console.log('ERROR!', e); console.log('ERROR!', e);
if (e.name === OpenViduErrorName.DEVICE_ALREADY_IN_USE) { if (e.name === OpenViduErrorName.DEVICE_ALREADY_IN_USE) {
this.log.w('Video device already in use. Disabling video device...'); 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 // Allow access to the room with only mic if camera device is already in use
this.deviceSrv.disableVideoDevices(); this.deviceSrv.disableVideoDevices();
return this.initwebcamPublisher(); return await this.initwebcamPublisher();
} }
if (e.name === OpenViduErrorName.DEVICE_ACCESS_DENIED) { if (e.name === OpenViduErrorName.DEVICE_ACCESS_DENIED) {
message = this.translateService.translate('ERRORS.MEDIA_ACCESS'); message = this.translateService.translate('ERRORS.MEDIA_ACCESS');
this.deviceSrv.disableVideoDevices(); this.deviceSrv.disableVideoDevices();
this.deviceSrv.disableAudioDevices(); this.deviceSrv.disableAudioDevices();
return this.initwebcamPublisher(); return await this.initwebcamPublisher();
} else if (e.name === OpenViduErrorName.NO_INPUT_SOURCE_SET) { } else if (e.name === OpenViduErrorName.NO_INPUT_SOURCE_SET) {
message = this.translateService.translate('ERRORS.DEVICE_NOT_FOUND'); 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 { ParticipantPanelItemMuteButtonDirective } from './participant-panel-item.directive';
import { RecordingActivityRecordingErrorDirective, RecordingActivityRecordingsListDirective } from './recording-activity.directive'; import { RecordingActivityRecordingErrorDirective, RecordingActivityRecordingsListDirective } from './recording-activity.directive';
import { import {
StreamDisplayParticipantNameDirective,
StreamDisplayAudioDetectionDirective, StreamDisplayAudioDetectionDirective,
StreamDisplayParticipantNameDirective,
StreamSettingsButtonDirective StreamSettingsButtonDirective
} from './stream.directive'; } from './stream.directive';
import { import {
ToolbarScreenshareButtonDirective, ToolbarActivitiesPanelButtonDirective,
ToolbarBackgroundEffectsButtonDirective,
ToolbarCaptionsButtonDirective,
ToolbarChatPanelButtonDirective,
ToolbarDisplayLogoDirective,
ToolbarDisplaySessionNameDirective,
ToolbarFullscreenButtonDirective, ToolbarFullscreenButtonDirective,
ToolbarLeaveButtonDirective, ToolbarLeaveButtonDirective,
ToolbarParticipantsPanelButtonDirective, ToolbarParticipantsPanelButtonDirective,
ToolbarChatPanelButtonDirective,
ToolbarDisplaySessionNameDirective,
ToolbarDisplayLogoDirective,
ToolbarActivitiesPanelButtonDirective,
ToolbarBackgroundEffectsButtonDirective,
ToolbarRecordingButtonDirective, ToolbarRecordingButtonDirective,
ToolbarSettingsButtonDirective, ToolbarScreenshareButtonDirective,
ToolbarCaptionsButtonDirective ToolbarSettingsButtonDirective
} from './toolbar.directive'; } from './toolbar.directive';
import { import {
AudioMutedDirective, AudioMutedDirective,
CaptionsLangDirective,
LangDirective,
MinimalDirective, MinimalDirective,
PrejoinDirective,
VideoMutedDirective,
ParticipantNameDirective, ParticipantNameDirective,
LangDirective PrejoinDirective,
VideoMutedDirective
} from './videoconference.directive'; } from './videoconference.directive';
@NgModule({ @NgModule({
declarations: [ declarations: [
MinimalDirective, MinimalDirective,
LangDirective, LangDirective,
CaptionsLangDirective,
PrejoinDirective, PrejoinDirective,
VideoMutedDirective, VideoMutedDirective,
AudioMutedDirective, AudioMutedDirective,
@ -66,6 +68,7 @@ import {
exports: [ exports: [
MinimalDirective, MinimalDirective,
LangDirective, LangDirective,
CaptionsLangDirective,
PrejoinDirective, PrejoinDirective,
VideoMutedDirective, VideoMutedDirective,
AudioMutedDirective, 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 * Load default OpenVidu logo if custom one is not exist
* @internal * @internal
*/ */
@Directive({ @Directive({
selector: 'img[ovLogo]' selector: 'img[ovLogo]'
}) })
export class LogoDirective { 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'; 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}. * And it also can be used in the {@link ToolbarComponent}.
* @example * @example
* <ov-toolbar [captionsButton]="false"></ov-toolbar> * <ov-toolbar [captionsButton]="false"></ov-toolbar>
*
* TODO: Make it public when speech to text is integrated
* @internal
*/ */
@Directive({ @Directive({
selector: 'ov-videoconference[toolbarCaptionsButton], ov-toolbar[captionsButton]' selector: 'ov-videoconference[toolbarCaptionsButton], ov-toolbar[captionsButton]'
@ -298,8 +295,8 @@ export class ToolbarCaptionsButtonDirective implements AfterViewInit, OnDestroy
} }
private update(value: boolean) { private update(value: boolean) {
if (this.libService.subtitlesButton.getValue() !== value) { if (this.libService.captionsButton.getValue() !== value) {
this.libService.subtitlesButton.next(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 { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
import { TranslateService } from '../../services/translate/translate.service'; import { TranslateService } from '../../services/translate/translate.service';
/** /**
* The **minimal** directive applies a minimal UI hiding all controls except for cam and mic. * 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}. * 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. * 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":"更多选项", "MORE_OPTIONS":"更多选项",
"FULLSCREEN":"全屏", "FULLSCREEN":"全屏",
"EXIT_FULLSCREEN": "退出全屏", "EXIT_FULLSCREEN": "退出全屏",
"ENABLE_SUBTITLES": "启用字幕", "ENABLE_CAPTIONS": "启用字幕",
"DISABLE_SUBTITLES": "禁用字幕", "DISABLE_CAPTIONS": "禁用字幕",
"BACKGROUND":"背景效果", "BACKGROUND":"背景效果",
"START_RECORDING": "开始录音", "START_RECORDING": "开始录音",
"STOP_RECORDING": "停止录制", "STOP_RECORDING": "停止录制",
@ -73,7 +73,7 @@
"VIDEO": "视频", "VIDEO": "视频",
"AUDIO": "声音的", "AUDIO": "声音的",
"LANGUAGE": "语", "LANGUAGE": "语",
"SUBTITLE": "字幕" "CAPTIONS": "字幕"
}, },
"BACKGROUND": { "BACKGROUND": {
"TITLE": "背景效果", "TITLE": "背景效果",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,8 +34,8 @@
"MORE_OPTIONS": "Mais opções", "MORE_OPTIONS": "Mais opções",
"FULLSCREEN": "Tela cheia", "FULLSCREEN": "Tela cheia",
"EXIT_FULLSCREEN": "Sair da tela cheia", "EXIT_FULLSCREEN": "Sair da tela cheia",
"ENABLE_SUBTITLES": "Ativar legendas", "ENABLE_CAPTIONS": "Ativar legendas",
"DISABLE_SUBTITLES": "Desativar legendas", "DISABLE_CAPTIONS": "Desativar legendas",
"BACKGROUND": "Efeitos de fundo", "BACKGROUND": "Efeitos de fundo",
"START_RECORDING": "Iniciar_gravação", "START_RECORDING": "Iniciar_gravação",
"STOP_RECORDING": "Parar de gravar", "STOP_RECORDING": "Parar de gravar",
@ -73,7 +73,7 @@
"VIDEO": "Vídeo", "VIDEO": "Vídeo",
"AUDIO": "Áudio", "AUDIO": "Áudio",
"LANGUAGE":"Linguagem", "LANGUAGE":"Linguagem",
"SUBTITLE": "Legendas" "CAPTIONS": "Legendas"
}, },
"BACKGROUND": { "BACKGROUND": {
"TITLE": "Efeitos de fundo", "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', GENERAL = 'general',
AUDIO = 'audio', AUDIO = 'audio',
VIDEO = 'video', VIDEO = 'video',
SUBTITLES = 'subtitles' CAPTIONS = 'captions'
} }

View File

@ -7,5 +7,6 @@ export enum Storage {
AUDIO_DEVICE = 'openviduCallAudioDevice', AUDIO_DEVICE = 'openviduCallAudioDevice',
AUDIO_MUTED = 'openviduCallAudioMuted', AUDIO_MUTED = 'openviduCallAudioMuted',
VIDEO_MUTED = 'openviduCallVideoMuted', 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 { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router'; 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 { DeleteDialogComponent } from './components/dialogs/delete-recording.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 { DialogTemplateComponent } from './components/dialogs/dialog.component'; import { DialogTemplateComponent } from './components/dialogs/dialog.component';
import { RecordingDialogComponent } from './components/dialogs/recording-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 { LinkifyPipe } from './pipes/linkify.pipe';
import { TranslatePipe } from './pipes/translate.pipe'; import { ParticipantStreamsPipe, StreamTypesEnabledPipe } from './pipes/participant.pipe';
import { StreamTypesEnabledPipe, ParticipantStreamsPipe } from './pipes/participant.pipe';
import { DurationFromSecondsPipe, SearchByStringPropertyPipe, ThumbnailFromUrlPipe } from './pipes/recording.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 { CdkOverlayContainer } from './config/custom-cdk-overlay';
import { DeviceService } from './services/device/device.service'; import { OpenViduAngularConfig } from './config/openvidu-angular.config';
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 { ActionService } from './services/action/action.service'; import { ActionService } from './services/action/action.service';
import { ChatService } from './services/chat/chat.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 { DocumentService } from './services/document/document.service';
import { LayoutService } from './services/layout/layout.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 { PanelService } from './services/panel/panel.service';
import { ParticipantService } from './services/participant/participant.service'; import { ParticipantService } from './services/participant/participant.service';
import { PlatformService } from './services/platform/platform.service';
import { RecordingService } from './services/recording/recording.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 { 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 { 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 { 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 { AdminDashboardComponent } from './admin/dashboard/dashboard.component';
import { AdminLoginComponent } from './admin/login/login.component'; import { AdminLoginComponent } from './admin/login/login.component';
import { AppMaterialModule } from './openvidu-angular.material.module'; import { AvatarProfileComponent } from './components/avatar-profile/avatar-profile.component';
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 { CaptionsComponent } from './components/captions/captions.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 { 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 = [ const publicComponents = [
AdminDashboardComponent, AdminDashboardComponent,
@ -92,7 +92,7 @@ const privateComponents = [
NicknameInputComponent, NicknameInputComponent,
LangSelectorComponent, LangSelectorComponent,
RecordingActivityComponent, RecordingActivityComponent,
SubtitlesSettingComponent CaptionsSettingComponent
]; ];
@NgModule({ @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); fullscreenButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
fullscreenButtonObs: Observable<boolean>; fullscreenButtonObs: Observable<boolean>;
subtitlesButton = <BehaviorSubject<boolean>>new BehaviorSubject(true); captionsButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
subtitlesButtonObs: Observable<boolean>; captionsButtonObs: Observable<boolean>;
toolbarSettingsButton = <BehaviorSubject<boolean>>new BehaviorSubject(true); toolbarSettingsButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
toolbarSettingsButtonObs: Observable<boolean>; toolbarSettingsButtonObs: Observable<boolean>;
@ -95,7 +95,7 @@ export class OpenViduAngularConfigService {
this.displayLogoObs = this.displayLogo.asObservable(); this.displayLogoObs = this.displayLogo.asObservable();
this.recordingButtonObs = this.recordingButton.asObservable(); this.recordingButtonObs = this.recordingButton.asObservable();
this.toolbarSettingsButtonObs = this.toolbarSettingsButton.asObservable(); this.toolbarSettingsButtonObs = this.toolbarSettingsButton.asObservable();
this.subtitlesButtonObs = this.subtitlesButton.asObservable(); this.captionsButtonObs = this.captionsButton.asObservable();
//Stream observables //Stream observables
this.displayParticipantNameObs = this.displayParticipantName.asObservable(); this.displayParticipantNameObs = this.displayParticipantName.asObservable();
this.displayAudioDetectionObs = this.displayAudioDetection.asObservable(); this.displayAudioDetectionObs = this.displayAudioDetection.asObservable();

View File

@ -13,17 +13,7 @@ export class DocumentService {
screenSizeObs: Observable<MediaChange[]>; screenSizeObs: Observable<MediaChange[]>;
constructor(private media: MediaObserver) { constructor(private media: MediaObserver) {
this.screenSizeObs= this.media.asObservable(); 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;
} }
toggleFullscreen(elementId: string) { toggleFullscreen(elementId: string) {

View File

@ -1,7 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { LayoutAlignment, LayoutClass, OpenViduLayout, OpenViduLayoutOptions } from '../../models/layout.model'; import { LayoutAlignment, LayoutClass, OpenViduLayout, OpenViduLayoutOptions } from '../../models/layout.model';
import { DocumentService } from '../document/document.service';
/** /**
* @internal * @internal
@ -10,26 +9,24 @@ import { DocumentService } from '../document/document.service';
providedIn: 'root' providedIn: 'root'
}) })
export class LayoutService { export class LayoutService {
layoutContainer: HTMLElement | null = null; layoutContainer: HTMLElement | null = null;
layoutWidthObs: Observable<number>; layoutWidthObs: Observable<number>;
subtitlesTogglingObs: Observable<boolean>; captionsTogglingObs: Observable<boolean>;
private layoutWidth: BehaviorSubject<number> = new BehaviorSubject(0); private layoutWidth: BehaviorSubject<number> = new BehaviorSubject(0);
private openviduLayout: OpenViduLayout; private openviduLayout: OpenViduLayout;
private openviduLayoutOptions: OpenViduLayoutOptions; private openviduLayoutOptions: OpenViduLayoutOptions;
private subtitlesToggling: BehaviorSubject<boolean> = new BehaviorSubject(false); private captionsToggling: BehaviorSubject<boolean> = new BehaviorSubject(false);
constructor() {
constructor(private documentService: DocumentService) {
this.layoutWidthObs = this.layoutWidth.asObservable(); this.layoutWidthObs = this.layoutWidth.asObservable();
this.subtitlesTogglingObs = this.subtitlesToggling.asObservable(); this.captionsTogglingObs = this.captionsToggling.asObservable();
} }
initialize(container: HTMLElement) { initialize(container: HTMLElement) {
this.layoutContainer = container; this.layoutContainer = container;
this.openviduLayout = new OpenViduLayout(); this.openviduLayout = new OpenViduLayout();
this.openviduLayoutOptions = this.getOptions(); this.openviduLayoutOptions = this.getOptions();
if(this.layoutContainer){ if (this.layoutContainer) {
this.openviduLayout.initLayoutContainer(this.layoutContainer, this.openviduLayoutOptions); this.openviduLayout.initLayoutContainer(this.layoutContainer, this.openviduLayoutOptions);
} }
this.sendLayoutWidthEvent(); this.sendLayoutWidthEvent();
@ -66,8 +63,8 @@ export class LayoutService {
return options; return options;
} }
toggleSubtitles() { toggleCaptions() {
this.subtitlesToggling.next(!this.subtitlesToggling.getValue()); this.captionsToggling.next(!this.captionsToggling.getValue());
} }
update(timeout: number = null) { update(timeout: number = null) {
@ -93,7 +90,7 @@ export class LayoutService {
} }
private sendLayoutWidthEvent() { private sendLayoutWidthEvent() {
const sidenavLayoutElement = this.documentService.getHTMLElementByClassName( const sidenavLayoutElement = this.getHTMLElementByClassName(
this.openviduLayout?.getLayoutContainer(), this.openviduLayout?.getLayoutContainer(),
LayoutClass.SIDENAV_CONTAINER LayoutClass.SIDENAV_CONTAINER
); );
@ -101,4 +98,14 @@ export class LayoutService {
this.layoutWidth.next(sidenavLayoutElement.clientWidth); 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 { Injectable } from '@angular/core';
import { ILogger } from '../../models/logger.model'; import { ILogger } from '../../models/logger.model';
import { LoggerService } from '../logger/logger.service';
import { Storage } from '../../models/storage.model'; import { Storage } from '../../models/storage.model';
import { LoggerService } from '../logger/logger.service';
/** /**
* @internal * @internal
@ -62,6 +62,14 @@ export class StorageService {
return this.get(Storage.LANG); 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) { private set(key: string, item: any) {
const value = JSON.stringify({ item: item }); const value = JSON.stringify({ item: item });
// this.log.d('Storing on localStorage "' + key + '" with value "' + value + '"'); // this.log.d('Storing on localStorage "' + key + '" with value "' + value + '"');

View File

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