選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

app.js 51KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575
  1. /* jshint -W117 */
  2. /* application specific logic */
  3. var connection = null;
  4. var focus = null;
  5. var activecall = null;
  6. var RTC = null;
  7. var nickname = null;
  8. var sharedKey = '';
  9. var roomUrl = null;
  10. var ssrc2jid = {};
  11. /**
  12. * Indicates whether ssrc is camera video or desktop stream.
  13. * FIXME: remove those maps
  14. */
  15. var ssrc2videoType = {};
  16. var videoSrcToSsrc = {};
  17. var localVideoSrc = null;
  18. var flipXLocalVideo = true;
  19. var isFullScreen = false;
  20. var toolbarTimeout = null;
  21. var currentVideoWidth = null;
  22. var currentVideoHeight = null;
  23. /**
  24. * Method used to calculate large video size.
  25. * @type {function()}
  26. */
  27. var getVideoSize;
  28. /* window.onbeforeunload = closePageWarning; */
  29. function init() {
  30. RTC = setupRTC();
  31. if (RTC === null) {
  32. window.location.href = 'webrtcrequired.html';
  33. return;
  34. } else if (RTC.browser !== 'chrome') {
  35. window.location.href = 'chromeonly.html';
  36. return;
  37. }
  38. connection = new Strophe.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind');
  39. if (nickname) {
  40. connection.emuc.addDisplayNameToPresence(nickname);
  41. }
  42. if (connection.disco) {
  43. // for chrome, add multistream cap
  44. }
  45. connection.jingle.pc_constraints = RTC.pc_constraints;
  46. if (config.useIPv6) {
  47. // https://code.google.com/p/webrtc/issues/detail?id=2828
  48. if (!connection.jingle.pc_constraints.optional) connection.jingle.pc_constraints.optional = [];
  49. connection.jingle.pc_constraints.optional.push({googIPv6: true});
  50. }
  51. var jid = document.getElementById('jid').value || config.hosts.domain || window.location.hostname;
  52. connection.connect(jid, document.getElementById('password').value, function (status) {
  53. if (status === Strophe.Status.CONNECTED) {
  54. console.log('connected');
  55. if (config.useStunTurn) {
  56. connection.jingle.getStunAndTurnCredentials();
  57. }
  58. obtainAudioAndVideoPermissions(function(){
  59. getUserMediaWithConstraints( ['audio'], audioStreamReady,
  60. function(error){
  61. console.error('failed to obtain audio stream - stop', error);
  62. });
  63. });
  64. document.getElementById('connect').disabled = true;
  65. } else {
  66. console.log('status', status);
  67. }
  68. });
  69. }
  70. /**
  71. * HTTPS only:
  72. * We first ask for audio and video combined stream in order to get permissions and not to ask twice.
  73. * Then we dispose the stream and continue with separate audio, video streams(required for desktop sharing).
  74. */
  75. function obtainAudioAndVideoPermissions(callback){
  76. // This makes sense only on https sites otherwise we'll be asked for permissions every time
  77. if(location.protocol !== 'https:') {
  78. callback();
  79. return;
  80. }
  81. // Get AV
  82. getUserMediaWithConstraints(
  83. ['audio', 'video'],
  84. function(avStream) {
  85. avStream.stop();
  86. callback();
  87. },
  88. function(error){
  89. console.error('failed to obtain audio/video stream - stop', error);
  90. });
  91. }
  92. function audioStreamReady(stream) {
  93. change_local_audio(stream);
  94. if(RTC.browser !== 'firefox') {
  95. getUserMediaWithConstraints( ['video'], videoStreamReady, videoStreamFailed, config.resolution || '360' );
  96. } else {
  97. doJoin();
  98. }
  99. }
  100. function videoStreamReady(stream) {
  101. change_local_video(stream, true);
  102. doJoin();
  103. }
  104. function videoStreamFailed(error) {
  105. console.warn("Failed to obtain video stream - continue anyway", error);
  106. doJoin();
  107. }
  108. function doJoin() {
  109. var roomnode = null;
  110. var path = window.location.pathname;
  111. var roomjid;
  112. // determinde the room node from the url
  113. // TODO: just the roomnode or the whole bare jid?
  114. if (config.getroomnode && typeof config.getroomnode === 'function') {
  115. // custom function might be responsible for doing the pushstate
  116. roomnode = config.getroomnode(path);
  117. } else {
  118. /* fall back to default strategy
  119. * this is making assumptions about how the URL->room mapping happens.
  120. * It currently assumes deployment at root, with a rewrite like the
  121. * following one (for nginx):
  122. location ~ ^/([a-zA-Z0-9]+)$ {
  123. rewrite ^/(.*)$ / break;
  124. }
  125. */
  126. if (path.length > 1) {
  127. roomnode = path.substr(1).toLowerCase();
  128. } else {
  129. roomnode = Math.random().toString(36).substr(2, 20);
  130. window.history.pushState('VideoChat',
  131. 'Room: ' + roomnode, window.location.pathname + roomnode);
  132. }
  133. }
  134. roomjid = roomnode + '@' + config.hosts.muc;
  135. if (config.useNicks) {
  136. var nick = window.prompt('Your nickname (optional)');
  137. if (nick) {
  138. roomjid += '/' + nick;
  139. } else {
  140. roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
  141. }
  142. } else {
  143. roomjid += '/' + Strophe.getNodeFromJid(connection.jid).substr(0,8);
  144. }
  145. connection.emuc.doJoin(roomjid);
  146. }
  147. function change_local_audio(stream) {
  148. connection.jingle.localAudio = stream;
  149. RTC.attachMediaStream($('#localAudio'), stream);
  150. document.getElementById('localAudio').autoplay = true;
  151. document.getElementById('localAudio').volume = 0;
  152. }
  153. function change_local_video(stream, flipX) {
  154. connection.jingle.localVideo = stream;
  155. var localVideo = document.createElement('video');
  156. localVideo.id = 'localVideo_'+stream.id;
  157. localVideo.autoplay = true;
  158. localVideo.volume = 0; // is it required if audio is separated ?
  159. localVideo.oncontextmenu = function () { return false; };
  160. var localVideoContainer = document.getElementById('localVideoWrapper');
  161. localVideoContainer.appendChild(localVideo);
  162. var localVideoSelector = $('#' + localVideo.id);
  163. // Add click handler
  164. localVideoSelector.click(function () { handleVideoThumbClicked(localVideo.src); } );
  165. // Add stream ended handler
  166. stream.onended = function () {
  167. localVideoContainer.removeChild(localVideo);
  168. checkChangeLargeVideo(localVideo.src);
  169. };
  170. // Flip video x axis if needed
  171. flipXLocalVideo = flipX;
  172. if(flipX) {
  173. localVideoSelector.addClass("flipVideoX");
  174. }
  175. // Attach WebRTC stream
  176. RTC.attachMediaStream(localVideoSelector, stream);
  177. localVideoSrc = localVideo.src;
  178. updateLargeVideo(localVideoSrc, 0);
  179. }
  180. $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
  181. function waitForRemoteVideo(selector, sid, ssrc) {
  182. if(selector.removed) {
  183. console.warn("media removed before had started", selector);
  184. return;
  185. }
  186. var sess = connection.jingle.sessions[sid];
  187. if (data.stream.id === 'mixedmslabel') return;
  188. var videoTracks = data.stream.getVideoTracks();
  189. console.log("waiting..", videoTracks, selector[0]);
  190. if (videoTracks.length === 0 || selector[0].currentTime > 0) {
  191. RTC.attachMediaStream(selector, data.stream); // FIXME: why do i have to do this for FF?
  192. // FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
  193. // in order to get rid of too many maps
  194. if(ssrc) {
  195. videoSrcToSsrc[sel.attr('src')] = ssrc;
  196. } else {
  197. console.warn("No ssrc given for video", sel);
  198. }
  199. $(document).trigger('callactive.jingle', [selector, sid]);
  200. console.log('waitForremotevideo', sess.peerconnection.iceConnectionState, sess.peerconnection.signalingState);
  201. } else {
  202. setTimeout(function () { waitForRemoteVideo(selector, sid, ssrc); }, 250);
  203. }
  204. }
  205. var sess = connection.jingle.sessions[sid];
  206. var thessrc;
  207. // look up an associated JID for a stream id
  208. if (data.stream.id.indexOf('mixedmslabel') === -1) {
  209. var ssrclines = SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc');
  210. ssrclines = ssrclines.filter(function (line) {
  211. return line.indexOf('mslabel:' + data.stream.label) !== -1;
  212. });
  213. if (ssrclines.length) {
  214. thessrc = ssrclines[0].substring(7).split(' ')[0];
  215. // ok to overwrite the one from focus? might save work in colibri.js
  216. console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
  217. if (ssrc2jid[thessrc]) {
  218. data.peerjid = ssrc2jid[thessrc];
  219. }
  220. }
  221. }
  222. var container;
  223. var remotes = document.getElementById('remoteVideos');
  224. if (data.peerjid) {
  225. container = document.getElementById(
  226. 'participant_' + Strophe.getResourceFromJid(data.peerjid));
  227. if (!container) {
  228. console.error('no container for', data.peerjid);
  229. } else {
  230. //console.log('found container for', data.peerjid);
  231. }
  232. } else {
  233. if (data.stream.id !== 'mixedmslabel') {
  234. console.error('can not associate stream', data.stream.id, 'with a participant');
  235. // We don't want to add it here since it will cause troubles
  236. return;
  237. }
  238. // FIXME: for the mixed ms we dont need a video -- currently
  239. container = document.createElement('span');
  240. container.className = 'videocontainer';
  241. remotes.appendChild(container);
  242. Util.playSoundNotification('userJoined');
  243. }
  244. var isVideo = data.stream.getVideoTracks().length > 0;
  245. var vid = isVideo ? document.createElement('video') : document.createElement('audio');
  246. var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_') + sid + '_' + data.stream.id;
  247. vid.id = id;
  248. vid.autoplay = true;
  249. vid.oncontextmenu = function () { return false; };
  250. container.appendChild(vid);
  251. // TODO: make mixedstream display:none via css?
  252. if (id.indexOf('mixedmslabel') !== -1) {
  253. container.id = 'mixedstream';
  254. $(container).hide();
  255. }
  256. var sel = $('#' + id);
  257. sel.hide();
  258. RTC.attachMediaStream(sel, data.stream);
  259. if(isVideo) {
  260. waitForRemoteVideo(sel, sid, thessrc);
  261. }
  262. data.stream.onended = function () {
  263. console.log('stream ended', this.id);
  264. // Mark video as removed to cancel waiting loop(if video is removed before has started)
  265. sel.removed = true;
  266. sel.remove();
  267. var audioCount = $('#'+container.id+'>audio').length;
  268. var videoCount = $('#'+container.id+'>video').length;
  269. if(!audioCount && !videoCount) {
  270. console.log("Remove whole user");
  271. // Remove whole container
  272. container.remove();
  273. Util.playSoundNotification('userLeft');
  274. resizeThumbnails();
  275. }
  276. checkChangeLargeVideo(vid.src);
  277. };
  278. // Add click handler
  279. sel.click(function () { handleVideoThumbClicked(vid.src); });
  280. // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
  281. if (isVideo
  282. && data.peerjid && sess.peerjid === data.peerjid &&
  283. data.stream.getVideoTracks().length === 0 &&
  284. connection.jingle.localVideo.getVideoTracks().length > 0) {
  285. window.setTimeout(function() {
  286. sendKeyframe(sess.peerconnection);
  287. }, 3000);
  288. }
  289. });
  290. function handleVideoThumbClicked(videoSrc) {
  291. $(document).trigger("video.selected", [false]);
  292. updateLargeVideo(videoSrc, 1);
  293. $('audio').each(function (idx, el) {
  294. if (el.id.indexOf('mixedmslabel') !== -1) {
  295. el.volume = 0;
  296. el.volume = 1;
  297. }
  298. });
  299. }
  300. /**
  301. * Checks if removed video is currently displayed and tries to display another one instead.
  302. * @param removedVideoSrc src stream identifier of the video.
  303. */
  304. function checkChangeLargeVideo(removedVideoSrc){
  305. if (removedVideoSrc === $('#largeVideo').attr('src')) {
  306. // this is currently displayed as large
  307. // pick the last visible video in the row
  308. // if nobody else is left, this picks the local video
  309. var pick = $('#remoteVideos>span[id!="mixedstream"]:visible:last>video').get(0);
  310. if(!pick) {
  311. console.info("Last visible video no longer exists");
  312. pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
  313. }
  314. // mute if localvideo
  315. if (pick) {
  316. updateLargeVideo(pick.src, pick.volume);
  317. } else {
  318. console.warn("Failed to elect large video");
  319. }
  320. }
  321. }
  322. // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
  323. function sendKeyframe(pc) {
  324. console.log('sendkeyframe', pc.iceConnectionState);
  325. if (pc.iceConnectionState !== 'connected') return; // safe...
  326. pc.setRemoteDescription(
  327. pc.remoteDescription,
  328. function () {
  329. pc.createAnswer(
  330. function (modifiedAnswer) {
  331. pc.setLocalDescription(modifiedAnswer,
  332. function () {
  333. },
  334. function (error) {
  335. console.log('triggerKeyframe setLocalDescription failed', error);
  336. }
  337. );
  338. },
  339. function (error) {
  340. console.log('triggerKeyframe createAnswer failed', error);
  341. }
  342. );
  343. },
  344. function (error) {
  345. console.log('triggerKeyframe setRemoteDescription failed', error);
  346. }
  347. );
  348. }
  349. function demonstrateabug(pc) {
  350. // funny way of doing mute. the subsequent offer contains things like rtcp-mux
  351. // and triggers all new ice candidates (ice restart)
  352. // this code is here to demonstrate a bug
  353. pc.createOffer(
  354. function (offer) {
  355. console.log(offer);
  356. var sdp = new SDP(offer.sdp);
  357. if (sdp.media.length > 1) {
  358. sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
  359. sdp.raw = sdp.session + sdp.media.join('');
  360. offer.sdp = sdp.raw;
  361. pc.setLocalDescription(offer,
  362. function () {
  363. console.log('mute SLD ok');
  364. },
  365. function(error) {
  366. console.log('mute SLD error');
  367. }
  368. );
  369. }
  370. },
  371. function (error) {
  372. console.warn(error);
  373. },
  374. {mandatory: {OfferToReceiveAudio: true, OfferToReceiveVideo: false}}
  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. }
  401. );
  402. },
  403. function (error) {
  404. console.log(error);
  405. }
  406. );
  407. },
  408. function (error) {
  409. console.log('muteVideo SRD error');
  410. }
  411. );
  412. }
  413. $(document).bind('callincoming.jingle', function (event, sid) {
  414. var sess = connection.jingle.sessions[sid];
  415. // TODO: do we check activecall == null?
  416. activecall = sess;
  417. // TODO: check affiliation and/or role
  418. console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
  419. sess.usedrip = true; // not-so-naive trickle ice
  420. sess.sendAnswer();
  421. sess.accept();
  422. });
  423. $(document).bind('callactive.jingle', function (event, videoelem, sid) {
  424. if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
  425. // ignore mixedmslabela0 and v0
  426. videoelem.show();
  427. resizeThumbnails();
  428. updateLargeVideo(videoelem.attr('src'), 1);
  429. showFocusIndicator();
  430. }
  431. });
  432. $(document).bind('callterminated.jingle', function (event, sid, reason) {
  433. // FIXME
  434. });
  435. $(document).bind('setLocalDescription.jingle', function (event, sid) {
  436. // put our ssrcs into presence so other clients can identify our stream
  437. var sess = connection.jingle.sessions[sid];
  438. var newssrcs = {};
  439. var directions = {};
  440. var localSDP = new SDP(sess.peerconnection.localDescription.sdp);
  441. localSDP.media.forEach(function (media) {
  442. var type = SDPUtil.parse_mline(media.split('\r\n')[0]).media;
  443. if (SDPUtil.find_line(media, 'a=ssrc:')) {
  444. // assumes a single local ssrc
  445. var ssrc = SDPUtil.find_line(media, 'a=ssrc:').substring(7).split(' ')[0];
  446. newssrcs[type] = ssrc;
  447. directions[type] = (
  448. SDPUtil.find_line(media, 'a=sendrecv')
  449. || SDPUtil.find_line(media, 'a=recvonly')
  450. || SDPUtil.find_line('a=sendonly')
  451. || SDPUtil.find_line('a=inactive')
  452. || 'a=sendrecv' ).substr(2);
  453. }
  454. });
  455. console.log('new ssrcs', newssrcs);
  456. // Have to clear presence map to get rid of removed streams
  457. connection.emuc.clearPresenceMedia();
  458. var i = 0;
  459. Object.keys(newssrcs).forEach(function (mtype) {
  460. i++;
  461. var type = mtype;
  462. // Change video type to screen
  463. if(mtype === 'video' && isUsingScreenStream) {
  464. type = 'screen';
  465. }
  466. connection.emuc.addMediaToPresence(i, type, newssrcs[mtype], directions[mtype]);
  467. });
  468. if (i > 0) {
  469. connection.emuc.sendPresence();
  470. }
  471. });
  472. $(document).bind('joined.muc', function (event, jid, info) {
  473. updateRoomUrl(window.location.href);
  474. document.getElementById('localNick').appendChild(
  475. document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)')
  476. );
  477. if (Object.keys(connection.emuc.members).length < 1) {
  478. focus = new ColibriFocus(connection, config.hosts.bridge);
  479. }
  480. if (focus && config.etherpad_base) {
  481. Etherpad.init();
  482. }
  483. showFocusIndicator();
  484. // Once we've joined the muc show the toolbar
  485. showToolbar();
  486. var displayName = '';
  487. if (info.displayName)
  488. displayName = info.displayName + ' (me)';
  489. showDisplayName('localVideoContainer', displayName);
  490. });
  491. $(document).bind('entered.muc', function (event, jid, info, pres) {
  492. console.log('entered', jid, info);
  493. console.log('is focus?' + focus ? 'true' : 'false');
  494. // Add Peer's container
  495. ensurePeerContainerExists(jid);
  496. if (focus !== null) {
  497. // FIXME: this should prepare the video
  498. if (focus.confid === null) {
  499. console.log('make new conference with', jid);
  500. focus.makeConference(Object.keys(connection.emuc.members));
  501. } else {
  502. console.log('invite', jid, 'into conference');
  503. focus.addNewParticipant(jid);
  504. }
  505. }
  506. else if (sharedKey) {
  507. updateLockButton();
  508. }
  509. });
  510. $(document).bind('left.muc', function (event, jid) {
  511. console.log('left', jid);
  512. connection.jingle.terminateByJid(jid);
  513. var container = document.getElementById('participant_' + Strophe.getResourceFromJid(jid));
  514. if (container) {
  515. // hide here, wait for video to close before removing
  516. $(container).hide();
  517. resizeThumbnails();
  518. }
  519. if (focus === null && connection.emuc.myroomjid === connection.emuc.list_members[0]) {
  520. console.log('welcome to our new focus... myself');
  521. focus = new ColibriFocus(connection, config.hosts.bridge);
  522. if (Object.keys(connection.emuc.members).length > 0) {
  523. focus.makeConference(Object.keys(connection.emuc.members));
  524. }
  525. $(document).trigger('focusechanged.muc', [focus]);
  526. }
  527. else if (focus && Object.keys(connection.emuc.members).length === 0) {
  528. console.log('everyone left');
  529. // FIXME: closing the connection is a hack to avoid some
  530. // problemswith reinit
  531. disposeConference();
  532. focus = new ColibriFocus(connection, config.hosts.bridge);
  533. }
  534. if (connection.emuc.getPrezi(jid)) {
  535. $(document).trigger('presentationremoved.muc', [jid, connection.emuc.getPrezi(jid)]);
  536. }
  537. });
  538. $(document).bind('presence.muc', function (event, jid, info, pres) {
  539. // Remove old ssrcs coming from the jid
  540. Object.keys(ssrc2jid).forEach(function(ssrc){
  541. if(ssrc2jid[ssrc] == jid){
  542. delete ssrc2jid[ssrc];
  543. }
  544. if(ssrc2videoType == jid){
  545. delete ssrc2videoType[ssrc];
  546. }
  547. });
  548. $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
  549. //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
  550. var ssrcV = ssrc.getAttribute('ssrc');
  551. ssrc2jid[ssrcV] = jid;
  552. var type = ssrc.getAttribute('type');
  553. ssrc2videoType[ssrcV] = type;
  554. // might need to update the direction if participant just went from sendrecv to recvonly
  555. if (type === 'video' || type === 'screen') {
  556. var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video');
  557. switch(ssrc.getAttribute('direction')) {
  558. case 'sendrecv':
  559. el.show();
  560. break;
  561. case 'recvonly':
  562. el.hide();
  563. // FIXME: Check if we have to change large video
  564. //checkChangeLargeVideo(el);
  565. break;
  566. }
  567. }
  568. });
  569. if (info.displayName) {
  570. if (jid === connection.emuc.myroomjid) {
  571. showDisplayName('localVideoContainer', info.displayName + ' (me)');
  572. } else {
  573. ensurePeerContainerExists(jid);
  574. showDisplayName('participant_' + Strophe.getResourceFromJid(jid), info.displayName);
  575. }
  576. }
  577. });
  578. $(document).bind('passwordrequired.muc', function (event, jid) {
  579. console.log('on password required', jid);
  580. $.prompt('<h2>Password required</h2>' +
  581. '<input id="lockKey" type="text" placeholder="shared key" autofocus>',
  582. {
  583. persistent: true,
  584. buttons: { "Ok": true , "Cancel": false},
  585. defaultButton: 1,
  586. loaded: function(event) {
  587. document.getElementById('lockKey').focus();
  588. },
  589. submit: function(e,v,m,f){
  590. if(v)
  591. {
  592. var lockKey = document.getElementById('lockKey');
  593. if (lockKey.value !== null)
  594. {
  595. setSharedKey(lockKey.value);
  596. connection.emuc.doJoin(jid, lockKey.value);
  597. }
  598. }
  599. }
  600. });
  601. });
  602. /**
  603. * Updates the large video with the given new video source.
  604. */
  605. function updateLargeVideo(newSrc, vol) {
  606. console.log('hover in', newSrc);
  607. if ($('#largeVideo').attr('src') != newSrc) {
  608. var isVisible = $('#largeVideo').is(':visible');
  609. $('#largeVideo').fadeOut(300, function () {
  610. $(this).attr('src', newSrc);
  611. // Screen stream is already rotated
  612. var flipX = (newSrc === localVideoSrc) && flipXLocalVideo;
  613. var videoTransform = document.getElementById('largeVideo').style.webkitTransform;
  614. if (flipX && videoTransform !== 'scaleX(-1)') {
  615. document.getElementById('largeVideo').style.webkitTransform = "scaleX(-1)";
  616. }
  617. else if (!flipX && videoTransform === 'scaleX(-1)') {
  618. document.getElementById('largeVideo').style.webkitTransform = "none";
  619. }
  620. // Change the way we'll be measuring large video
  621. getVideoSize = isVideoSrcDesktop(newSrc) ? getVideoSizeFit : getVideoSizeCover;
  622. if (isVisible)
  623. $(this).fadeIn(300);
  624. });
  625. }
  626. }
  627. /**
  628. * Checks if video identified by given src is desktop stream.
  629. * @param videoSrc eg. blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
  630. * @returns {boolean}
  631. */
  632. function isVideoSrcDesktop(videoSrc){
  633. // FIXME: fix this mapping mess...
  634. // figure out if large video is desktop stream or just a camera
  635. var isDesktop = false;
  636. if(localVideoSrc === videoSrc) {
  637. // local video
  638. isDesktop = isUsingScreenStream;
  639. } else {
  640. // Do we have associations...
  641. var videoSsrc = videoSrcToSsrc[videoSrc];
  642. if(videoSsrc) {
  643. var videoType = ssrc2videoType[videoSsrc];
  644. if(videoType) {
  645. // Finally there...
  646. isDesktop = videoType === 'screen';
  647. } else {
  648. console.error("No video type for ssrc: " + videoSsrc);
  649. }
  650. } else {
  651. console.error("No ssrc for src: " + videoSrc);
  652. }
  653. }
  654. return isDesktop;
  655. }
  656. /**
  657. * Shows/hides the large video.
  658. */
  659. function setLargeVideoVisible(isVisible) {
  660. if (isVisible) {
  661. $('#largeVideo').css({visibility:'visible'});
  662. $('#watermark').css({visibility:'visible'});
  663. }
  664. else {
  665. $('#largeVideo').css({visibility:'hidden'});
  666. $('#watermark').css({visibility:'hidden'});
  667. }
  668. }
  669. function getConferenceHandler() {
  670. return focus ? focus : activecall;
  671. }
  672. function toggleVideo() {
  673. if (!(connection && connection.jingle.localVideo)) return;
  674. var sess = getConferenceHandler();
  675. if (sess) {
  676. sess.toggleVideoMute(
  677. function(isMuted){
  678. if(isMuted) {
  679. $('#video').removeClass("fa fa-video-camera fa-lg");
  680. $('#video').addClass("fa fa-video-camera no-fa-video-camera fa-lg");
  681. } else {
  682. $('#video').removeClass("fa fa-video-camera no-fa-video-camera fa-lg");
  683. $('#video').addClass("fa fa-video-camera fa-lg");
  684. }
  685. }
  686. );
  687. }
  688. var sess = focus || activecall;
  689. if (!sess) {
  690. return;
  691. }
  692. sess.pendingop = ismuted ? 'unmute' : 'mute';
  693. sess.modifySources();
  694. }
  695. function toggleAudio() {
  696. if (!(connection && connection.jingle.localAudio)) return;
  697. var localAudio = connection.jingle.localAudio;
  698. for (var idx = 0; idx < localAudio.getAudioTracks().length; idx++) {
  699. localAudio.getAudioTracks()[idx].enabled = !localAudio.getAudioTracks()[idx].enabled;
  700. }
  701. }
  702. /**
  703. * Positions the large video.
  704. *
  705. * @param videoWidth the stream video width
  706. * @param videoHeight the stream video height
  707. */
  708. var positionLarge = function(videoWidth, videoHeight) {
  709. var videoSpaceWidth = $('#videospace').width();
  710. var videoSpaceHeight = window.innerHeight;
  711. var videoSize = getVideoSize( videoWidth,
  712. videoHeight,
  713. videoSpaceWidth,
  714. videoSpaceHeight);
  715. var largeVideoWidth = videoSize[0];
  716. var largeVideoHeight = videoSize[1];
  717. var videoPosition = getVideoPosition( largeVideoWidth,
  718. largeVideoHeight,
  719. videoSpaceWidth,
  720. videoSpaceHeight);
  721. var horizontalIndent = videoPosition[0];
  722. var verticalIndent = videoPosition[1];
  723. positionVideo( $('#largeVideo'),
  724. largeVideoWidth,
  725. largeVideoHeight,
  726. horizontalIndent, verticalIndent);
  727. };
  728. /**
  729. * Returns an array of the video horizontal and vertical indents,
  730. * so that if fits its parent.
  731. *
  732. * @return an array with 2 elements, the horizontal indent and the vertical
  733. * indent
  734. */
  735. var getVideoPosition = function ( videoWidth,
  736. videoHeight,
  737. videoSpaceWidth,
  738. videoSpaceHeight) {
  739. // Parent height isn't completely calculated when we position the video in
  740. // full screen mode and this is why we use the screen height in this case.
  741. // Need to think it further at some point and implement it properly.
  742. var isFullScreen = document.fullScreen
  743. || document.mozFullScreen
  744. || document.webkitIsFullScreen;
  745. if (isFullScreen)
  746. videoSpaceHeight = window.innerHeight;
  747. var horizontalIndent = (videoSpaceWidth - videoWidth)/2;
  748. var verticalIndent = (videoSpaceHeight - videoHeight)/2;
  749. return [horizontalIndent, verticalIndent];
  750. };
  751. /**
  752. * Returns an array of the video dimensions, so that it covers the screen.
  753. * It leaves no empty areas, but some parts of the video might not be visible.
  754. *
  755. * @return an array with 2 elements, the video width and the video height
  756. */
  757. function getVideoSizeCover(videoWidth,
  758. videoHeight,
  759. videoSpaceWidth,
  760. videoSpaceHeight) {
  761. if (!videoWidth)
  762. videoWidth = currentVideoWidth;
  763. if (!videoHeight)
  764. videoHeight = currentVideoHeight;
  765. var aspectRatio = videoWidth / videoHeight;
  766. var availableWidth = Math.max(videoWidth, videoSpaceWidth);
  767. var availableHeight = Math.max(videoHeight, videoSpaceHeight);
  768. if (availableWidth / aspectRatio < videoSpaceHeight) {
  769. availableHeight = videoSpaceHeight;
  770. availableWidth = availableHeight*aspectRatio;
  771. }
  772. if (availableHeight*aspectRatio < videoSpaceWidth) {
  773. availableWidth = videoSpaceWidth;
  774. availableHeight = availableWidth / aspectRatio;
  775. }
  776. return [availableWidth, availableHeight];
  777. }
  778. /**
  779. * Returns an array of the video dimensions, so that it keeps it's aspect ratio and fits available area with it's
  780. * larger dimension. This method ensures that whole video will be visible and can leave empty areas.
  781. *
  782. * @return an array with 2 elements, the video width and the video height
  783. */
  784. function getVideoSizeFit(videoWidth,
  785. videoHeight,
  786. videoSpaceWidth,
  787. videoSpaceHeight) {
  788. if (!videoWidth)
  789. videoWidth = currentVideoWidth;
  790. if (!videoHeight)
  791. videoHeight = currentVideoHeight;
  792. var aspectRatio = videoWidth / videoHeight;
  793. var availableWidth = Math.max(videoWidth, videoSpaceWidth);
  794. var availableHeight = Math.max(videoHeight, videoSpaceHeight);
  795. if (availableWidth / aspectRatio >= videoSpaceHeight) {
  796. availableHeight = videoSpaceHeight;
  797. availableWidth = availableHeight*aspectRatio;
  798. }
  799. if (availableHeight*aspectRatio >= videoSpaceWidth) {
  800. availableWidth = videoSpaceWidth;
  801. availableHeight = availableWidth / aspectRatio;
  802. }
  803. return [availableWidth, availableHeight];
  804. }
  805. /**
  806. * Sets the size and position of the given video element.
  807. *
  808. * @param video the video element to position
  809. * @param width the desired video width
  810. * @param height the desired video height
  811. * @param horizontalIndent the left and right indent
  812. * @param verticalIndent the top and bottom indent
  813. */
  814. function positionVideo( video,
  815. width,
  816. height,
  817. horizontalIndent,
  818. verticalIndent) {
  819. video.width(width);
  820. video.height(height);
  821. video.css({ top: verticalIndent + 'px',
  822. bottom: verticalIndent + 'px',
  823. left: horizontalIndent + 'px',
  824. right: horizontalIndent + 'px'});
  825. }
  826. var resizeLargeVideoContainer = function () {
  827. Chat.resizeChat();
  828. var availableHeight = window.innerHeight;
  829. var availableWidth = Util.getAvailableVideoWidth();
  830. if (availableWidth < 0 || availableHeight < 0) return;
  831. $('#videospace').width(availableWidth);
  832. $('#videospace').height(availableHeight);
  833. $('#largeVideoContainer').width(availableWidth);
  834. $('#largeVideoContainer').height(availableHeight);
  835. resizeThumbnails();
  836. };
  837. function resizeThumbnails() {
  838. // Calculate the available height, which is the inner window height minus
  839. // 39px for the header minus 2px for the delimiter lines on the top and
  840. // bottom of the large video, minus the 36px space inside the remoteVideos
  841. // container used for highlighting shadow.
  842. var availableHeight = 100;
  843. var numvids = $('#remoteVideos>span:visible').length;
  844. // Remove the 1px borders arround videos and the chat width.
  845. var availableWinWidth = $('#remoteVideos').width() - 2 * numvids - 50;
  846. var availableWidth = availableWinWidth / numvids;
  847. var aspectRatio = 16.0 / 9.0;
  848. var maxHeight = Math.min(160, availableHeight);
  849. availableHeight = Math.min(maxHeight, availableWidth / aspectRatio);
  850. if (availableHeight < availableWidth / aspectRatio) {
  851. availableWidth = Math.floor(availableHeight * aspectRatio);
  852. }
  853. // size videos so that while keeping AR and max height, we have a nice fit
  854. $('#remoteVideos').height(availableHeight);
  855. $('#remoteVideos>span').width(availableWidth);
  856. $('#remoteVideos>span').height(availableHeight);
  857. }
  858. $(document).ready(function () {
  859. Chat.init();
  860. // Set the defaults for prompt dialogs.
  861. jQuery.prompt.setDefaults({persistent: false});
  862. // Set default desktop sharing method
  863. setDesktopSharing(config.desktopSharing);
  864. // By default we cover the whole screen with video
  865. getVideoSize = getVideoSizeCover;
  866. resizeLargeVideoContainer();
  867. $(window).resize(function () {
  868. resizeLargeVideoContainer();
  869. positionLarge();
  870. });
  871. // Listen for large video size updates
  872. document.getElementById('largeVideo')
  873. .addEventListener('loadedmetadata', function(e){
  874. currentVideoWidth = this.videoWidth;
  875. currentVideoHeight = this.videoHeight;
  876. positionLarge(currentVideoWidth, currentVideoHeight);
  877. });
  878. if (!$('#settings').is(':visible')) {
  879. console.log('init');
  880. init();
  881. } else {
  882. loginInfo.onsubmit = function (e) {
  883. if (e.preventDefault) e.preventDefault();
  884. $('#settings').hide();
  885. init();
  886. };
  887. }
  888. });
  889. $(window).bind('beforeunload', function () {
  890. if (connection && connection.connected) {
  891. // ensure signout
  892. $.ajax({
  893. type: 'POST',
  894. url: config.bosh,
  895. async: false,
  896. cache: false,
  897. contentType: 'application/xml',
  898. data: "<body rid='" + (connection.rid || connection._proto.rid) + "' xmlns='http://jabber.org/protocol/httpbind' sid='" + (connection.sid || connection._proto.sid) + "' type='terminate'><presence xmlns='jabber:client' type='unavailable'/></body>",
  899. success: function (data) {
  900. console.log('signed out');
  901. console.log(data);
  902. },
  903. error: function (XMLHttpRequest, textStatus, errorThrown) {
  904. console.log('signout error', textStatus + ' (' + errorThrown + ')');
  905. }
  906. });
  907. }
  908. disposeConference();
  909. });
  910. function disposeConference() {
  911. var handler = getConferenceHandler();
  912. if(handler && handler.peerconnection) {
  913. // FIXME: probably removing streams is not required and close() should be enough
  914. if(connection.jingle.localAudio) {
  915. handler.peerconnection.removeStream(connection.jingle.localAudio);
  916. }
  917. if(connection.jingle.localVideo) {
  918. handler.peerconnection.removeStream(connection.jingle.localVideo);
  919. }
  920. handler.peerconnection.close();
  921. }
  922. focus = null;
  923. activecall = null;
  924. }
  925. function dump(elem, filename){
  926. elem = elem.parentNode;
  927. elem.download = filename || 'meetlog.json';
  928. elem.href = 'data:application/json;charset=utf-8,\n';
  929. var data = {};
  930. if (connection.jingle) {
  931. Object.keys(connection.jingle.sessions).forEach(function (sid) {
  932. var session = connection.jingle.sessions[sid];
  933. if (session.peerconnection && session.peerconnection.updateLog) {
  934. // FIXME: should probably be a .dump call
  935. data["jingle_" + session.sid] = {
  936. updateLog: session.peerconnection.updateLog,
  937. stats: session.peerconnection.stats,
  938. url: window.location.href}
  939. ;
  940. }
  941. });
  942. }
  943. metadata = {};
  944. metadata.time = new Date();
  945. metadata.url = window.location.href;
  946. metadata.ua = navigator.userAgent;
  947. if (connection.logger) {
  948. metadata.xmpp = connection.logger.log;
  949. }
  950. data.metadata = metadata;
  951. elem.href += encodeURIComponent(JSON.stringify(data, null, ' '));
  952. return false;
  953. }
  954. /**
  955. * Changes the style class of the element given by id.
  956. */
  957. function buttonClick(id, classname) {
  958. $(id).toggleClass(classname); // add the class to the clicked element
  959. }
  960. /**
  961. * Opens the lock room dialog.
  962. */
  963. function openLockDialog() {
  964. // Only the focus is able to set a shared key.
  965. if (focus === null) {
  966. if (sharedKey)
  967. $.prompt("This conversation is currently protected by a shared secret key.",
  968. {
  969. title: "Secrect key",
  970. persistent: false
  971. });
  972. else
  973. $.prompt("This conversation isn't currently protected by a secret key. Only the owner of the conference could set a shared key.",
  974. {
  975. title: "Secrect key",
  976. persistent: false
  977. });
  978. }
  979. else {
  980. if (sharedKey)
  981. $.prompt("Are you sure you would like to remove your secret key?",
  982. {
  983. title: "Remove secrect key",
  984. persistent: false,
  985. buttons: { "Remove": true, "Cancel": false},
  986. defaultButton: 1,
  987. submit: function(e,v,m,f){
  988. if(v)
  989. {
  990. setSharedKey('');
  991. lockRoom(false);
  992. }
  993. }
  994. });
  995. else
  996. $.prompt('<h2>Set a secrect key to lock your room</h2>' +
  997. '<input id="lockKey" type="text" placeholder="your shared key" autofocus>',
  998. {
  999. persistent: false,
  1000. buttons: { "Save": true , "Cancel": false},
  1001. defaultButton: 1,
  1002. loaded: function(event) {
  1003. document.getElementById('lockKey').focus();
  1004. },
  1005. submit: function(e,v,m,f){
  1006. if(v)
  1007. {
  1008. var lockKey = document.getElementById('lockKey');
  1009. if (lockKey.value)
  1010. {
  1011. setSharedKey(Util.escapeHtml(lockKey.value));
  1012. lockRoom(true);
  1013. }
  1014. }
  1015. }
  1016. });
  1017. }
  1018. }
  1019. /**
  1020. * Opens the invite link dialog.
  1021. */
  1022. function openLinkDialog() {
  1023. $.prompt('<input id="inviteLinkRef" type="text" value="'
  1024. + encodeURI(roomUrl) + '" onclick="this.select();" readonly>',
  1025. {
  1026. title: "Share this link with everyone you want to invite",
  1027. persistent: false,
  1028. buttons: { "Cancel": false},
  1029. loaded: function(event) {
  1030. document.getElementById('inviteLinkRef').select();
  1031. }
  1032. });
  1033. }
  1034. /**
  1035. * Opens the settings dialog.
  1036. */
  1037. function openSettingsDialog() {
  1038. $.prompt('<h2>Configure your conference</h2>' +
  1039. '<input type="checkbox" id="initMuted"> Participants join muted<br/>' +
  1040. '<input type="checkbox" id="requireNicknames"> Require nicknames<br/><br/>' +
  1041. 'Set a secrect key to lock your room: <input id="lockKey" type="text" placeholder="your shared key" autofocus>',
  1042. {
  1043. persistent: false,
  1044. buttons: { "Save": true , "Cancel": false},
  1045. defaultButton: 1,
  1046. loaded: function(event) {
  1047. document.getElementById('lockKey').focus();
  1048. },
  1049. submit: function(e,v,m,f){
  1050. if(v)
  1051. {
  1052. if ($('#initMuted').is(":checked"))
  1053. {
  1054. // it is checked
  1055. }
  1056. if ($('#requireNicknames').is(":checked"))
  1057. {
  1058. // it is checked
  1059. }
  1060. /*
  1061. var lockKey = document.getElementById('lockKey');
  1062. if (lockKey.value)
  1063. {
  1064. setSharedKey(lockKey.value);
  1065. lockRoom(true);
  1066. }
  1067. */
  1068. }
  1069. }
  1070. });
  1071. }
  1072. /**
  1073. * Locks / unlocks the room.
  1074. */
  1075. function lockRoom(lock) {
  1076. if (lock)
  1077. connection.emuc.lockRoom(sharedKey);
  1078. else
  1079. connection.emuc.lockRoom('');
  1080. updateLockButton();
  1081. }
  1082. /**
  1083. * Sets the shared key.
  1084. */
  1085. function setSharedKey(sKey) {
  1086. sharedKey = sKey;
  1087. }
  1088. /**
  1089. * Updates the lock button state.
  1090. */
  1091. function updateLockButton() {
  1092. buttonClick("#lockIcon", "icon-security icon-security-locked");
  1093. }
  1094. /**
  1095. * Hides the toolbar.
  1096. */
  1097. var hideToolbar = function() {
  1098. var isToolbarHover = false;
  1099. $('#header').find('*').each(function(){
  1100. var id = $(this).attr('id');
  1101. if ($("#" + id + ":hover").length > 0) {
  1102. isToolbarHover = true;
  1103. }
  1104. });
  1105. clearTimeout(toolbarTimeout);
  1106. toolbarTimeout = null;
  1107. if (!isToolbarHover) {
  1108. $('#header').hide("slide", { direction: "up", duration: 300});
  1109. }
  1110. else {
  1111. toolbarTimeout = setTimeout(hideToolbar, 2000);
  1112. }
  1113. };
  1114. /**
  1115. * Shows the call main toolbar.
  1116. */
  1117. function showToolbar() {
  1118. if (!$('#header').is(':visible')) {
  1119. $('#header').show("slide", { direction: "up", duration: 300});
  1120. if (toolbarTimeout) {
  1121. clearTimeout(toolbarTimeout);
  1122. toolbarTimeout = null;
  1123. }
  1124. toolbarTimeout = setTimeout(hideToolbar, 2000);
  1125. }
  1126. if (focus != null)
  1127. {
  1128. // TODO: Enable settings functionality. Need to uncomment the settings button in index.html.
  1129. // $('#settingsButton').css({visibility:"visible"});
  1130. }
  1131. // Show/hide desktop sharing button
  1132. showDesktopSharingButton();
  1133. }
  1134. /**
  1135. * Docks/undocks the toolbar.
  1136. *
  1137. * @param isDock indicates what operation to perform
  1138. */
  1139. function dockToolbar(isDock) {
  1140. if (isDock) {
  1141. // First make sure the toolbar is shown.
  1142. if (!$('#header').is(':visible')) {
  1143. showToolbar();
  1144. }
  1145. // Then clear the time out, to dock the toolbar.
  1146. clearTimeout(toolbarTimeout);
  1147. toolbarTimeout = null;
  1148. }
  1149. else {
  1150. if (!$('#header').is(':visible')) {
  1151. showToolbar();
  1152. }
  1153. else {
  1154. toolbarTimeout = setTimeout(hideToolbar, 2000);
  1155. }
  1156. }
  1157. }
  1158. /**
  1159. * Updates the room invite url.
  1160. */
  1161. function updateRoomUrl(newRoomUrl) {
  1162. roomUrl = newRoomUrl;
  1163. }
  1164. /**
  1165. * Warning to the user that the conference window is about to be closed.
  1166. */
  1167. function closePageWarning() {
  1168. if (focus !== null)
  1169. return "You are the owner of this conference call and you are about to end it.";
  1170. else
  1171. return "You are about to leave this conversation.";
  1172. }
  1173. /**
  1174. * Shows a visual indicator for the focus of the conference.
  1175. * Currently if we're not the owner of the conference we obtain the focus
  1176. * from the connection.jingle.sessions.
  1177. */
  1178. function showFocusIndicator() {
  1179. if (focus !== null) {
  1180. var indicatorSpan = $('#localVideoContainer .focusindicator');
  1181. if (indicatorSpan.children().length === 0)
  1182. {
  1183. createFocusIndicatorElement(indicatorSpan[0]);
  1184. }
  1185. }
  1186. else if (Object.keys(connection.jingle.sessions).length > 0) {
  1187. // If we're only a participant the focus will be the only session we have.
  1188. var session = connection.jingle.sessions[Object.keys(connection.jingle.sessions)[0]];
  1189. var focusId = 'participant_' + Strophe.getResourceFromJid(session.peerjid);
  1190. var focusContainer = document.getElementById(focusId);
  1191. if(!focusContainer) {
  1192. console.error("No focus container!");
  1193. return;
  1194. }
  1195. var indicatorSpan = $('#' + focusId + ' .focusindicator');
  1196. if (!indicatorSpan || indicatorSpan.length === 0) {
  1197. indicatorSpan = document.createElement('span');
  1198. indicatorSpan.className = 'focusindicator';
  1199. focusContainer.appendChild(indicatorSpan);
  1200. createFocusIndicatorElement(indicatorSpan);
  1201. }
  1202. }
  1203. }
  1204. /**
  1205. * Checks if container for participant identified by given peerJid exists in the document and creates it eventually.
  1206. * @param peerJid peer Jid to check.
  1207. */
  1208. function ensurePeerContainerExists(peerJid){
  1209. var peerResource = Strophe.getResourceFromJid(peerJid);
  1210. var videoSpanId = 'participant_' + peerResource;
  1211. if($('#'+videoSpanId).length > 0) {
  1212. return;
  1213. }
  1214. var container = addRemoteVideoContainer(videoSpanId);
  1215. var nickfield = document.createElement('span');
  1216. nickfield.className = "nick";
  1217. nickfield.appendChild(document.createTextNode(peerResource));
  1218. container.appendChild(nickfield);
  1219. resizeThumbnails();
  1220. }
  1221. function addRemoteVideoContainer(id) {
  1222. var container = document.createElement('span');
  1223. container.id = id;
  1224. container.className = 'videocontainer';
  1225. var remotes = document.getElementById('remoteVideos');
  1226. remotes.appendChild(container);
  1227. return container;
  1228. }
  1229. /**
  1230. * Creates the element indicating the focus of the conference.
  1231. */
  1232. function createFocusIndicatorElement(parentElement) {
  1233. var focusIndicator = document.createElement('i');
  1234. focusIndicator.className = 'fa fa-star';
  1235. focusIndicator.title = "The owner of this conference";
  1236. parentElement.appendChild(focusIndicator);
  1237. }
  1238. /**
  1239. * Toggles the application in and out of full screen mode
  1240. * (a.k.a. presentation mode in Chrome).
  1241. */
  1242. function toggleFullScreen() {
  1243. var fsElement = document.documentElement;
  1244. if (!document.mozFullScreen && !document.webkitIsFullScreen){
  1245. //Enter Full Screen
  1246. if (fsElement.mozRequestFullScreen) {
  1247. fsElement.mozRequestFullScreen();
  1248. }
  1249. else {
  1250. fsElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
  1251. }
  1252. } else {
  1253. //Exit Full Screen
  1254. if (document.mozCancelFullScreen) {
  1255. document.mozCancelFullScreen();
  1256. } else {
  1257. document.webkitCancelFullScreen();
  1258. }
  1259. }
  1260. }
  1261. /**
  1262. * Shows the display name for the given video.
  1263. */
  1264. function showDisplayName(videoSpanId, displayName) {
  1265. var escDisplayName = Util.escapeHtml(displayName);
  1266. var nameSpan = $('#' + videoSpanId + '>span.displayname');
  1267. // If we already have a display name for this video.
  1268. if (nameSpan.length > 0) {
  1269. var nameSpanElement = nameSpan.get(0);
  1270. if (nameSpanElement.id === 'localDisplayName'
  1271. && $('#localDisplayName').html() !== escDisplayName)
  1272. $('#localDisplayName').html(escDisplayName);
  1273. else
  1274. $('#' + videoSpanId + '_name').html(escDisplayName);
  1275. }
  1276. else {
  1277. var editButton = null;
  1278. if (videoSpanId === 'localVideoContainer') {
  1279. editButton = createEditDisplayNameButton();
  1280. }
  1281. if (escDisplayName.length) {
  1282. nameSpan = document.createElement('span');
  1283. nameSpan.className = 'displayname';
  1284. nameSpan.innerHTML = escDisplayName;
  1285. $('#' + videoSpanId)[0].appendChild(nameSpan);
  1286. }
  1287. if (!editButton) {
  1288. nameSpan.id = videoSpanId + '_name';
  1289. }
  1290. else {
  1291. nameSpan.id = 'localDisplayName';
  1292. $('#' + videoSpanId)[0].appendChild(editButton);
  1293. var editableText = document.createElement('input');
  1294. editableText.className = 'displayname';
  1295. editableText.id = 'editDisplayName';
  1296. if (escDisplayName.length)
  1297. editableText.value
  1298. = escDisplayName.substring(0, escDisplayName.indexOf(' (me)'));
  1299. editableText.setAttribute('style', 'display:none;');
  1300. editableText.setAttribute('placeholder', 'ex. Jane Pink');
  1301. $('#' + videoSpanId)[0].appendChild(editableText);
  1302. $('#localVideoContainer .displayname').bind("click", function(e) {
  1303. e.preventDefault();
  1304. $('#localDisplayName').hide();
  1305. $('#editDisplayName').show();
  1306. $('#editDisplayName').focus();
  1307. $('#editDisplayName').select();
  1308. var inputDisplayNameHandler = function(name) {
  1309. if (nickname !== name) {
  1310. nickname = Util.escapeHtml(name);
  1311. window.localStorage.displayname = nickname;
  1312. connection.emuc.addDisplayNameToPresence(nickname);
  1313. connection.emuc.sendPresence();
  1314. Chat.setChatConversationMode(true);
  1315. }
  1316. if (!$('#localDisplayName').is(":visible")) {
  1317. $('#localDisplayName').html(nickname + " (me)");
  1318. $('#localDisplayName').show();
  1319. $('#editDisplayName').hide();
  1320. }
  1321. };
  1322. $('#editDisplayName').one("focusout", function (e) {
  1323. inputDisplayNameHandler(this.value);
  1324. });
  1325. $('#editDisplayName').on('keydown', function (e) {
  1326. if (e.keyCode === 13) {
  1327. e.preventDefault();
  1328. inputDisplayNameHandler(this.value);
  1329. }
  1330. });
  1331. });
  1332. }
  1333. }
  1334. }
  1335. /**
  1336. * Creates the edit display name button.
  1337. *
  1338. * @returns the edit button
  1339. */
  1340. function createEditDisplayNameButton() {
  1341. var editButton = document.createElement('a');
  1342. editButton.className = 'displayname';
  1343. editButton.innerHTML = '<i class="fa fa-pencil"></i>';
  1344. return editButton;
  1345. }
  1346. /**
  1347. * Resizes and repositions videos in full screen mode.
  1348. */
  1349. $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange',
  1350. function() {
  1351. resizeLargeVideoContainer();
  1352. positionLarge();
  1353. isFullScreen = document.fullScreen
  1354. || document.mozFullScreen
  1355. || document.webkitIsFullScreen;
  1356. if (isFullScreen) {
  1357. setView("fullscreen");
  1358. }
  1359. else {
  1360. setView("default");
  1361. }
  1362. });
  1363. /**
  1364. * Sets the current view.
  1365. */
  1366. function setView(viewName) {
  1367. // if (viewName == "fullscreen") {
  1368. // document.getElementById('videolayout_fullscreen').disabled = false;
  1369. // document.getElementById('videolayout_default').disabled = true;
  1370. // }
  1371. // else {
  1372. // document.getElementById('videolayout_default').disabled = false;
  1373. // document.getElementById('videolayout_fullscreen').disabled = true;
  1374. // }
  1375. }