You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

recording.js 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. /* global $, $iq, config, connection, focusMucJid, messageHandler,
  2. Toolbar, Util, Promise */
  3. var XMPPEvents = require("../../service/XMPP/XMPPEvents");
  4. var logger = require("jitsi-meet-logger").getLogger(__filename);
  5. function Recording(type, eventEmitter, connection, focusMucJid, jirecon,
  6. roomjid) {
  7. this.eventEmitter = eventEmitter;
  8. this.connection = connection;
  9. this.state = "off";
  10. this.focusMucJid = focusMucJid;
  11. this.jirecon = jirecon;
  12. this.url = null;
  13. this.type = type;
  14. this._isSupported = false;
  15. /**
  16. * The ID of the jirecon recording session. Jirecon generates it when we
  17. * initially start recording, and it needs to be used in subsequent requests
  18. * to jirecon.
  19. */
  20. this.jireconRid = null;
  21. this.roomjid = roomjid;
  22. }
  23. Recording.types = {
  24. COLIBRI: "colibri",
  25. JIRECON: "jirecon",
  26. JIBRI: "jibri"
  27. };
  28. Recording.prototype.handleJibriPresence = function (jibri) {
  29. var attributes = jibri.attributes;
  30. if(!attributes)
  31. return;
  32. this._isSupported =
  33. (attributes.status && attributes.status !== "undefined");
  34. if(this._isSupported) {
  35. this.url = attributes.url || null;
  36. this.state = attributes.status || "off";
  37. }
  38. this.eventEmitter.emit(XMPPEvents.RECORDING_STATE_CHANGED);
  39. };
  40. Recording.prototype.setRecordingJibri = function (state, callback, errCallback,
  41. options) {
  42. if (state == this.state){
  43. errCallback(new Error("Invalid state!"));
  44. }
  45. options = options || {};
  46. // FIXME jibri does not accept IQ without 'url' attribute set ?
  47. var iq = $iq({to: this.focusMucJid, type: 'set'})
  48. .c('jibri', {
  49. "xmlns": 'http://jitsi.org/protocol/jibri',
  50. "action": (state === 'on') ? 'start' : 'stop',
  51. "streamid": options.streamId,
  52. "follow-entity": options.followEntity
  53. }).up();
  54. logger.log('Set jibri recording: '+state, iq.nodeTree);
  55. console.log(iq.nodeTree);
  56. this.connection.sendIQ(
  57. iq,
  58. function (result) {
  59. callback($(result).find('jibri').attr('state'),
  60. $(result).find('jibri').attr('url'));
  61. },
  62. function (error) {
  63. logger.log('Failed to start recording, error: ', error);
  64. errCallback(error);
  65. });
  66. };
  67. Recording.prototype.setRecordingJirecon =
  68. function (state, callback, errCallback, options) {
  69. if (state == this.state){
  70. errCallback(new Error("Invalid state!"));
  71. }
  72. var iq = $iq({to: this.jirecon, type: 'set'})
  73. .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
  74. action: (state === 'on') ? 'start' : 'stop',
  75. mucjid: this.roomjid});
  76. if (state === 'off'){
  77. iq.attrs({rid: this.jireconRid});
  78. }
  79. console.log('Start recording');
  80. var self = this;
  81. this.connection.sendIQ(
  82. iq,
  83. function (result) {
  84. // TODO wait for an IQ with the real status, since this is
  85. // provisional?
  86. self.jireconRid = $(result).find('recording').attr('rid');
  87. console.log('Recording ' +
  88. ((state === 'on') ? 'started' : 'stopped') +
  89. '(jirecon)' + result);
  90. self.state = state;
  91. if (state === 'off'){
  92. self.jireconRid = null;
  93. }
  94. callback(state);
  95. },
  96. function (error) {
  97. console.log('Failed to start recording, error: ', error);
  98. errCallback(error);
  99. });
  100. };
  101. // Sends a COLIBRI message which enables or disables (according to 'state')
  102. // the recording on the bridge. Waits for the result IQ and calls 'callback'
  103. // with the new recording state, according to the IQ.
  104. Recording.prototype.setRecordingColibri =
  105. function (state, callback, errCallback, options) {
  106. var elem = $iq({to: this.focusMucJid, type: 'set'});
  107. elem.c('conference', {
  108. xmlns: 'http://jitsi.org/protocol/colibri'
  109. });
  110. elem.c('recording', {state: state, token: options.token});
  111. var self = this;
  112. this.connection.sendIQ(elem,
  113. function (result) {
  114. console.log('Set recording "', state, '". Result:', result);
  115. var recordingElem = $(result).find('>conference>recording');
  116. var newState = recordingElem.attr('state');
  117. self.state = newState;
  118. callback(newState);
  119. if (newState === 'pending') {
  120. connection.addHandler(function(iq){
  121. var state = $(iq).find('recording').attr('state');
  122. if (state) {
  123. self.state = newState;
  124. callback(state);
  125. }
  126. }, 'http://jitsi.org/protocol/colibri', 'iq', null, null, null);
  127. }
  128. },
  129. function (error) {
  130. console.warn(error);
  131. errCallback(error);
  132. }
  133. );
  134. };
  135. Recording.prototype.setRecording =
  136. function (state, callback, errCallback, options) {
  137. switch(this.type){
  138. case Recording.types.JIRECON:
  139. this.setRecordingJirecon(state, callback, errCallback, options);
  140. break;
  141. case Recording.types.COLIBRI:
  142. this.setRecordingColibri(state, callback, errCallback, options);
  143. break;
  144. case Recording.types.JIBRI:
  145. this.setRecordingJibri(state, callback, errCallback, options);
  146. break;
  147. default:
  148. console.error("Unknown recording type!");
  149. return;
  150. }
  151. };
  152. Recording.prototype.toggleRecording = function (options) {
  153. var self = this;
  154. return new Promise(function(resolve, reject) {
  155. if ((!options.token && self.type === Recording.types.COLIBRI) ||
  156. (!options.streamId && self.type === Recording.types.JIBRI)){
  157. reject(new Error("No token passed!"));
  158. logger.error("No token passed!");
  159. return;
  160. }
  161. var oldState = self.state;
  162. var newState = (oldState === 'off' || !oldState) ? 'on' : 'off';
  163. self.setRecording(newState,
  164. function (state, url) {
  165. logger.log("New recording state: ", state);
  166. if (state && state !== oldState) {
  167. if(state !== "on" && state !== "off") {
  168. //state === "pending" we are waiting for the real state
  169. return;
  170. }
  171. self.state = state;
  172. self.url = url;
  173. resolve();
  174. } else {
  175. reject(new Error("State not changed!"));
  176. }
  177. }, function (error) {
  178. reject(error);
  179. }, options);
  180. });
  181. };
  182. /**
  183. * Returns true if the recording is supproted and false if not.
  184. */
  185. Recording.prototype.isSupported = function () {
  186. return this._isSupported;
  187. };
  188. /**
  189. * Returns null if the recording is not supported, "on" if the recording started
  190. * and "off" if the recording is not started.
  191. */
  192. Recording.prototype.getState = function () {
  193. return this.state;
  194. };
  195. /**
  196. * Returns the url of the recorded video.
  197. */
  198. Recording.prototype.getURL = function () {
  199. return this.url;
  200. };
  201. module.exports = Recording;