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

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019
  1. /* jshint -W117 */
  2. /* application specific logic */
  3. var connection = null;
  4. var authenticatedUser = false;
  5. var activecall = null;
  6. var nickname = null;
  7. var focusMucJid = null;
  8. var roomName = null;
  9. var ssrc2jid = {};
  10. var bridgeIsDown = false;
  11. //TODO: this array must be removed when firefox implement multistream support
  12. var notReceivedSSRCs = [];
  13. var jid2Ssrc = {};
  14. /**
  15. * Indicates whether ssrc is camera video or desktop stream.
  16. * FIXME: remove those maps
  17. */
  18. var ssrc2videoType = {};
  19. /**
  20. * Currently focused video "src"(displayed in large video).
  21. * @type {String}
  22. */
  23. var focusedVideoInfo = null;
  24. var mutedAudios = {};
  25. /**
  26. * Remembers if we were muted by the focus.
  27. * @type {boolean}
  28. */
  29. var forceMuted = false;
  30. /**
  31. * Indicates if we have muted our audio before the conference has started.
  32. * @type {boolean}
  33. */
  34. var preMuted = false;
  35. var localVideoSrc = null;
  36. var flipXLocalVideo = true;
  37. var isFullScreen = false;
  38. var currentVideoWidth = null;
  39. var currentVideoHeight = null;
  40. /**
  41. * Method used to calculate large video size.
  42. * @type {function ()}
  43. */
  44. var getVideoSize;
  45. /**
  46. * Method used to get large video position.
  47. * @type {function ()}
  48. */
  49. var getVideoPosition;
  50. /* window.onbeforeunload = closePageWarning; */
  51. var sessionTerminated = false;
  52. function init() {
  53. RTC.addStreamListener(maybeDoJoin, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
  54. RTC.start();
  55. var jid = document.getElementById('jid').value || config.hosts.anonymousdomain || config.hosts.domain || window.location.hostname;
  56. connect(jid);
  57. }
  58. function connect(jid, password) {
  59. var localAudio, localVideo;
  60. if (connection && connection.jingle) {
  61. localAudio = connection.jingle.localAudio;
  62. localVideo = connection.jingle.localVideo;
  63. }
  64. connection = new Strophe.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind');
  65. var settings = UI.getSettings();
  66. var email = settings.email;
  67. var displayName = settings.displayName;
  68. if(email) {
  69. connection.emuc.addEmailToPresence(email);
  70. } else {
  71. connection.emuc.addUserIdToPresence(settings.uid);
  72. }
  73. if(displayName) {
  74. connection.emuc.addDisplayNameToPresence(displayName);
  75. }
  76. if (connection.disco) {
  77. // for chrome, add multistream cap
  78. }
  79. connection.jingle.pc_constraints = RTC.getPCConstraints();
  80. if (config.useIPv6) {
  81. // https://code.google.com/p/webrtc/issues/detail?id=2828
  82. if (!connection.jingle.pc_constraints.optional) connection.jingle.pc_constraints.optional = [];
  83. connection.jingle.pc_constraints.optional.push({googIPv6: true});
  84. }
  85. if (localAudio) connection.jingle.localAudio = localAudio;
  86. if (localVideo) connection.jingle.localVideo = localVideo;
  87. if(!password)
  88. password = document.getElementById('password').value;
  89. var anonymousConnectionFailed = false;
  90. connection.connect(jid, password, function (status, msg) {
  91. console.log('Strophe status changed to', Strophe.getStatusString(status));
  92. if (status === Strophe.Status.CONNECTED) {
  93. if (config.useStunTurn) {
  94. connection.jingle.getStunAndTurnCredentials();
  95. }
  96. document.getElementById('connect').disabled = true;
  97. console.info("My Jabber ID: " + connection.jid);
  98. if(password)
  99. authenticatedUser = true;
  100. maybeDoJoin();
  101. } else if (status === Strophe.Status.CONNFAIL) {
  102. if(msg === 'x-strophe-bad-non-anon-jid') {
  103. anonymousConnectionFailed = true;
  104. }
  105. } else if (status === Strophe.Status.DISCONNECTED) {
  106. if(anonymousConnectionFailed) {
  107. // prompt user for username and password
  108. $(document).trigger('passwordrequired.main');
  109. }
  110. } else if (status === Strophe.Status.AUTHFAIL) {
  111. // wrong password or username, prompt user
  112. $(document).trigger('passwordrequired.main');
  113. }
  114. });
  115. }
  116. function maybeDoJoin() {
  117. if (connection && connection.connected && Strophe.getResourceFromJid(connection.jid) // .connected is true while connecting?
  118. && (connection.jingle.localAudio || connection.jingle.localVideo)) {
  119. doJoin();
  120. }
  121. }
  122. function doJoin() {
  123. if (!roomName) {
  124. UI.generateRoomName();
  125. }
  126. Moderator.allocateConferenceFocus(
  127. roomName, doJoinAfterFocus);
  128. }
  129. function doJoinAfterFocus() {
  130. var roomjid;
  131. roomjid = roomName;
  132. if (config.useNicks) {
  133. var nick = window.prompt('Your nickname (optional)');
  134. if (nick) {
  135. roomjid += '/' + nick;
  136. } else {
  137. roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
  138. }
  139. } else {
  140. var tmpJid = Strophe.getNodeFromJid(connection.jid);
  141. if(!authenticatedUser)
  142. tmpJid = tmpJid.substr(0, 8);
  143. roomjid += '/' + tmpJid;
  144. }
  145. connection.emuc.doJoin(roomjid);
  146. }
  147. function waitForRemoteVideo(selector, ssrc, stream, jid) {
  148. // XXX(gp) so, every call to this function is *always* preceded by a call
  149. // to the RTC.attachMediaStream() function but that call is *not* followed
  150. // by an update to the videoSrcToSsrc map!
  151. //
  152. // The above way of doing things results in video SRCs that don't correspond
  153. // to any SSRC for a short period of time (to be more precise, for as long
  154. // the waitForRemoteVideo takes to complete). This causes problems (see
  155. // bellow).
  156. //
  157. // I'm wondering why we need to do that; i.e. why call RTC.attachMediaStream()
  158. // a second time in here and only then update the videoSrcToSsrc map? Why
  159. // not simply update the videoSrcToSsrc map when the RTC.attachMediaStream()
  160. // is called the first time? I actually do that in the lastN changed event
  161. // handler because the "orphan" video SRC is causing troubles there. The
  162. // purpose of this method would then be to fire the "videoactive.jingle".
  163. //
  164. // Food for though I guess :-)
  165. if (selector.removed || !selector.parent().is(":visible")) {
  166. console.warn("Media removed before had started", selector);
  167. return;
  168. }
  169. if (stream.id === 'mixedmslabel') return;
  170. if (selector[0].currentTime > 0) {
  171. var videoStream = simulcast.getReceivingVideoStream(stream);
  172. RTC.attachMediaStream(selector, videoStream); // FIXME: why do i have to do this for FF?
  173. // FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
  174. // in order to get rid of too many maps
  175. if (ssrc && jid) {
  176. jid2Ssrc[Strophe.getResourceFromJid(jid)] = ssrc;
  177. } else {
  178. console.warn("No ssrc given for jid", jid);
  179. }
  180. $(document).trigger('videoactive.jingle', [selector]);
  181. } else {
  182. setTimeout(function () {
  183. waitForRemoteVideo(selector, ssrc, stream, jid);
  184. }, 250);
  185. }
  186. }
  187. $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
  188. waitForPresence(data, sid);
  189. });
  190. function waitForPresence(data, sid) {
  191. var sess = connection.jingle.sessions[sid];
  192. var thessrc;
  193. // look up an associated JID for a stream id
  194. if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) {
  195. // look only at a=ssrc: and _not_ at a=ssrc-group: lines
  196. var ssrclines
  197. = SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc:');
  198. ssrclines = ssrclines.filter(function (line) {
  199. // NOTE(gp) previously we filtered on the mslabel, but that property
  200. // is not always present.
  201. // return line.indexOf('mslabel:' + data.stream.label) !== -1;
  202. return ((line.indexOf('msid:' + data.stream.id) !== -1));
  203. });
  204. if (ssrclines.length) {
  205. thessrc = ssrclines[0].substring(7).split(' ')[0];
  206. // We signal our streams (through Jingle to the focus) before we set
  207. // our presence (through which peers associate remote streams to
  208. // jids). So, it might arrive that a remote stream is added but
  209. // ssrc2jid is not yet updated and thus data.peerjid cannot be
  210. // successfully set. Here we wait for up to a second for the
  211. // presence to arrive.
  212. if (!ssrc2jid[thessrc]) {
  213. // TODO(gp) limit wait duration to 1 sec.
  214. setTimeout(function(d, s) {
  215. return function() {
  216. waitForPresence(d, s);
  217. }
  218. }(data, sid), 250);
  219. return;
  220. }
  221. // ok to overwrite the one from focus? might save work in colibri.js
  222. console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
  223. if (ssrc2jid[thessrc]) {
  224. data.peerjid = ssrc2jid[thessrc];
  225. }
  226. }
  227. }
  228. //TODO: this code should be removed when firefox implement multistream support
  229. if(RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_FIREFOX)
  230. {
  231. if((notReceivedSSRCs.length == 0) ||
  232. !ssrc2jid[notReceivedSSRCs[notReceivedSSRCs.length - 1]])
  233. {
  234. // TODO(gp) limit wait duration to 1 sec.
  235. setTimeout(function(d, s) {
  236. return function() {
  237. waitForPresence(d, s);
  238. }
  239. }(data, sid), 250);
  240. return;
  241. }
  242. thessrc = notReceivedSSRCs.pop();
  243. if (ssrc2jid[thessrc]) {
  244. data.peerjid = ssrc2jid[thessrc];
  245. }
  246. }
  247. RTC.createRemoteStream(data, sid, thessrc);
  248. var isVideo = data.stream.getVideoTracks().length > 0;
  249. // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
  250. if (isVideo &&
  251. data.peerjid && sess.peerjid === data.peerjid &&
  252. data.stream.getVideoTracks().length === 0 &&
  253. connection.jingle.localVideo.getVideoTracks().length > 0) {
  254. //
  255. window.setTimeout(function () {
  256. sendKeyframe(sess.peerconnection);
  257. }, 3000);
  258. }
  259. }
  260. // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
  261. function sendKeyframe(pc) {
  262. console.log('sendkeyframe', pc.iceConnectionState);
  263. if (pc.iceConnectionState !== 'connected') return; // safe...
  264. pc.setRemoteDescription(
  265. pc.remoteDescription,
  266. function () {
  267. pc.createAnswer(
  268. function (modifiedAnswer) {
  269. pc.setLocalDescription(
  270. modifiedAnswer,
  271. function () {
  272. // noop
  273. },
  274. function (error) {
  275. console.log('triggerKeyframe setLocalDescription failed', error);
  276. UI.messageHandler.showError();
  277. }
  278. );
  279. },
  280. function (error) {
  281. console.log('triggerKeyframe createAnswer failed', error);
  282. UI.messageHandler.showError();
  283. }
  284. );
  285. },
  286. function (error) {
  287. console.log('triggerKeyframe setRemoteDescription failed', error);
  288. UI.messageHandler.showError();
  289. }
  290. );
  291. }
  292. // Really mute video, i.e. dont even send black frames
  293. function muteVideo(pc, unmute) {
  294. // FIXME: this probably needs another of those lovely state safeguards...
  295. // which checks for iceconn == connected and sigstate == stable
  296. pc.setRemoteDescription(pc.remoteDescription,
  297. function () {
  298. pc.createAnswer(
  299. function (answer) {
  300. var sdp = new SDP(answer.sdp);
  301. if (sdp.media.length > 1) {
  302. if (unmute)
  303. sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
  304. else
  305. sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
  306. sdp.raw = sdp.session + sdp.media.join('');
  307. answer.sdp = sdp.raw;
  308. }
  309. pc.setLocalDescription(answer,
  310. function () {
  311. console.log('mute SLD ok');
  312. },
  313. function (error) {
  314. console.log('mute SLD error');
  315. UI.messageHandler.showError('Error',
  316. 'Oops! Something went wrong and we failed to ' +
  317. 'mute! (SLD Failure)');
  318. }
  319. );
  320. },
  321. function (error) {
  322. console.log(error);
  323. UI.messageHandler.showError();
  324. }
  325. );
  326. },
  327. function (error) {
  328. console.log('muteVideo SRD error');
  329. UI.messageHandler.showError('Error',
  330. 'Oops! Something went wrong and we failed to stop video!' +
  331. '(SRD Failure)');
  332. }
  333. );
  334. }
  335. $(document).bind('setLocalDescription.jingle', function (event, sid) {
  336. // put our ssrcs into presence so other clients can identify our stream
  337. var sess = connection.jingle.sessions[sid];
  338. var newssrcs = [];
  339. var media = simulcast.parseMedia(sess.peerconnection.localDescription);
  340. media.forEach(function (media) {
  341. if(Object.keys(media.sources).length > 0) {
  342. // TODO(gp) maybe exclude FID streams?
  343. Object.keys(media.sources).forEach(function (ssrc) {
  344. newssrcs.push({
  345. 'ssrc': ssrc,
  346. 'type': media.type,
  347. 'direction': media.direction
  348. });
  349. });
  350. }
  351. else if(sess.localStreamsSSRC && sess.localStreamsSSRC[media.type])
  352. {
  353. newssrcs.push({
  354. 'ssrc': sess.localStreamsSSRC[media.type],
  355. 'type': media.type,
  356. 'direction': media.direction
  357. });
  358. }
  359. });
  360. console.log('new ssrcs', newssrcs);
  361. // Have to clear presence map to get rid of removed streams
  362. connection.emuc.clearPresenceMedia();
  363. if (newssrcs.length > 0) {
  364. for (var i = 1; i <= newssrcs.length; i ++) {
  365. // Change video type to screen
  366. if (newssrcs[i-1].type === 'video' && isUsingScreenStream) {
  367. newssrcs[i-1].type = 'screen';
  368. }
  369. connection.emuc.addMediaToPresence(i,
  370. newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
  371. }
  372. connection.emuc.sendPresence();
  373. }
  374. });
  375. $(document).bind('iceconnectionstatechange.jingle', function (event, sid, session) {
  376. switch (session.peerconnection.iceConnectionState) {
  377. case 'checking':
  378. session.timeChecking = (new Date()).getTime();
  379. session.firstconnect = true;
  380. break;
  381. case 'completed': // on caller side
  382. case 'connected':
  383. if (session.firstconnect) {
  384. session.firstconnect = false;
  385. var metadata = {};
  386. metadata.setupTime = (new Date()).getTime() - session.timeChecking;
  387. session.peerconnection.getStats(function (res) {
  388. if(res && res.result) {
  389. res.result().forEach(function (report) {
  390. if (report.type == 'googCandidatePair' && report.stat('googActiveConnection') == 'true') {
  391. metadata.localCandidateType = report.stat('googLocalCandidateType');
  392. metadata.remoteCandidateType = report.stat('googRemoteCandidateType');
  393. // log pair as well so we can get nice pie charts
  394. metadata.candidatePair = report.stat('googLocalCandidateType') + ';' + report.stat('googRemoteCandidateType');
  395. if (report.stat('googRemoteAddress').indexOf('[') === 0) {
  396. metadata.ipv6 = true;
  397. }
  398. }
  399. });
  400. trackUsage('iceConnected', metadata);
  401. }
  402. });
  403. }
  404. break;
  405. }
  406. });
  407. $(document).bind('joined.muc', function (event, jid, info) {
  408. });
  409. $(document).bind('presence.muc', function (event, jid, info, pres) {
  410. //check if the video bridge is available
  411. if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
  412. bridgeIsDown = true;
  413. UI.messageHandler.showError("Error",
  414. "Jitsi Videobridge is currently unavailable. Please try again later!");
  415. }
  416. if (info.isFocus)
  417. {
  418. return;
  419. }
  420. // Remove old ssrcs coming from the jid
  421. Object.keys(ssrc2jid).forEach(function (ssrc) {
  422. if (ssrc2jid[ssrc] == jid) {
  423. delete ssrc2jid[ssrc];
  424. delete ssrc2videoType[ssrc];
  425. }
  426. });
  427. $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
  428. //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
  429. var ssrcV = ssrc.getAttribute('ssrc');
  430. ssrc2jid[ssrcV] = jid;
  431. notReceivedSSRCs.push(ssrcV);
  432. var type = ssrc.getAttribute('type');
  433. ssrc2videoType[ssrcV] = type;
  434. // might need to update the direction if participant just went from sendrecv to recvonly
  435. if (type === 'video' || type === 'screen') {
  436. var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video');
  437. switch (ssrc.getAttribute('direction')) {
  438. case 'sendrecv':
  439. el.show();
  440. break;
  441. case 'recvonly':
  442. el.hide();
  443. // FIXME: Check if we have to change large video
  444. //VideoLayout.updateLargeVideo(el);
  445. break;
  446. }
  447. }
  448. });
  449. var displayName = !config.displayJids
  450. ? info.displayName : Strophe.getResourceFromJid(jid);
  451. if (displayName && displayName.length > 0)
  452. $(document).trigger('displaynamechanged',
  453. [jid, displayName]);
  454. /*if (focus !== null && info.displayName !== null) {
  455. focus.setEndpointDisplayName(jid, info.displayName);
  456. }*/
  457. //check if the video bridge is available
  458. if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
  459. bridgeIsDown = true;
  460. UI.messageHandler.showError("Error",
  461. "Jitsi Videobridge is currently unavailable. Please try again later!");
  462. }
  463. var id = $(pres).find('>userID').text();
  464. var email = $(pres).find('>email');
  465. if(email.length > 0) {
  466. id = email.text();
  467. }
  468. UI.setUserAvatar(jid, id);
  469. });
  470. $(document).bind('kicked.muc', function (event, jid) {
  471. console.info(jid + " has been kicked from MUC!");
  472. if (connection.emuc.myroomjid === jid) {
  473. sessionTerminated = true;
  474. disposeConference(false);
  475. connection.emuc.doLeave();
  476. UI.messageHandler.openMessageDialog("Session Terminated",
  477. "Ouch! You have been kicked out of the meet!");
  478. }
  479. });
  480. $(document).bind('passwordrequired.main', function (event) {
  481. console.log('password is required');
  482. UI.messageHandler.openTwoButtonDialog(null,
  483. '<h2>Password required</h2>' +
  484. '<input id="passwordrequired.username" type="text" placeholder="user@domain.net" autofocus>' +
  485. '<input id="passwordrequired.password" type="password" placeholder="user password">',
  486. true,
  487. "Ok",
  488. function (e, v, m, f) {
  489. if (v) {
  490. var username = document.getElementById('passwordrequired.username');
  491. var password = document.getElementById('passwordrequired.password');
  492. if (username.value !== null && password.value != null) {
  493. connect(username.value, password.value);
  494. }
  495. }
  496. },
  497. function (event) {
  498. document.getElementById('passwordrequired.username').focus();
  499. }
  500. );
  501. });
  502. /**
  503. * Checks if video identified by given src is desktop stream.
  504. * @param videoSrc eg.
  505. * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
  506. * @returns {boolean}
  507. */
  508. function isVideoSrcDesktop(jid) {
  509. // FIXME: fix this mapping mess...
  510. // figure out if large video is desktop stream or just a camera
  511. if(!jid)
  512. return false;
  513. var isDesktop = false;
  514. if (connection.emuc.myroomjid &&
  515. Strophe.getResourceFromJid(connection.emuc.myroomjid) === jid) {
  516. // local video
  517. isDesktop = isUsingScreenStream;
  518. } else {
  519. // Do we have associations...
  520. var videoSsrc = jid2Ssrc[jid];
  521. if (videoSsrc) {
  522. var videoType = ssrc2videoType[videoSsrc];
  523. if (videoType) {
  524. // Finally there...
  525. isDesktop = videoType === 'screen';
  526. } else {
  527. console.error("No video type for ssrc: " + videoSsrc);
  528. }
  529. } else {
  530. console.error("No ssrc for jid: " + jid);
  531. }
  532. }
  533. return isDesktop;
  534. }
  535. function getConferenceHandler() {
  536. return activecall;
  537. }
  538. /**
  539. * Mutes/unmutes the local video.
  540. *
  541. * @param mute <tt>true</tt> to mute the local video; otherwise, <tt>false</tt>
  542. * @param options an object which specifies optional arguments such as the
  543. * <tt>boolean</tt> key <tt>byUser</tt> with default value <tt>true</tt> which
  544. * specifies whether the method was initiated in response to a user command (in
  545. * contrast to an automatic decision taken by the application logic)
  546. */
  547. function setVideoMute(mute, options) {
  548. if (connection && connection.jingle.localVideo) {
  549. var session = getConferenceHandler();
  550. if (session) {
  551. session.setVideoMute(
  552. mute,
  553. function (mute) {
  554. var video = $('#video');
  555. var communicativeClass = "icon-camera";
  556. var muteClass = "icon-camera icon-camera-disabled";
  557. if (mute) {
  558. video.removeClass(communicativeClass);
  559. video.addClass(muteClass);
  560. } else {
  561. video.removeClass(muteClass);
  562. video.addClass(communicativeClass);
  563. }
  564. connection.emuc.addVideoInfoToPresence(mute);
  565. connection.emuc.sendPresence();
  566. },
  567. options);
  568. }
  569. }
  570. }
  571. $(document).on('inlastnchanged', function (event, oldValue, newValue) {
  572. if (config.muteLocalVideoIfNotInLastN) {
  573. setVideoMute(!newValue, { 'byUser': false });
  574. }
  575. });
  576. /**
  577. * Mutes/unmutes the local video.
  578. */
  579. function toggleVideo() {
  580. buttonClick("#video", "icon-camera icon-camera-disabled");
  581. if (connection && connection.jingle.localVideo) {
  582. var session = getConferenceHandler();
  583. if (session) {
  584. setVideoMute(!session.isVideoMute());
  585. }
  586. }
  587. }
  588. /**
  589. * Mutes / unmutes audio for the local participant.
  590. */
  591. function toggleAudio() {
  592. setAudioMuted(!isAudioMuted());
  593. }
  594. /**
  595. * Sets muted audio state for the local participant.
  596. */
  597. function setAudioMuted(mute) {
  598. if (!(connection && connection.jingle.localAudio)) {
  599. preMuted = mute;
  600. // We still click the button.
  601. buttonClick("#mute", "icon-microphone icon-mic-disabled");
  602. return;
  603. }
  604. if (forceMuted && !mute) {
  605. console.info("Asking focus for unmute");
  606. connection.moderate.setMute(connection.emuc.myroomjid, mute);
  607. // FIXME: wait for result before resetting muted status
  608. forceMuted = false;
  609. }
  610. if (mute == isAudioMuted()) {
  611. // Nothing to do
  612. return;
  613. }
  614. // It is not clear what is the right way to handle multiple tracks.
  615. // So at least make sure that they are all muted or all unmuted and
  616. // that we send presence just once.
  617. var localAudioTracks = connection.jingle.localAudio.getAudioTracks();
  618. if (localAudioTracks.length > 0) {
  619. for (var idx = 0; idx < localAudioTracks.length; idx++) {
  620. localAudioTracks[idx].enabled = !mute;
  621. }
  622. }
  623. // isMuted is the opposite of audioEnabled
  624. connection.emuc.addAudioInfoToPresence(mute);
  625. connection.emuc.sendPresence();
  626. UI.showLocalAudioIndicator(mute);
  627. buttonClick("#mute", "icon-microphone icon-mic-disabled");
  628. }
  629. /**
  630. * Checks whether the audio is muted or not.
  631. * @returns {boolean} true if audio is muted and false if not.
  632. */
  633. function isAudioMuted()
  634. {
  635. var localAudio = connection.jingle.localAudio;
  636. for (var idx = 0; idx < localAudio.getAudioTracks().length; idx++) {
  637. if(localAudio.getAudioTracks()[idx].enabled === true)
  638. return false;
  639. }
  640. return true;
  641. }
  642. /**
  643. * Returns an array of the video horizontal and vertical indents,
  644. * so that if fits its parent.
  645. *
  646. * @return an array with 2 elements, the horizontal indent and the vertical
  647. * indent
  648. */
  649. function getCameraVideoPosition(videoWidth,
  650. videoHeight,
  651. videoSpaceWidth,
  652. videoSpaceHeight) {
  653. // Parent height isn't completely calculated when we position the video in
  654. // full screen mode and this is why we use the screen height in this case.
  655. // Need to think it further at some point and implement it properly.
  656. var isFullScreen = document.fullScreen ||
  657. document.mozFullScreen ||
  658. document.webkitIsFullScreen;
  659. if (isFullScreen)
  660. videoSpaceHeight = window.innerHeight;
  661. var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
  662. var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
  663. return [horizontalIndent, verticalIndent];
  664. }
  665. /**
  666. * Returns an array of the video horizontal and vertical indents.
  667. * Centers horizontally and top aligns vertically.
  668. *
  669. * @return an array with 2 elements, the horizontal indent and the vertical
  670. * indent
  671. */
  672. function getDesktopVideoPosition(videoWidth,
  673. videoHeight,
  674. videoSpaceWidth,
  675. videoSpaceHeight) {
  676. var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
  677. var verticalIndent = 0;// Top aligned
  678. return [horizontalIndent, verticalIndent];
  679. }
  680. /**
  681. * Returns an array of the video dimensions, so that it covers the screen.
  682. * It leaves no empty areas, but some parts of the video might not be visible.
  683. *
  684. * @return an array with 2 elements, the video width and the video height
  685. */
  686. function getCameraVideoSize(videoWidth,
  687. videoHeight,
  688. videoSpaceWidth,
  689. videoSpaceHeight) {
  690. if (!videoWidth)
  691. videoWidth = currentVideoWidth;
  692. if (!videoHeight)
  693. videoHeight = currentVideoHeight;
  694. var aspectRatio = videoWidth / videoHeight;
  695. var availableWidth = Math.max(videoWidth, videoSpaceWidth);
  696. var availableHeight = Math.max(videoHeight, videoSpaceHeight);
  697. if (availableWidth / aspectRatio < videoSpaceHeight) {
  698. availableHeight = videoSpaceHeight;
  699. availableWidth = availableHeight * aspectRatio;
  700. }
  701. if (availableHeight * aspectRatio < videoSpaceWidth) {
  702. availableWidth = videoSpaceWidth;
  703. availableHeight = availableWidth / aspectRatio;
  704. }
  705. return [availableWidth, availableHeight];
  706. }
  707. $(document).ready(function () {
  708. if(APIConnector.isEnabled())
  709. APIConnector.init();
  710. UI.start();
  711. statistics.addConnectionStatsListener(ConnectionQuality.updateLocalStats);
  712. statistics.addRemoteStatsStopListener(ConnectionQuality.stopSendingStats);
  713. statistics.start();
  714. Moderator.init();
  715. // Set default desktop sharing method
  716. setDesktopSharing(config.desktopSharing);
  717. // Initialize Chrome extension inline installs
  718. if (config.chromeExtensionId) {
  719. initInlineInstalls();
  720. }
  721. // By default we use camera
  722. getVideoSize = getCameraVideoSize;
  723. getVideoPosition = getCameraVideoPosition;
  724. });
  725. $(window).bind('beforeunload', function () {
  726. if (connection && connection.connected) {
  727. // ensure signout
  728. $.ajax({
  729. type: 'POST',
  730. url: config.bosh,
  731. async: false,
  732. cache: false,
  733. contentType: 'application/xml',
  734. data: "<body rid='" + (connection.rid || connection._proto.rid)
  735. + "' xmlns='http://jabber.org/protocol/httpbind' sid='"
  736. + (connection.sid || connection._proto.sid)
  737. + "' type='terminate'><presence xmlns='jabber:client' type='unavailable'/></body>",
  738. success: function (data) {
  739. console.log('signed out');
  740. console.log(data);
  741. },
  742. error: function (XMLHttpRequest, textStatus, errorThrown) {
  743. console.log('signout error', textStatus + ' (' + errorThrown + ')');
  744. }
  745. });
  746. }
  747. disposeConference(true);
  748. if(APIConnector.isEnabled())
  749. APIConnector.dispose();
  750. });
  751. function disposeConference(onUnload) {
  752. UI.onDisposeConference(onUnload);
  753. var handler = getConferenceHandler();
  754. if (handler && handler.peerconnection) {
  755. // FIXME: probably removing streams is not required and close() should
  756. // be enough
  757. if (connection.jingle.localAudio) {
  758. handler.peerconnection.removeStream(connection.jingle.localAudio, onUnload);
  759. }
  760. if (connection.jingle.localVideo) {
  761. handler.peerconnection.removeStream(connection.jingle.localVideo, onUnload);
  762. }
  763. handler.peerconnection.close();
  764. }
  765. statistics.onDisposeConference(onUnload);
  766. activecall = null;
  767. }
  768. function dump(elem, filename) {
  769. elem = elem.parentNode;
  770. elem.download = filename || 'meetlog.json';
  771. elem.href = 'data:application/json;charset=utf-8,\n';
  772. var data = populateData();
  773. elem.href += encodeURIComponent(JSON.stringify(data, null, ' '));
  774. return false;
  775. }
  776. /**
  777. * Populates the log data
  778. */
  779. function populateData() {
  780. var data = {};
  781. if (connection.jingle) {
  782. Object.keys(connection.jingle.sessions).forEach(function (sid) {
  783. var session = connection.jingle.sessions[sid];
  784. if (session.peerconnection && session.peerconnection.updateLog) {
  785. // FIXME: should probably be a .dump call
  786. data["jingle_" + session.sid] = {
  787. updateLog: session.peerconnection.updateLog,
  788. stats: session.peerconnection.stats,
  789. url: window.location.href
  790. };
  791. }
  792. });
  793. }
  794. var metadata = {};
  795. metadata.time = new Date();
  796. metadata.url = window.location.href;
  797. metadata.ua = navigator.userAgent;
  798. if (connection.logger) {
  799. metadata.xmpp = connection.logger.log;
  800. }
  801. data.metadata = metadata;
  802. return data;
  803. }
  804. /**
  805. * Changes the style class of the element given by id.
  806. */
  807. function buttonClick(id, classname) {
  808. $(id).toggleClass(classname); // add the class to the clicked element
  809. }
  810. /**
  811. * Warning to the user that the conference window is about to be closed.
  812. */
  813. function closePageWarning() {
  814. /*
  815. FIXME: do we need a warning when the focus is a server-side one now ?
  816. if (focus !== null)
  817. return "You are the owner of this conference call and"
  818. + " you are about to end it.";
  819. else*/
  820. return "You are about to leave this conversation.";
  821. }
  822. $(document).bind('error.jingle',
  823. function (event, session, error)
  824. {
  825. console.error("Jingle error", error);
  826. }
  827. );
  828. $(document).bind('fatalError.jingle',
  829. function (event, session, error)
  830. {
  831. sessionTerminated = true;
  832. connection.emuc.doLeave();
  833. UI.messageHandler.showError( "Sorry",
  834. "Internal application error[setRemoteDescription]");
  835. }
  836. );
  837. function callSipButtonClicked()
  838. {
  839. var defaultNumber
  840. = config.defaultSipNumber ? config.defaultSipNumber : '';
  841. UI.messageHandler.openTwoButtonDialog(null,
  842. '<h2>Enter SIP number</h2>' +
  843. '<input id="sipNumber" type="text"' +
  844. ' value="' + defaultNumber + '" autofocus>',
  845. false,
  846. "Dial",
  847. function (e, v, m, f) {
  848. if (v) {
  849. var numberInput = document.getElementById('sipNumber');
  850. if (numberInput.value) {
  851. connection.rayo.dial(
  852. numberInput.value, 'fromnumber', roomName);
  853. }
  854. }
  855. },
  856. function (event) {
  857. document.getElementById('sipNumber').focus();
  858. }
  859. );
  860. }
  861. function hangup() {
  862. disposeConference();
  863. sessionTerminated = true;
  864. connection.emuc.doLeave();
  865. if(config.enableWelcomePage)
  866. {
  867. setTimeout(function()
  868. {
  869. window.localStorage.welcomePageDisabled = false;
  870. window.location.pathname = "/";
  871. }, 10000);
  872. }
  873. $.prompt("Session Terminated",
  874. {
  875. title: "You hung up the call",
  876. persistent: true,
  877. buttons: {
  878. "Join again": true
  879. },
  880. closeText: '',
  881. submit: function(event, value, message, formVals)
  882. {
  883. window.location.reload();
  884. return false;
  885. }
  886. }
  887. );
  888. }