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.

JitsiLocalTrack.js 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. /* global Promise */
  2. var JitsiTrack = require("./JitsiTrack");
  3. var RTCBrowserType = require("./RTCBrowserType");
  4. var JitsiTrackEvents = require('../../JitsiTrackEvents');
  5. var JitsiTrackErrors = require("../../JitsiTrackErrors");
  6. var RTCUtils = require("./RTCUtils");
  7. /**
  8. * Represents a single media track (either audio or video).
  9. * @constructor
  10. */
  11. function JitsiLocalTrack(stream, videoType,
  12. resolution, deviceId)
  13. {
  14. this.videoType = videoType;
  15. this.dontFireRemoveEvent = false;
  16. this.resolution = resolution;
  17. this.deviceId = deviceId;
  18. this.startMuted = false;
  19. this.ssrc = null;
  20. this.disposed = false;
  21. //FIXME: This dependacy is not necessary.
  22. this.conference = null;
  23. JitsiTrack.call(this, null, stream,
  24. function () {
  25. if(!this.dontFireRemoveEvent)
  26. this.eventEmitter.emit(
  27. JitsiTrackEvents.LOCAL_TRACK_STOPPED);
  28. this.dontFireRemoveEvent = false;
  29. }.bind(this));
  30. this.initialMSID = this.getMSID();
  31. this.inMuteOrUnmuteProcess = false;
  32. }
  33. JitsiLocalTrack.prototype = Object.create(JitsiTrack.prototype);
  34. JitsiLocalTrack.prototype.constructor = JitsiLocalTrack;
  35. /**
  36. * Mutes the track. Will reject the Promise if there is mute/unmute operation
  37. * in progress.
  38. * @returns {Promise}
  39. */
  40. JitsiLocalTrack.prototype.mute = function () {
  41. return new Promise(function (resolve, reject) {
  42. if(this.inMuteOrUnmuteProcess) {
  43. reject(new Error(JitsiTrackErrors.TRACK_MUTE_UNMUTE_IN_PROGRESS));
  44. return;
  45. }
  46. this.inMuteOrUnmuteProcess = true;
  47. this._setMute(true,
  48. function(){
  49. this.inMuteOrUnmuteProcess = false;
  50. resolve();
  51. }.bind(this),
  52. function(status){
  53. this.inMuteOrUnmuteProcess = false;
  54. reject(status);
  55. }.bind(this));
  56. }.bind(this));
  57. }
  58. /**
  59. * Unmutes the stream. Will reject the Promise if there is mute/unmute operation
  60. * in progress.
  61. * @returns {Promise}
  62. */
  63. JitsiLocalTrack.prototype.unmute = function () {
  64. return new Promise(function (resolve, reject) {
  65. if(this.inMuteOrUnmuteProcess) {
  66. reject(new Error(JitsiTrackErrors.TRACK_MUTE_UNMUTE_IN_PROGRESS));
  67. return;
  68. }
  69. this.inMuteOrUnmuteProcess = true;
  70. this._setMute(false,
  71. function(){
  72. this.inMuteOrUnmuteProcess = false;
  73. resolve();
  74. }.bind(this),
  75. function(status){
  76. this.inMuteOrUnmuteProcess = false;
  77. reject(status);
  78. }.bind(this));
  79. }.bind(this));
  80. }
  81. /**
  82. * Mutes / unmutes the track.
  83. * @param mute {boolean} if true the track will be muted. Otherwise the track will be unmuted.
  84. */
  85. JitsiLocalTrack.prototype._setMute = function (mute, resolve, reject) {
  86. if (this.isMuted() === mute) {
  87. resolve();
  88. return;
  89. }
  90. if(!this.rtc) {
  91. this.startMuted = mute;
  92. resolve();
  93. return;
  94. }
  95. var isAudio = this.type === JitsiTrack.AUDIO;
  96. this.dontFireRemoveEvent = false;
  97. var setStreamToNull = false;
  98. // the callback that will notify that operation had finished
  99. var callbackFunction = function() {
  100. if(setStreamToNull)
  101. this.stream = null;
  102. this.eventEmitter.emit(JitsiTrackEvents.TRACK_MUTE_CHANGED);
  103. resolve();
  104. }.bind(this);
  105. if ((window.location.protocol != "https:") ||
  106. (isAudio) || this.videoType === "desktop" ||
  107. // FIXME FF does not support 'removeStream' method used to mute
  108. RTCBrowserType.isFirefox()) {
  109. var tracks = this._getTracks();
  110. for (var idx = 0; idx < tracks.length; idx++) {
  111. tracks[idx].enabled = !mute;
  112. }
  113. if(isAudio)
  114. this.rtc.room.setAudioMute(mute, callbackFunction);
  115. else
  116. this.rtc.room.setVideoMute(mute, callbackFunction);
  117. } else {
  118. if (mute) {
  119. this.dontFireRemoveEvent = true;
  120. this.rtc.room.removeStream(this.stream, function () {},
  121. {mtype: this.type, type: "mute", ssrc: this.ssrc});
  122. RTCUtils.stopMediaStream(this.stream);
  123. setStreamToNull = true;
  124. if(isAudio)
  125. this.rtc.room.setAudioMute(mute, callbackFunction);
  126. else
  127. this.rtc.room.setVideoMute(mute, callbackFunction);
  128. //FIXME: Maybe here we should set the SRC for the containers to something
  129. } else {
  130. var self = this;
  131. var streamOptions = {
  132. devices: (isAudio ? ["audio"] : ["video"]),
  133. resolution: self.resolution
  134. };
  135. if (isAudio) {
  136. streamOptions['micDeviceId'] = self.deviceId;
  137. } else if(self.videoType === 'camera') {
  138. streamOptions['cameraDeviceId'] = self.deviceId;
  139. }
  140. RTCUtils.obtainAudioAndVideoPermissions(streamOptions)
  141. .then(function (streams) {
  142. var stream = null;
  143. for(var i = 0; i < streams.length; i++) {
  144. stream = streams[i];
  145. if(stream.type === self.type) {
  146. self.stream = stream.stream;
  147. self.videoType = stream.videoType;
  148. break;
  149. }
  150. }
  151. if(!stream) {
  152. reject(new Error('track.no_stream_found'));
  153. return;
  154. }
  155. for(var i = 0; i < self.containers.length; i++)
  156. {
  157. self.containers[i]
  158. = RTCUtils.attachMediaStream(
  159. self.containers[i], self.stream);
  160. }
  161. self.rtc.room.addStream(self.stream,
  162. function () {
  163. if(isAudio)
  164. self.rtc.room.setAudioMute(
  165. mute, callbackFunction);
  166. else
  167. self.rtc.room.setVideoMute(
  168. mute, callbackFunction);
  169. }, {
  170. mtype: self.type,
  171. type: "unmute",
  172. ssrc: self.ssrc,
  173. msid: self.getMSID()});
  174. });
  175. }
  176. }
  177. }
  178. /**
  179. * Stops sending the media track. And removes it from the HTML.
  180. * NOTE: Works for local tracks only.
  181. * @returns {Promise}
  182. */
  183. JitsiLocalTrack.prototype.dispose = function () {
  184. var promise = Promise.resolve();
  185. if (this.conference){
  186. promise = this.conference.removeTrack(this);
  187. }
  188. if (this.stream) {
  189. RTCUtils.stopMediaStream(this.stream);
  190. this.detach();
  191. }
  192. this.disposed = true;
  193. return promise;
  194. };
  195. /**
  196. * Returns <tt>true</tt> - if the stream is muted
  197. * and <tt>false</tt> otherwise.
  198. * @returns {boolean} <tt>true</tt> - if the stream is muted
  199. * and <tt>false</tt> otherwise.
  200. */
  201. JitsiLocalTrack.prototype.isMuted = function () {
  202. if (!this.stream)
  203. return true;
  204. var tracks = [];
  205. var isAudio = this.type === JitsiTrack.AUDIO;
  206. if (isAudio) {
  207. tracks = this.stream.getAudioTracks();
  208. } else {
  209. if (!this.isActive())
  210. return true;
  211. tracks = this.stream.getVideoTracks();
  212. }
  213. for (var idx = 0; idx < tracks.length; idx++) {
  214. if(tracks[idx].enabled)
  215. return false;
  216. }
  217. return true;
  218. };
  219. /**
  220. * Private method. Updates rtc property of the track.
  221. * @param rtc the rtc instance.
  222. */
  223. JitsiLocalTrack.prototype._setRTC = function (rtc) {
  224. this.rtc = rtc;
  225. // We want to keep up with postponed events which should have been fired
  226. // on "attach" call, but for local track we not always have the conference
  227. // before attaching. However this may result in duplicated events if they
  228. // have been triggered on "attach" already.
  229. for(var i = 0; i < this.containers.length; i++)
  230. {
  231. this._maybeFireTrackAttached(this.containers[i]);
  232. }
  233. };
  234. /**
  235. * Updates the SSRC associated with the MediaStream in JitsiLocalTrack object.
  236. * @ssrc the new ssrc
  237. */
  238. JitsiLocalTrack.prototype._setSSRC = function (ssrc) {
  239. this.ssrc = ssrc;
  240. }
  241. //FIXME: This dependacy is not necessary. This is quick fix.
  242. /**
  243. * Sets the JitsiConference object associated with the track. This is temp
  244. * solution.
  245. * @param conference the JitsiConference object
  246. */
  247. JitsiLocalTrack.prototype._setConference = function(conference) {
  248. this.conference = conference;
  249. }
  250. /**
  251. * Gets the SSRC of this local track if it's available already or <tt>null</tt>
  252. * otherwise. That's because we don't know the SSRC until local description is
  253. * created.
  254. * In case of video and simulcast returns the the primarySSRC.
  255. * @returns {string} or {null}
  256. */
  257. JitsiLocalTrack.prototype.getSSRC = function () {
  258. if(this.ssrc && this.ssrc.groups && this.ssrc.groups.length)
  259. return this.ssrc.groups[0].primarySSRC;
  260. else if(this.ssrc && this.ssrc.ssrcs && this.ssrc.ssrcs.length)
  261. return this.ssrc.ssrcs[0];
  262. else
  263. return null;
  264. };
  265. /**
  266. * Return true;
  267. */
  268. JitsiLocalTrack.prototype.isLocal = function () {
  269. return true;
  270. }
  271. module.exports = JitsiLocalTrack;