Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

recording.js 8.8KB

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