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.

app.js 51KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591
  1. /* jshint -W117 */
  2. /* application specific logic */
  3. var connection = null;
  4. var authenticatedUser = false;
  5. var focus = null;
  6. var activecall = null;
  7. var RTC = null;
  8. var nickname = null;
  9. var sharedKey = '';
  10. var recordingToken ='';
  11. var roomUrl = null;
  12. var roomName = null;
  13. var ssrc2jid = {};
  14. var mediaStreams = [];
  15. var bridgeIsDown = false;
  16. /**
  17. * The stats collector that process stats data and triggers updates to app.js.
  18. * @type {StatsCollector}
  19. */
  20. var statsCollector = null;
  21. /**
  22. * The stats collector for the local stream.
  23. * @type {LocalStatsCollector}
  24. */
  25. var localStatsCollector = null;
  26. /**
  27. * Indicates whether ssrc is camera video or desktop stream.
  28. * FIXME: remove those maps
  29. */
  30. var ssrc2videoType = {};
  31. var videoSrcToSsrc = {};
  32. /**
  33. * Currently focused video "src"(displayed in large video).
  34. * @type {String}
  35. */
  36. var focusedVideoSrc = null;
  37. var mutedAudios = {};
  38. var localVideoSrc = null;
  39. var flipXLocalVideo = true;
  40. var isFullScreen = false;
  41. var currentVideoWidth = null;
  42. var currentVideoHeight = null;
  43. /**
  44. * Method used to calculate large video size.
  45. * @type {function ()}
  46. */
  47. var getVideoSize;
  48. /**
  49. * Method used to get large video position.
  50. * @type {function ()}
  51. */
  52. var getVideoPosition;
  53. /* window.onbeforeunload = closePageWarning; */
  54. var sessionTerminated = false;
  55. function init() {
  56. Toolbar.setupButtonsFromConfig();
  57. RTC = setupRTC();
  58. if (RTC === null) {
  59. window.location.href = 'webrtcrequired.html';
  60. return;
  61. } else if (RTC.browser !== 'chrome') {
  62. window.location.href = 'chromeonly.html';
  63. return;
  64. }
  65. obtainAudioAndVideoPermissions(function (stream) {
  66. var audioStream = new webkitMediaStream();
  67. var videoStream = new webkitMediaStream();
  68. var audioTracks = stream.getAudioTracks();
  69. var videoTracks = stream.getVideoTracks();
  70. for (var i = 0; i < audioTracks.length; i++) {
  71. audioStream.addTrack(audioTracks[i]);
  72. }
  73. VideoLayout.changeLocalAudio(audioStream);
  74. startLocalRtpStatsCollector(audioStream);
  75. for (i = 0; i < videoTracks.length; i++) {
  76. videoStream.addTrack(videoTracks[i]);
  77. }
  78. VideoLayout.changeLocalVideo(videoStream, true);
  79. maybeDoJoin();
  80. });
  81. var jid = document.getElementById('jid').value || config.hosts.anonymousdomain || config.hosts.domain || window.location.hostname;
  82. connect(jid);
  83. }
  84. function connect(jid, password) {
  85. var localAudio, localVideo;
  86. if (connection && connection.jingle) {
  87. localAudio = connection.jingle.localAudio;
  88. localVideo = connection.jingle.localVideo;
  89. }
  90. connection = new Strophe.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind');
  91. if (nickname) {
  92. connection.emuc.addDisplayNameToPresence(nickname);
  93. }
  94. if (connection.disco) {
  95. // for chrome, add multistream cap
  96. }
  97. connection.jingle.pc_constraints = RTC.pc_constraints;
  98. if (config.useIPv6) {
  99. // https://code.google.com/p/webrtc/issues/detail?id=2828
  100. if (!connection.jingle.pc_constraints.optional) connection.jingle.pc_constraints.optional = [];
  101. connection.jingle.pc_constraints.optional.push({googIPv6: true});
  102. }
  103. if (localAudio) connection.jingle.localAudio = localAudio;
  104. if (localVideo) connection.jingle.localVideo = localVideo;
  105. if(!password)
  106. password = document.getElementById('password').value;
  107. var anonymousConnectionFailed = false;
  108. connection.connect(jid, password, function (status, msg) {
  109. console.log('Strophe status changed to', Strophe.getStatusString(status));
  110. if (status === Strophe.Status.CONNECTED) {
  111. if (config.useStunTurn) {
  112. connection.jingle.getStunAndTurnCredentials();
  113. }
  114. document.getElementById('connect').disabled = true;
  115. if(password)
  116. authenticatedUser = true;
  117. maybeDoJoin();
  118. } else if (status === Strophe.Status.CONNFAIL) {
  119. if(msg === 'x-strophe-bad-non-anon-jid') {
  120. anonymousConnectionFailed = true;
  121. }
  122. } else if (status === Strophe.Status.DISCONNECTED) {
  123. if(anonymousConnectionFailed) {
  124. // prompt user for username and password
  125. $(document).trigger('passwordrequired.main');
  126. }
  127. } else if (status === Strophe.Status.AUTHFAIL) {
  128. // wrong password or username, prompt user
  129. $(document).trigger('passwordrequired.main');
  130. }
  131. });
  132. }
  133. /**
  134. * We ask for audio and video combined stream in order to get permissions and
  135. * not to ask twice.
  136. */
  137. function obtainAudioAndVideoPermissions(callback) {
  138. // Get AV
  139. getUserMediaWithConstraints(
  140. ['audio', 'video'],
  141. function (avStream) {
  142. callback(avStream);
  143. trackUsage('localMedia', {
  144. audio: avStream.getAudioTracks().length,
  145. video: avStream.getVideoTracks().length
  146. });
  147. },
  148. function (error) {
  149. console.error('failed to obtain audio/video stream - stop', error);
  150. trackUsage('localMediaError', {
  151. media: error.media || 'video',
  152. name : error.name
  153. });
  154. messageHandler.showError("Error",
  155. "Failed to obtain permissions to use the local microphone" +
  156. "and/or camera.");
  157. },
  158. config.resolution || '360');
  159. }
  160. function maybeDoJoin() {
  161. if (connection && connection.connected && Strophe.getResourceFromJid(connection.jid) // .connected is true while connecting?
  162. && (connection.jingle.localAudio || connection.jingle.localVideo)) {
  163. doJoin();
  164. }
  165. }
  166. function doJoin() {
  167. var roomnode = null;
  168. var path = window.location.pathname;
  169. var roomjid;
  170. // determinde the room node from the url
  171. // TODO: just the roomnode or the whole bare jid?
  172. if (config.getroomnode && typeof config.getroomnode === 'function') {
  173. // custom function might be responsible for doing the pushstate
  174. roomnode = config.getroomnode(path);
  175. } else {
  176. /* fall back to default strategy
  177. * this is making assumptions about how the URL->room mapping happens.
  178. * It currently assumes deployment at root, with a rewrite like the
  179. * following one (for nginx):
  180. location ~ ^/([a-zA-Z0-9]+)$ {
  181. rewrite ^/(.*)$ / break;
  182. }
  183. */
  184. if (path.length > 1) {
  185. roomnode = path.substr(1).toLowerCase();
  186. } else {
  187. var word = RoomNameGenerator.generateRoomWithoutSeparator();
  188. roomnode = word.toLowerCase();
  189. window.history.pushState('VideoChat',
  190. 'Room: ' + word, window.location.pathname + word);
  191. }
  192. }
  193. roomName = roomnode + '@' + config.hosts.muc;
  194. roomjid = roomName;
  195. if (config.useNicks) {
  196. var nick = window.prompt('Your nickname (optional)');
  197. if (nick) {
  198. roomjid += '/' + nick;
  199. } else {
  200. roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
  201. }
  202. } else {
  203. var tmpJid = Strophe.getNodeFromJid(connection.jid);
  204. if(!authenticatedUser)
  205. tmpJid = tmpJid.substr(0, 8);
  206. roomjid += '/' + tmpJid;
  207. }
  208. connection.emuc.doJoin(roomjid);
  209. }
  210. function waitForRemoteVideo(selector, ssrc, stream) {
  211. if (selector.removed || !selector.parent().is(":visible")) {
  212. console.warn("Media removed before had started", selector);
  213. return;
  214. }
  215. if (stream.id === 'mixedmslabel') return;
  216. if (selector[0].currentTime > 0) {
  217. var simulcast = new Simulcast();
  218. var videoStream = simulcast.getReceivingVideoStream(stream);
  219. RTC.attachMediaStream(selector, videoStream); // FIXME: why do i have to do this for FF?
  220. // FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
  221. // in order to get rid of too many maps
  222. if (ssrc && selector.attr('src')) {
  223. videoSrcToSsrc[selector.attr('src')] = ssrc;
  224. } else {
  225. console.warn("No ssrc given for video", selector);
  226. messageHandler.showError('Warning', 'No ssrc was given for the video.');
  227. }
  228. $(document).trigger('videoactive.jingle', [selector]);
  229. } else {
  230. setTimeout(function () {
  231. waitForRemoteVideo(selector, ssrc, stream);
  232. }, 250);
  233. }
  234. }
  235. $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
  236. waitForPresence(data, sid);
  237. });
  238. function waitForPresence(data, sid) {
  239. var sess = connection.jingle.sessions[sid];
  240. var thessrc;
  241. // look up an associated JID for a stream id
  242. if (data.stream.id.indexOf('mixedmslabel') === -1) {
  243. // look only at a=ssrc: and _not_ at a=ssrc-group: lines
  244. var ssrclines
  245. = SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc:');
  246. ssrclines = ssrclines.filter(function (line) {
  247. // NOTE(gp) previously we filtered on the mslabel, but that property
  248. // is not always present.
  249. // return line.indexOf('mslabel:' + data.stream.label) !== -1;
  250. return line.indexOf('msid:' + data.stream.id) !== -1;
  251. });
  252. if (ssrclines.length) {
  253. thessrc = ssrclines[0].substring(7).split(' ')[0];
  254. // We signal our streams (through Jingle to the focus) before we set
  255. // our presence (through which peers associate remote streams to
  256. // jids). So, it might arrive that a remote stream is added but
  257. // ssrc2jid is not yet updated and thus data.peerjid cannot be
  258. // successfully set. Here we wait for up to a second for the
  259. // presence to arrive.
  260. if (!ssrc2jid[thessrc]) {
  261. // TODO(gp) limit wait duration to 1 sec.
  262. setTimeout(function(d, s) {
  263. return function() {
  264. waitForPresence(d, s);
  265. }
  266. }(data, sid), 250);
  267. return;
  268. }
  269. // ok to overwrite the one from focus? might save work in colibri.js
  270. console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
  271. if (ssrc2jid[thessrc]) {
  272. data.peerjid = ssrc2jid[thessrc];
  273. }
  274. }
  275. }
  276. // NOTE(gp) now that we have simulcast, a media stream can have more than 1
  277. // ssrc. We should probably take that into account in our MediaStream
  278. // wrapper.
  279. mediaStreams.push(new MediaStream(data, sid, thessrc));
  280. var container;
  281. var remotes = document.getElementById('remoteVideos');
  282. if (data.peerjid) {
  283. VideoLayout.ensurePeerContainerExists(data.peerjid);
  284. container = document.getElementById(
  285. 'participant_' + Strophe.getResourceFromJid(data.peerjid));
  286. } else {
  287. if (data.stream.id !== 'mixedmslabel') {
  288. console.error('can not associate stream',
  289. data.stream.id,
  290. 'with a participant');
  291. messageHandler.showError('Oops',
  292. 'We could not associate the current stream with a participant.');
  293. // We don't want to add it here since it will cause troubles
  294. return;
  295. }
  296. // FIXME: for the mixed ms we dont need a video -- currently
  297. container = document.createElement('span');
  298. container.id = 'mixedstream';
  299. container.className = 'videocontainer';
  300. remotes.appendChild(container);
  301. Util.playSoundNotification('userJoined');
  302. }
  303. var isVideo = data.stream.getVideoTracks().length > 0;
  304. if (container) {
  305. VideoLayout.addRemoteStreamElement( container,
  306. sid,
  307. data.stream,
  308. data.peerjid,
  309. thessrc);
  310. }
  311. // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
  312. if (isVideo &&
  313. data.peerjid && sess.peerjid === data.peerjid &&
  314. data.stream.getVideoTracks().length === 0 &&
  315. connection.jingle.localVideo.getVideoTracks().length > 0) {
  316. //
  317. window.setTimeout(function () {
  318. sendKeyframe(sess.peerconnection);
  319. }, 3000);
  320. }
  321. }
  322. /**
  323. * Returns the JID of the user to whom given <tt>videoSrc</tt> belongs.
  324. * @param videoSrc the video "src" identifier.
  325. * @returns {null | String} the JID of the user to whom given <tt>videoSrc</tt>
  326. * belongs.
  327. */
  328. function getJidFromVideoSrc(videoSrc)
  329. {
  330. if (videoSrc === localVideoSrc)
  331. return connection.emuc.myroomjid;
  332. var ssrc = videoSrcToSsrc[videoSrc];
  333. if (!ssrc)
  334. {
  335. return null;
  336. }
  337. return ssrc2jid[ssrc];
  338. }
  339. // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
  340. function sendKeyframe(pc) {
  341. console.log('sendkeyframe', pc.iceConnectionState);
  342. if (pc.iceConnectionState !== 'connected') return; // safe...
  343. pc.setRemoteDescription(
  344. pc.remoteDescription,
  345. function () {
  346. pc.createAnswer(
  347. function (modifiedAnswer) {
  348. pc.setLocalDescription(
  349. modifiedAnswer,
  350. function () {
  351. // noop
  352. },
  353. function (error) {
  354. console.log('triggerKeyframe setLocalDescription failed', error);
  355. messageHandler.showError();
  356. }
  357. );
  358. },
  359. function (error) {
  360. console.log('triggerKeyframe createAnswer failed', error);
  361. messageHandler.showError();
  362. }
  363. );
  364. },
  365. function (error) {
  366. console.log('triggerKeyframe setRemoteDescription failed', error);
  367. messageHandler.showError();
  368. }
  369. );
  370. }
  371. // Really mute video, i.e. dont even send black frames
  372. function muteVideo(pc, unmute) {
  373. // FIXME: this probably needs another of those lovely state safeguards...
  374. // which checks for iceconn == connected and sigstate == stable
  375. pc.setRemoteDescription(pc.remoteDescription,
  376. function () {
  377. pc.createAnswer(
  378. function (answer) {
  379. var sdp = new SDP(answer.sdp);
  380. if (sdp.media.length > 1) {
  381. if (unmute)
  382. sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
  383. else
  384. sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
  385. sdp.raw = sdp.session + sdp.media.join('');
  386. answer.sdp = sdp.raw;
  387. }
  388. pc.setLocalDescription(answer,
  389. function () {
  390. console.log('mute SLD ok');
  391. },
  392. function (error) {
  393. console.log('mute SLD error');
  394. messageHandler.showError('Error',
  395. 'Oops! Something went wrong and we failed to ' +
  396. 'mute! (SLD Failure)');
  397. }
  398. );
  399. },
  400. function (error) {
  401. console.log(error);
  402. messageHandler.showError();
  403. }
  404. );
  405. },
  406. function (error) {
  407. console.log('muteVideo SRD error');
  408. messageHandler.showError('Error',
  409. 'Oops! Something went wrong and we failed to stop video!' +
  410. '(SRD Failure)');
  411. }
  412. );
  413. }
  414. /**
  415. * Callback for audio levels changed.
  416. * @param jid JID of the user
  417. * @param audioLevel the audio level value
  418. */
  419. function audioLevelUpdated(jid, audioLevel)
  420. {
  421. var resourceJid;
  422. if(jid === LocalStatsCollector.LOCAL_JID)
  423. {
  424. resourceJid = AudioLevels.LOCAL_LEVEL;
  425. if(isAudioMuted())
  426. {
  427. audioLevel = 0;
  428. }
  429. }
  430. else
  431. {
  432. resourceJid = Strophe.getResourceFromJid(jid);
  433. }
  434. AudioLevels.updateAudioLevel(resourceJid, audioLevel);
  435. }
  436. /**
  437. * Starts the {@link StatsCollector} if the feature is enabled in config.js.
  438. */
  439. function startRtpStatsCollector()
  440. {
  441. stopRTPStatsCollector();
  442. if (config.enableRtpStats)
  443. {
  444. statsCollector = new StatsCollector(
  445. getConferenceHandler().peerconnection, 200, audioLevelUpdated);
  446. statsCollector.start();
  447. }
  448. }
  449. /**
  450. * Stops the {@link StatsCollector}.
  451. */
  452. function stopRTPStatsCollector()
  453. {
  454. if (statsCollector)
  455. {
  456. statsCollector.stop();
  457. statsCollector = null;
  458. }
  459. }
  460. /**
  461. * Starts the {@link LocalStatsCollector} if the feature is enabled in config.js
  462. * @param stream the stream that will be used for collecting statistics.
  463. */
  464. function startLocalRtpStatsCollector(stream)
  465. {
  466. if(config.enableRtpStats)
  467. {
  468. localStatsCollector = new LocalStatsCollector(stream, 100, audioLevelUpdated);
  469. localStatsCollector.start();
  470. }
  471. }
  472. /**
  473. * Stops the {@link LocalStatsCollector}.
  474. */
  475. function stopLocalRtpStatsCollector()
  476. {
  477. if(localStatsCollector)
  478. {
  479. localStatsCollector.stop();
  480. localStatsCollector = null;
  481. }
  482. }
  483. $(document).bind('callincoming.jingle', function (event, sid) {
  484. var sess = connection.jingle.sessions[sid];
  485. // TODO: do we check activecall == null?
  486. activecall = sess;
  487. startRtpStatsCollector();
  488. // Bind data channel listener in case we're a regular participant
  489. if (config.openSctp)
  490. {
  491. bindDataChannelListener(sess.peerconnection);
  492. }
  493. // TODO: check affiliation and/or role
  494. console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
  495. sess.usedrip = true; // not-so-naive trickle ice
  496. sess.sendAnswer();
  497. sess.accept();
  498. });
  499. $(document).bind('conferenceCreated.jingle', function (event, focus)
  500. {
  501. startRtpStatsCollector();
  502. });
  503. $(document).bind('conferenceCreated.jingle', function (event, focus)
  504. {
  505. // Bind data channel listener in case we're the focus
  506. if (config.openSctp)
  507. {
  508. bindDataChannelListener(focus.peerconnection);
  509. }
  510. });
  511. $(document).bind('callterminated.jingle', function (event, sid, jid, reason) {
  512. // Leave the room if my call has been remotely terminated.
  513. if (connection.emuc.joined && focus == null && reason === 'kick') {
  514. sessionTerminated = true;
  515. connection.emuc.doLeave();
  516. messageHandler.openMessageDialog("Session Terminated",
  517. "Ouch! You have been kicked out of the meet!");
  518. }
  519. });
  520. $(document).bind('setLocalDescription.jingle', function (event, sid) {
  521. // put our ssrcs into presence so other clients can identify our stream
  522. var sess = connection.jingle.sessions[sid];
  523. var newssrcs = [];
  524. var simulcast = new Simulcast();
  525. var media = simulcast.parseMedia(sess.peerconnection.localDescription);
  526. media.forEach(function (media) {
  527. // TODO(gp) maybe exclude FID streams?
  528. Object.keys(media.sources).forEach(function(ssrc) {
  529. newssrcs.push({
  530. 'ssrc': ssrc,
  531. 'type': media.type,
  532. 'direction': media.direction
  533. });
  534. });
  535. });
  536. console.log('new ssrcs', newssrcs);
  537. // Have to clear presence map to get rid of removed streams
  538. connection.emuc.clearPresenceMedia();
  539. if (newssrcs.length > 0) {
  540. for (var i = 1; i <= newssrcs.length; i ++) {
  541. // Change video type to screen
  542. if (newssrcs[i-1].type === 'video' && isUsingScreenStream) {
  543. newssrcs[i-1].type = 'screen';
  544. }
  545. connection.emuc.addMediaToPresence(i,
  546. newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
  547. }
  548. connection.emuc.sendPresence();
  549. }
  550. });
  551. $(document).bind('iceconnectionstatechange.jingle', function (event, sid, session) {
  552. switch (session.peerconnection.iceConnectionState) {
  553. case 'checking':
  554. session.timeChecking = (new Date()).getTime();
  555. session.firstconnect = true;
  556. break;
  557. case 'completed': // on caller side
  558. case 'connected':
  559. if (session.firstconnect) {
  560. session.firstconnect = false;
  561. var metadata = {};
  562. metadata.setupTime = (new Date()).getTime() - session.timeChecking;
  563. session.peerconnection.getStats(function (res) {
  564. res.result().forEach(function (report) {
  565. if (report.type == 'googCandidatePair' && report.stat('googActiveConnection') == 'true') {
  566. metadata.localCandidateType = report.stat('googLocalCandidateType');
  567. metadata.remoteCandidateType = report.stat('googRemoteCandidateType');
  568. // log pair as well so we can get nice pie charts
  569. metadata.candidatePair = report.stat('googLocalCandidateType') + ';' + report.stat('googRemoteCandidateType');
  570. if (report.stat('googRemoteAddress').indexOf('[') === 0) {
  571. metadata.ipv6 = true;
  572. }
  573. }
  574. });
  575. trackUsage('iceConnected', metadata);
  576. });
  577. }
  578. break;
  579. }
  580. });
  581. $(document).bind('joined.muc', function (event, jid, info) {
  582. updateRoomUrl(window.location.href);
  583. document.getElementById('localNick').appendChild(
  584. document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)')
  585. );
  586. if (Object.keys(connection.emuc.members).length < 1) {
  587. focus = new ColibriFocus(connection, config.hosts.bridge);
  588. if (nickname !== null) {
  589. focus.setEndpointDisplayName(connection.emuc.myroomjid,
  590. nickname);
  591. }
  592. Toolbar.showSipCallButton(true);
  593. Toolbar.showRecordingButton(false);
  594. }
  595. if (!focus)
  596. {
  597. Toolbar.showSipCallButton(false);
  598. }
  599. if (focus && config.etherpad_base) {
  600. Etherpad.init();
  601. }
  602. VideoLayout.showFocusIndicator();
  603. // Add myself to the contact list.
  604. ContactList.addContact(jid);
  605. // Once we've joined the muc show the toolbar
  606. ToolbarToggler.showToolbar();
  607. if (info.displayName)
  608. $(document).trigger('displaynamechanged',
  609. ['localVideoContainer', info.displayName + ' (me)']);
  610. });
  611. $(document).bind('entered.muc', function (event, jid, info, pres) {
  612. console.log('entered', jid, info);
  613. console.log('is focus? ' + (focus ? 'true' : 'false'));
  614. // Add Peer's container
  615. VideoLayout.ensurePeerContainerExists(jid);
  616. if (focus !== null) {
  617. // FIXME: this should prepare the video
  618. if (focus.confid === null) {
  619. console.log('make new conference with', jid);
  620. focus.makeConference(Object.keys(connection.emuc.members),
  621. function(error) {
  622. connection.emuc.addBridgeIsDownToPresence();
  623. connection.emuc.sendPresence();
  624. }
  625. );
  626. Toolbar.showRecordingButton(true);
  627. } else {
  628. console.log('invite', jid, 'into conference');
  629. focus.addNewParticipant(jid);
  630. }
  631. }
  632. else if (sharedKey) {
  633. Toolbar.updateLockButton();
  634. }
  635. });
  636. $(document).bind('left.muc', function (event, jid) {
  637. console.log('left.muc', jid);
  638. // Need to call this with a slight delay, otherwise the element couldn't be
  639. // found for some reason.
  640. window.setTimeout(function () {
  641. var container = document.getElementById(
  642. 'participant_' + Strophe.getResourceFromJid(jid));
  643. if (container) {
  644. // hide here, wait for video to close before removing
  645. $(container).hide();
  646. VideoLayout.resizeThumbnails();
  647. }
  648. }, 10);
  649. // Unlock large video
  650. if (focusedVideoSrc)
  651. {
  652. if (getJidFromVideoSrc(focusedVideoSrc) === jid)
  653. {
  654. console.info("Focused video owner has left the conference");
  655. focusedVideoSrc = null;
  656. }
  657. }
  658. connection.jingle.terminateByJid(jid);
  659. if (focus == null
  660. // I shouldn't be the one that left to enter here.
  661. && jid !== connection.emuc.myroomjid
  662. && connection.emuc.myroomjid === connection.emuc.list_members[0]
  663. // If our session has been terminated for some reason
  664. // (kicked, hangup), don't try to become the focus
  665. && !sessionTerminated) {
  666. console.log('welcome to our new focus... myself');
  667. focus = new ColibriFocus(connection, config.hosts.bridge);
  668. if (nickname !== null) {
  669. focus.setEndpointDisplayName(connection.emuc.myroomjid,
  670. nickname);
  671. }
  672. Toolbar.showSipCallButton(true);
  673. if (Object.keys(connection.emuc.members).length > 0) {
  674. focus.makeConference(Object.keys(connection.emuc.members));
  675. Toolbar.showRecordingButton(true);
  676. }
  677. $(document).trigger('focusechanged.muc', [focus]);
  678. }
  679. else if (focus && Object.keys(connection.emuc.members).length === 0) {
  680. console.log('everyone left');
  681. // FIXME: closing the connection is a hack to avoid some
  682. // problems with reinit
  683. disposeConference();
  684. focus = new ColibriFocus(connection, config.hosts.bridge);
  685. if (nickname !== null) {
  686. focus.setEndpointDisplayName(connection.emuc.myroomjid,
  687. nickname);
  688. }
  689. Toolbar.showSipCallButton(true);
  690. Toolbar.showRecordingButton(false);
  691. }
  692. if (connection.emuc.getPrezi(jid)) {
  693. $(document).trigger('presentationremoved.muc',
  694. [jid, connection.emuc.getPrezi(jid)]);
  695. }
  696. });
  697. $(document).bind('presence.muc', function (event, jid, info, pres) {
  698. // Remove old ssrcs coming from the jid
  699. Object.keys(ssrc2jid).forEach(function (ssrc) {
  700. if (ssrc2jid[ssrc] == jid) {
  701. delete ssrc2jid[ssrc];
  702. }
  703. if (ssrc2videoType[ssrc] == jid) {
  704. delete ssrc2videoType[ssrc];
  705. }
  706. });
  707. $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
  708. //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
  709. var ssrcV = ssrc.getAttribute('ssrc');
  710. ssrc2jid[ssrcV] = jid;
  711. var type = ssrc.getAttribute('type');
  712. ssrc2videoType[ssrcV] = type;
  713. // might need to update the direction if participant just went from sendrecv to recvonly
  714. if (type === 'video' || type === 'screen') {
  715. var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video');
  716. switch (ssrc.getAttribute('direction')) {
  717. case 'sendrecv':
  718. el.show();
  719. break;
  720. case 'recvonly':
  721. el.hide();
  722. // FIXME: Check if we have to change large video
  723. //VideoLayout.updateLargeVideo(el);
  724. break;
  725. }
  726. }
  727. });
  728. if (info.displayName && info.displayName.length > 0)
  729. $(document).trigger('displaynamechanged',
  730. [jid, info.displayName]);
  731. if (focus !== null && info.displayName !== null) {
  732. focus.setEndpointDisplayName(jid, info.displayName);
  733. }
  734. //check if the video bridge is available
  735. if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
  736. bridgeIsDown = true;
  737. messageHandler.showError("Error",
  738. "Jitsi Videobridge is currently unavailable. Please try again later!");
  739. }
  740. });
  741. $(document).bind('presence.status.muc', function (event, jid, info, pres) {
  742. VideoLayout.setPresenceStatus(
  743. 'participant_' + Strophe.getResourceFromJid(jid), info.status);
  744. });
  745. $(document).bind('passwordrequired.muc', function (event, jid) {
  746. console.log('on password required', jid);
  747. messageHandler.openTwoButtonDialog(null,
  748. '<h2>Password required</h2>' +
  749. '<input id="lockKey" type="text" placeholder="password" autofocus>',
  750. true,
  751. "Ok",
  752. function (e, v, m, f) {
  753. if (v) {
  754. var lockKey = document.getElementById('lockKey');
  755. if (lockKey.value !== null) {
  756. setSharedKey(lockKey.value);
  757. connection.emuc.doJoin(jid, lockKey.value);
  758. }
  759. }
  760. },
  761. function (event) {
  762. document.getElementById('lockKey').focus();
  763. }
  764. );
  765. });
  766. $(document).bind('passwordrequired.main', function (event) {
  767. console.log('password is required');
  768. messageHandler.openTwoButtonDialog(null,
  769. '<h2>Password required</h2>' +
  770. '<input id="passwordrequired.username" type="text" placeholder="user@domain.net" autofocus>' +
  771. '<input id="passwordrequired.password" type="password" placeholder="user password">',
  772. true,
  773. "Ok",
  774. function (e, v, m, f) {
  775. if (v) {
  776. var username = document.getElementById('passwordrequired.username');
  777. var password = document.getElementById('passwordrequired.password');
  778. if (username.value !== null && password.value != null) {
  779. connect(username.value, password.value);
  780. }
  781. }
  782. },
  783. function (event) {
  784. document.getElementById('passwordrequired.username').focus();
  785. }
  786. );
  787. });
  788. /**
  789. * Checks if video identified by given src is desktop stream.
  790. * @param videoSrc eg.
  791. * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
  792. * @returns {boolean}
  793. */
  794. function isVideoSrcDesktop(videoSrc) {
  795. // FIXME: fix this mapping mess...
  796. // figure out if large video is desktop stream or just a camera
  797. var isDesktop = false;
  798. if (localVideoSrc === videoSrc) {
  799. // local video
  800. isDesktop = isUsingScreenStream;
  801. } else {
  802. // Do we have associations...
  803. var videoSsrc = videoSrcToSsrc[videoSrc];
  804. if (videoSsrc) {
  805. var videoType = ssrc2videoType[videoSsrc];
  806. if (videoType) {
  807. // Finally there...
  808. isDesktop = videoType === 'screen';
  809. } else {
  810. console.error("No video type for ssrc: " + videoSsrc);
  811. }
  812. } else {
  813. console.error("No ssrc for src: " + videoSrc);
  814. }
  815. }
  816. return isDesktop;
  817. }
  818. function getConferenceHandler() {
  819. return focus ? focus : activecall;
  820. }
  821. function toggleVideo() {
  822. buttonClick("#video", "icon-camera icon-camera-disabled");
  823. if (!(connection && connection.jingle.localVideo))
  824. return;
  825. var sess = getConferenceHandler();
  826. if (sess) {
  827. sess.toggleVideoMute(
  828. function (isMuted) {
  829. if (isMuted) {
  830. $('#video').removeClass("icon-camera");
  831. $('#video').addClass("icon-camera icon-camera-disabled");
  832. } else {
  833. $('#video').removeClass("icon-camera icon-camera-disabled");
  834. $('#video').addClass("icon-camera");
  835. }
  836. connection.emuc.addVideoInfoToPresence(isMuted);
  837. connection.emuc.sendPresence();
  838. }
  839. );
  840. }
  841. }
  842. /**
  843. * Mutes / unmutes audio for the local participant.
  844. */
  845. function toggleAudio() {
  846. if (!(connection && connection.jingle.localAudio)) {
  847. // We still click the button.
  848. buttonClick("#mute", "icon-microphone icon-mic-disabled");
  849. return;
  850. }
  851. // It is not clear what is the right way to handle multiple tracks.
  852. // So at least make sure that they are all muted or all unmuted and
  853. // that we send presence just once.
  854. var localAudioTracks = connection.jingle.localAudio.getAudioTracks();
  855. if (localAudioTracks.length > 0) {
  856. var audioEnabled = localAudioTracks[0].enabled;
  857. for (var idx = 0; idx < localAudioTracks.length; idx++) {
  858. localAudioTracks[idx].enabled = !audioEnabled;
  859. }
  860. // isMuted is the opposite of audioEnabled
  861. connection.emuc.addAudioInfoToPresence(audioEnabled);
  862. connection.emuc.sendPresence();
  863. VideoLayout.showLocalAudioIndicator(audioEnabled);
  864. }
  865. buttonClick("#mute", "icon-microphone icon-mic-disabled");
  866. }
  867. /**
  868. * Checks whether the audio is muted or not.
  869. * @returns {boolean} true if audio is muted and false if not.
  870. */
  871. function isAudioMuted()
  872. {
  873. var localAudio = connection.jingle.localAudio;
  874. for (var idx = 0; idx < localAudio.getAudioTracks().length; idx++) {
  875. if(localAudio.getAudioTracks()[idx].enabled === true)
  876. return false;
  877. }
  878. return true;
  879. }
  880. // Starts or stops the recording for the conference.
  881. function toggleRecording() {
  882. if (focus === null || focus.confid === null) {
  883. console.log('non-focus, or conference not yet organized: not enabling recording');
  884. return;
  885. }
  886. if (!recordingToken)
  887. {
  888. messageHandler.openTwoButtonDialog(null,
  889. '<h2>Enter recording token</h2>' +
  890. '<input id="recordingToken" type="text" placeholder="token" autofocus>',
  891. false,
  892. "Save",
  893. function (e, v, m, f) {
  894. if (v) {
  895. var token = document.getElementById('recordingToken');
  896. if (token.value) {
  897. setRecordingToken(Util.escapeHtml(token.value));
  898. toggleRecording();
  899. }
  900. }
  901. },
  902. function (event) {
  903. document.getElementById('recordingToken').focus();
  904. }
  905. );
  906. return;
  907. }
  908. var oldState = focus.recordingEnabled;
  909. Toolbar.toggleRecordingButtonState();
  910. focus.setRecording(!oldState,
  911. recordingToken,
  912. function (state) {
  913. console.log("New recording state: ", state);
  914. if (state == oldState) //failed to change, reset the token because it might have been wrong
  915. {
  916. Toolbar.toggleRecordingButtonState();
  917. setRecordingToken(null);
  918. }
  919. }
  920. );
  921. }
  922. /**
  923. * Returns an array of the video horizontal and vertical indents,
  924. * so that if fits its parent.
  925. *
  926. * @return an array with 2 elements, the horizontal indent and the vertical
  927. * indent
  928. */
  929. function getCameraVideoPosition(videoWidth,
  930. videoHeight,
  931. videoSpaceWidth,
  932. videoSpaceHeight) {
  933. // Parent height isn't completely calculated when we position the video in
  934. // full screen mode and this is why we use the screen height in this case.
  935. // Need to think it further at some point and implement it properly.
  936. var isFullScreen = document.fullScreen ||
  937. document.mozFullScreen ||
  938. document.webkitIsFullScreen;
  939. if (isFullScreen)
  940. videoSpaceHeight = window.innerHeight;
  941. var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
  942. var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
  943. return [horizontalIndent, verticalIndent];
  944. }
  945. /**
  946. * Returns an array of the video horizontal and vertical indents.
  947. * Centers horizontally and top aligns vertically.
  948. *
  949. * @return an array with 2 elements, the horizontal indent and the vertical
  950. * indent
  951. */
  952. function getDesktopVideoPosition(videoWidth,
  953. videoHeight,
  954. videoSpaceWidth,
  955. videoSpaceHeight) {
  956. var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
  957. var verticalIndent = 0;// Top aligned
  958. return [horizontalIndent, verticalIndent];
  959. }
  960. /**
  961. * Returns an array of the video dimensions, so that it covers the screen.
  962. * It leaves no empty areas, but some parts of the video might not be visible.
  963. *
  964. * @return an array with 2 elements, the video width and the video height
  965. */
  966. function getCameraVideoSize(videoWidth,
  967. videoHeight,
  968. videoSpaceWidth,
  969. videoSpaceHeight) {
  970. if (!videoWidth)
  971. videoWidth = currentVideoWidth;
  972. if (!videoHeight)
  973. videoHeight = currentVideoHeight;
  974. var aspectRatio = videoWidth / videoHeight;
  975. var availableWidth = Math.max(videoWidth, videoSpaceWidth);
  976. var availableHeight = Math.max(videoHeight, videoSpaceHeight);
  977. if (availableWidth / aspectRatio < videoSpaceHeight) {
  978. availableHeight = videoSpaceHeight;
  979. availableWidth = availableHeight * aspectRatio;
  980. }
  981. if (availableHeight * aspectRatio < videoSpaceWidth) {
  982. availableWidth = videoSpaceWidth;
  983. availableHeight = availableWidth / aspectRatio;
  984. }
  985. return [availableWidth, availableHeight];
  986. }
  987. $(document).ready(function () {
  988. document.title = brand.appName;
  989. if(config.enableWelcomePage && window.location.pathname == "/" &&
  990. (!window.localStorage.welcomePageDisabled
  991. || window.localStorage.welcomePageDisabled == "false"))
  992. {
  993. $("#videoconference_page").hide();
  994. $("#domain_name").text(
  995. window.location.protocol + "//" + window.location.host + "/");
  996. $("span[name='appName']").text(brand.appName);
  997. if (interfaceConfig.SHOW_JITSI_WATERMARK) {
  998. var leftWatermarkDiv
  999. = $("#welcome_page_header div[class='watermark leftwatermark']");
  1000. if(leftWatermarkDiv && leftWatermarkDiv.length > 0)
  1001. {
  1002. leftWatermarkDiv.css({display: 'block'});
  1003. leftWatermarkDiv.parent().get(0).href
  1004. = interfaceConfig.JITSI_WATERMARK_LINK;
  1005. }
  1006. }
  1007. if (interfaceConfig.SHOW_BRAND_WATERMARK) {
  1008. var rightWatermarkDiv
  1009. = $("#welcome_page_header div[class='watermark rightwatermark']");
  1010. if(rightWatermarkDiv && rightWatermarkDiv.length > 0) {
  1011. rightWatermarkDiv.css({display: 'block'});
  1012. rightWatermarkDiv.parent().get(0).href
  1013. = interfaceConfig.BRAND_WATERMARK_LINK;
  1014. rightWatermarkDiv.get(0).style.backgroundImage
  1015. = "url(images/rightwatermark.png)";
  1016. }
  1017. }
  1018. if (interfaceConfig.SHOW_POWERED_BY) {
  1019. $("#welcome_page_header>a[class='poweredby']")
  1020. .css({display: 'block'});
  1021. }
  1022. function enter_room()
  1023. {
  1024. var val = $("#enter_room_field").val();
  1025. if(!val) {
  1026. val = $("#enter_room_field").attr("room_name");
  1027. }
  1028. if (val) {
  1029. window.location.pathname = "/" + val;
  1030. }
  1031. }
  1032. $("#enter_room_button").click(function()
  1033. {
  1034. enter_room();
  1035. });
  1036. $("#enter_room_field").keydown(function (event) {
  1037. if (event.keyCode === 13 /* enter */) {
  1038. enter_room();
  1039. }
  1040. });
  1041. if (!(interfaceConfig.GENERATE_ROOMNAMES_ON_WELCOME_PAGE === false)){
  1042. var updateTimeout;
  1043. var animateTimeout;
  1044. $("#reload_roomname").click(function () {
  1045. clearTimeout(updateTimeout);
  1046. clearTimeout(animateTimeout);
  1047. update_roomname();
  1048. });
  1049. $("#reload_roomname").show();
  1050. function animate(word) {
  1051. var currentVal = $("#enter_room_field").attr("placeholder");
  1052. $("#enter_room_field").attr("placeholder", currentVal + word.substr(0, 1));
  1053. animateTimeout = setTimeout(function() {
  1054. animate(word.substring(1, word.length))
  1055. }, 70);
  1056. }
  1057. function update_roomname()
  1058. {
  1059. var word = RoomNameGenerator.generateRoomWithoutSeparator();
  1060. $("#enter_room_field").attr("room_name", word);
  1061. $("#enter_room_field").attr("placeholder", "");
  1062. clearTimeout(animateTimeout);
  1063. animate(word);
  1064. updateTimeout = setTimeout(update_roomname, 10000);
  1065. }
  1066. update_roomname();
  1067. }
  1068. $("#disable_welcome").click(function () {
  1069. window.localStorage.welcomePageDisabled
  1070. = $("#disable_welcome").is(":checked");
  1071. });
  1072. return;
  1073. }
  1074. if (interfaceConfig.SHOW_JITSI_WATERMARK) {
  1075. var leftWatermarkDiv
  1076. = $("#largeVideoContainer div[class='watermark leftwatermark']");
  1077. leftWatermarkDiv.css({display: 'block'});
  1078. leftWatermarkDiv.parent().get(0).href
  1079. = interfaceConfig.JITSI_WATERMARK_LINK;
  1080. }
  1081. if (interfaceConfig.SHOW_BRAND_WATERMARK) {
  1082. var rightWatermarkDiv
  1083. = $("#largeVideoContainer div[class='watermark rightwatermark']");
  1084. rightWatermarkDiv.css({display: 'block'});
  1085. rightWatermarkDiv.parent().get(0).href
  1086. = interfaceConfig.BRAND_WATERMARK_LINK;
  1087. rightWatermarkDiv.get(0).style.backgroundImage
  1088. = "url(images/rightwatermark.png)";
  1089. }
  1090. if (interfaceConfig.SHOW_POWERED_BY) {
  1091. $("#largeVideoContainer>a[class='poweredby']").css({display: 'block'});
  1092. }
  1093. $("#welcome_page").hide();
  1094. Chat.init();
  1095. $('body').popover({ selector: '[data-toggle=popover]',
  1096. trigger: 'click hover',
  1097. content: function() {
  1098. return this.getAttribute("content") +
  1099. KeyboardShortcut.getShortcut(this.getAttribute("shortcut"));
  1100. }
  1101. });
  1102. // Set the defaults for prompt dialogs.
  1103. jQuery.prompt.setDefaults({persistent: false});
  1104. // Set default desktop sharing method
  1105. setDesktopSharing(config.desktopSharing);
  1106. // Initialize Chrome extension inline installs
  1107. if (config.chromeExtensionId) {
  1108. initInlineInstalls();
  1109. }
  1110. // By default we use camera
  1111. getVideoSize = getCameraVideoSize;
  1112. getVideoPosition = getCameraVideoPosition;
  1113. VideoLayout.resizeLargeVideoContainer();
  1114. $(window).resize(function () {
  1115. VideoLayout.resizeLargeVideoContainer();
  1116. VideoLayout.positionLarge();
  1117. });
  1118. // Listen for large video size updates
  1119. document.getElementById('largeVideo')
  1120. .addEventListener('loadedmetadata', function (e) {
  1121. currentVideoWidth = this.videoWidth;
  1122. currentVideoHeight = this.videoHeight;
  1123. VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight);
  1124. });
  1125. if (!$('#settings').is(':visible')) {
  1126. console.log('init');
  1127. init();
  1128. } else {
  1129. loginInfo.onsubmit = function (e) {
  1130. if (e.preventDefault) e.preventDefault();
  1131. $('#settings').hide();
  1132. init();
  1133. };
  1134. }
  1135. });
  1136. $(window).bind('beforeunload', function () {
  1137. if (connection && connection.connected) {
  1138. // ensure signout
  1139. $.ajax({
  1140. type: 'POST',
  1141. url: config.bosh,
  1142. async: false,
  1143. cache: false,
  1144. contentType: 'application/xml',
  1145. data: "<body rid='" + (connection.rid || connection._proto.rid)
  1146. + "' xmlns='http://jabber.org/protocol/httpbind' sid='"
  1147. + (connection.sid || connection._proto.sid)
  1148. + "' type='terminate'><presence xmlns='jabber:client' type='unavailable'/></body>",
  1149. success: function (data) {
  1150. console.log('signed out');
  1151. console.log(data);
  1152. },
  1153. error: function (XMLHttpRequest, textStatus, errorThrown) {
  1154. console.log('signout error', textStatus + ' (' + errorThrown + ')');
  1155. }
  1156. });
  1157. }
  1158. disposeConference(true);
  1159. });
  1160. function disposeConference(onUnload) {
  1161. var handler = getConferenceHandler();
  1162. if (handler && handler.peerconnection) {
  1163. // FIXME: probably removing streams is not required and close() should
  1164. // be enough
  1165. if (connection.jingle.localAudio) {
  1166. handler.peerconnection.removeStream(connection.jingle.localAudio);
  1167. }
  1168. if (connection.jingle.localVideo) {
  1169. handler.peerconnection.removeStream(connection.jingle.localVideo);
  1170. }
  1171. handler.peerconnection.close();
  1172. }
  1173. stopRTPStatsCollector();
  1174. if(onUnload) {
  1175. stopLocalRtpStatsCollector();
  1176. }
  1177. focus = null;
  1178. activecall = null;
  1179. }
  1180. function dump(elem, filename) {
  1181. elem = elem.parentNode;
  1182. elem.download = filename || 'meetlog.json';
  1183. elem.href = 'data:application/json;charset=utf-8,\n';
  1184. var data = populateData();
  1185. elem.href += encodeURIComponent(JSON.stringify(data, null, ' '));
  1186. return false;
  1187. }
  1188. /**
  1189. * Populates the log data
  1190. */
  1191. function populateData() {
  1192. var data = {};
  1193. if (connection.jingle) {
  1194. Object.keys(connection.jingle.sessions).forEach(function (sid) {
  1195. var session = connection.jingle.sessions[sid];
  1196. if (session.peerconnection && session.peerconnection.updateLog) {
  1197. // FIXME: should probably be a .dump call
  1198. data["jingle_" + session.sid] = {
  1199. updateLog: session.peerconnection.updateLog,
  1200. stats: session.peerconnection.stats,
  1201. url: window.location.href
  1202. };
  1203. }
  1204. });
  1205. }
  1206. var metadata = {};
  1207. metadata.time = new Date();
  1208. metadata.url = window.location.href;
  1209. metadata.ua = navigator.userAgent;
  1210. if (connection.logger) {
  1211. metadata.xmpp = connection.logger.log;
  1212. }
  1213. data.metadata = metadata;
  1214. return data;
  1215. }
  1216. /**
  1217. * Changes the style class of the element given by id.
  1218. */
  1219. function buttonClick(id, classname) {
  1220. $(id).toggleClass(classname); // add the class to the clicked element
  1221. }
  1222. /**
  1223. * Locks / unlocks the room.
  1224. */
  1225. function lockRoom(lock) {
  1226. if (lock)
  1227. connection.emuc.lockRoom(sharedKey);
  1228. else
  1229. connection.emuc.lockRoom('');
  1230. Toolbar.updateLockButton();
  1231. }
  1232. /**
  1233. * Sets the shared key.
  1234. */
  1235. function setSharedKey(sKey) {
  1236. sharedKey = sKey;
  1237. }
  1238. function setRecordingToken(token) {
  1239. recordingToken = token;
  1240. }
  1241. /**
  1242. * Updates the room invite url.
  1243. */
  1244. function updateRoomUrl(newRoomUrl) {
  1245. roomUrl = newRoomUrl;
  1246. // If the invite dialog has been already opened we update the information.
  1247. var inviteLink = document.getElementById('inviteLinkRef');
  1248. if (inviteLink) {
  1249. inviteLink.value = roomUrl;
  1250. inviteLink.select();
  1251. document.getElementById('jqi_state0_buttonInvite').disabled = false;
  1252. }
  1253. }
  1254. /**
  1255. * Warning to the user that the conference window is about to be closed.
  1256. */
  1257. function closePageWarning() {
  1258. if (focus !== null)
  1259. return "You are the owner of this conference call and"
  1260. + " you are about to end it.";
  1261. else
  1262. return "You are about to leave this conversation.";
  1263. }
  1264. /**
  1265. * Resizes and repositions videos in full screen mode.
  1266. */
  1267. $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange',
  1268. function () {
  1269. VideoLayout.resizeLargeVideoContainer();
  1270. VideoLayout.positionLarge();
  1271. isFullScreen = document.fullScreen ||
  1272. document.mozFullScreen ||
  1273. document.webkitIsFullScreen;
  1274. if (isFullScreen) {
  1275. setView("fullscreen");
  1276. }
  1277. else {
  1278. setView("default");
  1279. }
  1280. }
  1281. );
  1282. /**
  1283. * Sets the current view.
  1284. */
  1285. function setView(viewName) {
  1286. // if (viewName == "fullscreen") {
  1287. // document.getElementById('videolayout_fullscreen').disabled = false;
  1288. // document.getElementById('videolayout_default').disabled = true;
  1289. // }
  1290. // else {
  1291. // document.getElementById('videolayout_default').disabled = false;
  1292. // document.getElementById('videolayout_fullscreen').disabled = true;
  1293. // }
  1294. }
  1295. function hangUp() {
  1296. if (connection && connection.connected) {
  1297. // ensure signout
  1298. $.ajax({
  1299. type: 'POST',
  1300. url: config.bosh,
  1301. async: false,
  1302. cache: false,
  1303. contentType: 'application/xml',
  1304. data: "<body rid='" + (connection.rid || connection._proto.rid) + "' xmlns='http://jabber.org/protocol/httpbind' sid='" + (connection.sid || connection._proto.sid) + "' type='terminate'><presence xmlns='jabber:client' type='unavailable'/></body>",
  1305. success: function (data) {
  1306. console.log('signed out');
  1307. console.log(data);
  1308. },
  1309. error: function (XMLHttpRequest, textStatus, errorThrown) {
  1310. console.log('signout error', textStatus + ' (' + errorThrown + ')');
  1311. }
  1312. });
  1313. }
  1314. disposeConference(true);
  1315. }
  1316. $(document).bind('fatalError.jingle',
  1317. function (event, session, error)
  1318. {
  1319. sessionTerminated = true;
  1320. connection.emuc.doLeave();
  1321. messageHandler.showError( "Sorry",
  1322. "Your browser version is too old. Please update and try again...");
  1323. }
  1324. );
  1325. function onSelectedEndpointChanged(userJid)
  1326. {
  1327. console.log('selected endpoint changed: ', userJid);
  1328. if (_dataChannels && _dataChannels.length != 0)
  1329. {
  1330. _dataChannels.some(function (dataChannel) {
  1331. if (dataChannel.readyState == 'open')
  1332. {
  1333. dataChannel.send(JSON.stringify({
  1334. 'colibriClass': 'SelectedEndpointChangedEvent',
  1335. 'selectedEndpoint': (!userJid || userJid == null)
  1336. ? null : Strophe.getResourceFromJid(userJid)
  1337. }));
  1338. return true;
  1339. }
  1340. });
  1341. }
  1342. }
  1343. $(document).bind("selectedendpointchanged", function(event, userJid) {
  1344. onSelectedEndpointChanged(userJid);
  1345. });
  1346. function callSipButtonClicked()
  1347. {
  1348. var defaultNumber
  1349. = config.defaultSipNumber ? config.defaultSipNumber : '';
  1350. messageHandler.openTwoButtonDialog(null,
  1351. '<h2>Enter SIP number</h2>' +
  1352. '<input id="sipNumber" type="text"' +
  1353. ' value="' + defaultNumber + '" autofocus>',
  1354. false,
  1355. "Dial",
  1356. function (e, v, m, f) {
  1357. if (v) {
  1358. var numberInput = document.getElementById('sipNumber');
  1359. if (numberInput.value) {
  1360. connection.rayo.dial(
  1361. numberInput.value, 'fromnumber', roomName);
  1362. }
  1363. }
  1364. },
  1365. function (event) {
  1366. document.getElementById('sipNumber').focus();
  1367. }
  1368. );
  1369. }
  1370. function hangup() {
  1371. disposeConference();
  1372. sessionTerminated = true;
  1373. connection.emuc.doLeave();
  1374. if(config.enableWelcomePage)
  1375. {
  1376. setTimeout(function()
  1377. {
  1378. window.localStorage.welcomePageDisabled = false;
  1379. window.location.pathname = "/";
  1380. }, 10000);
  1381. }
  1382. $.prompt("Session Terminated",
  1383. {
  1384. title: "You hung up the call",
  1385. persistent: true,
  1386. buttons: {
  1387. "Join again": true
  1388. },
  1389. closeText: '',
  1390. submit: function(event, value, message, formVals)
  1391. {
  1392. window.location.reload();
  1393. return false;
  1394. }
  1395. }
  1396. );
  1397. }