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

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