|
@@ -1,19 +1,26 @@
|
1
|
1
|
/* global $, $iq, config, connection, focusMucJid, messageHandler,
|
2
|
2
|
Toolbar, Util, Promise */
|
3
|
3
|
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
|
4
|
+var JitsiRecorderErrors = require("../../JitsiRecorderErrors");
|
|
5
|
+
|
4
|
6
|
var logger = require("jitsi-meet-logger").getLogger(__filename);
|
5
|
7
|
|
6
|
8
|
function Recording(type, eventEmitter, connection, focusMucJid, jirecon,
|
7
|
9
|
roomjid) {
|
8
|
10
|
this.eventEmitter = eventEmitter;
|
9
|
11
|
this.connection = connection;
|
10
|
|
- this.state = "off";
|
|
12
|
+ this.state = null;
|
11
|
13
|
this.focusMucJid = focusMucJid;
|
12
|
14
|
this.jirecon = jirecon;
|
13
|
15
|
this.url = null;
|
14
|
16
|
this.type = type;
|
15
|
|
- this._isSupported = ((type === Recording.types.JIBRI)
|
16
|
|
- || (type === Recording.types.JIRECON && !this.jirecon))? false : true;
|
|
17
|
+ this._isSupported
|
|
18
|
+ = ( type === Recording.types.JIRECON && !this.jirecon
|
|
19
|
+ || type === Recording.types.JIBRI && !this._isServiceAvailable)
|
|
20
|
+ ? false : true;
|
|
21
|
+
|
|
22
|
+ this._isServiceAvailable = false;
|
|
23
|
+
|
17
|
24
|
/**
|
18
|
25
|
* The ID of the jirecon recording session. Jirecon generates it when we
|
19
|
26
|
* initially start recording, and it needs to be used in subsequent requests
|
|
@@ -29,18 +36,43 @@ Recording.types = {
|
29
|
36
|
JIBRI: "jibri"
|
30
|
37
|
};
|
31
|
38
|
|
|
39
|
+Recording.status = {
|
|
40
|
+ ON: "on",
|
|
41
|
+ OFF: "off",
|
|
42
|
+ AVAILABLE: "available",
|
|
43
|
+ UNAVAILABLE: "unavailable",
|
|
44
|
+ START: "start",
|
|
45
|
+ STOP: "stop",
|
|
46
|
+ PENDING: "pending"
|
|
47
|
+};
|
|
48
|
+
|
32
|
49
|
Recording.prototype.handleJibriPresence = function (jibri) {
|
33
|
50
|
var attributes = jibri.attributes;
|
34
|
51
|
if(!attributes)
|
35
|
52
|
return;
|
36
|
53
|
|
37
|
|
- this._isSupported =
|
38
|
|
- (attributes.status && attributes.status !== "undefined");
|
39
|
|
- if(this._isSupported) {
|
40
|
|
- this.url = attributes.url || null;
|
41
|
|
- this.state = attributes.status || "off";
|
42
|
|
- }
|
43
|
|
- this.eventEmitter.emit(XMPPEvents.RECORDING_STATE_CHANGED);
|
|
54
|
+ var newState = attributes.status;
|
|
55
|
+ console.log("handle jibri presence : ", newState);
|
|
56
|
+ var oldIsAvailable = this._isServiceAvailable;
|
|
57
|
+ // The service is available if the statu isn't undefined.
|
|
58
|
+ this._isServiceAvailable =
|
|
59
|
+ (newState && newState !== "undefined");
|
|
60
|
+
|
|
61
|
+ if (newState === "undefined"
|
|
62
|
+ || oldIsAvailable != this._isServiceAvailable
|
|
63
|
+ // If we receive an OFF state without any recording in progress we
|
|
64
|
+ // consider this to be an initial available state.
|
|
65
|
+ || (this.state === Recording.status.AVAILABLE
|
|
66
|
+ && newState === Recording.status.OFF))
|
|
67
|
+ this.state = (newState === "undefined")
|
|
68
|
+ ? Recording.status.UNAVAILABLE
|
|
69
|
+ : Recording.status.AVAILABLE;
|
|
70
|
+ else
|
|
71
|
+ this.state = attributes.status;
|
|
72
|
+
|
|
73
|
+ logger.log("Handle Jibri presence: ", this.state);
|
|
74
|
+
|
|
75
|
+ this.eventEmitter.emit(XMPPEvents.RECORDER_STATE_CHANGED, this.state);
|
44
|
76
|
};
|
45
|
77
|
|
46
|
78
|
Recording.prototype.setRecordingJibri = function (state, callback, errCallback,
|
|
@@ -49,21 +81,23 @@ Recording.prototype.setRecordingJibri = function (state, callback, errCallback,
|
49
|
81
|
errCallback(new Error("Invalid state!"));
|
50
|
82
|
}
|
51
|
83
|
options = options || {};
|
52
|
|
- // FIXME jibri does not accept IQ without 'url' attribute set ?
|
53
|
84
|
|
|
85
|
+ // FIXME jibri does not accept IQ without 'url' attribute set ?
|
54
|
86
|
var iq = $iq({to: this.focusMucJid, type: 'set'})
|
55
|
87
|
.c('jibri', {
|
56
|
|
- "xmlns": 'http://jitsi.org/protocol/jibri',
|
57
|
|
- "action": (state === 'on') ? 'start' : 'stop',
|
58
|
|
- "streamid": options.streamId,
|
59
|
|
- "follow-entity": options.followEntity
|
|
88
|
+ "xmlns": 'http://jitsi.org/protocol/jibri',
|
|
89
|
+ "action": (state === Recording.status.ON)
|
|
90
|
+ ? Recording.status.START
|
|
91
|
+ : Recording.status.STOP,
|
|
92
|
+ "streamid": options.streamId,
|
60
|
93
|
}).up();
|
61
|
94
|
|
62
|
|
- logger.log('Set jibri recording: '+state, iq.nodeTree);
|
63
|
|
- console.log(iq.nodeTree);
|
|
95
|
+ logger.log('Set jibri recording: ' + state, iq.nodeTree);
|
|
96
|
+ logger.log(iq.nodeTree);
|
64
|
97
|
this.connection.sendIQ(
|
65
|
98
|
iq,
|
66
|
99
|
function (result) {
|
|
100
|
+ logger.log("Result", result);
|
67
|
101
|
callback($(result).find('jibri').attr('state'),
|
68
|
102
|
$(result).find('jibri').attr('url'));
|
69
|
103
|
},
|
|
@@ -74,20 +108,23 @@ Recording.prototype.setRecordingJibri = function (state, callback, errCallback,
|
74
|
108
|
};
|
75
|
109
|
|
76
|
110
|
Recording.prototype.setRecordingJirecon =
|
77
|
|
-function (state, callback, errCallback, options) {
|
|
111
|
+ function (state, callback, errCallback, options) {
|
|
112
|
+
|
78
|
113
|
if (state == this.state){
|
79
|
114
|
errCallback(new Error("Invalid state!"));
|
80
|
115
|
}
|
81
|
116
|
|
82
|
117
|
var iq = $iq({to: this.jirecon, type: 'set'})
|
83
|
118
|
.c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
|
84
|
|
- action: (state === 'on') ? 'start' : 'stop',
|
|
119
|
+ action: (state === Recording.status.ON)
|
|
120
|
+ ? Recording.status.START
|
|
121
|
+ : Recording.status.STOP,
|
85
|
122
|
mucjid: this.roomjid});
|
86
|
123
|
if (state === 'off'){
|
87
|
124
|
iq.attrs({rid: this.jireconRid});
|
88
|
125
|
}
|
89
|
126
|
|
90
|
|
- console.log('Start recording');
|
|
127
|
+ logger.log('Start recording');
|
91
|
128
|
var self = this;
|
92
|
129
|
this.connection.sendIQ(
|
93
|
130
|
iq,
|
|
@@ -96,10 +133,10 @@ function (state, callback, errCallback, options) {
|
96
|
133
|
// provisional?
|
97
|
134
|
self.jireconRid = $(result).find('recording').attr('rid');
|
98
|
135
|
console.log('Recording ' +
|
99
|
|
- ((state === 'on') ? 'started' : 'stopped') +
|
|
136
|
+ ((state === Recording.status.ON) ? 'started' : 'stopped') +
|
100
|
137
|
'(jirecon)' + result);
|
101
|
138
|
self.state = state;
|
102
|
|
- if (state === 'off'){
|
|
139
|
+ if (state === Recording.status.OFF){
|
103
|
140
|
self.jireconRid = null;
|
104
|
141
|
}
|
105
|
142
|
|
|
@@ -168,30 +205,46 @@ function (state, callback, errCallback, options) {
|
168
|
205
|
};
|
169
|
206
|
|
170
|
207
|
/**
|
171
|
|
- *Starts/stops the recording
|
|
208
|
+ * Starts/stops the recording.
|
172
|
209
|
* @param token token for authentication
|
173
|
210
|
* @param statusChangeHandler {function} receives the new status as argument.
|
174
|
211
|
*/
|
175
|
212
|
Recording.prototype.toggleRecording = function (options, statusChangeHandler) {
|
176
|
|
- if ((!options.token && this.type === Recording.types.COLIBRI) ||
|
177
|
|
- (!options.streamId && this.type === Recording.types.JIBRI)){
|
178
|
|
- statusChangeHandler("error", new Error("No token passed!"));
|
|
213
|
+ var oldState = this.state;
|
|
214
|
+
|
|
215
|
+ // If the recorder is currently unavailable we throw an error.
|
|
216
|
+ if (oldState === Recording.status.UNAVAILABLE)
|
|
217
|
+ statusChangeHandler("error",
|
|
218
|
+ new Error(JitsiRecorderErrors.RECORDER_UNAVAILABLE));
|
|
219
|
+
|
|
220
|
+ // If we're about to turn ON the recording we need either a streamId or
|
|
221
|
+ // an authentication token depending on the recording type. If we don't
|
|
222
|
+ // have any of those we throw an error.
|
|
223
|
+ if ((oldState === Recording.status.OFF
|
|
224
|
+ || oldState === Recording.status.AVAILABLE)
|
|
225
|
+ && ((!options.token && this.type === Recording.types.COLIBRI) ||
|
|
226
|
+ (!options.streamId && this.type === Recording.types.JIBRI))) {
|
|
227
|
+ statusChangeHandler("error",
|
|
228
|
+ new Error(JitsiRecorderErrors.NO_TOKEN));
|
179
|
229
|
logger.error("No token passed!");
|
180
|
230
|
return;
|
181
|
231
|
}
|
182
|
232
|
|
183
|
|
- var oldState = this.state;
|
184
|
|
- var newState = (oldState === 'off' || !oldState) ? 'on' : 'off';
|
|
233
|
+ var newState = (oldState === Recording.status.AVAILABLE
|
|
234
|
+ || oldState === Recording.status.OFF)
|
|
235
|
+ ? Recording.status.ON
|
|
236
|
+ : Recording.status.OFF;
|
|
237
|
+
|
185
|
238
|
var self = this;
|
|
239
|
+ logger.log("Toggle recording (old state, new state): ", oldState, newState);
|
186
|
240
|
this.setRecording(newState,
|
187
|
241
|
function (state, url) {
|
188
|
|
- logger.log("New recording state: ", state);
|
|
242
|
+ // If the state is undefined we're going to wait for presence
|
|
243
|
+ // update.
|
189
|
244
|
if (state && state !== oldState) {
|
190
|
245
|
self.state = state;
|
191
|
246
|
self.url = url;
|
192
|
247
|
statusChangeHandler(state);
|
193
|
|
- } else {
|
194
|
|
- statusChangeHandler("error", new Error("Status not changed!"));
|
195
|
248
|
}
|
196
|
249
|
}, function (error) {
|
197
|
250
|
statusChangeHandler("error", error);
|