Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

recording.js 8.9KB

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