openvidu-server updated to recording

pull/30/head
pabloFuente 2018-01-29 15:26:31 +01:00
parent 3f80d2cbc4
commit 75afcf16e1
37 changed files with 1156 additions and 300 deletions

View File

@ -31,6 +31,7 @@ export class OpenViduInternal {
private localStream: Stream;
private remoteStreams: Stream[] = [];
private secret: string;
private recorder: boolean = false;
/* NEW METHODS */
initSession(sessionId) {
@ -170,6 +171,14 @@ export class OpenViduInternal {
this.secret = secret;
}
getRecorder() {
return this.recorder;
}
setRecorder(recorder: boolean) {
this.recorder = recorder;
}
getOpenViduServerURL() {
return 'https://' + this.wsUri.split("wss://")[1].split("/room")[0];
}

View File

@ -6,6 +6,7 @@ import { Publisher } from '../OpenVidu/Publisher';
import EventEmitter = require('wolfy87-eventemitter');
const SECRET_PARAM = '?secret=';
const RECORDER_PARAM = '&recorder=';
export interface SessionOptions {
sessionId: string;
@ -46,18 +47,38 @@ export class SessionInternal {
}
private processOpenViduUrl(url: string) {
this.openVidu.setSecret(this.getSecretFromUrl(url));
let secret = this.getSecretFromUrl(url);
let recorder = this.getRecorderFromUrl(url);
if (!(secret == null)) {
this.openVidu.setSecret(secret);
}
if (!(recorder == null)) {
this.openVidu.setRecorder(recorder);
}
this.openVidu.setWsUri(this.getFinalUrl(url));
}
private getSecretFromUrl(url: string): string {
let secret = '';
if (url.indexOf(SECRET_PARAM) !== -1) {
let endOfSecret = url.lastIndexOf(RECORDER_PARAM);
if (endOfSecret !== -1) {
secret = url.substring(url.lastIndexOf(SECRET_PARAM) + SECRET_PARAM.length, endOfSecret);
} else {
secret = url.substring(url.lastIndexOf(SECRET_PARAM) + SECRET_PARAM.length, url.length);
}
}
return secret;
}
private getRecorderFromUrl(url: string): boolean {
let recorder = '';
if (url.indexOf(RECORDER_PARAM) !== -1) {
recorder = url.substring(url.lastIndexOf(RECORDER_PARAM) + RECORDER_PARAM.length, url.length);
}
return new Boolean(recorder).valueOf();;
}
private getUrlWithoutSecret(url: string): string {
if (!url) {
console.error('sessionId is not defined');
@ -102,6 +123,7 @@ export class SessionInternal {
session: this.sessionId,
metadata: this.options.metadata,
secret: this.openVidu.getSecret(),
recorder: this.openVidu.getRecorder(),
dataChannels: false
}

View File

@ -36,6 +36,7 @@ public class ProtocolElements {
public static final String JOINROOM_ROOM_PARAM = "session";
public static final String JOINROOM_METADATA_PARAM = "metadata";
public static final String JOINROOM_SECRET_PARAM = "secret";
public static final String JOINROOM_RECORDER_PARAM = "recorder";
public static final String JOINROOM_DATACHANNELS_PARAM = "dataChannels";
public static final String JOINROOM_PEERID_PARAM = "id";
@ -111,4 +112,6 @@ public class ProtocolElements {
public static final String ICECANDIDATE_SDPMLINEINDEX_PARAM = "sdpMLineIndex";
public static final String CUSTOM_NOTIFICATION = "custonNotification";
public static final String RECORDER_PARTICIPANT_ID_PUBLICID = "RECORDER";
}

View File

@ -8,6 +8,9 @@ RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key
apt-get update && apt-get install -y google-chrome-stable
# Install media packages
RUN apt-get install -y software-properties-common
RUN add-apt-repository ppa:mc3man/xerus-media
RUN apt-get update
RUN apt-get install -y ffmpeg pulseaudio xvfb
# Clean

View File

@ -1,28 +1,47 @@
#!/bin/bash
adduser --uid $USER_ID --disabled-password --gecos "" myuser
URL="${URL:-https://www.youtube.com/watch?v=JMuzlEQz3uo}"
RESOLUTION="${RESOLUTION:-1920x1080}"
FRAMERATE="${FRAMERATE:-30}"
VIDEO_SIZE="$RESOLUTION"
ARRAY=(${VIDEO_SIZE//x/ })
VIDEO_NAME="${VIDEO_NAME:-video}"
VIDEO_FORMAT="${VIDEO_FORMAT:-avi}"
VIDEO_FORMAT="${VIDEO_FORMAT:-mp4}"
pulseaudio -D
xvfb-run -s "-ac -screen 0 ${RESOLUTION}x16" google-chrome -no-sandbox -disable-infobars -window-size=${ARRAY[0]},${ARRAY[1]} -start-fullscreen -no-first-run $URL &> xvfb.log &
echo "----------------------------------------"
echo "Recording URL -> $URL"
echo "----------------------------------------"
sleep 4
DISPLAY_NUM=99
DONE="no"
while [ "$DONE" == "no" ]
do
out=$(xdpyinfo -display :$DISPLAY_NUM 2>&1)
if [[ "$out" == name* ]] || [[ "$out" == Invalid* ]]
then
# command succeeded; or failed with access error; display exists
(( DISPLAY_NUM+=1 ))
else
# display doesn't exist
DONE="yes"
fi
done
echo "First available display -> :$DISPLAY_NUM"
echo "----------------------------------------"
su myuser -c "pulseaudio -D"
touch xvfb.log
chmod 777 xvfb.log
su myuser -c "xvfb-run --server-num=${DISPLAY_NUM} --server-args='-ac -screen 0 ${RESOLUTION}x24 -noreset' google-chrome -no-sandbox -disable-infobars -window-size=${ARRAY[0]},${ARRAY[1]} -start-fullscreen -no-first-run -ignore-certificate-errors $URL &> xvfb.log &"
sleep 3
touch stop
<./stop ffmpeg -y -video_size $RESOLUTION -framerate $FRAMERATE -f x11grab -i :99 -f pulse -ac 2 -i default -strict -2 /recordings/${VIDEO_NAME}.${VIDEO_FORMAT}
chmod 777 /recordings
su myuser -c "<./stop ffmpeg -y -video_size $RESOLUTION -framerate $FRAMERATE -f x11grab -i :${DISPLAY_NUM} -f pulse -ac 2 -i default /recordings/${VIDEO_NAME}.${VIDEO_FORMAT}"
# TO START THE CONTAINER
# docker run --name=recording openvidu/openvidu-recording
# TO STOP THE RECORDING
# docker exec recording bash -c "echo 'q' > stop"
# TO GET THE VIDEO FILE
# docker cp recording:recordings/video.mp4 ./video.mp4

View File

@ -1,5 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
@ -201,6 +202,16 @@
<artifactId>spring-boot-starter-logging</artifactId>
<version>${version.spring-boot}</version>
</dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
<version>3.0.6</version>
</dependency>
<dependency>
<groupId>io.openvidu</groupId>
<artifactId>openvidu-java-client</artifactId>
<version>1.7.0</version>
</dependency>
<!-- Test dependencies -->

View File

@ -19,7 +19,7 @@
"zone.js": "0.8.18"
},
"devDependencies": {
"@angular/cli": "1.5.5",
"@angular/cli": "^1.6.0",
"@angular/compiler-cli": "5.0.5",
"@types/jasmine": "2.5.38",
"@types/node": "~6.0.60",

View File

@ -8,44 +8,6 @@ import { InfoService } from 'app/services/info.service';
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
websocket: WebSocket;
constructor(private infoService: InfoService) { }
ngOnInit() {
const protocol = location.protocol.includes('https') ? 'wss://' : 'ws://';
const port = (location.port) ? (':' + location.port) : '';
this.websocket = new WebSocket(protocol + location.hostname + port + '/info');
this.websocket.onopen = (event) => {
console.log('Info websocket connected');
};
this.websocket.onclose = (event) => {
console.log('Info websocket closed');
};
this.websocket.onerror = (event) => {
console.log('Info websocket error');
};
this.websocket.onmessage = (event) => {
console.log('Info websocket message');
console.log(event.data);
this.infoService.updateInfo(event.data);
};
}
ngOnDestroy() {
this.websocket.close();
}
@HostListener('window:beforeunload', ['$event'])
beforeUnloadHander(event) {
console.warn('Closing info websocket');
this.websocket.close();
}
export class AppComponent {
}

View File

@ -15,6 +15,7 @@ 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';
@NgModule({
@ -23,6 +24,7 @@ import { CredentialsDialogComponent } from './components/dashboard/credentials-d
DashboardComponent,
SessionDetailsComponent,
CredentialsDialogComponent,
LayoutBestFitComponent,
],
imports: [
BrowserModule,

View File

@ -3,16 +3,25 @@ import { Routes, RouterModule } from '@angular/router';
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';
const appRoutes: Routes = [
{
path: '',
component: DashboardComponent
component: DashboardComponent,
pathMatch: 'full'
},
{
path: 'session/:id',
component: SessionDetailsComponent
path: 'session/:sessionId',
component: SessionDetailsComponent,
pathMatch: 'full'
},
{
path: 'layout-best-fit/:sessionId/:secret',
component: LayoutBestFitComponent,
pathMatch: 'full'
}
];
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes, { useHash: true });

View File

@ -16,6 +16,8 @@ declare const $;
})
export class DashboardComponent implements OnInit, OnDestroy {
websocket: WebSocket;
@ViewChild('scrollMe') private myScrollContainer: ElementRef;
lockScroll = false;
@ -41,21 +43,43 @@ export class DashboardComponent implements OnInit, OnDestroy {
ngOnInit() {
const protocol = location.protocol.includes('https') ? 'wss://' : 'ws://';
const port = (location.port) ? (':' + location.port) : '';
this.websocket = new WebSocket(protocol + location.hostname + port + '/info');
this.websocket.onopen = (event) => {
console.log('Info websocket connected');
};
this.websocket.onclose = (event) => {
console.log('Info websocket closed');
};
this.websocket.onerror = (event) => {
console.log('Info websocket error');
};
this.websocket.onmessage = (event) => {
console.log('Info websocket message');
console.log(event.data);
this.infoService.updateInfo(event.data);
};
}
@HostListener('window:beforeunload')
beforeunloadHandler() {
// On window closed leave test session
// On window closed leave test session and close info websocket
if (this.session) {
this.endTestVideo();
}
this.websocket.close();
}
ngOnDestroy() {
// On component destroyed leave test session
// On component destroyed leave test session and close info websocket
if (this.session) {
this.endTestVideo();
}
this.websocket.close();
}
toggleTestVideo() {

View File

@ -0,0 +1,10 @@
.bounds {
background-color: black;
height: 100%;
overflow: hidden;
cursor: none !important;
}
video {
height: 100%;
}

View File

@ -0,0 +1,8 @@
<div class="bounds">
<div *ngFor="let streams of remoteStreams" class="content" fxLayout="row" fxFlexFill [style.height]="100 / remoteStreams.length + '%'"
[style.min-height]="100 / remoteStreams.length + '%'">
<div *ngFor="let s of streams" [fxFlex]="100 / streams">
<video [id]="'native-video-' + s.streamId" autoplay="true" [srcObject]="s.getMediaStream()"></video>
</div>
</div>
</div>

View File

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

View File

@ -0,0 +1,133 @@
import { Component, OnInit, OnDestroy, HostListener } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { OpenVidu, Session, Stream } from 'openvidu-browser';
@Component({
selector: 'app-layout-best-fit',
templateUrl: './layout-best-fit.component.html',
styleUrls: ['./layout-best-fit.component.css']
})
export class LayoutBestFitComponent implements OnInit, OnDestroy {
sessionId: string;
secret: string;
session: Session;
numberOfVideos = 0;
remoteStreams = [];
constructor(private route: ActivatedRoute) {
this.route.params.subscribe(params => {
this.sessionId = params.sessionId;
this.secret = params.secret;
});
}
@HostListener('window:beforeunload')
beforeunloadHandler() {
this.leaveSession();
}
ngOnDestroy() {
this.leaveSession();
}
ngOnInit() {
const OV = new OpenVidu();
const fullSessionId = 'wss://' + location.hostname + ':8443/' + this.sessionId + '?secret=' + this.secret + '&recorder=true';
this.session = OV.initSession(fullSessionId);
this.session.on('streamCreated', (event) => {
this.numberOfVideos++;
this.addRemoteStream(event.stream);
this.session.subscribe(event.stream, '');
});
this.session.on('streamDestroyed', (event) => {
this.numberOfVideos--;
event.preventDefault();
this.deleteRemoteStream(event.stream);
});
this.session.connect(null, (error) => {
if (error) {
console.error(error);
}
});
}
private addRemoteStream(stream: Stream): void {
switch (true) {
case (this.numberOfVideos <= 2):
if (this.remoteStreams[0] == null) { this.remoteStreams[0] = []; }
this.remoteStreams[0].push(stream);
break;
case (this.numberOfVideos <= 4):
if (this.remoteStreams[1] == null) { this.remoteStreams[1] = []; }
this.remoteStreams[1].push(stream);
break;
case (this.numberOfVideos <= 5):
this.remoteStreams[0].push(stream);
break;
case (this.numberOfVideos <= 6):
this.remoteStreams[1].push(stream);
break;
default:
if (this.remoteStreams[2] == null) { this.remoteStreams[2] = []; }
this.remoteStreams[2].push(stream);
break;
}
}
private deleteRemoteStream(stream: Stream): void {
for (let i = 0; i < this.remoteStreams.length; i++) {
const index = this.remoteStreams[i].indexOf(stream, 0);
if (index > -1) {
this.remoteStreams[i].splice(index, 1);
this.reArrangeVideos();
break;
}
}
}
private reArrangeVideos(): void {
switch (true) {
case (this.numberOfVideos === 1):
if (this.remoteStreams[0].length === 0) {
this.remoteStreams[0].push(this.remoteStreams[1].pop());
}
break;
case (this.numberOfVideos === 2):
if (this.remoteStreams[0].length === 1) {
this.remoteStreams[0].push(this.remoteStreams[1].pop());
}
break;
case (this.numberOfVideos === 3):
if (this.remoteStreams[0].length === 1) {
this.remoteStreams[0].push(this.remoteStreams[1].pop());
}
break;
case (this.numberOfVideos === 4):
if (this.remoteStreams[0].length === 3) {
this.remoteStreams[1].unshift(this.remoteStreams[0].pop());
}
break;
case (this.numberOfVideos === 5):
if (this.remoteStreams[0].length === 2) {
this.remoteStreams[0].push(this.remoteStreams[1].shift());
}
break;
}
this.remoteStreams = this.remoteStreams.filter((array) => { return array.length > 0 });
}
leaveSession() {
if (this.session) { this.session.disconnect(); };
this.remoteStreams = [];
this.numberOfVideos = 0;
this.session = null;
}
}

View File

@ -46,6 +46,7 @@ import io.openvidu.server.kurento.KurentoClientProvider;
import io.openvidu.server.kurento.core.KurentoSessionEventsHandler;
import io.openvidu.server.kurento.core.KurentoSessionManager;
import io.openvidu.server.kurento.kms.FixedOneKmsManager;
import io.openvidu.server.recording.RecordingService;
import io.openvidu.server.rest.NgrokRestController;
import io.openvidu.server.rpc.RpcHandler;
import io.openvidu.server.rpc.RpcNotificationService;
@ -193,6 +194,35 @@ public class OpenViduServer implements JsonRpcConfigurer {
OpenViduServer.publicUrl = "wss://localhost:" + openviduConf.getServerPort();
}
boolean recordingModuleEnabled = openviduConf.isRecordingModuleEnabled();
if (recordingModuleEnabled) {
RecordingService recordingService = context.getBean(RecordingService.class);
System.out.println("Recording module required: Downloading openvidu/openvidu-recording Docker image (800 MB aprox)");
if (recordingService.recordingImageExistsLocally()) {
System.out.println("Docker image already exists locally");
} else {
Thread t = new Thread(() -> {
boolean keep = true;
System.out.print("Downloading ");
while (keep) {
System.out.print(".");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
keep = false;
System.out.println("\nDownload complete");
}
}
});
t.start();
recordingService.downloadRecordingImage();
t.interrupt();
t.join();
System.out.println("Docker image available");
}
}
System.out.println("OpenVidu Server using " + type + " URL: " + OpenViduServer.publicUrl);
}

View File

@ -18,6 +18,15 @@ public class OpenviduConfig {
@Value("${openvidu.cdr}")
private boolean openviduCdr;
@Value("${openvidu.recording}")
private boolean openviduRecording;
@Value("${openvidu.recording.path}")
private String openviduRecordingPath;
@Value("${openvidu.recording.free-access}")
boolean openviduRecordingFreeAccess;
public String getOpenViduPublicUrl() {
return this.openviduPublicUrl;
}
@ -38,4 +47,16 @@ public class OpenviduConfig {
return this.openviduCdr;
}
public boolean isRecordingModuleEnabled() {
return this.openviduRecording;
}
public String getOpenViduRecordingPath() {
return this.openviduRecordingPath;
}
public boolean getOpenViduRecordingFreeAccess() {
return this.openviduRecordingFreeAccess;
}
}

View File

@ -6,6 +6,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
@Configuration
@ -16,12 +17,19 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry conf = http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/sessions").authenticated()
.antMatchers(HttpMethod.POST, "/api/tokens").authenticated()
.antMatchers("/").authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.antMatchers("/").authenticated();
if (openviduConf.getOpenViduRecordingFreeAccess()) {
conf = conf.antMatchers("/recordings/*").anonymous();
} else {
conf = conf.antMatchers("/recordings/*").authenticated();
}
conf.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().httpBasic();
}

View File

@ -2,10 +2,14 @@ package io.openvidu.server.core;
import java.util.Set;
import io.openvidu.java.client.SessionProperties;
public interface Session {
String getSessionId();
SessionProperties getSessionProperties();
void join(Participant participant);
void leave(String participantPrivateId);

View File

@ -18,6 +18,8 @@ import com.google.gson.JsonObject;
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.client.internal.ProtocolElements;
import io.openvidu.java.client.SessionProperties;
import io.openvidu.server.OpenViduServer;
public abstract class SessionManager {
@ -25,6 +27,7 @@ public abstract class SessionManager {
private static final Logger log = LoggerFactory.getLogger(SessionManager.class);
protected ConcurrentMap<String, Session> sessions = new ConcurrentHashMap<>();
protected ConcurrentMap<String, SessionProperties> sessionProperties = new ConcurrentHashMap<>();
protected ConcurrentMap<String, ConcurrentHashMap<String, Token>> sessionidTokenTokenobj = new ConcurrentHashMap<>();
protected ConcurrentMap<String, ConcurrentHashMap<String, Participant>> sessionidParticipantpublicidParticipant = new ConcurrentHashMap<>();
protected ConcurrentMap<String, Boolean> insecureUsers = new ConcurrentHashMap<>();
@ -136,12 +139,13 @@ public abstract class SessionManager {
return null;
}
public String newSessionId() {
public String newSessionId(SessionProperties sessionProperties) {
String sessionId = OpenViduServer.publicUrl;
sessionId += "/" + new BigInteger(130, new SecureRandom()).toString(32);
this.sessionidTokenTokenobj.put(sessionId, new ConcurrentHashMap<>());
this.sessionidParticipantpublicidParticipant.put(sessionId, new ConcurrentHashMap<>());
this.sessionProperties.put(sessionId, sessionProperties);
showTokens();
return sessionId;
@ -237,6 +241,18 @@ public abstract class SessionManager {
}
}
public Participant newRecorderParticipant(String sessionId, String participantPrivatetId, Token token,
String clientMetadata) {
if (this.sessionidParticipantpublicidParticipant.get(sessionId) != null) {
String participantPublicId = ProtocolElements.RECORDER_PARTICIPANT_ID_PUBLICID;
Participant p = new Participant(participantPrivatetId, participantPublicId, token, clientMetadata);
this.sessionidParticipantpublicidParticipant.get(sessionId).put(participantPublicId, p);
return p;
} else {
throw new OpenViduException(Code.ROOM_NOT_FOUND_ERROR_CODE, sessionId);
}
}
public Token consumeToken(String sessionId, String participantPrivateId, String token) {
if (this.sessionidTokenTokenobj.get(sessionId) != null) {
Token t = this.sessionidTokenTokenobj.get(sessionId).remove(token);
@ -324,6 +340,7 @@ public abstract class SessionManager {
session.close();
sessions.remove(sessionId);
sessionProperties.remove(sessionId);
sessionidParticipantpublicidParticipant.remove(sessionId);
sessionidTokenTokenobj.remove(sessionId);

View File

@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.java.client.SessionProperties;
import io.openvidu.server.core.Participant;
import io.openvidu.server.core.Session;
@ -32,6 +33,7 @@ public class KurentoSession implements Session {
private final ConcurrentMap<String, KurentoParticipant> participants = new ConcurrentHashMap<>();
private String sessionId;
private SessionProperties sessionProperties;
private MediaPipeline pipeline;
private CountDownLatch pipelineLatch = new CountDownLatch(1);
@ -49,9 +51,10 @@ public class KurentoSession implements Session {
private volatile boolean pipelineReleased = false;
private boolean destroyKurentoClient;
public KurentoSession(String sessionId, KurentoClient kurentoClient, KurentoSessionEventsHandler kurentoSessionHandler,
public KurentoSession(String sessionId, SessionProperties sessionProperties, KurentoClient kurentoClient, KurentoSessionEventsHandler kurentoSessionHandler,
boolean destroyKurentoClient) {
this.sessionId = sessionId;
this.sessionProperties = sessionProperties;
this.kurentoClient = kurentoClient;
this.destroyKurentoClient = destroyKurentoClient;
this.kurentoSessionHandler = kurentoSessionHandler;
@ -63,6 +66,11 @@ public class KurentoSession implements Session {
return this.sessionId;
}
@Override
public SessionProperties getSessionProperties() {
return this.sessionProperties;
}
@Override
public void join(Participant participant) {
checkClosed();

View File

@ -18,12 +18,17 @@ import com.google.gson.JsonSyntaxException;
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.client.internal.ProtocolElements;
import io.openvidu.java.client.ArchiveMode;
import io.openvidu.java.client.MediaMode;
import io.openvidu.java.client.SessionProperties;
import io.openvidu.server.core.SessionManager;
import io.openvidu.server.kurento.KurentoClientProvider;
import io.openvidu.server.kurento.KurentoClientSessionInfo;
import io.openvidu.server.kurento.OpenViduKurentoClientSessionInfo;
import io.openvidu.server.kurento.endpoint.SdpType;
import io.openvidu.server.recording.RecordingService;
import io.openvidu.server.rpc.RpcHandler;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.MediaOptions;
import io.openvidu.server.core.Participant;
import io.openvidu.server.core.Session;
@ -38,6 +43,12 @@ public class KurentoSessionManager extends SessionManager {
@Autowired
private KurentoSessionEventsHandler sessionHandler;
@Autowired
private RecordingService recordingService;
@Autowired
OpenviduConfig openviduConfig;
@Override
public synchronized void joinRoom(Participant participant, String sessionId, Integer transactionId) {
Set<Participant> existingParticipants = null;
@ -48,7 +59,12 @@ public class KurentoSessionManager extends SessionManager {
KurentoSession session = (KurentoSession) sessions.get(sessionId);
if (session == null && kcSessionInfo != null) {
createSession(kcSessionInfo);
SessionProperties properties = sessionProperties.get(sessionId);
if (properties == null && this.isInsecureParticipant(participant.getParticipantPrivateId())) {
properties = new SessionProperties.Builder().mediaMode(MediaMode.ROUTED)
.archiveMode(ArchiveMode.ALWAYS).build();
}
createSession(kcSessionInfo, properties);
}
session = (KurentoSession) sessions.get(sessionId);
if (session == null) {
@ -118,12 +134,13 @@ public class KurentoSessionManager extends SessionManager {
remainingParticipants = Collections.emptySet();
}
if (remainingParticipants.isEmpty()) {
log.debug("No more participants in session '{}', removing it and closing it", sessionId);
log.info("No more participants in session '{}', removing it and closing it", sessionId);
if (session.close()) {
sessionHandler.onSessionClosed(sessionId);
}
sessions.remove(sessionId);
sessionProperties.remove(sessionId);
sessionidParticipantpublicidParticipant.remove(sessionId);
sessionidTokenTokenobj.remove(sessionId);
@ -131,7 +148,16 @@ public class KurentoSessionManager extends SessionManager {
log.warn("Session '{}' removed and closed", sessionId);
}
if (
remainingParticipants.size() == 1 &&
openviduConfig.isRecordingModuleEnabled() &&
MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode()) &&
ProtocolElements.RECORDER_PARTICIPANT_ID_PUBLICID.equals(remainingParticipants.iterator().next().getParticipantPublicId())
) {
log.info("Last participant left. Stopping recording for session {}", sessionId);
evictParticipant(session.getParticipantByPublicId("RECORDER").getParticipantPrivateId());
recordingService.stopRecording(session);
}
sessionHandler.onParticipantLeft(participant, sessionId, remainingParticipants, transactionId, null);
}
@ -192,7 +218,15 @@ public class KurentoSessionManager extends SessionManager {
OpenViduException e = new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
"Error generating SDP response for publishing user " + participant.getParticipantPublicId());
log.error("PARTICIPANT {}: Error publishing media", participant.getParticipantPublicId(), e);
sessionHandler.onPublishMedia(participant, session.getSessionId(), mediaOptions, sdpAnswer, participants, transactionId, e);
sessionHandler.onPublishMedia(participant, session.getSessionId(), mediaOptions, sdpAnswer, participants,
transactionId, e);
}
if (this.openviduConfig.isRecordingModuleEnabled()
&& MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode())
&& ArchiveMode.ALWAYS.equals(session.getSessionProperties().archiveMode())
&& session.getActivePublishers() == 0) {
recordingService.startRecording(session);
}
session.newPublisher(participant);
@ -204,7 +238,8 @@ public class KurentoSessionManager extends SessionManager {
participants = kurentoParticipant.getSession().getParticipants();
if (sdpAnswer != null) {
sessionHandler.onPublishMedia(participant, session.getSessionId(), mediaOptions, sdpAnswer, participants, transactionId, null);
sessionHandler.onPublishMedia(participant, session.getSessionId(), mediaOptions, sdpAnswer, participants,
transactionId, null);
}
}
@ -327,7 +362,6 @@ public class KurentoSessionManager extends SessionManager {
}
}
/**
* Creates a session if it doesn't already exist. The session's id will be
* indicated by the session info bean.
@ -339,7 +373,8 @@ public class KurentoSessionManager extends SessionManager {
* @throws OpenViduException
* in case of error while creating the session
*/
public void createSession(KurentoClientSessionInfo kcSessionInfo) throws OpenViduException {
public void createSession(KurentoClientSessionInfo kcSessionInfo, SessionProperties sessionProperties)
throws OpenViduException {
String sessionId = kcSessionInfo.getRoomName();
KurentoSession session = (KurentoSession) sessions.get(sessionId);
if (session != null) {
@ -347,7 +382,8 @@ public class KurentoSessionManager extends SessionManager {
"Session '" + sessionId + "' already exists");
}
KurentoClient kurentoClient = kcProvider.getKurentoClient(kcSessionInfo);
session = new KurentoSession(sessionId, kurentoClient, sessionHandler, kcProvider.destroyWhenUnused());
session = new KurentoSession(sessionId, sessionProperties, kurentoClient, sessionHandler,
kcProvider.destroyWhenUnused());
KurentoSession oldSession = (KurentoSession) sessions.putIfAbsent(sessionId, session);
if (oldSession != null) {

View File

@ -0,0 +1,167 @@
package io.openvidu.server.recording;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.exception.ConflictException;
import com.github.dockerjava.api.exception.DockerClientException;
import com.github.dockerjava.api.exception.InternalServerErrorException;
import com.github.dockerjava.api.exception.NotFoundException;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.Volume;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import com.github.dockerjava.core.command.PullImageResultCallback;
import io.openvidu.server.CommandExecutor;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.Session;
@Service
public class RecordingService {
private Logger log = LoggerFactory.getLogger(RecordingService.class);
@Autowired
OpenviduConfig openviduConfig;
private static final Map<String, String> createdContainers = new HashMap<>();
private final String IMAGE_NAME = "openvidu/openvidu-recording";
private DockerClient dockerClient;
private Map<String, String> recordingSessions = new HashMap<>();;
public RecordingService() {
DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder().build();
this.dockerClient = DockerClientBuilder.getInstance(config).build();
}
public void startRecording(Session session) {
List<String> envs = new ArrayList<>();
String shortSessionId = session.getSessionId().substring(session.getSessionId().lastIndexOf('/') + 1,
session.getSessionId().length());
String secret = openviduConfig.getOpenViduSecret();
String uid = null;
try {
uid = System.getenv("MY_UID");
if (uid==null) {
uid = CommandExecutor.execCommand("/bin/sh", "-c", "id -u " + System.getProperty("user.name"));
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
envs.add("URL=https://OPENVIDUAPP:" + secret + "@localhost:8443/#/layout-best-fit/" + shortSessionId + "/"
+ secret);
envs.add("RESOLUTION=1920x1080");
envs.add("FRAMERATE=30");
envs.add("VIDEO_NAME=" + shortSessionId);
envs.add("VIDEO_FORMAT=mp4");
envs.add("USER_ID=" + uid);
System.out.println(
"https://OPENVIDUAPP:" + secret + "@localhost:8443/#/layout-best-fit/" + shortSessionId + "/" + secret);
String containerId = this.runRecordingContainer(envs, "recording" + shortSessionId);
this.recordingSessions.putIfAbsent(session.getSessionId(), containerId);
}
public void stopRecording(Session session) {
String containerId = this.recordingSessions.remove(session.getSessionId());
ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
.withAttachStdout(true)
.withAttachStderr(true)
.withCmd("bash", "-c", "echo 'q' > stop")
.exec();
try {
dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(new ExecStartResultCallback()).awaitCompletion();
} catch (InterruptedException e) {
e.printStackTrace();
}
this.stopDockerContainer(containerId);
this.removeDockerContainer(containerId);
}
public boolean recordingImageExistsLocally() {
boolean imageExists = false;
try {
dockerClient.inspectImageCmd(IMAGE_NAME).exec();
imageExists = true;
} catch (NotFoundException nfe) {
imageExists = false;
}
return imageExists;
}
public void downloadRecordingImage() {
try {
dockerClient.pullImageCmd(IMAGE_NAME).exec(new PullImageResultCallback()).awaitSuccess();
} catch (NotFoundException | InternalServerErrorException e) {
if (imageExistsLocally(IMAGE_NAME)) {
log.info("Docker image '{}' exists locally", IMAGE_NAME);
} else {
throw e;
}
} catch (DockerClientException e) {
log.info("Error on Pulling '{}' image. Probably because the user has stopped the execution",
IMAGE_NAME);
throw e;
}
}
private String runRecordingContainer(List<String> envs, String containerName) {
Volume volume1 = new Volume("/recordings");
CreateContainerCmd cmd = dockerClient.createContainerCmd(IMAGE_NAME).withName(containerName).withEnv(envs)
.withNetworkMode("host").withVolumes(volume1)
.withBinds(new Bind(openviduConfig.getOpenViduRecordingPath(), volume1));
CreateContainerResponse container = null;
try {
container = cmd.exec();
dockerClient.startContainerCmd(container.getId()).exec();
createdContainers.put(container.getId(), containerName);
log.info("Container ID: {}", container.getId());
return container.getId();
} catch (ConflictException e) {
log.error(
"The container name {} is already in use. Probably caused by a session with unique publisher re-publishing a stream",
containerName);
return null;
}
}
private void removeDockerContainer(String containerId) {
dockerClient.removeContainerCmd(containerId).exec();
createdContainers.remove(containerId);
}
private void stopDockerContainer(String containerId) {
dockerClient.stopContainerCmd(containerId).exec();
}
private boolean imageExistsLocally(String imageName) {
boolean imageExists = false;
try {
dockerClient.inspectImageCmd(imageName).exec();
imageExists = true;
} catch (NotFoundException nfe) {
imageExists = false;
}
return imageExists;
}
}

View File

@ -0,0 +1,27 @@
package io.openvidu.server.recording;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import io.openvidu.server.config.OpenviduConfig;
@Configuration
@ConditionalOnExpression("'${openvidu.recording}' == 'true'")
public class RecordingsHttpHandler extends WebMvcConfigurerAdapter {
@Autowired
OpenviduConfig openviduConfig;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String recordingsPath = openviduConfig.getOpenViduRecordingPath();
recordingsPath = recordingsPath.endsWith("/") ? recordingsPath : recordingsPath + "/";
registry.addResourceHandler("/recordings/**").addResourceLocations("file:" + recordingsPath);
}
}

View File

@ -32,6 +32,10 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import io.openvidu.client.OpenViduException;
import io.openvidu.java.client.ArchiveLayout;
import io.openvidu.java.client.ArchiveMode;
import io.openvidu.java.client.MediaMode;
import io.openvidu.java.client.SessionProperties;
import io.openvidu.server.core.ParticipantRole;
import io.openvidu.server.core.SessionManager;
@ -67,8 +71,37 @@ public class SessionRestController {
@SuppressWarnings("unchecked")
@RequestMapping(value = "/sessions", method = RequestMethod.POST)
public ResponseEntity<JSONObject> getSessionId() {
String sessionId = sessionManager.newSessionId();
public ResponseEntity<JSONObject> getSessionId(@RequestBody(required = false) Map<?, ?> params) {
SessionProperties.Builder builder = new SessionProperties.Builder();
if (params != null) {
String archiveModeString = (String) params.get("archiveMode");
String archiveLayoutString = (String) params.get("archiveLayout");
String mediaModeString = (String) params.get("mediaMode");
try {
if (archiveModeString != null) {
ArchiveMode archiveMode = ArchiveMode.valueOf(archiveModeString);
builder = builder.archiveMode(archiveMode);
}
if (archiveLayoutString != null) {
ArchiveLayout archiveLayout = ArchiveLayout.valueOf(archiveLayoutString);
builder = builder.archiveLayout(archiveLayout);
}
if (mediaModeString != null) {
MediaMode mediaMode = MediaMode.valueOf(mediaModeString);
builder = builder.mediaMode(mediaMode);
}
} catch (IllegalArgumentException e) {
return this.generateErrorResponse("ArchiveMode " + params.get("archiveMode") + " | " + "ArchiveLayout "
+ params.get("archiveLayout") + " | " + "MediaMode " + params.get("mediaMode")
+ " are not defined", "/api/tokens");
}
}
SessionProperties sessionProperties = builder.build();
String sessionId = sessionManager.newSessionId(sessionProperties);
JSONObject responseJson = new JSONObject();
responseJson.put("id", sessionId);
return new ResponseEntity<JSONObject>(responseJson, HttpStatus.OK);
@ -76,11 +109,13 @@ public class SessionRestController {
@SuppressWarnings("unchecked")
@RequestMapping(value = "/tokens", method = RequestMethod.POST)
public ResponseEntity<JSONObject> newToken(@RequestBody Map<?, ?> sessionIdRoleMetadata) {
public ResponseEntity<JSONObject> newToken(@RequestBody Map<?, ?> params) {
try {
String sessionId = (String) sessionIdRoleMetadata.get("session");
ParticipantRole role = ParticipantRole.valueOf((String) sessionIdRoleMetadata.get("role"));
String metadata = (String) sessionIdRoleMetadata.get("data");
String sessionId = (String) params.get("session");
ParticipantRole role = ParticipantRole.valueOf((String) params.get("role"));
String metadata = (String) params.get("data");
String token = sessionManager.newToken(sessionId, role, metadata);
JSONObject responseJson = new JSONObject();
responseJson.put("id", token);
@ -89,12 +124,13 @@ public class SessionRestController {
responseJson.put("data", metadata);
responseJson.put("token", token);
return new ResponseEntity<JSONObject>(responseJson, HttpStatus.OK);
} catch (IllegalArgumentException e) {
return this.generateErrorResponse("Role " + sessionIdRoleMetadata.get("1") + " is not defined",
"/api/tokens");
return this.generateErrorResponse("Role " + params.get("role") + " is not defined", "/api/tokens");
} catch (OpenViduException e) {
return this.generateErrorResponse("Metadata [" + sessionIdRoleMetadata.get("2")
+ "] unexpected format. Max length allowed is 1000 chars", "/api/tokens");
return this.generateErrorResponse(
"Metadata [" + params.get("data") + "] unexpected format. Max length allowed is 1000 chars",
"/api/tokens");
}
}

View File

@ -94,8 +94,21 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
String secret = getStringParam(request, ProtocolElements.JOINROOM_SECRET_PARAM);
String participantPrivatetId = rpcConnection.getParticipantPrivateId();
boolean recorder = false;
try {
recorder = getBooleanParam(request, ProtocolElements.JOINROOM_RECORDER_PARAM);
} catch (RuntimeException e) {
// Nothing happens. 'recorder' param to false
}
boolean generateRecorderParticipant = false;
if (openviduConfig.isOpenViduSecret(secret)) {
sessionManager.newInsecureParticipant(participantPrivatetId);
if (recorder) {
generateRecorderParticipant = true;
}
}
if (sessionManager.isTokenValidInSession(token, sessionId, participantPrivatetId)) {
@ -105,8 +118,15 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
if (sessionManager.isMetadataFormatCorrect(clientMetadata)) {
Token tokenObj = sessionManager.consumeToken(sessionId, participantPrivatetId, token);
Participant participant = sessionManager.newParticipant(sessionId, participantPrivatetId, tokenObj,
Participant participant;
if (generateRecorderParticipant) {
participant = sessionManager.newRecorderParticipant(sessionId, participantPrivatetId, tokenObj,
clientMetadata);
} else {
participant = sessionManager.newParticipant(sessionId, participantPrivatetId, tokenObj,
clientMetadata);
}
rpcConnection.setSessionId(sessionId);
sessionManager.joinRoom(participant, sessionId, request.getId());

View File

@ -0,0 +1,37 @@
{"properties": [
{
"name": "kms.uris",
"type": "java.lang.String",
"description": "KMS URL's to which OpenVidu Server will try to connect. They are tested in order until a valid one is found"
},
{
"name": "openvidu.secret",
"type": "java.lang.String",
"description": "Secret used to connect to OpenVidu Server. This value is required when using the REST API or any server client, as well as when connecting to openvidu-server dashboard"
},
{
"name": "openvidu.publicurl",
"type": "java.lang.String",
"description": "URL to connect clients to OpenVidu Server. This must be the full IP of your OpenVidu Server, including protocol, host and port (for example: https://my.openvidu.server.ip:8443). If no port argument is provided, 'server.port' param will be appended to it"
},
{
"name": "openvidu.cdr",
"type": "java.lang.String",
"description": "Whether to enable Call Detail Record or not"
},
{
"name": "openvidu.recording",
"type": "java.lang.String",
"description": "Whether to start OpenVidu Server with recording module service available or not (a Docker image will be downloaded during the first execution). Apart from setting this param to true, it is also necessary to explicitly configure sessions to be recorded"
},
{
"name": "openvidu.recording.path",
"type": "java.lang.String",
"description": "Where to store the recorded video files"
},
{
"name": "openvidu.recording.free-access",
"type": "java.lang.String",
"description": "'true' to allow free access to the video files specified in 'openviu.recording.path'. 'false' to only allow access to authenticated users"
}
]}

View File

@ -7,7 +7,9 @@ server.ssl.key-store-type: JKS
server.ssl.key-alias: openvidu-selfsigned
kms.uris=[\"ws://localhost:8888/kurento\"]
openvidu.secret: MY_SECRET
openvidu.publicurl: local
openvidu.cdr: false
openvidu.recording: false
openvidu.recording.path: /opt/openvidu/recordings
openvidu.recording.free-access: false

File diff suppressed because one or more lines are too long

View File

@ -2764,6 +2764,7 @@ var RpcBuilder = __webpack_require__("../../../../../../../../../openvidu-browse
var OpenViduInternal = /** @class */ (function () {
function OpenViduInternal() {
this.remoteStreams = [];
this.recorder = false;
}
/* NEW METHODS */
OpenViduInternal.prototype.initSession = function (sessionId) {
@ -2891,6 +2892,12 @@ var OpenViduInternal = /** @class */ (function () {
OpenViduInternal.prototype.setSecret = function (secret) {
this.secret = secret;
};
OpenViduInternal.prototype.getRecorder = function () {
return this.recorder;
};
OpenViduInternal.prototype.setRecorder = function (recorder) {
this.recorder = recorder;
};
OpenViduInternal.prototype.getOpenViduServerURL = function () {
return 'https://' + this.wsUri.split("wss://")[1].split("/room")[0];
};
@ -3102,6 +3109,7 @@ exports.__esModule = true;
var Connection_1 = __webpack_require__("../../../../../../../../../openvidu-browser/lib/OpenViduInternal/Connection.js");
var EventEmitter = __webpack_require__("../../../../../../../../../openvidu-browser/node_modules/wolfy87-eventemitter/EventEmitter.js");
var SECRET_PARAM = '?secret=';
var RECORDER_PARAM = '&recorder=';
var SessionInternal = /** @class */ (function () {
function SessionInternal(openVidu, sessionId) {
this.openVidu = openVidu;
@ -3117,16 +3125,37 @@ var SessionInternal = /** @class */ (function () {
}
}
SessionInternal.prototype.processOpenViduUrl = function (url) {
this.openVidu.setSecret(this.getSecretFromUrl(url));
var secret = this.getSecretFromUrl(url);
var recorder = this.getRecorderFromUrl(url);
if (!(secret == null)) {
this.openVidu.setSecret(secret);
}
if (!(recorder == null)) {
this.openVidu.setRecorder(recorder);
}
this.openVidu.setWsUri(this.getFinalUrl(url));
};
SessionInternal.prototype.getSecretFromUrl = function (url) {
var secret = '';
if (url.indexOf(SECRET_PARAM) !== -1) {
var endOfSecret = url.lastIndexOf(RECORDER_PARAM);
if (endOfSecret !== -1) {
secret = url.substring(url.lastIndexOf(SECRET_PARAM) + SECRET_PARAM.length, endOfSecret);
}
else {
secret = url.substring(url.lastIndexOf(SECRET_PARAM) + SECRET_PARAM.length, url.length);
}
}
return secret;
};
SessionInternal.prototype.getRecorderFromUrl = function (url) {
var recorder = '';
if (url.indexOf(RECORDER_PARAM) !== -1) {
recorder = url.substring(url.lastIndexOf(RECORDER_PARAM) + RECORDER_PARAM.length, url.length);
}
return new Boolean(recorder).valueOf();
;
};
SessionInternal.prototype.getUrlWithoutSecret = function (url) {
if (!url) {
console.error('sessionId is not defined');
@ -3165,6 +3194,7 @@ var SessionInternal = /** @class */ (function () {
session: _this.sessionId,
metadata: _this.options.metadata,
secret: _this.openVidu.getSecret(),
recorder: _this.openVidu.getRecorder(),
dataChannels: false
};
if (_this.localParticipant) {
@ -9819,9 +9849,9 @@ var __WEBPACK_AMD_DEFINE_RESULT__;/**
} else {
// requirejs env (optional)
if ("function" === FUNC_TYPE && __webpack_require__("../../../../webpack/buildin/amd-options.js")) {
!(__WEBPACK_AMD_DEFINE_RESULT__ = function () {
!(__WEBPACK_AMD_DEFINE_RESULT__ = (function () {
return UAParser;
}.call(exports, __webpack_require__, exports, module),
}).call(exports, __webpack_require__, exports, module),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else if (window) {
// browser env
@ -13071,9 +13101,9 @@ var __WEBPACK_AMD_DEFINE_RESULT__;/*!
// Expose the class either via AMD, CommonJS or the global object
if (true) {
!(__WEBPACK_AMD_DEFINE_RESULT__ = function () {
!(__WEBPACK_AMD_DEFINE_RESULT__ = (function () {
return EventEmitter;
}.call(exports, __webpack_require__, exports, module),
}).call(exports, __webpack_require__, exports, module),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
}
else if (typeof module === 'object' && module.exports){
@ -13135,62 +13165,22 @@ module.exports = "<main>\n <router-outlet></router-outlet>\n</main>"
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return AppComponent; });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__angular_core__ = __webpack_require__("../../../core/esm5/core.js");
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_app_services_info_service__ = __webpack_require__("../../../../../src/app/services/info.service.ts");
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var AppComponent = (function () {
function AppComponent(infoService) {
this.infoService = infoService;
function AppComponent() {
}
AppComponent.prototype.ngOnInit = function () {
var _this = this;
var protocol = location.protocol.includes('https') ? 'wss://' : 'ws://';
var port = (location.port) ? (':' + location.port) : '';
this.websocket = new WebSocket(protocol + location.hostname + port + '/info');
this.websocket.onopen = function (event) {
console.log('Info websocket connected');
};
this.websocket.onclose = function (event) {
console.log('Info websocket closed');
};
this.websocket.onerror = function (event) {
console.log('Info websocket error');
};
this.websocket.onmessage = function (event) {
console.log('Info websocket message');
console.log(event.data);
_this.infoService.updateInfo(event.data);
};
};
AppComponent.prototype.ngOnDestroy = function () {
this.websocket.close();
};
AppComponent.prototype.beforeUnloadHander = function (event) {
console.warn('Closing info websocket');
this.websocket.close();
};
__decorate([
Object(__WEBPACK_IMPORTED_MODULE_0__angular_core__["A" /* HostListener */])('window:beforeunload', ['$event']),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], AppComponent.prototype, "beforeUnloadHander", null);
AppComponent = __decorate([
Object(__WEBPACK_IMPORTED_MODULE_0__angular_core__["n" /* Component */])({
selector: 'app-root',
template: __webpack_require__("../../../../../src/app/app.component.html"),
styles: [__webpack_require__("../../../../../src/app/app.component.css")]
}),
__metadata("design:paramtypes", [__WEBPACK_IMPORTED_MODULE_1_app_services_info_service__["a" /* InfoService */]])
})
], AppComponent);
return AppComponent;
}());
@ -13275,6 +13265,7 @@ var AppMaterialModule = (function () {
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_10__components_dashboard_dashboard_component__ = __webpack_require__("../../../../../src/app/components/dashboard/dashboard.component.ts");
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_11__components_session_details_session_details_component__ = __webpack_require__("../../../../../src/app/components/session-details/session-details.component.ts");
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_12__components_dashboard_credentials_dialog_component__ = __webpack_require__("../../../../../src/app/components/dashboard/credentials-dialog.component.ts");
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_13__components_layouts_layout_best_fit_layout_best_fit_component__ = __webpack_require__("../../../../../src/app/components/layouts/layout-best-fit/layout-best-fit.component.ts");
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@ -13294,6 +13285,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
var AppModule = (function () {
function AppModule() {
}
@ -13304,6 +13296,7 @@ var AppModule = (function () {
__WEBPACK_IMPORTED_MODULE_10__components_dashboard_dashboard_component__["a" /* DashboardComponent */],
__WEBPACK_IMPORTED_MODULE_11__components_session_details_session_details_component__["a" /* SessionDetailsComponent */],
__WEBPACK_IMPORTED_MODULE_12__components_dashboard_credentials_dialog_component__["a" /* CredentialsDialogComponent */],
__WEBPACK_IMPORTED_MODULE_13__components_layouts_layout_best_fit_layout_best_fit_component__["a" /* LayoutBestFitComponent */],
],
imports: [
__WEBPACK_IMPORTED_MODULE_0__angular_platform_browser__["a" /* BrowserModule */],
@ -13335,20 +13328,29 @@ var AppModule = (function () {
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__angular_router__ = __webpack_require__("../../../router/esm5/router.js");
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_app_components_dashboard_dashboard_component__ = __webpack_require__("../../../../../src/app/components/dashboard/dashboard.component.ts");
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_app_components_session_details_session_details_component__ = __webpack_require__("../../../../../src/app/components/session-details/session-details.component.ts");
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3_app_components_layouts_layout_best_fit_layout_best_fit_component__ = __webpack_require__("../../../../../src/app/components/layouts/layout-best-fit/layout-best-fit.component.ts");
var appRoutes = [
{
path: '',
component: __WEBPACK_IMPORTED_MODULE_1_app_components_dashboard_dashboard_component__["a" /* DashboardComponent */]
component: __WEBPACK_IMPORTED_MODULE_1_app_components_dashboard_dashboard_component__["a" /* DashboardComponent */],
pathMatch: 'full'
},
{
path: 'session/:id',
component: __WEBPACK_IMPORTED_MODULE_2_app_components_session_details_session_details_component__["a" /* SessionDetailsComponent */]
path: 'session/:sessionId',
component: __WEBPACK_IMPORTED_MODULE_2_app_components_session_details_session_details_component__["a" /* SessionDetailsComponent */],
pathMatch: 'full'
},
{
path: 'layout-best-fit/:sessionId/:secret',
component: __WEBPACK_IMPORTED_MODULE_3_app_components_layouts_layout_best_fit_layout_best_fit_component__["a" /* LayoutBestFitComponent */],
pathMatch: 'full'
}
];
var routing = __WEBPACK_IMPORTED_MODULE_0__angular_router__["a" /* RouterModule */].forRoot(appRoutes);
var routing = __WEBPACK_IMPORTED_MODULE_0__angular_router__["b" /* RouterModule */].forRoot(appRoutes, { useHash: true });
/***/ }),
@ -13398,7 +13400,7 @@ exports = module.exports = __webpack_require__("../../../../css-loader/lib/css-b
// module
exports.push([module.i, "#dashboard-div {\n padding: 20px;\n}\n\n#log {\n height: 90%;\n}\n\n#log-content {\n height: 90%;\n font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;\n overflow-y: auto;\n overflow-x: hidden\n}\n\nul {\n margin: 0;\n}\n\n#test-btn {\n text-transform: uppercase;\n float: right;\n}\n\nmat-card-title button.blue {\n color: #ffffff;\n background-color: #0088aa;\n}\n\nmat-card-title button.yellow {\n color: rgba(0, 0, 0, 0.87);\n background-color: #ffcc00;\n}\n\nmat-spinner {\n position: absolute;\n top: 50%;\n left: 50%;\n -webkit-transform: translate(-50%, -50%);\n transform: translate(-50%, -50%);\n}\n\n#tick-div {\n width: 100px;\n height: 100px;\n z-index: 1;\n position: absolute;\n top: 50%;\n left: 50%;\n -webkit-transform: translate(-50%, -50%);\n transform: translate(-50%, -50%);\n}\n\n#tooltip-tick {\n position: absolute;\n width: 100%;\n height: 100%;\n z-index: 2;\n}\n\n.circ {\n opacity: 0;\n stroke-dasharray: 130;\n stroke-dashoffset: 130;\n transition: all 1s;\n}\n\n.tick {\n stroke-dasharray: 50;\n stroke-dashoffset: 50;\n transition: stroke-dashoffset 1s 0.5s ease-out;\n}\n\n.drawn+svg .path {\n opacity: 1;\n stroke-dashoffset: 0;\n}\n\n#mirrored-video {\n position: relative;\n}\n\n\n/* Pure CSS loader */\n\n#loader {\n width: 100px;\n height: 100px;\n z-index: 1;\n position: absolute;\n top: 50%;\n left: 50%;\n -webkit-transform: translate(-50%, -50%);\n transform: translate(-50%, -50%);\n}\n\n#loader * {\n box-sizing: border-box;\n}\n\n#loader ::after {\n box-sizing: border-box;\n}\n\n#loader ::before {\n box-sizing: border-box;\n}\n\n.loader-1 {\n height: 100px;\n width: 100px;\n -webkit-animation: loader-1-1 4.8s linear infinite;\n animation: loader-1-1 4.8s linear infinite;\n}\n\n@-webkit-keyframes loader-1-1 {\n 0% {\n -webkit-transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(360deg);\n }\n}\n\n@keyframes loader-1-1 {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n.loader-1 span {\n display: block;\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n margin: auto;\n height: 100px;\n width: 100px;\n clip: rect(0, 100px, 100px, 50px);\n -webkit-animation: loader-1-2 1.2s linear infinite;\n animation: loader-1-2 1.2s linear infinite;\n}\n\n@-webkit-keyframes loader-1-2 {\n 0% {\n -webkit-transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(220deg);\n }\n}\n\n@keyframes loader-1-2 {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(220deg);\n transform: rotate(220deg);\n }\n}\n\n.loader-1 span::after {\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n margin: auto;\n height: 100px;\n width: 100px;\n clip: rect(0, 100px, 100px, 50px);\n border: 8px solid #4d4d4d;\n border-radius: 50%;\n -webkit-animation: loader-1-3 1.2s cubic-bezier(0.770, 0.000, 0.175, 1.000) infinite;\n animation: loader-1-3 1.2s cubic-bezier(0.770, 0.000, 0.175, 1.000) infinite;\n}\n\n@-webkit-keyframes loader-1-3 {\n 0% {\n -webkit-transform: rotate(-140deg);\n }\n 50% {\n -webkit-transform: rotate(-160deg);\n }\n 100% {\n -webkit-transform: rotate(140deg);\n }\n}\n\n@keyframes loader-1-3 {\n 0% {\n -webkit-transform: rotate(-140deg);\n transform: rotate(-140deg);\n }\n 50% {\n -webkit-transform: rotate(-160deg);\n transform: rotate(-160deg);\n }\n 100% {\n -webkit-transform: rotate(140deg);\n transform: rotate(140deg);\n }\n}", ""]);
exports.push([module.i, "#dashboard-div {\n padding: 20px;\n}\n\n#log {\n height: 90%;\n}\n\n#log-content {\n height: 90%;\n font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;\n overflow-y: auto;\n overflow-x: hidden\n}\n\nul {\n margin: 0;\n}\n\n#test-btn {\n text-transform: uppercase;\n float: right;\n}\n\nmat-card-title button.blue {\n color: #ffffff;\n background-color: #0088aa;\n}\n\nmat-card-title button.yellow {\n color: rgba(0, 0, 0, 0.87);\n background-color: #ffcc00;\n}\n\nmat-spinner {\n position: absolute;\n top: 50%;\n left: 50%;\n -webkit-transform: translate(-50%, -50%);\n transform: translate(-50%, -50%);\n}\n\n#tick-div {\n width: 100px;\n height: 100px;\n z-index: 1;\n position: absolute;\n top: 50%;\n left: 50%;\n -webkit-transform: translate(-50%, -50%);\n transform: translate(-50%, -50%);\n}\n\n#tooltip-tick {\n position: absolute;\n width: 100%;\n height: 100%;\n z-index: 2;\n}\n\n.circ {\n opacity: 0;\n stroke-dasharray: 130;\n stroke-dashoffset: 130;\n -webkit-transition: all 1s;\n transition: all 1s;\n}\n\n.tick {\n stroke-dasharray: 50;\n stroke-dashoffset: 50;\n -webkit-transition: stroke-dashoffset 1s 0.5s ease-out;\n transition: stroke-dashoffset 1s 0.5s ease-out;\n}\n\n.drawn+svg .path {\n opacity: 1;\n stroke-dashoffset: 0;\n}\n\n#mirrored-video {\n position: relative;\n}\n\n/* Pure CSS loader */\n\n#loader {\n width: 100px;\n height: 100px;\n z-index: 1;\n position: absolute;\n top: 50%;\n left: 50%;\n -webkit-transform: translate(-50%, -50%);\n transform: translate(-50%, -50%);\n}\n\n#loader * {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n#loader ::after {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n#loader ::before {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n.loader-1 {\n height: 100px;\n width: 100px;\n -webkit-animation: loader-1-1 4.8s linear infinite;\n animation: loader-1-1 4.8s linear infinite;\n}\n\n@-webkit-keyframes loader-1-1 {\n 0% {\n -webkit-transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(360deg);\n }\n}\n\n@keyframes loader-1-1 {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n.loader-1 span {\n display: block;\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n margin: auto;\n height: 100px;\n width: 100px;\n clip: rect(0, 100px, 100px, 50px);\n -webkit-animation: loader-1-2 1.2s linear infinite;\n animation: loader-1-2 1.2s linear infinite;\n}\n\n@-webkit-keyframes loader-1-2 {\n 0% {\n -webkit-transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(220deg);\n }\n}\n\n@keyframes loader-1-2 {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(220deg);\n transform: rotate(220deg);\n }\n}\n\n.loader-1 span::after {\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n margin: auto;\n height: 100px;\n width: 100px;\n clip: rect(0, 100px, 100px, 50px);\n border: 8px solid #4d4d4d;\n border-radius: 50%;\n -webkit-animation: loader-1-3 1.2s cubic-bezier(0.770, 0.000, 0.175, 1.000) infinite;\n animation: loader-1-3 1.2s cubic-bezier(0.770, 0.000, 0.175, 1.000) infinite;\n}\n\n@-webkit-keyframes loader-1-3 {\n 0% {\n -webkit-transform: rotate(-140deg);\n }\n 50% {\n -webkit-transform: rotate(-160deg);\n }\n 100% {\n -webkit-transform: rotate(140deg);\n }\n}\n\n@keyframes loader-1-3 {\n 0% {\n -webkit-transform: rotate(-140deg);\n transform: rotate(-140deg);\n }\n 50% {\n -webkit-transform: rotate(-160deg);\n transform: rotate(-160deg);\n }\n 100% {\n -webkit-transform: rotate(140deg);\n transform: rotate(140deg);\n }\n}", ""]);
// exports
@ -13459,18 +13461,38 @@ var DashboardComponent = (function () {
});
}
DashboardComponent.prototype.ngOnInit = function () {
var _this = this;
var protocol = location.protocol.includes('https') ? 'wss://' : 'ws://';
var port = (location.port) ? (':' + location.port) : '';
this.websocket = new WebSocket(protocol + location.hostname + port + '/info');
this.websocket.onopen = function (event) {
console.log('Info websocket connected');
};
this.websocket.onclose = function (event) {
console.log('Info websocket closed');
};
this.websocket.onerror = function (event) {
console.log('Info websocket error');
};
this.websocket.onmessage = function (event) {
console.log('Info websocket message');
console.log(event.data);
_this.infoService.updateInfo(event.data);
};
};
DashboardComponent.prototype.beforeunloadHandler = function () {
// On window closed leave test session
// On window closed leave test session and close info websocket
if (this.session) {
this.endTestVideo();
}
this.websocket.close();
};
DashboardComponent.prototype.ngOnDestroy = function () {
// On component destroyed leave test session
// On component destroyed leave test session and close info websocket
if (this.session) {
this.endTestVideo();
}
this.websocket.close();
};
DashboardComponent.prototype.toggleTestVideo = function () {
if (!this.session) {
@ -13589,6 +13611,188 @@ var DashboardComponent = (function () {
/***/ }),
/***/ "../../../../../src/app/components/layouts/layout-best-fit/layout-best-fit.component.css":
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__("../../../../css-loader/lib/css-base.js")(false);
// imports
// module
exports.push([module.i, ".bounds {\n background-color: black;\n height: 100%;\n overflow: hidden;\n cursor: none !important;\n}\n\nvideo {\n height: 100%;\n}\n", ""]);
// exports
/*** EXPORTS FROM exports-loader ***/
module.exports = module.exports.toString();
/***/ }),
/***/ "../../../../../src/app/components/layouts/layout-best-fit/layout-best-fit.component.html":
/***/ (function(module, exports) {
module.exports = "<div class=\"bounds\">\n <div *ngFor=\"let streams of remoteStreams\" class=\"content\" fxLayout=\"row\" fxFlexFill [style.height]=\"100 / remoteStreams.length + '%'\"\n [style.min-height]=\"100 / remoteStreams.length + '%'\">\n <div *ngFor=\"let s of streams\" [fxFlex]=\"100 / streams\">\n <video [id]=\"'native-video-' + s.streamId\" autoplay=\"true\" [srcObject]=\"s.getMediaStream()\"></video>\n </div>\n </div>\n</div>\n"
/***/ }),
/***/ "../../../../../src/app/components/layouts/layout-best-fit/layout-best-fit.component.ts":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return LayoutBestFitComponent; });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__angular_core__ = __webpack_require__("../../../core/esm5/core.js");
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__angular_router__ = __webpack_require__("../../../router/esm5/router.js");
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_openvidu_browser__ = __webpack_require__("../../../../../../../../../openvidu-browser/lib/OpenVidu/index.js");
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_openvidu_browser___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2_openvidu_browser__);
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var LayoutBestFitComponent = (function () {
function LayoutBestFitComponent(route) {
var _this = this;
this.route = route;
this.numberOfVideos = 0;
this.remoteStreams = [];
this.route.params.subscribe(function (params) {
_this.sessionId = params.sessionId;
_this.secret = params.secret;
});
}
LayoutBestFitComponent.prototype.beforeunloadHandler = function () {
this.leaveSession();
};
LayoutBestFitComponent.prototype.ngOnDestroy = function () {
this.leaveSession();
};
LayoutBestFitComponent.prototype.ngOnInit = function () {
var _this = this;
var OV = new __WEBPACK_IMPORTED_MODULE_2_openvidu_browser__["OpenVidu"]();
var fullSessionId = 'wss://' + location.hostname + ':8443/' + this.sessionId + '?secret=' + this.secret + '&recorder=true';
this.session = OV.initSession(fullSessionId);
this.session.on('streamCreated', function (event) {
_this.numberOfVideos++;
_this.addRemoteStream(event.stream);
_this.session.subscribe(event.stream, '');
});
this.session.on('streamDestroyed', function (event) {
_this.numberOfVideos--;
event.preventDefault();
_this.deleteRemoteStream(event.stream);
});
this.session.connect(null, function (error) {
if (error) {
console.error(error);
}
});
};
LayoutBestFitComponent.prototype.addRemoteStream = function (stream) {
switch (true) {
case (this.numberOfVideos <= 2):
if (this.remoteStreams[0] == null) {
this.remoteStreams[0] = [];
}
this.remoteStreams[0].push(stream);
break;
case (this.numberOfVideos <= 4):
if (this.remoteStreams[1] == null) {
this.remoteStreams[1] = [];
}
this.remoteStreams[1].push(stream);
break;
case (this.numberOfVideos <= 5):
this.remoteStreams[0].push(stream);
break;
case (this.numberOfVideos <= 6):
this.remoteStreams[1].push(stream);
break;
default:
if (this.remoteStreams[2] == null) {
this.remoteStreams[2] = [];
}
this.remoteStreams[2].push(stream);
break;
}
};
LayoutBestFitComponent.prototype.deleteRemoteStream = function (stream) {
for (var i = 0; i < this.remoteStreams.length; i++) {
var index = this.remoteStreams[i].indexOf(stream, 0);
if (index > -1) {
this.remoteStreams[i].splice(index, 1);
this.reArrangeVideos();
break;
}
}
};
LayoutBestFitComponent.prototype.reArrangeVideos = function () {
switch (true) {
case (this.numberOfVideos === 1):
if (this.remoteStreams[0].length === 0) {
this.remoteStreams[0].push(this.remoteStreams[1].pop());
}
break;
case (this.numberOfVideos === 2):
if (this.remoteStreams[0].length === 1) {
this.remoteStreams[0].push(this.remoteStreams[1].pop());
}
break;
case (this.numberOfVideos === 3):
if (this.remoteStreams[0].length === 1) {
this.remoteStreams[0].push(this.remoteStreams[1].pop());
}
break;
case (this.numberOfVideos === 4):
if (this.remoteStreams[0].length === 3) {
this.remoteStreams[1].unshift(this.remoteStreams[0].pop());
}
break;
case (this.numberOfVideos === 5):
if (this.remoteStreams[0].length === 2) {
this.remoteStreams[0].push(this.remoteStreams[1].shift());
}
break;
}
this.remoteStreams = this.remoteStreams.filter(function (array) { return array.length > 0; });
};
LayoutBestFitComponent.prototype.leaveSession = function () {
if (this.session) {
this.session.disconnect();
}
;
this.remoteStreams = [];
this.numberOfVideos = 0;
this.session = null;
};
__decorate([
Object(__WEBPACK_IMPORTED_MODULE_0__angular_core__["A" /* HostListener */])('window:beforeunload'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], LayoutBestFitComponent.prototype, "beforeunloadHandler", null);
LayoutBestFitComponent = __decorate([
Object(__WEBPACK_IMPORTED_MODULE_0__angular_core__["n" /* Component */])({
selector: 'app-layout-best-fit',
template: __webpack_require__("../../../../../src/app/components/layouts/layout-best-fit/layout-best-fit.component.html"),
styles: [__webpack_require__("../../../../../src/app/components/layouts/layout-best-fit/layout-best-fit.component.css")]
}),
__metadata("design:paramtypes", [__WEBPACK_IMPORTED_MODULE_1__angular_router__["a" /* ActivatedRoute */]])
], LayoutBestFitComponent);
return LayoutBestFitComponent;
}());
/***/ }),
/***/ "../../../../../src/app/components/session-details/session-details.component.css":

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3028,9 +3028,9 @@ var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'und
freeGlobal.Hammer = Hammer;
if (true) {
!(__WEBPACK_AMD_DEFINE_RESULT__ = function() {
!(__WEBPACK_AMD_DEFINE_RESULT__ = (function() {
return Hammer;
}.call(exports, __webpack_require__, exports, module),
}).call(exports, __webpack_require__, exports, module),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else if (typeof module != 'undefined' && module.exports) {
module.exports = Hammer;
@ -20931,6 +20931,8 @@ function tryCatch(fn) {
/* unused harmony export __asyncDelegator */
/* unused harmony export __asyncValues */
/* unused harmony export __makeTemplateObject */
/* unused harmony export __importStar */
/* unused harmony export __importDefault */
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
@ -21098,6 +21100,18 @@ function __makeTemplateObject(cooked, raw) {
return cooked;
};
function __importStar(mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result.default = mod;
return result;
}
function __importDefault(mod) {
return (mod && mod.__esModule) ? mod : { default: mod };
}
/***/ }),
@ -153507,7 +153521,7 @@ var VERSION = new __WEBPACK_IMPORTED_MODULE_1__angular_core__["_11" /* Version *
/* unused harmony export ROUTES */
/* unused harmony export ROUTER_CONFIGURATION */
/* unused harmony export ROUTER_INITIALIZER */
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RouterModule; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return RouterModule; });
/* unused harmony export provideRoutes */
/* unused harmony export ChildrenOutletContexts */
/* unused harmony export OutletContext */
@ -153515,7 +153529,7 @@ var VERSION = new __WEBPACK_IMPORTED_MODULE_1__angular_core__["_11" /* Version *
/* unused harmony export PreloadAllModules */
/* unused harmony export PreloadingStrategy */
/* unused harmony export RouterPreloader */
/* unused harmony export ActivatedRoute */
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return ActivatedRoute; });
/* unused harmony export ActivatedRouteSnapshot */
/* unused harmony export RouterState */
/* unused harmony export RouterStateSnapshot */

File diff suppressed because one or more lines are too long