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 52KB

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