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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675
  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. /**
  18. * The stats collector that process stats data and triggers updates to app.js.
  19. * @type {StatsCollector}
  20. */
  21. var statsCollector = null;
  22. /**
  23. * The stats collector for the local stream.
  24. * @type {LocalStatsCollector}
  25. */
  26. var localStatsCollector = null;
  27. /**
  28. * Indicates whether ssrc is camera video or desktop stream.
  29. * FIXME: remove those maps
  30. */
  31. var ssrc2videoType = {};
  32. var videoSrcToSsrc = {};
  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) {
  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 && selector.attr('src')) {
  230. videoSrcToSsrc[selector.attr('src')] = ssrc;
  231. } else {
  232. console.warn("No ssrc given for video", selector);
  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);
  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. /**
  346. * Returns the JID of the user to whom given <tt>videoSrc</tt> belongs.
  347. * @param videoSrc the video "src" identifier.
  348. * @returns {null | String} the JID of the user to whom given <tt>videoSrc</tt>
  349. * belongs.
  350. */
  351. function getJidFromVideoSrc(videoSrc)
  352. {
  353. if (videoSrc === localVideoSrc)
  354. return connection.emuc.myroomjid;
  355. var ssrc = videoSrcToSsrc[videoSrc];
  356. if (!ssrc)
  357. {
  358. return null;
  359. }
  360. return ssrc2jid[ssrc];
  361. }
  362. // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
  363. function sendKeyframe(pc) {
  364. console.log('sendkeyframe', pc.iceConnectionState);
  365. if (pc.iceConnectionState !== 'connected') return; // safe...
  366. pc.setRemoteDescription(
  367. pc.remoteDescription,
  368. function () {
  369. pc.createAnswer(
  370. function (modifiedAnswer) {
  371. pc.setLocalDescription(
  372. modifiedAnswer,
  373. function () {
  374. // noop
  375. },
  376. function (error) {
  377. console.log('triggerKeyframe setLocalDescription failed', error);
  378. messageHandler.showError();
  379. }
  380. );
  381. },
  382. function (error) {
  383. console.log('triggerKeyframe createAnswer failed', error);
  384. messageHandler.showError();
  385. }
  386. );
  387. },
  388. function (error) {
  389. console.log('triggerKeyframe setRemoteDescription failed', error);
  390. messageHandler.showError();
  391. }
  392. );
  393. }
  394. // Really mute video, i.e. dont even send black frames
  395. function muteVideo(pc, unmute) {
  396. // FIXME: this probably needs another of those lovely state safeguards...
  397. // which checks for iceconn == connected and sigstate == stable
  398. pc.setRemoteDescription(pc.remoteDescription,
  399. function () {
  400. pc.createAnswer(
  401. function (answer) {
  402. var sdp = new SDP(answer.sdp);
  403. if (sdp.media.length > 1) {
  404. if (unmute)
  405. sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
  406. else
  407. sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
  408. sdp.raw = sdp.session + sdp.media.join('');
  409. answer.sdp = sdp.raw;
  410. }
  411. pc.setLocalDescription(answer,
  412. function () {
  413. console.log('mute SLD ok');
  414. },
  415. function (error) {
  416. console.log('mute SLD error');
  417. messageHandler.showError('Error',
  418. 'Oops! Something went wrong and we failed to ' +
  419. 'mute! (SLD Failure)');
  420. }
  421. );
  422. },
  423. function (error) {
  424. console.log(error);
  425. messageHandler.showError();
  426. }
  427. );
  428. },
  429. function (error) {
  430. console.log('muteVideo SRD error');
  431. messageHandler.showError('Error',
  432. 'Oops! Something went wrong and we failed to stop video!' +
  433. '(SRD Failure)');
  434. }
  435. );
  436. }
  437. /**
  438. * Callback for audio levels changed.
  439. * @param jid JID of the user
  440. * @param audioLevel the audio level value
  441. */
  442. function audioLevelUpdated(jid, audioLevel)
  443. {
  444. var resourceJid;
  445. if(jid === LocalStatsCollector.LOCAL_JID)
  446. {
  447. resourceJid = AudioLevels.LOCAL_LEVEL;
  448. if(isAudioMuted())
  449. {
  450. audioLevel = 0;
  451. }
  452. }
  453. else
  454. {
  455. resourceJid = Strophe.getResourceFromJid(jid);
  456. }
  457. AudioLevels.updateAudioLevel(resourceJid, audioLevel);
  458. }
  459. /**
  460. * Starts the {@link StatsCollector} if the feature is enabled in config.js.
  461. */
  462. function startRtpStatsCollector()
  463. {
  464. stopRTPStatsCollector();
  465. if (config.enableRtpStats)
  466. {
  467. statsCollector = new StatsCollector(
  468. getConferenceHandler().peerconnection, 200, audioLevelUpdated, 2000,
  469. ConnectionQuality.updateLocalStats);
  470. statsCollector.start();
  471. }
  472. }
  473. /**
  474. * Stops the {@link StatsCollector}.
  475. */
  476. function stopRTPStatsCollector()
  477. {
  478. if (statsCollector)
  479. {
  480. statsCollector.stop();
  481. statsCollector = null;
  482. ConnectionQuality.stopSendingStats();
  483. }
  484. }
  485. /**
  486. * Starts the {@link LocalStatsCollector} if the feature is enabled in config.js
  487. * @param stream the stream that will be used for collecting statistics.
  488. */
  489. function startLocalRtpStatsCollector(stream)
  490. {
  491. if(config.enableRtpStats)
  492. {
  493. localStatsCollector = new LocalStatsCollector(stream, 100, audioLevelUpdated);
  494. localStatsCollector.start();
  495. }
  496. }
  497. /**
  498. * Stops the {@link LocalStatsCollector}.
  499. */
  500. function stopLocalRtpStatsCollector()
  501. {
  502. if(localStatsCollector)
  503. {
  504. localStatsCollector.stop();
  505. localStatsCollector = null;
  506. }
  507. }
  508. $(document).bind('callincoming.jingle', function (event, sid) {
  509. var sess = connection.jingle.sessions[sid];
  510. // TODO: do we check activecall == null?
  511. activecall = sess;
  512. startRtpStatsCollector();
  513. // Bind data channel listener in case we're a regular participant
  514. if (config.openSctp)
  515. {
  516. bindDataChannelListener(sess.peerconnection);
  517. }
  518. // TODO: check affiliation and/or role
  519. console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
  520. sess.usedrip = true; // not-so-naive trickle ice
  521. sess.sendAnswer();
  522. sess.accept();
  523. });
  524. $(document).bind('conferenceCreated.jingle', function (event, focus)
  525. {
  526. startRtpStatsCollector();
  527. });
  528. $(document).bind('conferenceCreated.jingle', function (event, focus)
  529. {
  530. // Bind data channel listener in case we're the focus
  531. if (config.openSctp)
  532. {
  533. bindDataChannelListener(focus.peerconnection);
  534. }
  535. });
  536. $(document).bind('callterminated.jingle', function (event, sid, jid, reason) {
  537. // Leave the room if my call has been remotely terminated.
  538. if (connection.emuc.joined && focus == null && reason === 'kick') {
  539. sessionTerminated = true;
  540. connection.emuc.doLeave();
  541. messageHandler.openMessageDialog("Session Terminated",
  542. "Ouch! You have been kicked out of the meet!");
  543. }
  544. });
  545. $(document).bind('setLocalDescription.jingle', function (event, sid) {
  546. // put our ssrcs into presence so other clients can identify our stream
  547. var sess = connection.jingle.sessions[sid];
  548. var newssrcs = [];
  549. var media = simulcast.parseMedia(sess.peerconnection.localDescription);
  550. media.forEach(function (media) {
  551. if(Object.keys(media.sources) > 0) {
  552. // TODO(gp) maybe exclude FID streams?
  553. Object.keys(media.sources).forEach(function (ssrc) {
  554. newssrcs.push({
  555. 'ssrc': ssrc,
  556. 'type': media.type,
  557. 'direction': media.direction
  558. });
  559. });
  560. }
  561. else if(sess.localStreamsSSRC && sess.localStreamsSSRC[media.type])
  562. {
  563. newssrcs.push({
  564. 'ssrc': sess.localStreamsSSRC[media.type],
  565. 'type': media.type,
  566. 'direction': media.direction
  567. });
  568. }
  569. });
  570. console.log('new ssrcs', newssrcs);
  571. // Have to clear presence map to get rid of removed streams
  572. connection.emuc.clearPresenceMedia();
  573. if (newssrcs.length > 0) {
  574. for (var i = 1; i <= newssrcs.length; i ++) {
  575. // Change video type to screen
  576. if (newssrcs[i-1].type === 'video' && isUsingScreenStream) {
  577. newssrcs[i-1].type = 'screen';
  578. }
  579. connection.emuc.addMediaToPresence(i,
  580. newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
  581. }
  582. connection.emuc.sendPresence();
  583. }
  584. });
  585. $(document).bind('iceconnectionstatechange.jingle', function (event, sid, session) {
  586. switch (session.peerconnection.iceConnectionState) {
  587. case 'checking':
  588. session.timeChecking = (new Date()).getTime();
  589. session.firstconnect = true;
  590. break;
  591. case 'completed': // on caller side
  592. case 'connected':
  593. if (session.firstconnect) {
  594. session.firstconnect = false;
  595. var metadata = {};
  596. metadata.setupTime = (new Date()).getTime() - session.timeChecking;
  597. session.peerconnection.getStats(function (res) {
  598. if(res && res.result) {
  599. res.result().forEach(function (report) {
  600. if (report.type == 'googCandidatePair' && report.stat('googActiveConnection') == 'true') {
  601. metadata.localCandidateType = report.stat('googLocalCandidateType');
  602. metadata.remoteCandidateType = report.stat('googRemoteCandidateType');
  603. // log pair as well so we can get nice pie charts
  604. metadata.candidatePair = report.stat('googLocalCandidateType') + ';' + report.stat('googRemoteCandidateType');
  605. if (report.stat('googRemoteAddress').indexOf('[') === 0) {
  606. metadata.ipv6 = true;
  607. }
  608. }
  609. });
  610. trackUsage('iceConnected', metadata);
  611. }
  612. });
  613. }
  614. break;
  615. }
  616. });
  617. $(document).bind('joined.muc', function (event, jid, info) {
  618. updateRoomUrl(window.location.href);
  619. document.getElementById('localNick').appendChild(
  620. document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)')
  621. );
  622. if (Object.keys(connection.emuc.members).length < 1) {
  623. focus = new ColibriFocus(connection, config.hosts.bridge);
  624. if (nickname !== null) {
  625. focus.setEndpointDisplayName(connection.emuc.myroomjid,
  626. nickname);
  627. }
  628. Toolbar.showSipCallButton(true);
  629. Toolbar.showRecordingButton(false);
  630. }
  631. if (!focus)
  632. {
  633. Toolbar.showSipCallButton(false);
  634. }
  635. if (focus && config.etherpad_base) {
  636. Etherpad.init();
  637. }
  638. VideoLayout.showFocusIndicator();
  639. // Add myself to the contact list.
  640. ContactList.addContact(jid);
  641. // Once we've joined the muc show the toolbar
  642. ToolbarToggler.showToolbar();
  643. var displayName = !config.displayJids
  644. ? info.displayName : Strophe.getResourceFromJid(jid);
  645. if (displayName)
  646. $(document).trigger('displaynamechanged',
  647. ['localVideoContainer', displayName + ' (me)']);
  648. });
  649. $(document).bind('entered.muc', function (event, jid, info, pres) {
  650. console.log('entered', jid, info);
  651. messageHandler.notify(info.displayName || 'Somebody',
  652. 'connected',
  653. 'connected');
  654. console.log('is focus? ' + (focus ? 'true' : 'false'));
  655. // Add Peer's container
  656. VideoLayout.ensurePeerContainerExists(jid);
  657. if(APIConnector.isEnabled() && APIConnector.isEventEnabled("participantJoined"))
  658. {
  659. APIConnector.triggerEvent("participantJoined",{jid: jid});
  660. }
  661. if (focus !== null) {
  662. // FIXME: this should prepare the video
  663. if (focus.confid === null) {
  664. console.log('make new conference with', jid);
  665. focus.makeConference(Object.keys(connection.emuc.members),
  666. function(error) {
  667. connection.emuc.addBridgeIsDownToPresence();
  668. connection.emuc.sendPresence();
  669. }
  670. );
  671. Toolbar.showRecordingButton(true);
  672. } else {
  673. console.log('invite', jid, 'into conference');
  674. focus.addNewParticipant(jid);
  675. }
  676. }
  677. });
  678. $(document).bind('left.muc', function (event, jid) {
  679. console.log('left.muc', jid);
  680. var displayName = $('#participant_' + Strophe.getResourceFromJid(jid) +
  681. '>.displayname').text();
  682. messageHandler.notify(displayName || 'Somebody',
  683. 'disconnected',
  684. 'disconnected');
  685. // Need to call this with a slight delay, otherwise the element couldn't be
  686. // found for some reason.
  687. window.setTimeout(function () {
  688. var container = document.getElementById(
  689. 'participant_' + Strophe.getResourceFromJid(jid));
  690. if (container) {
  691. VideoLayout.removeConnectionIndicator(jid);
  692. // hide here, wait for video to close before removing
  693. $(container).hide();
  694. VideoLayout.resizeThumbnails();
  695. }
  696. }, 10);
  697. if(APIConnector.isEnabled() && APIConnector.isEventEnabled("participantLeft"))
  698. {
  699. APIConnector.triggerEvent("participantLeft",{jid: jid});
  700. }
  701. // Unlock large video
  702. if (focusedVideoSrc)
  703. {
  704. if (getJidFromVideoSrc(focusedVideoSrc) === jid)
  705. {
  706. console.info("Focused video owner has left the conference");
  707. focusedVideoSrc = null;
  708. }
  709. }
  710. connection.jingle.terminateByJid(jid);
  711. if (focus == null
  712. // I shouldn't be the one that left to enter here.
  713. && jid !== connection.emuc.myroomjid
  714. && connection.emuc.myroomjid === connection.emuc.list_members[0]
  715. // If our session has been terminated for some reason
  716. // (kicked, hangup), don't try to become the focus
  717. && !sessionTerminated) {
  718. console.log('welcome to our new focus... myself');
  719. focus = new ColibriFocus(connection, config.hosts.bridge);
  720. if (nickname !== null) {
  721. focus.setEndpointDisplayName(connection.emuc.myroomjid,
  722. nickname);
  723. }
  724. Toolbar.showSipCallButton(true);
  725. if (Object.keys(connection.emuc.members).length > 0) {
  726. focus.makeConference(Object.keys(connection.emuc.members));
  727. Toolbar.showRecordingButton(true);
  728. }
  729. $(document).trigger('focusechanged.muc', [focus]);
  730. }
  731. else if (focus && Object.keys(connection.emuc.members).length === 0) {
  732. console.log('everyone left');
  733. // FIXME: closing the connection is a hack to avoid some
  734. // problems with reinit
  735. disposeConference();
  736. focus = new ColibriFocus(connection, config.hosts.bridge);
  737. if (nickname !== null) {
  738. focus.setEndpointDisplayName(connection.emuc.myroomjid,
  739. nickname);
  740. }
  741. Toolbar.showSipCallButton(true);
  742. Toolbar.showRecordingButton(false);
  743. }
  744. if (connection.emuc.getPrezi(jid)) {
  745. $(document).trigger('presentationremoved.muc',
  746. [jid, connection.emuc.getPrezi(jid)]);
  747. }
  748. });
  749. $(document).bind('presence.muc', function (event, jid, info, pres) {
  750. // Remove old ssrcs coming from the jid
  751. Object.keys(ssrc2jid).forEach(function (ssrc) {
  752. if (ssrc2jid[ssrc] == jid) {
  753. delete ssrc2jid[ssrc];
  754. }
  755. if (ssrc2videoType[ssrc] == jid) {
  756. delete ssrc2videoType[ssrc];
  757. }
  758. });
  759. $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
  760. //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
  761. var ssrcV = ssrc.getAttribute('ssrc');
  762. ssrc2jid[ssrcV] = jid;
  763. notReceivedSSRCs.push(ssrcV);
  764. var type = ssrc.getAttribute('type');
  765. ssrc2videoType[ssrcV] = type;
  766. // might need to update the direction if participant just went from sendrecv to recvonly
  767. if (type === 'video' || type === 'screen') {
  768. var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video');
  769. switch (ssrc.getAttribute('direction')) {
  770. case 'sendrecv':
  771. el.show();
  772. break;
  773. case 'recvonly':
  774. el.hide();
  775. // FIXME: Check if we have to change large video
  776. //VideoLayout.updateLargeVideo(el);
  777. break;
  778. }
  779. }
  780. });
  781. var displayName = !config.displayJids
  782. ? info.displayName : Strophe.getResourceFromJid(jid);
  783. if (displayName && displayName.length > 0)
  784. $(document).trigger('displaynamechanged',
  785. [jid, displayName]);
  786. if (focus !== null && info.displayName !== null) {
  787. focus.setEndpointDisplayName(jid, info.displayName);
  788. }
  789. //check if the video bridge is available
  790. if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
  791. bridgeIsDown = true;
  792. messageHandler.showError("Error",
  793. "Jitsi Videobridge is currently unavailable. Please try again later!");
  794. }
  795. });
  796. $(document).bind('presence.status.muc', function (event, jid, info, pres) {
  797. VideoLayout.setPresenceStatus(
  798. 'participant_' + Strophe.getResourceFromJid(jid), info.status);
  799. });
  800. $(document).bind('passwordrequired.muc', function (event, jid) {
  801. console.log('on password required', jid);
  802. // password is required
  803. Toolbar.lockLockButton();
  804. messageHandler.openTwoButtonDialog(null,
  805. '<h2>Password required</h2>' +
  806. '<input id="lockKey" type="text" placeholder="password" autofocus>',
  807. true,
  808. "Ok",
  809. function (e, v, m, f) {},
  810. function (event) {
  811. document.getElementById('lockKey').focus();
  812. },
  813. function (e, v, m, f) {
  814. if (v) {
  815. var lockKey = document.getElementById('lockKey');
  816. if (lockKey.value !== null) {
  817. setSharedKey(lockKey.value);
  818. connection.emuc.doJoin(jid, lockKey.value);
  819. }
  820. }
  821. }
  822. );
  823. });
  824. $(document).bind('passwordrequired.main', function (event) {
  825. console.log('password is required');
  826. messageHandler.openTwoButtonDialog(null,
  827. '<h2>Password required</h2>' +
  828. '<input id="passwordrequired.username" type="text" placeholder="user@domain.net" autofocus>' +
  829. '<input id="passwordrequired.password" type="password" placeholder="user password">',
  830. true,
  831. "Ok",
  832. function (e, v, m, f) {
  833. if (v) {
  834. var username = document.getElementById('passwordrequired.username');
  835. var password = document.getElementById('passwordrequired.password');
  836. if (username.value !== null && password.value != null) {
  837. connect(username.value, password.value);
  838. }
  839. }
  840. },
  841. function (event) {
  842. document.getElementById('passwordrequired.username').focus();
  843. }
  844. );
  845. });
  846. /**
  847. * Checks if video identified by given src is desktop stream.
  848. * @param videoSrc eg.
  849. * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
  850. * @returns {boolean}
  851. */
  852. function isVideoSrcDesktop(videoSrc) {
  853. // FIXME: fix this mapping mess...
  854. // figure out if large video is desktop stream or just a camera
  855. var isDesktop = false;
  856. if (localVideoSrc === videoSrc) {
  857. // local video
  858. isDesktop = isUsingScreenStream;
  859. } else {
  860. // Do we have associations...
  861. var videoSsrc = videoSrcToSsrc[videoSrc];
  862. if (videoSsrc) {
  863. var videoType = ssrc2videoType[videoSsrc];
  864. if (videoType) {
  865. // Finally there...
  866. isDesktop = videoType === 'screen';
  867. } else {
  868. console.error("No video type for ssrc: " + videoSsrc);
  869. }
  870. } else {
  871. console.error("No ssrc for src: " + videoSrc);
  872. }
  873. }
  874. return isDesktop;
  875. }
  876. function getConferenceHandler() {
  877. return focus ? focus : activecall;
  878. }
  879. function toggleVideo() {
  880. buttonClick("#video", "icon-camera icon-camera-disabled");
  881. if (!(connection && connection.jingle.localVideo))
  882. return;
  883. var sess = getConferenceHandler();
  884. if (sess) {
  885. sess.toggleVideoMute(
  886. function (isMuted) {
  887. if (isMuted) {
  888. $('#video').removeClass("icon-camera");
  889. $('#video').addClass("icon-camera icon-camera-disabled");
  890. } else {
  891. $('#video').removeClass("icon-camera icon-camera-disabled");
  892. $('#video').addClass("icon-camera");
  893. }
  894. connection.emuc.addVideoInfoToPresence(isMuted);
  895. connection.emuc.sendPresence();
  896. }
  897. );
  898. }
  899. }
  900. /**
  901. * Mutes / unmutes audio for the local participant.
  902. */
  903. function toggleAudio() {
  904. if (!(connection && connection.jingle.localAudio)) {
  905. // We still click the button.
  906. buttonClick("#mute", "icon-microphone icon-mic-disabled");
  907. return;
  908. }
  909. // It is not clear what is the right way to handle multiple tracks.
  910. // So at least make sure that they are all muted or all unmuted and
  911. // that we send presence just once.
  912. var localAudioTracks = connection.jingle.localAudio.getAudioTracks();
  913. if (localAudioTracks.length > 0) {
  914. var audioEnabled = localAudioTracks[0].enabled;
  915. for (var idx = 0; idx < localAudioTracks.length; idx++) {
  916. localAudioTracks[idx].enabled = !audioEnabled;
  917. }
  918. // isMuted is the opposite of audioEnabled
  919. connection.emuc.addAudioInfoToPresence(audioEnabled);
  920. connection.emuc.sendPresence();
  921. VideoLayout.showLocalAudioIndicator(audioEnabled);
  922. }
  923. buttonClick("#mute", "icon-microphone icon-mic-disabled");
  924. }
  925. /**
  926. * Checks whether the audio is muted or not.
  927. * @returns {boolean} true if audio is muted and false if not.
  928. */
  929. function isAudioMuted()
  930. {
  931. var localAudio = connection.jingle.localAudio;
  932. for (var idx = 0; idx < localAudio.getAudioTracks().length; idx++) {
  933. if(localAudio.getAudioTracks()[idx].enabled === true)
  934. return false;
  935. }
  936. return true;
  937. }
  938. // Starts or stops the recording for the conference.
  939. function toggleRecording() {
  940. if (focus === null || focus.confid === null) {
  941. console.log('non-focus, or conference not yet organized: not enabling recording');
  942. return;
  943. }
  944. if (!recordingToken)
  945. {
  946. messageHandler.openTwoButtonDialog(null,
  947. '<h2>Enter recording token</h2>' +
  948. '<input id="recordingToken" type="text" placeholder="token" autofocus>',
  949. false,
  950. "Save",
  951. function (e, v, m, f) {
  952. if (v) {
  953. var token = document.getElementById('recordingToken');
  954. if (token.value) {
  955. setRecordingToken(Util.escapeHtml(token.value));
  956. toggleRecording();
  957. }
  958. }
  959. },
  960. function (event) {
  961. document.getElementById('recordingToken').focus();
  962. }
  963. );
  964. return;
  965. }
  966. var oldState = focus.recordingEnabled;
  967. Toolbar.toggleRecordingButtonState();
  968. focus.setRecording(!oldState,
  969. recordingToken,
  970. function (state) {
  971. console.log("New recording state: ", state);
  972. if (state == oldState) //failed to change, reset the token because it might have been wrong
  973. {
  974. Toolbar.toggleRecordingButtonState();
  975. setRecordingToken(null);
  976. }
  977. }
  978. );
  979. }
  980. /**
  981. * Returns an array of the video horizontal and vertical indents,
  982. * so that if fits its parent.
  983. *
  984. * @return an array with 2 elements, the horizontal indent and the vertical
  985. * indent
  986. */
  987. function getCameraVideoPosition(videoWidth,
  988. videoHeight,
  989. videoSpaceWidth,
  990. videoSpaceHeight) {
  991. // Parent height isn't completely calculated when we position the video in
  992. // full screen mode and this is why we use the screen height in this case.
  993. // Need to think it further at some point and implement it properly.
  994. var isFullScreen = document.fullScreen ||
  995. document.mozFullScreen ||
  996. document.webkitIsFullScreen;
  997. if (isFullScreen)
  998. videoSpaceHeight = window.innerHeight;
  999. var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
  1000. var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
  1001. return [horizontalIndent, verticalIndent];
  1002. }
  1003. /**
  1004. * Returns an array of the video horizontal and vertical indents.
  1005. * Centers horizontally and top aligns vertically.
  1006. *
  1007. * @return an array with 2 elements, the horizontal indent and the vertical
  1008. * indent
  1009. */
  1010. function getDesktopVideoPosition(videoWidth,
  1011. videoHeight,
  1012. videoSpaceWidth,
  1013. videoSpaceHeight) {
  1014. var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
  1015. var verticalIndent = 0;// Top aligned
  1016. return [horizontalIndent, verticalIndent];
  1017. }
  1018. /**
  1019. * Returns an array of the video dimensions, so that it covers the screen.
  1020. * It leaves no empty areas, but some parts of the video might not be visible.
  1021. *
  1022. * @return an array with 2 elements, the video width and the video height
  1023. */
  1024. function getCameraVideoSize(videoWidth,
  1025. videoHeight,
  1026. videoSpaceWidth,
  1027. videoSpaceHeight) {
  1028. if (!videoWidth)
  1029. videoWidth = currentVideoWidth;
  1030. if (!videoHeight)
  1031. videoHeight = currentVideoHeight;
  1032. var aspectRatio = videoWidth / videoHeight;
  1033. var availableWidth = Math.max(videoWidth, videoSpaceWidth);
  1034. var availableHeight = Math.max(videoHeight, videoSpaceHeight);
  1035. if (availableWidth / aspectRatio < videoSpaceHeight) {
  1036. availableHeight = videoSpaceHeight;
  1037. availableWidth = availableHeight * aspectRatio;
  1038. }
  1039. if (availableHeight * aspectRatio < videoSpaceWidth) {
  1040. availableWidth = videoSpaceWidth;
  1041. availableHeight = availableWidth / aspectRatio;
  1042. }
  1043. return [availableWidth, availableHeight];
  1044. }
  1045. $(document).ready(function () {
  1046. document.title = interfaceConfig.APP_NAME;
  1047. if(APIConnector.isEnabled())
  1048. APIConnector.init();
  1049. if(config.enableWelcomePage && window.location.pathname == "/" &&
  1050. (!window.localStorage.welcomePageDisabled
  1051. || window.localStorage.welcomePageDisabled == "false"))
  1052. {
  1053. $("#videoconference_page").hide();
  1054. $("#domain_name").text(
  1055. window.location.protocol + "//" + window.location.host + "/");
  1056. $("span[name='appName']").text(interfaceConfig.APP_NAME);
  1057. if (interfaceConfig.SHOW_JITSI_WATERMARK) {
  1058. var leftWatermarkDiv
  1059. = $("#welcome_page_header div[class='watermark leftwatermark']");
  1060. if(leftWatermarkDiv && leftWatermarkDiv.length > 0)
  1061. {
  1062. leftWatermarkDiv.css({display: 'block'});
  1063. leftWatermarkDiv.parent().get(0).href
  1064. = interfaceConfig.JITSI_WATERMARK_LINK;
  1065. }
  1066. }
  1067. if (interfaceConfig.SHOW_BRAND_WATERMARK) {
  1068. var rightWatermarkDiv
  1069. = $("#welcome_page_header div[class='watermark rightwatermark']");
  1070. if(rightWatermarkDiv && rightWatermarkDiv.length > 0) {
  1071. rightWatermarkDiv.css({display: 'block'});
  1072. rightWatermarkDiv.parent().get(0).href
  1073. = interfaceConfig.BRAND_WATERMARK_LINK;
  1074. rightWatermarkDiv.get(0).style.backgroundImage
  1075. = "url(images/rightwatermark.png)";
  1076. }
  1077. }
  1078. if (interfaceConfig.SHOW_POWERED_BY) {
  1079. $("#welcome_page_header>a[class='poweredby']")
  1080. .css({display: 'block'});
  1081. }
  1082. function enter_room()
  1083. {
  1084. var val = $("#enter_room_field").val();
  1085. if(!val) {
  1086. val = $("#enter_room_field").attr("room_name");
  1087. }
  1088. if (val) {
  1089. window.location.pathname = "/" + val;
  1090. }
  1091. }
  1092. $("#enter_room_button").click(function()
  1093. {
  1094. enter_room();
  1095. });
  1096. $("#enter_room_field").keydown(function (event) {
  1097. if (event.keyCode === 13 /* enter */) {
  1098. enter_room();
  1099. }
  1100. });
  1101. if (!(interfaceConfig.GENERATE_ROOMNAMES_ON_WELCOME_PAGE === false)){
  1102. var updateTimeout;
  1103. var animateTimeout;
  1104. $("#reload_roomname").click(function () {
  1105. clearTimeout(updateTimeout);
  1106. clearTimeout(animateTimeout);
  1107. update_roomname();
  1108. });
  1109. $("#reload_roomname").show();
  1110. function animate(word) {
  1111. var currentVal = $("#enter_room_field").attr("placeholder");
  1112. $("#enter_room_field").attr("placeholder", currentVal + word.substr(0, 1));
  1113. animateTimeout = setTimeout(function() {
  1114. animate(word.substring(1, word.length))
  1115. }, 70);
  1116. }
  1117. function update_roomname()
  1118. {
  1119. var word = RoomNameGenerator.generateRoomWithoutSeparator();
  1120. $("#enter_room_field").attr("room_name", word);
  1121. $("#enter_room_field").attr("placeholder", "");
  1122. clearTimeout(animateTimeout);
  1123. animate(word);
  1124. updateTimeout = setTimeout(update_roomname, 10000);
  1125. }
  1126. update_roomname();
  1127. }
  1128. $("#disable_welcome").click(function () {
  1129. window.localStorage.welcomePageDisabled
  1130. = $("#disable_welcome").is(":checked");
  1131. });
  1132. return;
  1133. }
  1134. if (interfaceConfig.SHOW_JITSI_WATERMARK) {
  1135. var leftWatermarkDiv
  1136. = $("#largeVideoContainer div[class='watermark leftwatermark']");
  1137. leftWatermarkDiv.css({display: 'block'});
  1138. leftWatermarkDiv.parent().get(0).href
  1139. = interfaceConfig.JITSI_WATERMARK_LINK;
  1140. }
  1141. if (interfaceConfig.SHOW_BRAND_WATERMARK) {
  1142. var rightWatermarkDiv
  1143. = $("#largeVideoContainer div[class='watermark rightwatermark']");
  1144. rightWatermarkDiv.css({display: 'block'});
  1145. rightWatermarkDiv.parent().get(0).href
  1146. = interfaceConfig.BRAND_WATERMARK_LINK;
  1147. rightWatermarkDiv.get(0).style.backgroundImage
  1148. = "url(images/rightwatermark.png)";
  1149. }
  1150. if (interfaceConfig.SHOW_POWERED_BY) {
  1151. $("#largeVideoContainer>a[class='poweredby']").css({display: 'block'});
  1152. }
  1153. $("#welcome_page").hide();
  1154. Chat.init();
  1155. $('body').popover({ selector: '[data-toggle=popover]',
  1156. trigger: 'click hover',
  1157. content: function() {
  1158. return this.getAttribute("content") +
  1159. KeyboardShortcut.getShortcut(this.getAttribute("shortcut"));
  1160. }
  1161. });
  1162. // Set the defaults for prompt dialogs.
  1163. jQuery.prompt.setDefaults({persistent: false});
  1164. // Set default desktop sharing method
  1165. setDesktopSharing(config.desktopSharing);
  1166. // Initialize Chrome extension inline installs
  1167. if (config.chromeExtensionId) {
  1168. initInlineInstalls();
  1169. }
  1170. // By default we use camera
  1171. getVideoSize = getCameraVideoSize;
  1172. getVideoPosition = getCameraVideoPosition;
  1173. VideoLayout.resizeLargeVideoContainer();
  1174. $(window).resize(function () {
  1175. VideoLayout.resizeLargeVideoContainer();
  1176. VideoLayout.positionLarge();
  1177. });
  1178. // Listen for large video size updates
  1179. document.getElementById('largeVideo')
  1180. .addEventListener('loadedmetadata', function (e) {
  1181. currentVideoWidth = this.videoWidth;
  1182. currentVideoHeight = this.videoHeight;
  1183. VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight);
  1184. });
  1185. document.getElementById('largeVideo').volume = 0;
  1186. if (!$('#settings').is(':visible')) {
  1187. console.log('init');
  1188. init();
  1189. } else {
  1190. loginInfo.onsubmit = function (e) {
  1191. if (e.preventDefault) e.preventDefault();
  1192. $('#settings').hide();
  1193. init();
  1194. };
  1195. }
  1196. toastr.options = {
  1197. "closeButton": true,
  1198. "debug": false,
  1199. "positionClass": "notification-bottom-right",
  1200. "onclick": null,
  1201. "showDuration": "300",
  1202. "hideDuration": "1000",
  1203. "timeOut": "2000",
  1204. "extendedTimeOut": "1000",
  1205. "showEasing": "swing",
  1206. "hideEasing": "linear",
  1207. "showMethod": "fadeIn",
  1208. "hideMethod": "fadeOut",
  1209. "reposition": function() {
  1210. if(Chat.isVisible() || ContactList.isVisible()) {
  1211. $("#toast-container").addClass("toast-bottom-right-center");
  1212. } else {
  1213. $("#toast-container").removeClass("toast-bottom-right-center");
  1214. }
  1215. },
  1216. "newestOnTop": false
  1217. }
  1218. });
  1219. $(window).bind('beforeunload', function () {
  1220. if (connection && connection.connected) {
  1221. // ensure signout
  1222. $.ajax({
  1223. type: 'POST',
  1224. url: config.bosh,
  1225. async: false,
  1226. cache: false,
  1227. contentType: 'application/xml',
  1228. data: "<body rid='" + (connection.rid || connection._proto.rid)
  1229. + "' xmlns='http://jabber.org/protocol/httpbind' sid='"
  1230. + (connection.sid || connection._proto.sid)
  1231. + "' type='terminate'><presence xmlns='jabber:client' type='unavailable'/></body>",
  1232. success: function (data) {
  1233. console.log('signed out');
  1234. console.log(data);
  1235. },
  1236. error: function (XMLHttpRequest, textStatus, errorThrown) {
  1237. console.log('signout error', textStatus + ' (' + errorThrown + ')');
  1238. }
  1239. });
  1240. }
  1241. disposeConference(true);
  1242. if(APIConnector.isEnabled())
  1243. APIConnector.dispose();
  1244. });
  1245. function disposeConference(onUnload) {
  1246. var handler = getConferenceHandler();
  1247. if (handler && handler.peerconnection) {
  1248. // FIXME: probably removing streams is not required and close() should
  1249. // be enough
  1250. if (connection.jingle.localAudio) {
  1251. handler.peerconnection.removeStream(connection.jingle.localAudio);
  1252. }
  1253. if (connection.jingle.localVideo) {
  1254. handler.peerconnection.removeStream(connection.jingle.localVideo);
  1255. }
  1256. handler.peerconnection.close();
  1257. }
  1258. stopRTPStatsCollector();
  1259. if(onUnload) {
  1260. stopLocalRtpStatsCollector();
  1261. }
  1262. focus = null;
  1263. activecall = null;
  1264. }
  1265. function dump(elem, filename) {
  1266. elem = elem.parentNode;
  1267. elem.download = filename || 'meetlog.json';
  1268. elem.href = 'data:application/json;charset=utf-8,\n';
  1269. var data = populateData();
  1270. elem.href += encodeURIComponent(JSON.stringify(data, null, ' '));
  1271. return false;
  1272. }
  1273. /**
  1274. * Populates the log data
  1275. */
  1276. function populateData() {
  1277. var data = {};
  1278. if (connection.jingle) {
  1279. Object.keys(connection.jingle.sessions).forEach(function (sid) {
  1280. var session = connection.jingle.sessions[sid];
  1281. if (session.peerconnection && session.peerconnection.updateLog) {
  1282. // FIXME: should probably be a .dump call
  1283. data["jingle_" + session.sid] = {
  1284. updateLog: session.peerconnection.updateLog,
  1285. stats: session.peerconnection.stats,
  1286. url: window.location.href
  1287. };
  1288. }
  1289. });
  1290. }
  1291. var metadata = {};
  1292. metadata.time = new Date();
  1293. metadata.url = window.location.href;
  1294. metadata.ua = navigator.userAgent;
  1295. if (connection.logger) {
  1296. metadata.xmpp = connection.logger.log;
  1297. }
  1298. data.metadata = metadata;
  1299. return data;
  1300. }
  1301. /**
  1302. * Changes the style class of the element given by id.
  1303. */
  1304. function buttonClick(id, classname) {
  1305. $(id).toggleClass(classname); // add the class to the clicked element
  1306. }
  1307. /**
  1308. * Locks / unlocks the room.
  1309. */
  1310. function lockRoom(lock) {
  1311. if (lock)
  1312. connection.emuc.lockRoom(sharedKey);
  1313. else
  1314. connection.emuc.lockRoom('');
  1315. }
  1316. /**
  1317. * Sets the shared key.
  1318. */
  1319. function setSharedKey(sKey) {
  1320. sharedKey = sKey;
  1321. }
  1322. function setRecordingToken(token) {
  1323. recordingToken = token;
  1324. }
  1325. /**
  1326. * Updates the room invite url.
  1327. */
  1328. function updateRoomUrl(newRoomUrl) {
  1329. roomUrl = newRoomUrl;
  1330. // If the invite dialog has been already opened we update the information.
  1331. var inviteLink = document.getElementById('inviteLinkRef');
  1332. if (inviteLink) {
  1333. inviteLink.value = roomUrl;
  1334. inviteLink.select();
  1335. document.getElementById('jqi_state0_buttonInvite').disabled = false;
  1336. }
  1337. }
  1338. /**
  1339. * Warning to the user that the conference window is about to be closed.
  1340. */
  1341. function closePageWarning() {
  1342. if (focus !== null)
  1343. return "You are the owner of this conference call and"
  1344. + " you are about to end it.";
  1345. else
  1346. return "You are about to leave this conversation.";
  1347. }
  1348. /**
  1349. * Resizes and repositions videos in full screen mode.
  1350. */
  1351. $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange',
  1352. function () {
  1353. VideoLayout.resizeLargeVideoContainer();
  1354. VideoLayout.positionLarge();
  1355. isFullScreen = document.fullScreen ||
  1356. document.mozFullScreen ||
  1357. document.webkitIsFullScreen;
  1358. if (isFullScreen) {
  1359. setView("fullscreen");
  1360. }
  1361. else {
  1362. setView("default");
  1363. }
  1364. }
  1365. );
  1366. /**
  1367. * Sets the current view.
  1368. */
  1369. function setView(viewName) {
  1370. // if (viewName == "fullscreen") {
  1371. // document.getElementById('videolayout_fullscreen').disabled = false;
  1372. // document.getElementById('videolayout_default').disabled = true;
  1373. // }
  1374. // else {
  1375. // document.getElementById('videolayout_default').disabled = false;
  1376. // document.getElementById('videolayout_fullscreen').disabled = true;
  1377. // }
  1378. }
  1379. $(document).bind('fatalError.jingle',
  1380. function (event, session, error)
  1381. {
  1382. sessionTerminated = true;
  1383. connection.emuc.doLeave();
  1384. messageHandler.showError( "Sorry",
  1385. "Your browser version is too old. Please update and try again...");
  1386. }
  1387. );
  1388. function onSelectedEndpointChanged(userJid)
  1389. {
  1390. console.log('selected endpoint changed: ', userJid);
  1391. if (_dataChannels && _dataChannels.length != 0)
  1392. {
  1393. _dataChannels.some(function (dataChannel) {
  1394. if (dataChannel.readyState == 'open')
  1395. {
  1396. dataChannel.send(JSON.stringify({
  1397. 'colibriClass': 'SelectedEndpointChangedEvent',
  1398. 'selectedEndpoint': (!userJid || userJid == null)
  1399. ? null : Strophe.getResourceFromJid(userJid)
  1400. }));
  1401. return true;
  1402. }
  1403. });
  1404. }
  1405. }
  1406. $(document).bind("selectedendpointchanged", function(event, userJid) {
  1407. onSelectedEndpointChanged(userJid);
  1408. });
  1409. function callSipButtonClicked()
  1410. {
  1411. var defaultNumber
  1412. = config.defaultSipNumber ? config.defaultSipNumber : '';
  1413. messageHandler.openTwoButtonDialog(null,
  1414. '<h2>Enter SIP number</h2>' +
  1415. '<input id="sipNumber" type="text"' +
  1416. ' value="' + defaultNumber + '" autofocus>',
  1417. false,
  1418. "Dial",
  1419. function (e, v, m, f) {
  1420. if (v) {
  1421. var numberInput = document.getElementById('sipNumber');
  1422. if (numberInput.value) {
  1423. connection.rayo.dial(
  1424. numberInput.value, 'fromnumber', roomName);
  1425. }
  1426. }
  1427. },
  1428. function (event) {
  1429. document.getElementById('sipNumber').focus();
  1430. }
  1431. );
  1432. }
  1433. function hangup() {
  1434. disposeConference();
  1435. sessionTerminated = true;
  1436. connection.emuc.doLeave();
  1437. if(config.enableWelcomePage)
  1438. {
  1439. setTimeout(function()
  1440. {
  1441. window.localStorage.welcomePageDisabled = false;
  1442. window.location.pathname = "/";
  1443. }, 10000);
  1444. }
  1445. $.prompt("Session Terminated",
  1446. {
  1447. title: "You hung up the call",
  1448. persistent: true,
  1449. buttons: {
  1450. "Join again": true
  1451. },
  1452. closeText: '',
  1453. submit: function(event, value, message, formVals)
  1454. {
  1455. window.location.reload();
  1456. return false;
  1457. }
  1458. }
  1459. );
  1460. }