您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

recording.js 7.1KB

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