Commit e5bc8b81 authored by Tom JORQUERA's avatar Tom JORQUERA
Browse files

Merge branch 'Issue_55' into 'master'

Issue 55 - Manage reconnection

Allow the bot to reconnect to a conference with a proper initialisation
state

Closes #56

See merge request !34
parents 3cc24a3e 95196bad
Pipeline #4175 passed with stage
in 36 seconds
......@@ -30,9 +30,12 @@ const room = arguments[0];
robotController = {
$scope: angular.element(document.body).scope().$root,
$injector: angular.element(document.body).injector(),
chatService: angular.element(document.body).injector().get('chat'),
getWebRTCAdapter: () => robotController.$injector.get('easyRTCAdapter'),
getMyId: () => {
return easyrtc.myEasyrtcid;
},
......
......@@ -35,6 +35,7 @@ robot = {
recordedParticipantsWS: {},
participantsMediaRecorders: {},
isDisconnected: false,
intervalList: [],
processAudio(stream, callback, interval) {
const mediaRecorder = new MediaRecorder(stream);
......@@ -130,13 +131,35 @@ robot = {
}
},
clearConnection() {
for (let i = 0; i < robot.intervalList.length; i++) {
clearInterval(robot.intervalList[i]);
}
robot.intervalList = [];
const keyMap = Object.keys(robot.recordedParticipantsWS);
if (keyMap) {
for (let i = 0; i < keyMap.length; i++) {
try {
robot.stopRecordParticipant(keyMap[i]);
} catch (err) {
console.log('This user is inactive');
}
}
}
},
start: () => {
robotLib.stt = robotLib.stt(config);
robotLib.reco = robotLib.reco(config);
robotLib.archive = robotLib.archive(config);
robotController.onAttendeePush = (e, data) => {
robot.recordParticipant(data.easyrtcid);
if (data.easyrtcid === robotController.getMyId()) {
robot.onConnection();
} else {
robot.recordParticipant(data.easyrtcid);
}
};
robotController.onAttendeeRemove = (e, data) => {
......@@ -144,19 +167,25 @@ robot = {
robot.checkDisconnect();
};
function recoStartRetry() {
if (!robotLib.reco.start(room)) {
setTimeout(recoStartRetry, 8000);
robotController.getWebRTCAdapter().addDisconnectCallback(() => {
robot.clearConnection();
});
},
onConnection: () => {
const recoStartRetry = () => {
if (robotLib.reco.start(room)) {
clearInterval(robot.recoInterval);
}
}
// If start fails, schedule retry at fixed interval until success
recoStartRetry();
};
robot.recoInterval = setInterval(recoStartRetry, 8000);
setInterval(
() => robotLib.reco.getOnlineReco(room)
robot.intervalList.push(robot.recoInterval);
robot.intervalList.push(
setInterval(() => robotLib.reco.getOnlineReco(room)
.then(robot.processReco)
.catch(console.error),
8000);
8000));
// Record current participants already present in the room
// (except the robot itself)
......@@ -167,12 +196,13 @@ robot = {
}
// Wait 5 minute before leaving a room if alone
setInterval(robot.checkDisconnect, 300000);
robot.intervalList.push(setInterval(robot.checkDisconnect, 300000));
},
stop: () => {
robot.isDisconnected = true;
robotController.disconnect();
robot.clearConnection();
}
};
......
......@@ -38,7 +38,7 @@ const sttMock = {
}
};
describe('client/robot', () => {
describe('client/robot without init', () => {
beforeEach(() => {
global.MediaRecorder = function (stream) {
const res = {
......@@ -61,13 +61,6 @@ describe('client/robot', () => {
};
global.MediaRecorder.instances = {};
global._setIntervalCalls = [];
global.setInterval = function (f, timeout) {
global._setIntervalCalls.push([f, timeout]);
};
global.isDisconnected = false;
global.robotController = {
external: {
load: () => {}
......@@ -81,16 +74,12 @@ describe('client/robot', () => {
getRemoteStream: id => ({type: 'RemoteStream', id}),
disconnect: () => {
global.isDisconnected = true;
}
};
global.robotLib = {
stt: () => sttMock,
reco: () => ({
start: () => {},
getOnlineReco: () => new Promise(() => {}, () => {})
}),
archive: () => ({})
},
getWebRTCAdapter: () => ({
addDisconnectCallback: () => {
global.isReconnected = true;
}
})
};
/* eslint-disable import/no-unassigned-import */
......@@ -102,11 +91,6 @@ describe('client/robot', () => {
expect(global.robot).toBeDefined();
});
test('should set a interval for disconnection', () => {
global.robot.start();
expect.arrayContaining([[global.robot.checkDisconnect, 300000]]);
});
test('should return a started mediaRecorder on `processAudio`', () => {
const res = global.robot.processAudio({
type: 'stream'
......@@ -130,16 +114,86 @@ describe('client/robot', () => {
mediaRecorder.ondataavailable('somedata');
expect(callbackCalled).toBe('somedata');
});
});
test('should not record itself', () => {
describe('client/robot', () => {
const WebRTCAdapterMock = {
registeredFunc: null,
addDisconnectCallback: f => {
WebRTCAdapterMock.registeredFunc = f;
}
};
beforeEach(() => {
global.MediaRecorder = function (stream) {
const res = {
type: 'mediaRecorder',
stream,
started: false,
stopped: false,
interval: null,
start(interval) {
this.started = true;
this.interval = interval;
},
stop() {
this.started = false;
this.stopped = true;
}
};
global.MediaRecorder.instances[stream.id] = res;
return res;
};
global.MediaRecorder.instances = {};
global._setIntervalCalls = [];
global.setInterval = function (f, timeout) {
global._setIntervalCalls.push([f, timeout]);
};
global.isDisconnected = false;
global.isReconnected = false;
global.robotController = {
external: {
load: () => {}
},
getMyId: () => 'robotId',
getRemoteParticipants: () => [
'someid0',
'someid1',
'someid2'
],
getRemoteStream: id => ({type: 'RemoteStream', id}),
disconnect: () => {
global.isDisconnected = true;
},
getWebRTCAdapter: () => WebRTCAdapterMock
};
global.robotLib = {
stt: () => sttMock,
reco: () => ({
start: () => {},
getOnlineReco: () => new Promise(() => {}, () => {})
}),
archive: () => ({})
};
/* eslint-disable import/no-unassigned-import */
require('./robot.js');
global.robot.start();
global.robot.onConnection();
/* eslint-enable */
});
test('should not record itself', () => {
expect(global.MediaRecorder.instances)
.not.toHaveProperty(global.robotController.getMyId());
});
test('should start transcribing users already present on `start`', () => {
global.robot.start();
const e1 = {data: 'somedata1'};
global.MediaRecorder.instances.someid1.ondataavailable(e1);
expect(sttMock.sttDataSent[0]).toBe(e1.data);
......@@ -150,7 +204,6 @@ describe('client/robot', () => {
});
test('should start transcribing stream on user connection after `start`', () => {
global.robot.start();
global.robotController.onAttendeePush({}, {easyrtcid: 'testid'});
const e = {data: 'somedata'};
......@@ -159,8 +212,15 @@ describe('client/robot', () => {
expect(sttMock.sttDataSent[sttMock.sttDataSent.length - 1]).toBe(e.data);
});
test('should record and transcribe active participants', () => {
const keyMap = Object.keys(global.robot.participantsMediaRecorders);
for (let i = 0; i < keyMap.length; i++) {
expect(global.robot.participantsMediaRecorders[keyMap[i]].stopped).toBeFalsy();
expect(global.robot.recordedParticipantsWS[keyMap[i]].status).toBe('open');
}
});
test('should stop transcribing stream on user disconnect', () => {
global.robot.start();
global.robotController.onAttendeeRemove({}, {easyrtcid: 'someid1'});
expect(global.MediaRecorder.instances.someid1.stopped).toBeTruthy();
......@@ -168,25 +228,51 @@ describe('client/robot', () => {
});
test('should not disconnected with more than one user', () => {
global.robot.start();
global.robotController.onAttendeeRemove({}, {easyrtcid: 'someid1'});
expect(global.isDisconnected).toBe(false);
});
test('should trigger clearCOnnection when robot is disconnected', () => {
// Mock robot.clearConnection for this test
// Note: since robot is not cleanly reinitialized between each test
// (due to `require` not working as expected), we need to restore
// clearConnection at the end of the test.
let disconnected = false;
const oldClearConnection = global.robot.clearConnection;
global.robot.clearConnection = () => {
disconnected = true;
};
expect(WebRTCAdapterMock.registeredFunc).not.toBeNull();
// Trigger disconnection
WebRTCAdapterMock.registeredFunc();
expect(disconnected).toBeTruthy();
// Restore clearConnection
global.robot.clearConnection = oldClearConnection;
});
test('should clear the connection', () => {
global.robot.clearConnection();
const keyMap = Object.keys(global.robot.participantsMediaRecorders);
for (let i = 0; i < keyMap.length; i++) {
expect(global.robot.participantsMediaRecorders[keyMap[i]].stopped).toBeTruthy();
expect(global.robot.recordedParticipantsWS[keyMap[i]].status).toBe('closed');
}
expect(global.robot.intervalList.length).toBe(0);
});
test('should disconnected without user', () => {
global.robotController.getRemoteParticipants = function () { // Redefine getRemoteParticipants for no user
return [];
};
global.robot.start();
global.robotController.onAttendeeRemove({}, {easyrtcid: 'someid1'});
expect(global.isDisconnected).toBe(true);
});
test('should try to reopen a stt ws on error', () => {
global.robot.start();
// Trigger an error
sttMock.wsMock.status = 'error';
sttMock.wsMock.onerror('error');
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment