Recording layouts refactoring (prepare other layouts)

pull/255/head
pabloFuente 2019-03-20 16:01:56 +01:00
parent 984fd13374
commit 7cd9170d54
10 changed files with 293 additions and 159 deletions

View File

@ -1,23 +1,22 @@
import { BrowserModule } from '@angular/platform-browser';
import { FlexLayoutModule } from '@angular/flex-layout';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http'; import { HttpModule } from '@angular/http';
import { BrowserModule } from '@angular/platform-browser';
import 'hammerjs'; import 'hammerjs';
import { AppComponent } from './app.component';
import { routing } from './app.routing';
import { AppMaterialModule } from './app.material.module'; import { AppMaterialModule } from './app.material.module';
import { routing } from './app.routing';
import { CredentialsDialogComponent } from './components/dashboard/credentials-dialog.component';
import { DashboardComponent } from './components/dashboard/dashboard.component';
import { LayoutBestFitComponent } from './components/layouts/layout-best-fit/layout-best-fit.component';
import { LayoutHorizontalPresentationComponent } from './components/layouts/layout-horizontal-presentation/layout-horizontal-presentation.component';
import { LayoutVerticalPresentationComponent } from './components/layouts/layout-vertical-presentation/layout-vertical-presentation.component';
import { OpenViduVideoComponent } from './components/layouts/ov-video.component';
import { SessionDetailsComponent } from './components/session-details/session-details.component';
import { InfoService } from './services/info.service'; import { InfoService } from './services/info.service';
import { RestService } from './services/rest.service'; import { RestService } from './services/rest.service';
import { AppComponent } from './app.component';
import { DashboardComponent } from './components/dashboard/dashboard.component';
import { SessionDetailsComponent } from './components/session-details/session-details.component';
import { CredentialsDialogComponent } from './components/dashboard/credentials-dialog.component';
import { LayoutBestFitComponent } from './components/layouts/layout-best-fit/layout-best-fit.component';
import { OpenViduVideoComponent } from './components/layouts/ov-video.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
@ -25,7 +24,9 @@ import { OpenViduVideoComponent } from './components/layouts/ov-video.component'
SessionDetailsComponent, SessionDetailsComponent,
CredentialsDialogComponent, CredentialsDialogComponent,
LayoutBestFitComponent, LayoutBestFitComponent,
OpenViduVideoComponent, LayoutVerticalPresentationComponent,
LayoutHorizontalPresentationComponent,
OpenViduVideoComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -36,7 +37,7 @@ import { OpenViduVideoComponent } from './components/layouts/ov-video.component'
FlexLayoutModule FlexLayoutModule
], ],
entryComponents: [ entryComponents: [
CredentialsDialogComponent, CredentialsDialogComponent
], ],
providers: [InfoService, RestService], providers: [InfoService, RestService],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@ -1,9 +1,10 @@
import { ModuleWithProviders } from '@angular/core'; import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { DashboardComponent } from 'app/components/dashboard/dashboard.component'; import { DashboardComponent } from 'app/components/dashboard/dashboard.component';
import { SessionDetailsComponent } from 'app/components/session-details/session-details.component';
import { LayoutBestFitComponent } from 'app/components/layouts/layout-best-fit/layout-best-fit.component'; import { LayoutBestFitComponent } from 'app/components/layouts/layout-best-fit/layout-best-fit.component';
import { SessionDetailsComponent } from 'app/components/session-details/session-details.component';
import { LayoutHorizontalPresentationComponent } from './components/layouts/layout-horizontal-presentation/layout-horizontal-presentation.component';
import { LayoutVerticalPresentationComponent } from './components/layouts/layout-vertical-presentation/layout-vertical-presentation.component';
const appRoutes: Routes = [ const appRoutes: Routes = [
{ {
@ -21,6 +22,22 @@ const appRoutes: Routes = [
{ {
path: 'layout-best-fit/:sessionId/:secret/:onlyVideo', path: 'layout-best-fit/:sessionId/:secret/:onlyVideo',
component: LayoutBestFitComponent component: LayoutBestFitComponent
},
{
path: 'layout-vertical-presentation/:sessionId/:secret',
component: LayoutVerticalPresentationComponent
},
{
path: 'layout-vertical-presentation/:sessionId/:secret/:onlyVideo',
component: LayoutVerticalPresentationComponent
},
{
path: 'layout-horizontal-presentation/:sessionId/:secret',
component: LayoutHorizontalPresentationComponent
},
{
path: 'layout-horizontal-presentation/:sessionId/:secret/:onlyVideo',
component: LayoutHorizontalPresentationComponent
} }
]; ];

View File

@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LayoutBestFitComponent } from './layout-best-fit.component'; import { LayoutBaseComponent } from './layout-base.component';
describe('SessionDetailsComponent', () => { describe('LayoutBaseComponent', () => {
let component: LayoutBestFitComponent; let component: LayoutBaseComponent;
let fixture: ComponentFixture<LayoutBestFitComponent>; let fixture: ComponentFixture<LayoutBaseComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ LayoutBestFitComponent ] declarations: [ LayoutBaseComponent ]
}) })
.compileComponents(); .compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(LayoutBestFitComponent); fixture = TestBed.createComponent(LayoutBaseComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@ -0,0 +1,148 @@
import {
ApplicationRef,
Component,
HostListener,
OnDestroy,
OnInit,
ViewEncapsulation
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import {
OpenVidu,
Session,
StreamEvent,
StreamManagerEvent,
Subscriber
} from 'openvidu-browser';
import { OpenViduLayout, OpenViduLayoutOptions } from '../openvidu-layout';
@Component({
selector: 'app-layout-base',
templateUrl: './layout-base.component.html',
styleUrls: ['./layout-base.component.css'],
encapsulation: ViewEncapsulation.None
})
export class LayoutBaseComponent implements OnInit, OnDestroy {
openviduLayout: OpenViduLayout;
sessionId: string;
secret: string;
onlyVideo = false;
session: Session;
subscribers: Subscriber[] = [];
layout: any;
resizeTimeout;
numberOfScreenStreams = 0;
layoutOptions: OpenViduLayoutOptions;
constructor(private route: ActivatedRoute, private appRef: ApplicationRef) {
this.route.params.subscribe(params => {
this.sessionId = params.sessionId;
this.secret = params.secret;
if (params.onlyVideo != null) {
this.onlyVideo = JSON.parse(params.onlyVideo);
}
});
}
@HostListener('window:beforeunload')
beforeunloadHandler() {
this.leaveSession();
}
@HostListener('window:resize', ['$event'])
sizeChange(event) {
clearTimeout(this.resizeTimeout);
this.resizeTimeout = setTimeout(() => {
this.openviduLayout.updateLayout();
}, 20);
}
ngOnDestroy() {
this.leaveSession();
}
ngOnInit() {
const OV = new OpenVidu();
this.session = OV.initSession();
this.session.on('streamCreated', (event: StreamEvent) => {
if (!(this.onlyVideo && !event.stream.hasVideo)) {
let changeFixedRatio = false;
if (event.stream.typeOfVideo === 'SCREEN') {
this.numberOfScreenStreams++;
changeFixedRatio = true;
}
const subscriber: Subscriber = this.session.subscribe(
event.stream,
undefined,
{ subscribeToAudio: event.stream.hasAudio && !this.onlyVideo }
);
subscriber.on('streamPlaying', (e: StreamManagerEvent) => {
const video: HTMLVideoElement = subscriber.videos[0].video;
video.parentElement.parentElement.classList.remove('custom-class');
this.updateLayout(changeFixedRatio);
});
this.addSubscriber(subscriber);
}
});
this.session.on('streamDestroyed', (event: StreamEvent) => {
let changeFixedRatio = false;
if (event.stream.typeOfVideo === 'SCREEN') {
this.numberOfScreenStreams--;
changeFixedRatio = true;
}
this.deleteSubscriber(<Subscriber>event.stream.streamManager);
this.updateLayout(changeFixedRatio);
});
const port = !!location.port ? (':' + location.port) : '';
const token = 'wss://' + location.hostname + port + '?sessionId=' + this.sessionId + '&secret=' + this.secret + '&recorder=true';
this.session.connect(token)
.catch(error => {
console.error(error);
})
this.openviduLayout = new OpenViduLayout();
this.openviduLayout.initLayoutContainer(document.getElementById('layout'), this.layoutOptions);
}
private addSubscriber(subscriber: Subscriber): void {
this.subscribers.push(subscriber);
this.appRef.tick();
}
private deleteSubscriber(subscriber: Subscriber): void {
let index = -1;
for (let i = 0; i < this.subscribers.length; i++) {
if (this.subscribers[i] === subscriber) {
index = i;
break;
}
}
if (index > -1) {
this.subscribers.splice(index, 1);
}
this.appRef.tick();
}
leaveSession() {
if (this.session) { this.session.disconnect(); };
this.subscribers = [];
this.session = null;
}
updateLayout(changeFixedRatio: boolean) {
if (changeFixedRatio) {
this.layoutOptions.fixedRatio = this.numberOfScreenStreams > 0;
this.openviduLayout.setLayoutOptions(this.layoutOptions);
}
this.openviduLayout.updateLayout();
}
}

View File

@ -1,28 +1,13 @@
import { Component, OnInit, OnDestroy, HostListener, ViewEncapsulation, ApplicationRef } from '@angular/core'; import { Component, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { LayoutBaseComponent } from '../layout-base/layout-base.component';
import { OpenVidu, Session, Subscriber, StreamEvent, StreamManagerEvent } from 'openvidu-browser';
import { OpenViduLayout } from '../openvidu-layout';
@Component({ @Component({
selector: 'app-layout-best-fit', selector: 'app-layout-best-fit',
templateUrl: './layout-best-fit.component.html', templateUrl: '../layout-base/layout-base.component.html',
styleUrls: ['./layout-best-fit.component.css'], styleUrls: ['../layout-base/layout-base.component.css'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class LayoutBestFitComponent implements OnInit, OnDestroy { export class LayoutBestFitComponent extends LayoutBaseComponent {
openviduLayout: OpenViduLayout;
sessionId: string;
secret: string;
onlyVideo = false;
session: Session;
subscribers: Subscriber[] = [];
layout: any;
resizeTimeout;
numberOfScreenStreams = 0;
layoutOptions = { layoutOptions = {
maxRatio: 3 / 2, // The narrowest ratio that will be used (default 2x3) maxRatio: 3 / 2, // The narrowest ratio that will be used (default 2x3)
@ -35,113 +20,8 @@ export class LayoutBestFitComponent implements OnInit, OnDestroy {
bigMaxRatio: 3 / 2, // The narrowest ratio to use for the big elements (default 2x3) bigMaxRatio: 3 / 2, // The narrowest ratio to use for the big elements (default 2x3)
bigMinRatio: 9 / 16, // The widest ratio to use for the big elements (default 16x9) bigMinRatio: 9 / 16, // The widest ratio to use for the big elements (default 16x9)
bigFirst: true, // Whether to place the big one in the top left (true) or bottom right bigFirst: true, // Whether to place the big one in the top left (true) or bottom right
animate: true // Whether you want to animate the transitions animate: true, // Whether you want to animate the transitions
vertical: undefined // Whether to show small videos at the side or at the bottom
}; };
constructor(private route: ActivatedRoute, private appRef: ApplicationRef) {
this.route.params.subscribe(params => {
this.sessionId = params.sessionId;
this.secret = params.secret;
if (params.onlyVideo !== null) {
this.onlyVideo = JSON.parse(params.onlyVideo);
}
});
}
@HostListener('window:beforeunload')
beforeunloadHandler() {
this.leaveSession();
}
@HostListener('window:resize', ['$event'])
sizeChange(event) {
clearTimeout(this.resizeTimeout);
this.resizeTimeout = setTimeout(() => {
this.openviduLayout.updateLayout();
}, 20);
}
ngOnDestroy() {
this.leaveSession();
}
ngOnInit() {
const OV = new OpenVidu();
this.session = OV.initSession();
this.session.on('streamCreated', (event: StreamEvent) => {
if (!(this.onlyVideo && !event.stream.hasVideo)) {
let changeFixedRatio = false;
if (event.stream.typeOfVideo === 'SCREEN') {
this.numberOfScreenStreams++;
changeFixedRatio = true;
}
const subscriber: Subscriber = this.session.subscribe(
event.stream,
undefined,
{ subscribeToAudio: event.stream.hasAudio && !this.onlyVideo }
);
subscriber.on('streamPlaying', (e: StreamManagerEvent) => {
const video: HTMLVideoElement = subscriber.videos[0].video;
video.parentElement.parentElement.classList.remove('custom-class');
this.updateLayout(changeFixedRatio);
});
this.addSubscriber(subscriber);
}
});
this.session.on('streamDestroyed', (event: StreamEvent) => {
let changeFixedRatio = false;
if (event.stream.typeOfVideo === 'SCREEN') {
this.numberOfScreenStreams--;
changeFixedRatio = true;
}
this.deleteSubscriber(<Subscriber>event.stream.streamManager);
this.updateLayout(changeFixedRatio);
});
const port = !!location.port ? (':' + location.port) : '';
const token = 'wss://' + location.hostname + port + '?sessionId=' + this.sessionId + '&secret=' + this.secret + '&recorder=true';
this.session.connect(token)
.catch(error => {
console.error(error);
})
this.openviduLayout = new OpenViduLayout();
this.openviduLayout.initLayoutContainer(document.getElementById('layout'), this.layoutOptions);
}
private addSubscriber(subscriber: Subscriber): void {
this.subscribers.push(subscriber);
this.appRef.tick();
}
private deleteSubscriber(subscriber: Subscriber): void {
let index = -1;
for (let i = 0; i < this.subscribers.length; i++) {
if (this.subscribers[i] === subscriber) {
index = i;
break;
}
}
if (index > -1) {
this.subscribers.splice(index, 1);
}
this.appRef.tick();
}
leaveSession() {
if (this.session) { this.session.disconnect(); };
this.subscribers = [];
this.session = null;
}
updateLayout(changeFixedRatio: boolean) {
if (changeFixedRatio) {
this.layoutOptions.fixedRatio = this.numberOfScreenStreams > 0;
this.openviduLayout.setLayoutOptions(this.layoutOptions);
}
this.openviduLayout.updateLayout();
}
} }

View File

@ -0,0 +1,34 @@
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
import { LayoutBaseComponent } from '../layout-base/layout-base.component';
@Component({
selector: 'app-layout-horizontal-presentation',
templateUrl: '../layout-base/layout-base.component.html',
styleUrls: ['../layout-base/layout-base.component.css'],
encapsulation: ViewEncapsulation.None
})
export class LayoutHorizontalPresentationComponent extends LayoutBaseComponent implements OnInit {
layoutOptions = {
maxRatio: 3 / 2, // The narrowest ratio that will be used (default 2x3)
minRatio: 9 / 16, // The widest ratio that will be used (default 16x9)
fixedRatio: false, /* If this is true then the aspect ratio of the video is maintained
and minRatio and maxRatio are ignored (default false) */
bigClass: 'OV_big', // The class to add to elements that should be sized bigger
bigPercentage: 0.8, // The maximum percentage of space the big ones should take up
bigFixedRatio: false, // fixedRatio for the big ones
bigMaxRatio: 3 / 2, // The narrowest ratio to use for the big elements (default 2x3)
bigMinRatio: 9 / 16, // The widest ratio to use for the big elements (default 16x9)
bigFirst: true, // Whether to place the big one in the top left (true) or bottom right
animate: true, // Whether you want to animate the transitions
vertical: false // Whether to show small videos at the side or at the bottom
};
ngOnInit() {
super.ngOnInit();
this.session.on('signal:update-stream-layouts', event => {
// TODO: Add or remove class OV_big accordingly to each Subscriber video
});
}
}

View File

@ -0,0 +1,34 @@
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
import { LayoutBaseComponent } from '../layout-base/layout-base.component';
@Component({
selector: 'app-layout-vertical-presentation',
templateUrl: '../layout-base/layout-base.component.html',
styleUrls: ['../layout-base/layout-base.component.css'],
encapsulation: ViewEncapsulation.None
})
export class LayoutVerticalPresentationComponent extends LayoutBaseComponent implements OnInit {
layoutOptions = {
maxRatio: 3 / 2, // The narrowest ratio that will be used (default 2x3)
minRatio: 9 / 16, // The widest ratio that will be used (default 16x9)
fixedRatio: false, /* If this is true then the aspect ratio of the video is maintained
and minRatio and maxRatio are ignored (default false) */
bigClass: 'OV_big', // The class to add to elements that should be sized bigger
bigPercentage: 0.8, // The maximum percentage of space the big ones should take up
bigFixedRatio: false, // fixedRatio for the big ones
bigMaxRatio: 3 / 2, // The narrowest ratio to use for the big elements (default 2x3)
bigMinRatio: 9 / 16, // The widest ratio to use for the big elements (default 16x9)
bigFirst: false, // Whether to place the big one in the top left (true) or bottom right
animate: true, // Whether you want to animate the transitions
vertical: true // Whether to show small videos at the side or at the bottom
};
ngOnInit() {
super.ngOnInit();
this.session.on('signal:update-stream-layouts', event => {
// TODO: Add or remove class OV_big accordingly to each Subscriber video
});
}
}

View File

@ -10,7 +10,8 @@ export interface OpenViduLayoutOptions {
bigFixedRatio: any; bigFixedRatio: any;
bigMaxRatio: any; bigMaxRatio: any;
bigMinRatio: any; bigMinRatio: any;
bigFirst: any; bigFirst: boolean;
vertical: boolean;
} }
export class OpenViduLayout { export class OpenViduLayout {
@ -41,7 +42,7 @@ export class OpenViduLayout {
this.fixAspectRatio(elem, width); this.fixAspectRatio(elem, width);
if (animate && $) { if (!!animate && $) {
$(elem).stop(); $(elem).stop();
$(elem).animate(targetPosition, animate.duration || 200, animate.easing || 'swing', $(elem).animate(targetPosition, animate.duration || 200, animate.easing || 'swing',
() => { () => {
@ -296,21 +297,39 @@ export class OpenViduLayout {
if (bigOnes.length > 0 && smallOnes.length > 0) { if (bigOnes.length > 0 && smallOnes.length > 0) {
let bigWidth, bigHeight; let bigWidth, bigHeight;
if (availableRatio > this.getVideoRatio(bigOnes[0])) { const horizontal = () => {
// We are tall, going to take up the whole width and arrange small
// guys at the bottom
bigWidth = WIDTH; bigWidth = WIDTH;
bigHeight = Math.floor(HEIGHT * this.opts.bigPercentage); bigHeight = Math.floor(HEIGHT * this.opts.bigPercentage);
offsetTop = bigHeight; offsetTop = bigHeight;
bigOffsetTop = HEIGHT - offsetTop; bigOffsetTop = HEIGHT - offsetTop;
} else { }
// We are wide, going to take up the whole height and arrange the small const vertical = () => {
// guys on the right
bigHeight = HEIGHT; bigHeight = HEIGHT;
bigWidth = Math.floor(WIDTH * this.opts.bigPercentage); bigWidth = Math.floor(WIDTH * this.opts.bigPercentage);
offsetLeft = bigWidth; offsetLeft = bigWidth;
bigOffsetLeft = WIDTH - offsetLeft; bigOffsetLeft = WIDTH - offsetLeft;
} }
if (this.opts.vertical != null) {
if (!this.opts.vertical) {
// Horizontal presentation
horizontal();
} else {
// Vertical presentation
vertical();
}
} else {
// Dynamic presentation
if (availableRatio > this.getVideoRatio(bigOnes[0])) {
// We are tall, going to take up the whole width and arrange small
// guys at the bottom
horizontal();
} else {
// We are wide, going to take up the whole height and arrange the small
// guys on the right
vertical();
}
}
if (this.opts.bigFirst) { if (this.opts.bigFirst) {
this.arrange(bigOnes, bigWidth, bigHeight, 0, 0, this.opts.bigFixedRatio, this.opts.bigMinRatio, this.arrange(bigOnes, bigWidth, bigHeight, 0, 0, this.opts.bigFixedRatio, this.opts.bigMinRatio,
this.opts.bigMaxRatio, this.opts.animate); this.opts.bigMaxRatio, this.opts.animate);
@ -344,7 +363,8 @@ export class OpenViduLayout {
bigFixedRatio: (opts.bigFixedRatio != null) ? opts.bigFixedRatio : false, bigFixedRatio: (opts.bigFixedRatio != null) ? opts.bigFixedRatio : false,
bigMaxRatio: (opts.bigMaxRatio != null) ? opts.bigMaxRatio : 3 / 2, bigMaxRatio: (opts.bigMaxRatio != null) ? opts.bigMaxRatio : 3 / 2,
bigMinRatio: (opts.bigMinRatio != null) ? opts.bigMinRatio : 9 / 16, bigMinRatio: (opts.bigMinRatio != null) ? opts.bigMinRatio : 9 / 16,
bigFirst: (opts.bigFirst != null) ? opts.bigFirst : true bigFirst: (opts.bigFirst != null) ? opts.bigFirst : true,
vertical: opts.vertical
}; };
this.layoutContainer = typeof (container) === 'string' ? $(container) : container; this.layoutContainer = typeof (container) === 'string' ? $(container) : container;
} }