diff --git a/openvidu-server/src/angular/frontend/package.json b/openvidu-server/src/angular/frontend/package.json
index 1d053754..d75b373d 100644
--- a/openvidu-server/src/angular/frontend/package.json
+++ b/openvidu-server/src/angular/frontend/package.json
@@ -19,7 +19,7 @@
"@angular/flex-layout": "^2.0.0-beta.8",
"@angular/forms": "^4.0.0",
"@angular/http": "^4.0.0",
- "@angular/material": "2.0.0-beta.5",
+ "@angular/material": "2.0.0-beta.7",
"@angular/platform-browser": "^4.0.0",
"@angular/platform-browser-dynamic": "^4.0.0",
"@angular/router": "^4.0.0",
diff --git a/openvidu-server/src/angular/frontend/src/app/app.material.module.ts b/openvidu-server/src/angular/frontend/src/app/app.material.module.ts
index f3f3b192..1da1d80c 100644
--- a/openvidu-server/src/angular/frontend/src/app/app.material.module.ts
+++ b/openvidu-server/src/angular/frontend/src/app/app.material.module.ts
@@ -4,11 +4,29 @@ import {
MdButtonModule,
MdCheckboxModule,
MdCardModule,
- MdInputModule
+ MdInputModule,
+ MdProgressSpinnerModule,
+ MdTooltipModule
} from '@angular/material';
@NgModule({
- imports: [BrowserAnimationsModule, MdButtonModule, MdCheckboxModule, MdCardModule, MdInputModule],
- exports: [BrowserAnimationsModule, MdButtonModule, MdCheckboxModule, MdCardModule, MdInputModule],
+ imports: [
+ BrowserAnimationsModule,
+ MdButtonModule,
+ MdCheckboxModule,
+ MdCardModule,
+ MdInputModule,
+ MdProgressSpinnerModule,
+ MdTooltipModule
+ ],
+ exports: [
+ BrowserAnimationsModule,
+ MdButtonModule,
+ MdCheckboxModule,
+ MdCardModule,
+ MdInputModule,
+ MdProgressSpinnerModule,
+ MdTooltipModule
+ ],
})
export class AppMaterialModule { }
diff --git a/openvidu-server/src/angular/frontend/src/app/components/dashboard/dashboard.component.css b/openvidu-server/src/angular/frontend/src/app/components/dashboard/dashboard.component.css
index 70571c96..a7a78a9f 100644
--- a/openvidu-server/src/angular/frontend/src/app/components/dashboard/dashboard.component.css
+++ b/openvidu-server/src/angular/frontend/src/app/components/dashboard/dashboard.component.css
@@ -3,7 +3,7 @@
}
#log {
- height: 100%;
+ height: 90%;
}
#log-content {
@@ -17,6 +17,195 @@ ul {
margin: 0;
}
-md-card-title a {
- float: right;
+button.mat-raised-button {
+ text-transform: uppercase;
+ float: right;
+}
+
+md-card-title button.blue {
+ color: #ffffff;
+ background-color: #0088aa;
+}
+
+md-card-title button.yellow {
+ color: rgba(0, 0, 0, 0.87);
+ background-color: #ffcc00;
+}
+
+md-spinner {
+ position: absolute;
+ top: 55%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+#tick-div {
+ width: 100px;
+ height: 100px;
+ z-index: 1;
+ position: absolute;
+ top: 55%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+#tooltip-tick {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ z-index: 2;
+}
+
+.circ {
+ opacity: 0;
+ stroke-dasharray: 130;
+ stroke-dashoffset: 130;
+ -webkit-transition: all 1s;
+ -moz-transition: all 1s;
+ -ms-transition: all 1s;
+ -o-transition: all 1s;
+ transition: all 1s;
+}
+
+.tick {
+ stroke-dasharray: 50;
+ stroke-dashoffset: 50;
+ -webkit-transition: stroke-dashoffset 1s 0.5s ease-out;
+ -moz-transition: stroke-dashoffset 1s 0.5s ease-out;
+ -ms-transition: stroke-dashoffset 1s 0.5s ease-out;
+ -o-transition: stroke-dashoffset 1s 0.5s ease-out;
+ transition: stroke-dashoffset 1s 0.5s ease-out;
+}
+
+.drawn+svg .path {
+ opacity: 1;
+ stroke-dashoffset: 0;
+}
+
+
+/* Pure CSS loader */
+
+#loader {
+ width: 100px;
+ height: 100px;
+ z-index: 1;
+ position: absolute;
+ top: 55%;
+ left: 50%;
+ -webkit-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+}
+
+#loader * {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+#loader ::after {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+#loader ::before {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.loader-1 {
+ height: 100px;
+ width: 100px;
+ -webkit-animation: loader-1-1 4.8s linear infinite;
+ animation: loader-1-1 4.8s linear infinite;
+}
+
+@-webkit-keyframes loader-1-1 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ }
+}
+
+@keyframes loader-1-1 {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+.loader-1 span {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ margin: auto;
+ height: 100px;
+ width: 100px;
+ clip: rect(0, 100px, 100px, 50px);
+ -webkit-animation: loader-1-2 1.2s linear infinite;
+ animation: loader-1-2 1.2s linear infinite;
+}
+
+@-webkit-keyframes loader-1-2 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(220deg);
+ }
+}
+
+@keyframes loader-1-2 {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(220deg);
+ }
+}
+
+.loader-1 span::after {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ margin: auto;
+ height: 100px;
+ width: 100px;
+ clip: rect(0, 100px, 100px, 50px);
+ border: 8px solid #4d4d4d;
+ border-radius: 50%;
+ -webkit-animation: loader-1-3 1.2s cubic-bezier(0.770, 0.000, 0.175, 1.000) infinite;
+ animation: loader-1-3 1.2s cubic-bezier(0.770, 0.000, 0.175, 1.000) infinite;
+}
+
+@-webkit-keyframes loader-1-3 {
+ 0% {
+ -webkit-transform: rotate(-140deg);
+ }
+ 50% {
+ -webkit-transform: rotate(-160deg);
+ }
+ 100% {
+ -webkit-transform: rotate(140deg);
+ }
+}
+
+@keyframes loader-1-3 {
+ 0% {
+ transform: rotate(-140deg);
+ }
+ 50% {
+ transform: rotate(-160deg);
+ }
+ 100% {
+ transform: rotate(140deg);
+ }
}
\ No newline at end of file
diff --git a/openvidu-server/src/angular/frontend/src/app/components/dashboard/dashboard.component.html b/openvidu-server/src/angular/frontend/src/app/components/dashboard/dashboard.component.html
index e787decf..4179dc3a 100644
--- a/openvidu-server/src/angular/frontend/src/app/components/dashboard/dashboard.component.html
+++ b/openvidu-server/src/angular/frontend/src/app/components/dashboard/dashboard.component.html
@@ -16,10 +16,27 @@
- Test the connectionTest
+ Test the connection
+
-
-
+
diff --git a/openvidu-server/src/angular/frontend/src/app/components/dashboard/dashboard.component.ts b/openvidu-server/src/angular/frontend/src/app/components/dashboard/dashboard.component.ts
index c5305985..e243b2f1 100644
--- a/openvidu-server/src/angular/frontend/src/app/components/dashboard/dashboard.component.ts
+++ b/openvidu-server/src/angular/frontend/src/app/components/dashboard/dashboard.component.ts
@@ -1,22 +1,31 @@
-import { Component, OnInit, AfterViewChecked, ViewChild, ElementRef } from '@angular/core';
+import { Component, OnInit, AfterViewChecked, ViewChild, ElementRef, HostListener, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { InfoService } from '../../services/info.service';
-import { OpenVidu } from 'openvidu-browser';
+import { OpenVidu, Session } from 'openvidu-browser';
+
+declare const $;
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css'],
})
-export class DashboardComponent implements OnInit, AfterViewChecked {
+export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked {
@ViewChild('scrollMe') private myScrollContainer: ElementRef;
infoSubscription: Subscription;
info = [];
+ session: Session;
+
+ testStatus = 'DISCONNECTED';
+ testButton = 'Test';
+ tickClass = 'trigger';
+ showSpinner = false;
+
constructor(private infoService: InfoService) {
// Subscription to info updated event raised by InfoService
@@ -30,32 +39,81 @@ export class DashboardComponent implements OnInit, AfterViewChecked {
}
+ @HostListener('window:beforeunload')
+ beforeunloadHandler() {
+ // On window closed leave test session
+ if (this.session) {
+ this.endTestVideo();
+ }
+ }
+
+ ngOnDestroy() {
+ // On component destroyed leave test session
+ if (this.session) {
+ this.endTestVideo();
+ }
+ }
+
ngAfterViewChecked() {
this.scrollToBottom();
}
+ toggleTestVideo() {
+ if (!this.session) {
+ this.testVideo();
+ } else {
+ this.endTestVideo();
+ }
+ }
+
testVideo() {
let OV = new OpenVidu();
- let session = OV.initSession('wss://' + location.hostname + ':8443/testSession');
+ this.session = OV.initSession('wss://' + location.hostname + ':8443/testSession');
- session.on('streamCreated', (event) => {
- session.subscribe(event.stream, 'mirrored-video');
+ this.session.on('streamCreated', (event) => {
+ this.session.subscribe(event.stream, 'mirrored-video');
});
- session.connect('token', (error) => {
+ this.testStatus = 'CONNECTING';
+ this.testButton = 'Testing...';
+
+ this.session.connect('token', (error) => {
if (!error) {
- let publisher = OV.initPublisher('local-video', {
+
+ this.testStatus = 'CONNECTED';
+
+ const publisherRemote = OV.initPublisher('mirrored-video', {
audio: true,
video: true,
quality: 'MEDIUM'
});
- publisher.stream.subscribeToMyRemote();
- session.publish(publisher);
+ publisherRemote.on('videoElementCreated', (video) => {
+
+ this.showSpinner = true;
+
+ video.element.addEventListener('playing', () => {
+ console.warn('PLAYING!!');
+ this.testButton = 'End test';
+ this.testStatus = 'PLAYING';
+ this.showSpinner = false;
+ });
+ });
+
+ publisherRemote.stream.subscribeToMyRemote();
+ this.session.publish(publisherRemote);
}
});
}
+ endTestVideo() {
+ this.session.disconnect();
+ this.session = null;
+ this.testStatus = 'DISCONNECTED';
+ this.testButton = 'Test';
+ this.showSpinner = false;
+ }
+
scrollToBottom(): void {
try {
this.myScrollContainer.nativeElement.scrollTop = this.myScrollContainer.nativeElement.scrollHeight;
diff --git a/openvidu-server/src/angular/frontend/src/index.html b/openvidu-server/src/angular/frontend/src/index.html
index 452211bf..2a1fabf7 100644
--- a/openvidu-server/src/angular/frontend/src/index.html
+++ b/openvidu-server/src/angular/frontend/src/index.html
@@ -9,6 +9,9 @@
+
+
diff --git a/openvidu-server/src/angular/frontend/src/styles.css b/openvidu-server/src/angular/frontend/src/styles.css
index 55bb2ca9..c709b019 100644
--- a/openvidu-server/src/angular/frontend/src/styles.css
+++ b/openvidu-server/src/angular/frontend/src/styles.css
@@ -15,3 +15,11 @@ main {
li {
list-style: none;
}
+
+video {
+ width: 100%;
+}
+
+.mat-spinner path {
+ stroke: #4d4d4d;
+}
\ No newline at end of file
diff --git a/openvidu-server/src/main/resources/static/index.html b/openvidu-server/src/main/resources/static/index.html
index a456f898..00414299 100644
--- a/openvidu-server/src/main/resources/static/index.html
+++ b/openvidu-server/src/main/resources/static/index.html
@@ -9,6 +9,9 @@
+
+
diff --git a/openvidu-server/src/main/resources/static/inline.bundle.js.map b/openvidu-server/src/main/resources/static/inline.bundle.js.map
index 59ceec1d..9913e2bb 100644
--- a/openvidu-server/src/main/resources/static/inline.bundle.js.map
+++ b/openvidu-server/src/main/resources/static/inline.bundle.js.map
@@ -1 +1 @@
-{"version":3,"sources":["webpack:///webpack/bootstrap 78c33da7155d58071d00"],"names":[],"mappings":";AAAA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAQ,oBAAoB;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAY,2BAA2B;AACvC;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,YAAI;AACJ;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA,mDAA2C,cAAc;;AAEzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;AAEA;AACA,kDAA0C,oBAAoB,WAAW","file":"inline.bundle.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [], result;\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId])\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);\n \t\twhile(resolves.length)\n \t\t\tresolves.shift()();\n \t\tif(executeModules) {\n \t\t\tfor(i=0; i < executeModules.length; i++) {\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = executeModules[i]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// objects to store loaded and loading chunks\n \tvar installedChunks = {\n \t\t4: 0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n \t// This file contains only the entry chunk.\n \t// The chunk loading function for additional chunks\n \t__webpack_require__.e = function requireEnsure(chunkId) {\n \t\tif(installedChunks[chunkId] === 0)\n \t\t\treturn Promise.resolve();\n\n \t\t// an Promise means \"currently loading\".\n \t\tif(installedChunks[chunkId]) {\n \t\t\treturn installedChunks[chunkId][2];\n \t\t}\n \t\t// start chunk loading\n \t\tvar head = document.getElementsByTagName('head')[0];\n \t\tvar script = document.createElement('script');\n \t\tscript.type = 'text/javascript';\n \t\tscript.charset = 'utf-8';\n \t\tscript.async = true;\n \t\tscript.timeout = 120000;\n\n \t\tif (__webpack_require__.nc) {\n \t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n \t\t}\n \t\tscript.src = __webpack_require__.p + \"\" + chunkId + \".chunk.js\";\n \t\tvar timeout = setTimeout(onScriptComplete, 120000);\n \t\tscript.onerror = script.onload = onScriptComplete;\n \t\tfunction onScriptComplete() {\n \t\t\t// avoid mem leaks in IE.\n \t\t\tscript.onerror = script.onload = null;\n \t\t\tclearTimeout(timeout);\n \t\t\tvar chunk = installedChunks[chunkId];\n \t\t\tif(chunk !== 0) {\n \t\t\t\tif(chunk) chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));\n \t\t\t\tinstalledChunks[chunkId] = undefined;\n \t\t\t}\n \t\t};\n\n \t\tvar promise = new Promise(function(resolve, reject) {\n \t\t\tinstalledChunks[chunkId] = [resolve, reject];\n \t\t});\n \t\tinstalledChunks[chunkId][2] = promise;\n\n \t\thead.appendChild(script);\n \t\treturn promise;\n \t};\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 78c33da7155d58071d00"],"sourceRoot":""}
\ No newline at end of file
+{"version":3,"sources":["webpack:///webpack/bootstrap 73a01c6c88225c8a7f2f"],"names":[],"mappings":";AAAA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAQ,oBAAoB;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAY,2BAA2B;AACvC;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,YAAI;AACJ;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA,mDAA2C,cAAc;;AAEzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;AAEA;AACA,kDAA0C,oBAAoB,WAAW","file":"inline.bundle.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [], result;\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId])\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);\n \t\twhile(resolves.length)\n \t\t\tresolves.shift()();\n \t\tif(executeModules) {\n \t\t\tfor(i=0; i < executeModules.length; i++) {\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = executeModules[i]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// objects to store loaded and loading chunks\n \tvar installedChunks = {\n \t\t4: 0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n \t// This file contains only the entry chunk.\n \t// The chunk loading function for additional chunks\n \t__webpack_require__.e = function requireEnsure(chunkId) {\n \t\tif(installedChunks[chunkId] === 0)\n \t\t\treturn Promise.resolve();\n\n \t\t// an Promise means \"currently loading\".\n \t\tif(installedChunks[chunkId]) {\n \t\t\treturn installedChunks[chunkId][2];\n \t\t}\n \t\t// start chunk loading\n \t\tvar head = document.getElementsByTagName('head')[0];\n \t\tvar script = document.createElement('script');\n \t\tscript.type = 'text/javascript';\n \t\tscript.charset = 'utf-8';\n \t\tscript.async = true;\n \t\tscript.timeout = 120000;\n\n \t\tif (__webpack_require__.nc) {\n \t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n \t\t}\n \t\tscript.src = __webpack_require__.p + \"\" + chunkId + \".chunk.js\";\n \t\tvar timeout = setTimeout(onScriptComplete, 120000);\n \t\tscript.onerror = script.onload = onScriptComplete;\n \t\tfunction onScriptComplete() {\n \t\t\t// avoid mem leaks in IE.\n \t\t\tscript.onerror = script.onload = null;\n \t\t\tclearTimeout(timeout);\n \t\t\tvar chunk = installedChunks[chunkId];\n \t\t\tif(chunk !== 0) {\n \t\t\t\tif(chunk) chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));\n \t\t\t\tinstalledChunks[chunkId] = undefined;\n \t\t\t}\n \t\t};\n\n \t\tvar promise = new Promise(function(resolve, reject) {\n \t\t\tinstalledChunks[chunkId] = [resolve, reject];\n \t\t});\n \t\tinstalledChunks[chunkId][2] = promise;\n\n \t\thead.appendChild(script);\n \t\treturn promise;\n \t};\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 73a01c6c88225c8a7f2f"],"sourceRoot":""}
\ No newline at end of file
diff --git a/openvidu-server/src/main/resources/static/main.bundle.js b/openvidu-server/src/main/resources/static/main.bundle.js
index fc69d06a..2f50494c 100644
--- a/openvidu-server/src/main/resources/static/main.bundle.js
+++ b/openvidu-server/src/main/resources/static/main.bundle.js
@@ -1,12 +1,554 @@
webpackJsonp([1,4],{
+/***/ 104:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", { value: true });
+var EventEmitter = __webpack_require__(52);
+var kurentoUtils = __webpack_require__(275);
+var adapter = __webpack_require__(173);
+if (window) {
+ window["adapter"] = adapter;
+}
+function jq(id) {
+ return id.replace(/(@|:|\.|\[|\]|,)/g, "\\$1");
+}
+function show(id) {
+ document.getElementById(jq(id)).style.display = 'block';
+}
+function hide(id) {
+ document.getElementById(jq(id)).style.display = 'none';
+}
+var Stream = (function () {
+ function Stream(openVidu, local, room, options) {
+ var _this = this;
+ this.openVidu = openVidu;
+ this.local = local;
+ this.room = room;
+ this.ee = new EventEmitter();
+ this.videoElements = [];
+ this.elements = [];
+ this.showMyRemote = false;
+ this.localMirrored = false;
+ this.chanId = 0;
+ this.dataChannelOpened = false;
+ this.audioOnly = false;
+ this.isReady = false;
+ this.isVideoELementCreated = false;
+ this.accessIsAllowed = false;
+ this.accessIsDenied = false;
+ if (options.id) {
+ this.id = options.id;
+ }
+ else {
+ this.id = "webcam";
+ }
+ this.connection = options.connection;
+ this.recvVideo = options.recvVideo;
+ this.recvAudio = options.recvAudio;
+ this.dataChannel = options.data || false;
+ this.sendVideo = options.video;
+ this.sendAudio = options.audio;
+ this.mediaConstraints = options.mediaConstraints;
+ this.audioOnly = options.audioOnly || false;
+ this.addEventListener('src-added', function (srcEvent) {
+ _this.videoSrc = srcEvent.src;
+ if (_this.video)
+ _this.video.src = srcEvent.src;
+ console.warn("Videosrc [" + srcEvent.src + "] added to stream [" + _this.getId() + "]");
+ });
+ }
+ Stream.prototype.emitSrcEvent = function (wrstream) {
+ this.ee.emitEvent('src-added', [{
+ src: URL.createObjectURL(wrstream)
+ }]);
+ };
+ Stream.prototype.emitStreamReadyEvent = function () {
+ this.ee.emitEvent('stream-ready'), [{}];
+ };
+ Stream.prototype.getVideoSrc = function () {
+ return this.videoSrc;
+ };
+ Stream.prototype.removeVideo = function (parentElement) {
+ if (typeof parentElement === "string") {
+ document.getElementById(parentElement).removeChild(this.video);
+ }
+ else if (parentElement instanceof Element) {
+ parentElement.removeChild(this.video);
+ }
+ else if (!parentElement) {
+ if (document.getElementById(this.parentId)) {
+ document.getElementById(this.parentId).removeChild(this.video);
+ }
+ }
+ };
+ Stream.prototype.getVideoElement = function () {
+ return this.video;
+ };
+ Stream.prototype.setVideoElement = function (video) {
+ this.video = video;
+ };
+ Stream.prototype.getRecvVideo = function () {
+ return this.recvVideo;
+ };
+ Stream.prototype.getRecvAudio = function () {
+ return this.recvAudio;
+ };
+ Stream.prototype.subscribeToMyRemote = function () {
+ this.showMyRemote = true;
+ };
+ Stream.prototype.displayMyRemote = function () {
+ return this.showMyRemote;
+ };
+ Stream.prototype.mirrorLocalStream = function (wr) {
+ this.showMyRemote = true;
+ this.localMirrored = true;
+ if (wr) {
+ this.wrStream = wr;
+ this.emitSrcEvent(this.wrStream);
+ }
+ };
+ Stream.prototype.isLocalMirrored = function () {
+ return this.localMirrored;
+ };
+ Stream.prototype.getChannelName = function () {
+ return this.getId() + '_' + this.chanId++;
+ };
+ Stream.prototype.isDataChannelEnabled = function () {
+ return this.dataChannel;
+ };
+ Stream.prototype.isDataChannelOpened = function () {
+ return this.dataChannelOpened;
+ };
+ Stream.prototype.onDataChannelOpen = function (event) {
+ console.log('Data channel is opened');
+ this.dataChannelOpened = true;
+ };
+ Stream.prototype.onDataChannelClosed = function (event) {
+ console.log('Data channel is closed');
+ this.dataChannelOpened = false;
+ };
+ Stream.prototype.sendData = function (data) {
+ if (this.wp === undefined) {
+ throw new Error('WebRTC peer has not been created yet');
+ }
+ if (!this.dataChannelOpened) {
+ throw new Error('Data channel is not opened');
+ }
+ console.log("Sending through data channel: " + data);
+ this.wp.send(data);
+ };
+ Stream.prototype.getWrStream = function () {
+ return this.wrStream;
+ };
+ Stream.prototype.getWebRtcPeer = function () {
+ return this.wp;
+ };
+ Stream.prototype.addEventListener = function (eventName, listener) {
+ this.ee.addListener(eventName, listener);
+ };
+ Stream.prototype.addOnceEventListener = function (eventName, listener) {
+ this.ee.addOnceListener(eventName, listener);
+ };
+ Stream.prototype.removeListener = function (eventName) {
+ this.ee.removeAllListeners(eventName);
+ };
+ Stream.prototype.showSpinner = function (spinnerParentId) {
+ var progress = document.createElement('div');
+ progress.id = 'progress-' + this.getId();
+ progress.style.background = "center transparent url('img/spinner.gif') no-repeat";
+ var spinnerParent = document.getElementById(spinnerParentId);
+ if (spinnerParent) {
+ spinnerParent.appendChild(progress);
+ }
+ };
+ Stream.prototype.hideSpinner = function (spinnerId) {
+ spinnerId = (spinnerId === undefined) ? this.getId() : spinnerId;
+ hide('progress-' + spinnerId);
+ };
+ Stream.prototype.playOnlyVideo = function (parentElement, thumbnailId) {
+ // TO-DO: check somehow if the stream is audio only, so the element created is