openvidu-testapp first approach

pull/20/head
pabloFuente 2017-09-26 18:13:00 +02:00
parent 681309ecfb
commit 64a68bb42e
65 changed files with 1093 additions and 668 deletions

View File

@ -32,7 +32,7 @@ var logger = window.Logger || console
// }).eror(callback));
// }
try {
/*try {
require('kurento-browser-extensions')
} catch (error) {
if (typeof getScreenConstraints === 'undefined') {
@ -42,7 +42,7 @@ try {
callback(new Error("This library is not enabled for screen sharing"))
}
}
}
}*/
var MEDIA_CONSTRAINTS = {
audio: true,

View File

@ -1,14 +0,0 @@
import { OpenviduNgTestappPage } from './app.po';
describe('openvidu-ng-testapp App', () => {
let page: OpenviduNgTestappPage;
beforeEach(() => {
page = new OpenviduNgTestappPage();
});
it('should display message saying app works', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('app works!');
});
});

View File

@ -1,19 +0,0 @@
{
"compilerOptions": {
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"es2016"
],
"outDir": "../dist/out-tsc-e2e",
"module": "commonjs",
"target": "es6",
"types":[
"jasmine",
"node"
]
}
}

View File

@ -1,48 +0,0 @@
{
"name": "openvidu-ng-testapp",
"version": "1.1.0",
"license": "Apache-2.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/common": "^2.4.0",
"@angular/compiler": "^2.4.0",
"@angular/core": "^2.4.0",
"@angular/forms": "^2.4.0",
"@angular/http": "^2.4.0",
"@angular/platform-browser": "^2.4.0",
"@angular/platform-browser-dynamic": "^2.4.0",
"@angular/router": "^3.4.0",
"core-js": "^2.4.1",
"rxjs": "^5.1.0",
"ts-helpers": "^1.1.1",
"zone.js": "^0.7.6",
"openvidu-browser": "1.0.2"
},
"devDependencies": {
"@angular/cli": "1.0.0-rc.1",
"@angular/compiler-cli": "^2.4.0",
"@types/jasmine": "2.5.38",
"@types/node": "~6.0.60",
"codelyzer": "~2.0.0",
"jasmine-core": "~2.5.2",
"jasmine-spec-reporter": "~3.2.0",
"karma": "~1.4.1",
"karma-chrome-launcher": "~2.0.0",
"karma-cli": "~1.0.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"karma-coverage-istanbul-reporter": "^0.2.0",
"protractor": "~5.1.0",
"ts-node": "~2.0.0",
"tslint": "~4.4.2",
"typescript": "~2.0.0"
}
}

View File

@ -1,82 +0,0 @@
<div *ngIf="!session">
<h1>Join a video session</h1>
<form (submit)="joinSession()" accept-charset="UTF-8">
<div>
<label>Participant:</label>
<input type="text" name="participantId" [(ngModel)]="participantId" required>
</div>
<div>
<label>Session:</label>
<input type="text" name="sessionId" [(ngModel)]="sessionId" required>
</div>
<div>
<input type="checkbox" [(ngModel)]="joinWithVideo" id="join-with-video" name="join-with-video"> Send video
<div *ngIf="joinWithVideo">
<table>
<tr>
<th>Constraint</th>
<th>Value</th>
</tr>
<tr>
<td>width</td>
<td><input type="text" id="width" required></td>
</tr>
<tr>
<td>height</td>
<td><input type="text" id="height" required></td>
</tr>
<tr>
<td>frameRate</td>
<td><input type="text" id="frameRate" required></td>
</tr>
</table>
</div>
</div>
<div>
<input type="checkbox" [(ngModel)]="joinWithAudio" id="join-with-audio" name="join-with-audio"> Send audio
</div>
<div>
<input type="submit" name="commit" value="Join!">
</div>
</form>
</div>
<div *ngIf="session">
<h2>{{sessionId}}</h2>
<input type="button" (click)="leaveSession()" value="Leave session">
<input type="checkbox" id="toggle-video" name="toggle-video" [checked]="toggleVideo" (change)="updateToggleVideo($event)"> Toggle your video
<input type="checkbox" id="toggle-audio" name="toggle-audio" [checked]="toggleAudio" (change)="updateToggleAudio($event)"> Toggle your audio
<div>
<div id="publisher"></div>
<div id="subscriber"></div>
<div *ngFor="let s of streams; let i = index">
<div *ngIf="stats[i]">
<div style="margin-left: 50px;">
<input type="checkbox" id="toggle-state" name="toggle-state" [checked]="true" (change)="updateToggleState(i)"> Show conexion state
<div [attr.id]="'state-' + i" style="display: block;">
<p><b>Signaling state:</b> {{signalingState[i]}}</p>
<p><b>ICE connection state:</b> {{iceConnectionState[i]}}</p>
</div>
<input type="checkbox" id="toggle-statistics" name="toggle-statistics" (change)="updateToggleStatistics(i)"> Show
statistics
<div>
<h2 *ngIf="stats[i].bitrate">Bitrate: {{stats[i].bitrate}}</h2>
<h2 *ngIf="stats[i].framerate">Framerate: {{stats[i].framerate}}</h2>
</div>
<table [attr.id]="'table-' + i" style="display: none;">
<tr>
<th>Type</th>
<th>Time</th>
<th>Other attributes</th>
</tr>
<tr *ngFor="let st of stats[i].statsArray">
<td>{{st.type}}</td>
<td>{{st.timestamp}}</td>
<td>{{getStatAttributes(st.res)}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,278 +0,0 @@
import { Observable } from 'rxjs/Rx';
import { enableDebugTools } from '@angular/platform-browser';
import { Component } from '@angular/core';
import { OpenVidu, Session, Publisher, Stream } from 'openvidu-browser';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
private openVidu: OpenVidu;
private session: Session;
private publisher: Publisher;
cameraOptions: any;
// Join form
sessionId: string;
participantId: string;
// Session
streams: Stream[] = [];
// Publish options
joinWithVideo: boolean = false;
joinWithAudio: boolean = false;
toggleVideo: boolean;
toggleAudio: boolean;
//Statistics
stats = [];
bytesPrev = [];
framesPrev = [];
timestampPrev = [];
signalingState = [];
iceConnectionState = [];
constructor() {
this.generateParticipantInfo();
//this.obtainSupportedConstraints();
}
private generateParticipantInfo() {
this.sessionId = "SessionA";
this.participantId = "Participant" + Math.floor(Math.random() * 100);
}
private addVideoTag(stream: Stream) {
console.log("Stream added");
let ind = (this.streams.push(stream) - 1);
this.signalingState[ind] = '';
this.iceConnectionState[ind] = '';
//Connection events
stream.getRTCPeerConnection().onsignalingstatechange = (event) => {
this.signalingState[ind] += " => " + stream.getRTCPeerConnection().signalingState;
console.info("Stream " + stream.getId() + " signaling state: " + stream.getRTCPeerConnection().signalingState);
}
stream.getRTCPeerConnection().oniceconnectionstatechange = (event) => {
/*if (stream.getRTCPeerConnection().iceconnectionstate === "failed" ||
stream.getRTCPeerConnection().iceconnectionstate === "disconnected" ||
stream.getRTCPeerConnection().iceconnectionstate === "closed") {
// Handle the failure
};*/
this.iceConnectionState[ind] += " => " + stream.getRTCPeerConnection().iceConnectionState;
console.info("Stream " + stream.getId() + " ice connection state: " + stream.getRTCPeerConnection().iceConnectionState);
}
//For statistics
this.timestampPrev.push(0);
this.bytesPrev.push(0);
this.framesPrev.push(0);
}
private removeVideoTag(stream: Stream) {
console.log("Stream removed");
let index = this.streams.indexOf(stream);
this.streams.splice(index, 1);
this.stats.splice(index, 1);
this.timestampPrev.splice(index, 1);
this.bytesPrev.splice(index, 1);
this.framesPrev.splice(index, 1);
this.signalingState.splice(index, 1);
this.iceConnectionState.splice(index, 1);
}
joinSession() {
let mediaConstraints = this.generateMediaConstraints();
console.log(mediaConstraints);
this.cameraOptions = {
audio: this.joinWithAudio,
video: this.joinWithVideo,
data: true,
mediaConstraints: mediaConstraints
}
this.joinSessionShared();
}
joinSessionShared() {
this.toggleVideo = this.joinWithVideo;
this.toggleAudio = this.joinWithAudio;
this.openVidu = new OpenVidu("wss://" + location.hostname + ":8443/");
this.session = this.openVidu.initSession(this.sessionId);
this.session.on('streamCreated', (event) => {
this.session.subscribe(event.stream, 'subscriber', {
insertMode: 'append',
width: '100%',
height: '100%'
});
this.addVideoTag(event.stream);
});
this.session.on('streamDestroyed', (event) => {
this.removeVideoTag(event.stream);
});
this.session.connect(this.participantId, (error) => {
// If the connection is successful, initialize a publisher and publish to the session
if (!error) {
// 4) Get your own camera stream with the desired resolution and publish it, only if the user is supposed to do so
this.publisher = this.openVidu.initPublisher('publisher', this.cameraOptions);
// 5) Publish your stream
this.session.publish(this.publisher);
this.intervalStats().subscribe();
} else {
console.log('There was an error connecting to the session:', error.code, error.message);
}
});
}
leaveSession() {
this.session.disconnect();
this.session = null;
this.streams = [];
this.generateParticipantInfo();
}
updateToggleVideo(event) {
this.publisher.publishVideo(event.target.checked);
let msg = (event.target.checked) ? 'Publishing video...' : 'Unpublishing video...'
console.log(msg);
}
updateToggleAudio(event) {
this.publisher.publishAudio(event.target.checked);
let msg = (event.target.checked) ? 'Publishing audio...' : 'Unpublishing audio...'
console.log(msg);
}
updateToggleStatistics(i) {
let table = (<HTMLInputElement>document.getElementById('table-' + i));
(table.style.display == "none") ? table.style.display = "block" : table.style.display = "none";
}
updateToggleState(i) {
let state = (<HTMLInputElement>document.getElementById('state-' + i));
(state.style.display == "none") ? state.style.display = "block" : state.style.display = "none";
}
/*obtainSupportedConstraints() {
let constraints = Object.keys(navigator.mediaDevices.getSupportedConstraints());
this.supportedVideoContstraints = constraints.filter((e) => {
return this.mediaTrackSettingsVideo.indexOf(e) > -1;
});
this.supportedAudioContstraints = constraints.filter((e) => {
return this.mediaTrackSettingsAudio.indexOf(e) > -1;
});
console.log(constraints);
console.log(this.supportedVideoContstraints);
console.log(this.supportedAudioContstraints);
}*/
generateMediaConstraints() {
let mediaConstraints = {
audio: true,
video: {}
}
if (this.joinWithVideo) {
mediaConstraints.video['width'] = { exact: Number((<HTMLInputElement>document.getElementById('width')).value) };
mediaConstraints.video['height'] = { exact: Number((<HTMLInputElement>document.getElementById('height')).value) };
mediaConstraints.video['frameRate'] = { ideal: Number((<HTMLInputElement>document.getElementById('frameRate')).value) };
}
return mediaConstraints;
}
intervalStats() {
return Observable
.interval(1000)
.flatMap(() => {
let i = 0;
for (let str of this.streams) {
if (str.getWebRtcPeer().peerConnection) {
this.intervalStatsAux(i, str);
i++;
}
}
return [];
});
}
intervalStatsAux(i: number, stream: Stream) {
stream.getWebRtcPeer().peerConnection.getStats(null)
.then((results) => {
this.stats[i] = this.dumpStats(results, i);
console.info(results);
});
}
dumpStats(results, i) {
var statsArray = [];
let bitrate;
let frames;
results.forEach((res) => {
let date = new Date(res.timestamp);
statsArray.push({ res: res, type: res.type, timestamp: date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds() });
let now = res.timestamp;
if (res.type === 'inbound-rtp' && res.mediaType === 'video') {
// firefox calculates the bitrate for us
// https://bugzilla.mozilla.org/show_bug.cgi?id=951496
bitrate = Math.floor(res.bitrateMean / 1024);
if (res.framerateMean !== undefined && res.frameRate != "0") {
frames = (res.framerateMean).toFixed(2);
}
} else if (res.type === 'ssrc' && res.bytesReceived && res.googFrameRateReceived) {
// chrome does not so we need to do it ourselves
var bytes = res.bytesReceived;
frames = (res.googFrameRateOutput == "0") ? Number(this.framesPrev[i]) : Number(res.googFrameRateOutput);
if (this.timestampPrev[i]) {
bitrate = 8 * (bytes - this.bytesPrev[i]) / (now - this.timestampPrev[i]);
bitrate = Math.floor(bitrate);
}
this.bytesPrev[i] = bytes;
this.timestampPrev[i] = now;
}
});
if (bitrate) {
bitrate += ' kbits/sec';
}
if (frames) {
this.framesPrev[i] = frames;
frames += ' fps';
}
return { statsArray: statsArray, bitrate: bitrate, framerate: frames };
}
getStatAttributes(stat) {
let s = '';
Object.keys(stat).forEach((key) => {
if (key != 'type' && key != 'timestamp') s += (' | ' + key + ' | ');
});
return s;
}
}

View File

@ -1,21 +0,0 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { StreamComponent } from "./stream.component";
@NgModule({
declarations: [
AppComponent, StreamComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -1,2 +0,0 @@
export * from './app.component';
export * from './app.module';

View File

@ -1,46 +0,0 @@
import { Component, OnInit, Input, ViewChild } from '@angular/core';
import { Stream, Session } from 'openvidu-browser';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
@Component({
selector: 'stream',
styles: [`
.participant {
margin: 10px;
}
.participant video {
}`],
template: `
<div class='participant'>
<p>{{stream.getId()}}</p>
<video *ngIf="!stream.local" autoplay="true" [src]="videoSrc"></video>
<video *ngIf="stream.local" autoplay="true" [src]="videoSrc" muted></video>
</div>`
})
export class StreamComponent {
@Input()
stream: Stream;
videoSrc: SafeUrl;
constructor(private sanitizer: DomSanitizer) { }
ngOnInit() {
let int = setInterval(() => {
if (this.stream.getWrStream()) {
this.videoSrc = this.sanitizer.bypassSecurityTrustUrl(
URL.createObjectURL(this.stream.getWrStream()));
console.log("Video tag src=" + this.videoSrc);
clearInterval(int);
}
}, 1000);
//this.stream.addEventListener('src-added', () => {
// this.video.src = this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(this.stream.getWrStream())).toString();
//});
}
}

View File

@ -1,19 +0,0 @@
// This file includes polyfills needed by Angular 2 and is loaded before
// the app. You can add your own extra polyfills to this file.
import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/function';
import 'core-js/es6/parse-int';
import 'core-js/es6/parse-float';
import 'core-js/es6/number';
import 'core-js/es6/math';
import 'core-js/es6/string';
import 'core-js/es6/date';
import 'core-js/es6/array';
import 'core-js/es6/regexp';
import 'core-js/es6/map';
import 'core-js/es6/set';
import 'core-js/es6/reflect';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';

View File

@ -1 +0,0 @@
/* You can add global styles to this file, and also import other style files */

View File

@ -1,34 +0,0 @@
import './polyfills.ts';
import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/proxy.js';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async-test';
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
declare var __karma__: any;
declare var require: any;
// Prevent Karma from running prematurely.
__karma__.loaded = function () {};
Promise.all([
System.import('@angular/core/testing'),
System.import('@angular/platform-browser-dynamic/testing')
])
// First, initialize the Angular testing environment.
.then(([testing, testingBrowser]) => {
testing.getTestBed().initTestEnvironment(
testingBrowser.BrowserDynamicTestingModule,
testingBrowser.platformBrowserDynamicTesting()
);
})
// Then we find all the tests.
.then(() => require.context('./', true, /\.spec\.ts/))
// And load the modules.
.then(context => context.keys().map(context))
// Finally, start Karma to run the tests.
.then(__karma__.start, __karma__.error);

View File

@ -1,22 +0,0 @@
{
"compilerOptions": {
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"es2016",
"dom"
],
"outDir": "../out-tsc/app",
"target": "es5",
"module": "es2015",
"baseUrl": "",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}

View File

@ -1,26 +0,0 @@
{
"compilerOptions": {
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"es2016"
],
"outDir": "../out-tsc/spec",
"module": "commonjs",
"target": "es6",
"baseUrl": "",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts"
],
"include": [
"**/*.spec.ts"
]
}

View File

@ -1,5 +0,0 @@
// Typings reference file, see links for more information
// https://github.com/typings/typings
// https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html
declare var System: any;

View File

@ -1,7 +1,7 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "openvidu-ng-testapp"
"name": "openvidu-testapp"
},
"apps": [
{
@ -19,7 +19,8 @@
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"styles.css"
"styles.css",
"openvidu-theme.scss"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
@ -36,13 +37,16 @@
},
"lint": [
{
"project": "src/tsconfig.app.json"
"project": "src/tsconfig.app.json",
"exclude": "**/node_modules/**"
},
{
"project": "src/tsconfig.spec.json"
"project": "src/tsconfig.spec.json",
"exclude": "**/node_modules/**"
},
{
"project": "e2e/tsconfig.e2e.json"
"project": "e2e/tsconfig.e2e.json",
"exclude": "**/node_modules/**"
}
],
"test": {

View File

@ -3,6 +3,7 @@
# compiled output
/dist
/tmp
/out-tsc
# dependencies
/node_modules
@ -26,7 +27,7 @@
# misc
/.sass-cache
/connect.lock
/coverage/*
/coverage
/libpeerconnection.log
npm-debug.log
testem.log
@ -36,6 +37,6 @@ testem.log
/e2e/*.js
/e2e/*.map
#System Files
# System Files
.DS_Store
Thumbs.db

View File

@ -1,13 +1,14 @@
# OpenviduNgTestapp
# OpenviduTestApp
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-rc.1.
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.4.3.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
@ -20,7 +21,6 @@ Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
Before running the tests make sure you are serving the app via `ng serve`.
## Further help

View File

@ -0,0 +1,14 @@
import { AppPage } from './app.po';
describe('openvidu-testapp App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!');
});
});

View File

@ -1,6 +1,6 @@
import { browser, element, by } from 'protractor';
import { browser, by, element } from 'protractor';
export class OpenviduNgTestappPage {
export class AppPage {
navigateTo() {
return browser.get('/');
}

View File

@ -0,0 +1,14 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"baseUrl": "./",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View File

@ -1,5 +1,5 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
@ -15,15 +15,6 @@ module.exports = function (config) {
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
files: [
{ pattern: './src/test.ts', watched: false }
],
preprocessors: {
'./src/test.ts': ['@angular/cli']
},
mime: {
'text/x-typescript': ['ts','tsx']
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
@ -31,9 +22,7 @@ module.exports = function (config) {
angularCli: {
environment: 'dev'
},
reporters: config.angularCli && config.angularCli.codeCoverage
? ['progress', 'coverage-istanbul']
: ['progress', 'kjhtml'],
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,

View File

@ -0,0 +1,55 @@
{
"name": "openvidu-testapp",
"version": "0.0.0",
"license": "Apache-2.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "^4.4.3",
"@angular/cdk": "^2.0.0-beta.11",
"@angular/common": "^4.2.4",
"@angular/compiler": "^4.2.4",
"@angular/core": "^4.2.4",
"@angular/flex-layout": "^2.0.0-beta.9",
"@angular/forms": "^4.2.4",
"@angular/http": "^4.2.4",
"@angular/material": "^2.0.0-beta.11",
"@angular/platform-browser": "^4.2.4",
"@angular/platform-browser-dynamic": "^4.2.4",
"@angular/router": "^4.2.4",
"core-js": "^2.4.1",
"hammerjs": "^2.0.8",
"openvidu-browser": "1.1.0",
"openvidu-node-client": "1.1.0",
"rxjs": "^5.4.2",
"zone.js": "^0.8.14"
},
"devDependencies": {
"@angular/cli": "1.4.3",
"@angular/compiler-cli": "^4.2.4",
"@angular/language-service": "^4.2.4",
"@types/jasmine": "~2.5.53",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
"codelyzer": "~3.1.1",
"jasmine-core": "~2.6.2",
"jasmine-spec-reporter": "~4.1.0",
"karma": "~1.7.0",
"karma-chrome-launcher": "~2.1.1",
"karma-cli": "~1.0.1",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.1.2",
"ts-node": "~3.2.0",
"tslint": "~5.3.2",
"typescript": "~2.3.3"
}
}

View File

@ -19,12 +19,10 @@ exports.config = {
defaultTimeoutInterval: 30000,
print: function() {}
},
beforeLaunch: function() {
onPrepare() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
},
onPrepare() {
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

@ -0,0 +1,8 @@
#nav-img {
height: 32px;
vertical-align: middle;
}
main {
padding: 50px
}

View File

@ -0,0 +1,10 @@
<md-sidenav-container fullscreen>
<md-toolbar class="mat-elevation-z5" color="primary">
<div layout-align='center center' layout='column'>
<a routerLink="/"><img id="nav-img" src="assets/images/openvidu_vert_white_bg_trans_cropped.png"/> TestApp</a>
</div>
</md-toolbar>
<main>
<router-outlet></router-outlet>
</main>
</md-sidenav-container>

View File

@ -0,0 +1,27 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});

View File

@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private router: Router) { }
isVideoSessionUrl() {
return (this.router.url.substring(0, '/lesson/'.length) === '/lesson/');
}
}

View File

@ -0,0 +1,33 @@
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
MdButtonModule,
MdIconModule,
MdCheckboxModule,
MdCardModule,
MdInputModule,
MdProgressSpinnerModule,
MdTooltipModule,
MdDialogModule,
MdToolbarModule,
MdTabsModule,
MdSlideToggleModule
} from '@angular/material';
@NgModule({
exports: [
BrowserAnimationsModule,
MdButtonModule,
MdIconModule,
MdCheckboxModule,
MdCardModule,
MdInputModule,
MdProgressSpinnerModule,
MdTooltipModule,
MdDialogModule,
MdToolbarModule,
MdTabsModule,
MdSlideToggleModule
],
})
export class AppMaterialModule { }

View File

@ -0,0 +1,32 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FlexLayoutModule } from '@angular/flex-layout';
import { AppMaterialModule } from './app.material.module';
import { routing } from './app.routing';
import { AppComponent } from './app.component';
import { DashboardComponent } from './components/dashboard/dashboard.component';
import { SessionComponent } from './components/session/session.component';
import { OpenviduRestService } from './services/openvidu-rest.service';
@NgModule({
declarations: [
AppComponent,
DashboardComponent,
SessionComponent
],
imports: [
BrowserModule,
FormsModule,
BrowserAnimationsModule,
AppMaterialModule,
FlexLayoutModule,
routing
],
providers: [OpenviduRestService],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,18 @@
import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { DashboardComponent } from './components/dashboard/dashboard.component';
import { SessionComponent } from './components/session/session.component';
const appRoutes: Routes = [
{
path: '',
component: DashboardComponent
},
{
path: 'session/:session-name',
component: SessionComponent
}
];
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);

View File

@ -0,0 +1,41 @@
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
MdButtonModule,
MdIconModule,
MdCheckboxModule,
MdCardModule,
MdInputModule,
MdProgressSpinnerModule,
MdTooltipModule,
MdDialogModule,
MdSlideToggleModule
} from '@angular/material';
@NgModule({
imports: [
BrowserAnimationsModule,
MdButtonModule,
MdIconModule,
MdCheckboxModule,
MdCardModule,
MdInputModule,
MdProgressSpinnerModule,
MdTooltipModule,
MdDialogModule,
MdSlideToggleModule
],
exports: [
BrowserAnimationsModule,
MdButtonModule,
MdIconModule,
MdCheckboxModule,
MdCardModule,
MdInputModule,
MdProgressSpinnerModule,
MdTooltipModule,
MdDialogModule,
MdSlideToggleModule
],
})
export class AppMaterialModule { }

View File

@ -0,0 +1,93 @@
#join-dialog h1 {
color: #4d4d4d;
font-weight: bold;
text-align: center;
}
#img-div {
text-align: center;
position: absolute;
top: 19%;
left: 50%;
transform: translate(-50%, -50%);
}
#img-div img {
height: 15%;
}
#join-dialog label {
color: #0088aa;
}
#join-dialog input.btn {
margin-top: 15px;
}
#session-header {
margin-bottom: 20px;
}
#session-title {
display: inline-block;
}
#buttonLeaveSession {
float: right;
margin-top: 20px;
}
#video-container video {
position: relative;
float: left;
cursor: pointer;
}
#video-container p {
display: inline-block;
background: #f8f8f8;
padding-right: 5px;
padding-left: 5px;
color: #777777;
font-weight: bold;
border-bottom-right-radius: 4px;
}
video {
width: 100%;
height: auto;
}
#main-video p {
position: absolute;
display: inline-block;
background: #f8f8f8;
padding-right: 5px;
padding-left: 5px;
left: 0;
font-size: 22px;
color: #777777;
font-weight: bold;
border-bottom-right-radius: 4px;
}
#main-video video {
cursor: initial;
}
#session img {
width: 100%;
height: auto;
display: inline-block;
object-fit: contain;
vertical-align: baseline;
}
#session #video-container img {
position: relative;
float: left;
width: 50%;
cursor: pointer;
object-fit: cover;
height: 180px;
}

View File

@ -0,0 +1,47 @@
<md-tab-group class="demo-tab-group" dynamicHeight="true">
<md-tab id="audio-video" label="Audio + Video">
<div class="demo-tab-content">
</div>
</md-tab>
<md-tab id="audio-only" label="Audio only">
<div class="demo-tab-content">
</div>
</md-tab>
<md-tab id="video-only" label="Video only">
</md-tab>
<md-tab id="screen-audio" label="Screen + Audio">
<div class="demo-tab-content">
</div>
</md-tab>
<md-tab id="screen-only" label="Screen only">
</md-tab>
<md-tab id="api-rest" label="API REST">
<button md-raised-button color="primary" (click)="getSessionId()">Get sessionId</button>
<button md-raised-button color="primary" (click)="getToken()">Get token</button>
</md-tab>
</md-tab-group>
<!--<div id="join">
<div id="img-div"><img src="assets/images/openvidu_grey_bg_transp_cropped.png" /></div>
<div id="join-dialog" class="jumbotron vertical-center">
<h1>Join a video session</h1>
<form class="form-group" (submit)="joinSession()">
<p>
<label>Participant</label>
<input class="form-control" type="text" id="clientData" name="clientData" [(ngModel)]="clientData" required>
</p>
<p>
<label>Session</label>
<input class="form-control" type="text" id="sessionName" name="sessionName" [(ngModel)]="sessionName" required>
</p>
<p class="text-center">
<input class="btn btn-lg btn-success" type="submit" name="commit" value="Join!">
</p>
</form>
</div>
</div>-->

View File

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

View File

@ -0,0 +1,45 @@
import { Component, OnInit } from '@angular/core';
import { OpenviduRestService } from '../../services/openvidu-rest.service';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
// Join form
sessionName: string;
clientData: string;
constructor(private openviduRestService: OpenviduRestService) {
this.generateSessionInfo();
}
ngOnInit() { }
private generateSessionInfo() {
this.sessionName = 'TestSession';
this.clientData = 'RandomClient' + Math.floor(Math.random() * 100);
}
private getSessionId() {
this.openviduRestService.getSessionId()
.then((sessionId) => {
alert(sessionId);
})
.catch((error) => {
console.error('Error getting a sessionId', error);
});
}
private getToken() {
this.openviduRestService.getToken()
.then((token) => {
alert(token);
})
.catch((error) => {
console.error('Error getting a token', error);
});
}
}

View File

@ -0,0 +1,11 @@
<div *ngIf="session" id="session">
<div id="session-header">
<h1 id="session-title">{{sessionName}}</h1>
<input class="btn btn-large btn-danger" type="button" id="buttonLeaveSession" (click)="leaveSession()" value="Leave session">
</div>
<div id="main-video" class="col-md-6">
<p></p>
<video #mainVideoElement autoplay></video>
</div>
<div id="video-container" class="col-md-6"></div>
</div>

View File

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

View File

@ -0,0 +1,135 @@
import { OpenVidu, Session } from 'openvidu-browser';
import { Component, ElementRef, ViewChild, HostListener, OnDestroy } from '@angular/core';
declare var $: any;
@Component({
selector: 'app-session',
templateUrl: './session.component.html',
styleUrls: ['./session.component.css']
})
export class SessionComponent implements OnDestroy {
@ViewChild('mainVideoElement') elementRef: ElementRef;
mainVideoElement: HTMLVideoElement;
// OpenVidu objects
OV: OpenVidu;
session: Session;
// Join form
sessionName: string;
clientData: string;
constructor() { }
@HostListener('window:beforeunload')
beforeunloadHandler() {
// On window closed leave session
this.leaveSession();
}
ngOnDestroy() {
// On component destroyed leave session
this.leaveSession();
}
joinSession() {
this.OV = new OpenVidu();
this.session = this.OV.initSession('wss://' + location.hostname + ':8443/' + this.sessionName + '?secret=MY_SECRET');
this.mainVideoElement = this.elementRef.nativeElement;
this.session.on('streamCreated', (event) => {
const subscriber = this.session.subscribe(event.stream, 'video-container');
subscriber.on('videoElementCreated', (e) => {
this.appendUserData(e.element, subscriber.stream.connection);
});
});
this.session.on('streamDestroyed', (event) => {
this.removeUserData(event.stream.connection);
});
this.session.connect(null, this.clientData, (error) => {
if (!error) {
const publisher = this.OV.initPublisher('video-container', {
audio: true,
video: true,
quality: 'MEDIUM'
});
publisher.on('videoElementCreated', (event) => {
this.initMainVideo(event.element, this.clientData);
this.appendUserData(event.element, this.clientData);
event.element['muted'] = true;
});
this.session.publish(publisher);
} else {
console.log('There was an error connecting to the session:', error.code, error.message);
}
});
return false;
}
leaveSession() {
if (this.OV) {
this.session.disconnect();
}
this.removeAllUserData();
this.session = null;
this.OV = null;
}
private appendUserData(videoElement, connection) {
let userData;
let nodeId;
if (typeof connection === 'string') {
userData = connection;
nodeId = connection;
} else {
userData = JSON.parse(connection.data).clientData;
nodeId = connection.connectionId;
}
const dataNode = document.createElement('div');
dataNode.className = 'data-node';
dataNode.id = 'data-' + nodeId;
dataNode.innerHTML = '<p>' + userData + '</p>';
videoElement.parentNode.insertBefore(dataNode, videoElement.nextSibling);
this.addClickListener(videoElement, userData);
}
private removeUserData(connection) {
const dataNode = $('#data-' + connection.connectionId);
dataNode.parentNode.removeChild(dataNode);
}
private removeAllUserData() {
const nicknameElements = $('.data-node');
while (nicknameElements[0]) {
nicknameElements[0].parentNode.removeChild(nicknameElements[0]);
}
}
private addClickListener(videoElement: HTMLVideoElement, userData) {
videoElement.addEventListener('click', () => {
const mainUserData = $('#main-video p');
if (this.mainVideoElement.srcObject !== videoElement.srcObject) {
mainUserData.innerHTML = userData;
this.mainVideoElement.srcObject = videoElement.srcObject;
}
});
}
private initMainVideo(videoElement: HTMLVideoElement, userData) {
this.mainVideoElement.srcObject = videoElement.srcObject;
$('#main-video p').innerHTML = userData;
this.mainVideoElement['muted'] = true;
}
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { OpenviduRestService } from './openvidu-rest.service';
describe('OpenviduRestService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [OpenviduRestService]
});
});
it('should be created', inject([OpenviduRestService], (service: OpenviduRestService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,38 @@
import { Injectable } from '@angular/core';
import {
OpenVidu as OpenViduAPI,
Session as SessionAPI,
TokenOptions as TokenOptionsAPI,
OpenViduRole as OpenViduRoleAPI
} from 'openvidu-node-client';
import { environment } from '../../environments/environment';
@Injectable()
export class OpenviduRestService {
constructor() { }
getSessionId(): Promise<String> {
const OV = new OpenViduAPI(environment.OPENVIDU_URL, environment.OPENVIDU_SECRET);
const session = OV.createSession();
return new Promise(resolve => {
session.getSessionId((sessionId) => {
resolve(sessionId);
});
});
}
getToken(): Promise<String> {
const OV = new OpenViduAPI(environment.OPENVIDU_URL, environment.OPENVIDU_SECRET);
const session = OV.createSession();
return new Promise(resolve => {
let tokenOptions: TokenOptionsAPI;
session.generateToken((token) => {
resolve(token);
});
});
}
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { SessionConfService } from './session-conf.service';
describe('SessionConfService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [SessionConfService]
});
});
it('should be created', inject([SessionConfService], (service: SessionConfService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,17 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class SessionConfService {
private conf$ = new Subject();
getConf() {
return this.conf$;
}
updateConf(configuration: any) {
this.conf$.next(configuration);
}
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,8 +1,10 @@
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `angular-cli.json`.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = {
production: false
production: false,
OPENVIDU_URL: 'https://localhost:8443',
OPENVIDU_SECRET: 'MY_SECRET'
};

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1,14 +1,20 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>OpenviduNg2Example</title>
<title>OpenVidu TestApp</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>

View File

@ -1,12 +1,14 @@
import './polyfills.ts';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { AppModule } from './app/';
import 'hammerjs';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

View File

@ -0,0 +1,57 @@
@import '~@angular/material/theming';
// Plus imports for other components in your app.
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat-core();
$mat-openvidu: (
50: #eaeaea,
100: #cacaca,
200: #a6a6a6,
300: #828282,
400: #686868,
500: #4d4d4d,
600: #464646,
700: #3d3d3d,
800: #343434,
900: #252525,
A100: #83E9B1,
A200: #06D362,
A400: #04C850,
A700: #02B734,
contrast: (
50 : #fff9e0,
100 : #fff0b3,
200 : #ffe680,
300 : #ffdb4d,
400 : #ffd426,
500 : #ffcc00,
600 : #ffc700,
700 : #ffc000,
800 : #ffb900,
900 : #ffad00,
A100 : #ffffff,
A200 : #fffaf2,
A400 : #ffe8bf,
A700 : #ffdfa6,
)
);
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue.
$openvidu-primary: mat-palette($mat-openvidu);
$openvidu-accent: mat-palette($mat-openvidu, A200, A100, A400);
// The warn palette is optional (defaults to red).
$openvidu-warn: mat-palette($mat-red);
// Create the theme object (a Sass map containing all of the palettes).
$openvidu-theme: mat-light-theme($openvidu-primary, $openvidu-accent, $openvidu-warn);
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include angular-material-theme($openvidu-theme);

View File

@ -0,0 +1,72 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** Evergreen browsers require these. **/
import 'core-js/es6/reflect';
import 'core-js/es7/reflect';
/**
* Required to support Web Animations `@angular/platform-browser/animations`.
* Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
**/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/***************************************************************************************************
* Zone JS is required by Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
/**
* Date, currency, decimal and percent pipes.
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
*/
// import 'intl'; // Run `npm install --save intl`.
/**
* Need to import at least one locale-data with intl.
*/
// import 'intl/locale-data/jsonp/en';

View File

@ -0,0 +1,79 @@
* {
font-family: 'Exo 2', sans-serif;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0.05);
}
a {
color: inherit;
cursor: inherit;
text-decoration: inherit;
}
/* Elevation */
.z-depth-1 {
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
}
.z-depth-1-half {
box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.14), 0 1px 7px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -1px rgba(0, 0, 0, 0.2);
}
.z-depth-2 {
box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
}
.z-depth-3 {
box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.3);
}
.z-depth-4 {
box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.3);
}
.z-depth-5 {
box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.3);
}
/* Colors of Deep Purple Material Theme */
.back-primary {
background: #673ab7 !important;
}
.back-accent {
background: #ffd740;
}
.back-warn {
background: #f44336;
}
.back-secondary {
background: #D1C4E9;
}
.color-primary {
color: #673ab7 !important;
}
.color-accent {
color: #ffd740;
}
.color-warn {
color: #f44336;
}
.color-secondary {
color: #D1C4E9;
}

View File

@ -0,0 +1,32 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/proxy.js';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async-test';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
declare const __karma__: any;
declare const require: any;
// Prevent Karma from running prematurely.
__karma__.loaded = function () {};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
// Finally, start Karma to run the tests.
__karma__.start();

View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
"module": "es2015",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}

View File

@ -0,0 +1,20 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"baseUrl": "./",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

5
openvidu-testapp/src/typings.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
id: string;
}

View File

@ -8,8 +8,11 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2016",
"es2017",
"dom"
]
}

View File

@ -3,6 +3,7 @@
"node_modules/codelyzer"
],
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
@ -12,7 +13,10 @@
"curly": true,
"eofline": true,
"forin": true,
"import-blacklist": [true, "rxjs"],
"import-blacklist": [
true,
"rxjs"
],
"import-spacing": true,
"indent": [
true,
@ -27,8 +31,14 @@
"member-access": false,
"member-ordering": [
true,
"static-before-instance",
"variables-before-functions"
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
@ -42,16 +52,22 @@
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-variable": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [true, "ignore-params"],
"no-inferrable-types": [
true,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
@ -70,6 +86,7 @@
],
"radix": true,
"semicolon": [
true,
"always"
],
"triple-equals": [
@ -97,9 +114,18 @@
"check-separator",
"check-type"
],
"directive-selector": [true, "attribute", "app", "camelCase"],
"component-selector": [true, "element", "app", "kebab-case"],
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
@ -109,8 +135,6 @@
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true,
"no-access-missing-member": true,
"templates-use-public": true,
"invoke-injectable": true
}
}