openvidu-testapp/openvidu-test-e2e: individual stream recording

pull/203/head
pabloFuente 2019-01-21 18:46:40 +01:00
parent e752685b31
commit 0ba9bbb152
5 changed files with 336 additions and 57 deletions

View File

@ -22,10 +22,15 @@ import static org.openqa.selenium.OutputType.BASE64;
import java.awt.Color; import java.awt.Color;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -35,6 +40,8 @@ import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@ -170,9 +177,7 @@ public class OpenViduTestAppE2eTest {
@AfterEach @AfterEach
void dispose() { void dispose() {
if (user != null) { user.dispose();
user.dispose();
}
} }
@Test @Test
@ -974,9 +979,9 @@ public class OpenViduTestAppE2eTest {
void remoteComposedRecordTest() throws Exception { void remoteComposedRecordTest() throws Exception {
setupBrowser("chrome"); setupBrowser("chrome");
log.info("Remote record"); log.info("Remote composed record");
final String sessionName = "RECORDED_SESSION"; final String sessionName = "COMPOSED_RECORDED_SESSION";
user.getDriver().findElement(By.id("add-user-btn")).click(); user.getDriver().findElement(By.id("add-user-btn")).click();
user.getDriver().findElement(By.id("session-name-input-0")).clear(); user.getDriver().findElement(By.id("session-name-input-0")).clear();
@ -1073,9 +1078,9 @@ public class OpenViduTestAppE2eTest {
File file2 = new File(recordingsPath + sessionName + "/" + ".recording." + sessionName); File file2 = new File(recordingsPath + sessionName + "/" + ".recording." + sessionName);
File file3 = new File(recordingsPath + sessionName + "/" + sessionName + ".jpg"); File file3 = new File(recordingsPath + sessionName + "/" + sessionName + ".jpg");
Assert.assertTrue(file1.exists() || file1.length() > 0); Assert.assertTrue(file1.exists() && file1.length() > 0);
Assert.assertTrue(file2.exists() || file2.length() > 0); Assert.assertTrue(file2.exists() && file2.length() > 0);
Assert.assertTrue(file3.exists() || file3.length() > 0); Assert.assertTrue(file3.exists() && file3.length() > 0);
Assert.assertTrue( Assert.assertTrue(
this.recordedFileFine(file1, new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(sessionName))); this.recordedFileFine(file1, new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(sessionName)));
@ -1109,7 +1114,117 @@ public class OpenViduTestAppE2eTest {
@Test @Test
@DisplayName("Remote individual record") @DisplayName("Remote individual record")
void remoteIndividualRecordTest() throws Exception { void remoteIndividualRecordTest() throws Exception {
setupBrowser("chrome");
log.info("Remote individual record");
final String sessionName = "TestSession";
final String recordingName = "CUSTOM_NAME";
user.getDriver().findElement(By.id("add-user-btn")).click();
user.getDriver().findElement(By.id("session-name-input-0")).clear();
user.getDriver().findElement(By.id("session-name-input-0")).sendKeys(sessionName);
user.getDriver().findElement(By.id("auto-join-checkbox")).click();
user.getDriver().findElement(By.id("one2one-btn")).click();
user.getEventManager().waitUntilEventReaches("connectionCreated", 4);
user.getEventManager().waitUntilEventReaches("accessAllowed", 2);
user.getEventManager().waitUntilEventReaches("streamCreated", 4);
user.getEventManager().waitUntilEventReaches("streamPlaying", 4);
Assert.assertTrue(user.getEventManager().assertMediaTracks(user.getDriver().findElements(By.tagName("video")),
true, true));
user.getDriver().findElement(By.id("session-api-btn-0")).click();
Thread.sleep(1000);
user.getDriver().findElement(By.id("rec-properties-btn")).click();
Thread.sleep(500);
// Set recording name
user.getDriver().findElement(By.id("recording-name-field")).sendKeys(recordingName);
// Set OutputMode to INDIVIDUAL
user.getDriver().findElement(By.id("rec-outputmode-select")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("option-INDIVIDUAL")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("start-recording-btn")).click();
user.getWaiter().until(ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value",
"Recording started [" + sessionName + "]"));
user.getEventManager().waitUntilEventReaches("recordingStarted", 2);
Thread.sleep(8000);
user.getDriver().findElement(By.id("recording-id-field")).clear();
user.getDriver().findElement(By.id("recording-id-field")).sendKeys(sessionName);
// Try to start an ongoing recording
user.getDriver().findElement(By.id("start-recording-btn")).click();
user.getWaiter()
.until(ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value", "Error [409]"));
// Try to get an existing recording
user.getDriver().findElement(By.id("get-recording-btn")).click();
user.getWaiter().until(ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value",
"Recording got [" + sessionName + "]"));
// Try to delete an ongoing recording
user.getDriver().findElement(By.id("delete-recording-btn")).click();
user.getWaiter()
.until(ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value", "Error [409]"));
// List existing recordings (one)
user.getDriver().findElement(By.id("list-recording-btn")).click();
user.getWaiter().until(ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value",
"Recording list [" + sessionName + "]"));
// Stop ongoing recording
user.getDriver().findElement(By.id("stop-recording-btn")).click();
user.getWaiter().until(ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value",
"Recording stopped [" + sessionName + "]"));
user.getEventManager().waitUntilEventReaches("recordingStopped", 2);
String recordingsPath = "/opt/openvidu/recordings/";
// Should be only 2 files: zip and metadata
File folder = new File(recordingsPath + sessionName);
Assert.assertEquals(folder.listFiles().length, 2);
File file1 = new File(recordingsPath + sessionName + "/" + recordingName + ".zip");
File file2 = new File(recordingsPath + sessionName + "/" + ".recording." + sessionName);
Assert.assertTrue(file1.exists() && file1.length() > 0);
Assert.assertTrue(file2.exists() && file2.length() > 0);
unzipAndCheckFile(recordingsPath + sessionName + "/", recordingName + ".zip",
new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(sessionName));
// Try to get the stopped recording
user.getDriver().findElement(By.id("get-recording-btn")).click();
user.getWaiter().until(ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value",
"Recording got [" + sessionName + "]"));
// Try to list the stopped recording
user.getDriver().findElement(By.id("list-recording-btn")).click();
user.getWaiter().until(ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value",
"Recording list [" + sessionName + "]"));
// Delete the recording
user.getDriver().findElement(By.id("delete-recording-btn")).click();
user.getWaiter()
.until(ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value", "Recording deleted"));
Assert.assertFalse(file1.exists());
Assert.assertFalse(file2.exists());
user.getDriver().findElement(By.id("close-dialog-btn")).click();
Thread.sleep(1000);
gracefullyLeaveParticipants(2);
} }
@Test @Test
@ -1553,6 +1668,7 @@ public class OpenViduTestAppE2eTest {
// Get a frame at 75% duration // Get a frame at 75% duration
frame = FrameGrab.getFrameAtSec(file, (double) (recording.getDuration() * 0.75)); frame = FrameGrab.getFrameAtSec(file, (double) (recording.getDuration() * 0.75));
Map<String, Long> colorMap = this.averageColor(AWTUtil.toBufferedImage(frame)); Map<String, Long> colorMap = this.averageColor(AWTUtil.toBufferedImage(frame));
log.info("Recording map color: {}", colorMap.toString()); log.info("Recording map color: {}", colorMap.toString());
isFine = this.checkVideoAverageRgbGreen(colorMap); isFine = this.checkVideoAverageRgbGreen(colorMap);
} catch (IOException | JCodecException e) { } catch (IOException | JCodecException e) {
@ -1602,4 +1718,45 @@ public class OpenViduTestAppE2eTest {
return colorMap; return colorMap;
} }
private void unzipAndCheckFile(String path, String fileName, Recording recording) {
final int BUFFER = 2048;
final List<String> recordingFiles = new ArrayList<>();
try {
BufferedOutputStream dest = null;
FileInputStream fis = new FileInputStream(path + fileName);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
log.info("Extracting: " + entry);
if (entry.getName().endsWith(".webm")) {
recordingFiles.add(entry.getName());
}
int count;
byte data[] = new byte[BUFFER];
FileOutputStream fos = new FileOutputStream(path + entry.getName());
dest = new BufferedOutputStream(fos, BUFFER);
while ((count = zis.read(data, 0, BUFFER)) != -1) {
dest.write(data, 0, count);
}
dest.flush();
dest.close();
}
zis.close();
} catch (Exception e) {
e.printStackTrace();
}
long totalFileSize = 0;
for (String videoFileName : recordingFiles) {
File videoFile = new File(path + videoFileName);
totalFileSize += videoFile.length();
Assert.assertTrue(videoFile.exists() && videoFile.length() > 0);
videoFile.delete();
}
Assert.assertEquals(recording.getSize() / 1000, totalFileSize / 1000);
File jsonSyncFile = new File(path + recording.getSessionId() + ".json");
Assert.assertTrue(jsonSyncFile.exists() && jsonSyncFile.length() > 0);
jsonSyncFile.delete();
}
} }

View File

@ -0,0 +1,55 @@
#response-text-area {
width: 100%;
color: #808080;
}
#response-text-area textarea {
resize: none;
}
mat-dialog-content button,
mat-divider {
margin-bottom: 5px;
}
mat-dialog-content button {
height: 30px;
line-height: 30px;
padding-left: 12px;
padding-right: 12px;
display: inline-flex;
}
.label {
display: block;
font-size: 12px;
color: rgba(0, 0, 0, 0.54);
font-weight: 400;
margin-bottom: 5px;
margin-top: 13px
}
.inner-text-input {
margin-left: 16px;
}
#rec-properties-btn {
padding-left: 0;
padding-right: 0;
display: inline-block;
}
#rec-properties-div {
margin-bottom: 10px;
padding: 5px;
border: 1px solid #00000026;
border-radius: 3px;
}
#rec-properties-div mat-form-field {
max-width: 150px;
}
#rec-properties-div mat-checkbox {
margin-left: 16px;
}

View File

@ -0,0 +1,73 @@
<div>
<h2 mat-dialog-title>API REST</h2>
<mat-dialog-content>
<label class="label">Sessions</label>
<div>
<button mat-button id="get-session-btn" (click)="fetchActiveConnections()">Fetch</button>
<button mat-button id="list-sessions-btn" (click)="fetchActiveSessions()">Fetch all</button>
<button mat-button id="close-session-btn" (click)="closeSession()">Close this session</button>
</div>
<mat-form-field class="inner-text-input">
<input matInput id="resource-id-field" placeholder="resourceId" [(ngModel)]="resourceId">
</mat-form-field>
<div>
<button mat-button id="force-disconnect-api-btn" (click)="forceDisconnect()" [disabled]="!resourceId">Force
disconnect</button>
<button mat-button id="force-unpublish-api-btn" (click)="forceUnpublish()" [disabled]="!resourceId">Force
unpublish</button>
<mat-divider></mat-divider>
</div>
<label class="label">Recordings</label>
<button mat-button id="list-recording-btn" (click)="listRecordings()">List recordings</button>
<button mat-button id="start-recording-btn" (click)="startRecording()">Start recording</button>
<button id="rec-properties-btn" mat-icon-button style="width: 24px; height: 24px; line-height: 24px;" title="Recording properties"
(click)="toggleRecProperties()">
<mat-icon style="font-size: 18px; line-height: 18px; width: 18px; height: 18px" aria-label="Recording properties">{{recPropertiesIcon}}</mat-icon>
</button>
<div *ngIf="showRecProperties" id="rec-properties-div">
<div>
<mat-form-field class="inner-text-input">
<input matInput id="recording-name-field" placeholder="Recording name" [(ngModel)]="recordingProperties.name">
</mat-form-field>
<mat-form-field class="inner-text-input">
<mat-select id="rec-outputmode-select" placeholder="Output mode" [(ngModel)]="recordingProperties.outputMode">
<mat-option *ngFor="let enumerator of enumToArray(recMode)" [value]="enumerator">
<span [attr.id]="'option-' + enumerator">{{ enumerator }}</span>
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="recordingProperties.outputMode.toString() === recMode[recMode.COMPOSED]" id="rec-layout-div">
<mat-form-field class="inner-text-input">
<mat-select placeholder="Recording layout" [(ngModel)]="recordingProperties.recordingLayout">
<mat-option *ngFor="let enumerator of enumToArray(recLayouts)" [value]="enumerator">
{{ enumerator }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field *ngIf="recordingProperties.recordingLayout.toString() === recLayouts[recLayouts.CUSTOM]" class="inner-text-input">
<input matInput placeholder="Custom layout" type="text" [(ngModel)]="recordingProperties.customLayout">
</mat-form-field>
</div>
<div>
<mat-checkbox [(ngModel)]="recordingProperties.hasAudio">Has audio</mat-checkbox>
<mat-checkbox [(ngModel)]="recordingProperties.hasVideo">Has video</mat-checkbox>
</div>
</div>
<mat-form-field class="inner-text-input">
<input matInput id="recording-id-field" placeholder="recordingId" [(ngModel)]="recordingId">
</mat-form-field>
<div>
<button mat-button id="stop-recording-btn" (click)="stopRecording()" [disabled]="!recordingId">Stop recording</button>
<button mat-button id="get-recording-btn" (click)="getRecording()" [disabled]="!recordingId">Get recording</button>
<button mat-button id="delete-recording-btn" (click)="deleteRecording()" [disabled]="!recordingId">Delete
recording</button>
</div>
<mat-form-field *ngIf="!!response" id="response-text-area" appearance="fill">
<textarea id="api-response-text-area" [(ngModel)]="response" matInput readonly></textarea>
</mat-form-field>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button id="close-dialog-btn" [mat-dialog-close]="{session: session, recordingProperties: recordingProperties}">CLOSE</button>
</mat-dialog-actions>
</div>

View File

@ -1,50 +1,12 @@
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { OpenVidu as OpenViduAPI, Session as SessionAPI } from 'openvidu-node-client'; import { OpenVidu as OpenViduAPI, Session as SessionAPI, Recording, RecordingProperties, RecordingLayout } from 'openvidu-node-client';
@Component({ @Component({
selector: 'app-session-api-dialog', selector: 'app-session-api-dialog',
template: ` templateUrl: './session-api-dialog.component.html',
<div> styleUrls: ['./session-api-dialog.component.css']
<h2 mat-dialog-title>API REST</h2>
<mat-dialog-content>
<label class="label">Sessions</label>
<button mat-button id="get-session-btn" (click)="fetchActiveConnections()">Fetch</button>
<button mat-button id="list-sessions-btn" (click)="fetchActiveSessions()">Fetch all</button>
<button mat-button id="close-session-btn" (click)="closeSession()">Close this session</button>
<mat-form-field class="inner-text-input">
<input matInput id="resource-id-field" placeholder="resourceId" [(ngModel)]="resourceId">
</mat-form-field>
<button mat-button id="force-disconnect-api-btn" (click)="forceDisconnect()" [disabled]="!resourceId">Force disconnect</button>
<button mat-button id="force-unpublish-api-btn" (click)="forceUnpublish()" [disabled]="!resourceId">Force unpublish</button>
<mat-divider></mat-divider>
<label class="label">Recordings</label>
<button mat-button id="start-recording-btn" (click)="startRecording()">Start recording</button>
<button mat-button id="list-recording-btn" (click)="listRecordings()">List recordings</button>
<mat-form-field class="inner-text-input">
<input matInput id="recording-id-field" placeholder="recordingId" [(ngModel)]="recordingId">
</mat-form-field>
<button mat-button id="stop-recording-btn" (click)="stopRecording()" [disabled]="!recordingId">Stop recording</button>
<button mat-button id="get-recording-btn" (click)="getRecording()" [disabled]="!recordingId">Get recording</button>
<button mat-button id="delete-recording-btn" (click)="deleteRecording()" [disabled]="!recordingId">Delete recording</button>
<mat-form-field *ngIf="!!response" id="response-text-area" appearance="fill">
<textarea id="api-response-text-area" [(ngModel)]="response" matInput readonly></textarea>
</mat-form-field>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button id="close-dialog-btn" [mat-dialog-close]="{session: session}">CLOSE</button>
</mat-dialog-actions>
</div>
`,
styles: [
'#response-text-area { width: 100%; color: #808080; }',
'#response-text-area textarea { resize: none; }',
'mat-dialog-content button, mat-divider { margin-bottom: 5px; }',
'mat-dialog-content button { height: 30px; line-height: 30px; padding-left: 12px; padding-right: 12px; display: inline-flex;}',
'.label { display: block; font-size: 12px; color: rgba(0, 0, 0, 0.54); font-weight: 400; margin-bottom: 5px; margin-top: 13px}',
'.inner-text-input { margin-left: 16px; }'
]
}) })
export class SessionApiDialogComponent { export class SessionApiDialogComponent {
@ -55,11 +17,19 @@ export class SessionApiDialogComponent {
resourceId: string; resourceId: string;
response: string; response: string;
recordingProperties: RecordingProperties;
recMode = Recording.OutputMode;
recLayouts = RecordingLayout;
customLayout = '';
recPropertiesIcon = 'add_circle';
showRecProperties = false;
constructor(public dialogRef: MatDialogRef<SessionApiDialogComponent>, constructor(public dialogRef: MatDialogRef<SessionApiDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data) { @Inject(MAT_DIALOG_DATA) public data) {
this.OV = data.openVidu; this.OV = data.openVidu;
this.session = data.session; this.session = data.session;
this.sessionId = data.sessionId; this.sessionId = data.sessionId;
this.recordingProperties = data.recordingProperties;
} }
closeSession() { closeSession() {
@ -80,7 +50,7 @@ export class SessionApiDialogComponent {
startRecording() { startRecording() {
console.log('Starting recording'); console.log('Starting recording');
this.OV.startRecording(this.sessionId) this.OV.startRecording(this.sessionId, this.recordingProperties)
.then(recording => { .then(recording => {
this.response = 'Recording started [' + recording.id + ']'; this.response = 'Recording started [' + recording.id + ']';
}) })
@ -195,4 +165,13 @@ export class SessionApiDialogComponent {
}); });
} }
enumToArray(enumerator: any) {
return Object.keys(enumerator);
}
toggleRecProperties() {
this.showRecProperties = !this.showRecProperties;
this.recPropertiesIcon = this.showRecProperties ? 'remove_circle' : 'add_circle';
}
} }

View File

@ -16,7 +16,9 @@ import {
RecordingMode, RecordingMode,
RecordingLayout, RecordingLayout,
TokenOptions, TokenOptions,
OpenViduRole OpenViduRole,
RecordingProperties,
Recording
} from 'openvidu-node-client'; } from 'openvidu-node-client';
import { MatDialog, MAT_CHECKBOX_CLICK_ACTION } from '@angular/material'; import { MatDialog, MAT_CHECKBOX_CLICK_ACTION } from '@angular/material';
import { ExtensionDialogComponent } from '../dialogs/extension-dialog/extension-dialog.component'; import { ExtensionDialogComponent } from '../dialogs/extension-dialog/extension-dialog.component';
@ -71,6 +73,9 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
subscribeToRemote = false; subscribeToRemote = false;
optionsVideo = 'video'; optionsVideo = 'video';
// Recording options
recordingProperties: RecordingProperties;
// OpenVidu Browser objects // OpenVidu Browser objects
OV: OpenVidu; OV: OpenVidu;
session: Session; session: Session;
@ -441,7 +446,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
this.publisher = this.OV.initPublisher( this.publisher = this.OV.initPublisher(
undefined, undefined,
this.publisherProperties, this.publisherProperties,
(err) => { err => {
if (err) { if (err) {
console.warn(err); console.warn(err);
this.openviduError = err; this.openviduError = err;
@ -528,7 +533,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
width: '450px' width: '450px'
}); });
dialogRef.afterClosed().subscribe((result) => { dialogRef.afterClosed().subscribe(result => {
if (!!result) { if (!!result) {
this.sessionProperties = result.sessionProperties; this.sessionProperties = result.sessionProperties;
if (!!this.sessionProperties.customSessionId) { if (!!this.sessionProperties.customSessionId) {
@ -546,11 +551,20 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
openSessionApiDialog() { openSessionApiDialog() {
const dialogRef = this.dialog.open(SessionApiDialogComponent, { const dialogRef = this.dialog.open(SessionApiDialogComponent, {
data: { data: {
openVidu: this.OV_NodeClient ? this.OV_NodeClient : new OpenViduAPI(this.openviduUrl, this.openviduSecret), openVidu: !!this.OV_NodeClient ? this.OV_NodeClient : new OpenViduAPI(this.openviduUrl, this.openviduSecret),
session: this.sessionAPI, session: this.sessionAPI,
sessionId: !!this.session ? this.session.sessionId : this.sessionName sessionId: !!this.session ? this.session.sessionId : this.sessionName,
recordingProperties: !!this.recordingProperties ? this.recordingProperties :
{
name: '',
outputMode: Recording.OutputMode.COMPOSED,
recordingLayout: this.sessionProperties.defaultRecordingLayout,
customLayout: this.sessionProperties.defaultCustomLayout,
hasAudio: true,
hasVideo: true
}
}, },
width: '280px', width: '425px',
disableClose: true disableClose: true
}); });
@ -558,6 +572,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
if (!result.session) { if (!result.session) {
delete this.sessionAPI; delete this.sessionAPI;
} }
this.recordingProperties = result.recordingProperties;
document.getElementById('session-api-btn-' + this.index).classList.remove('cdk-program-focused'); document.getElementById('session-api-btn-' + this.index).classList.remove('cdk-program-focused');
}); });
} }
@ -588,7 +603,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
disableClose: true disableClose: true
}); });
dialogRef.afterClosed().subscribe((result) => { dialogRef.afterClosed().subscribe(result => {
if (!!this.session && JSON.stringify(this.sessionEvents) !== JSON.stringify(oldValues)) { if (!!this.session && JSON.stringify(this.sessionEvents) !== JSON.stringify(oldValues)) {
this.updateSessionEvents(oldValues, false); this.updateSessionEvents(oldValues, false);