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

videolayout.js 34KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  1. var VideoLayout = (function (my) {
  2. var preMuted = false;
  3. var currentDominantSpeaker = null;
  4. my.changeLocalAudio = function(stream) {
  5. connection.jingle.localAudio = stream;
  6. RTC.attachMediaStream($('#localAudio'), stream);
  7. document.getElementById('localAudio').autoplay = true;
  8. document.getElementById('localAudio').volume = 0;
  9. if (preMuted) {
  10. toggleAudio();
  11. preMuted = false;
  12. }
  13. };
  14. my.changeLocalVideo = function(stream, flipX) {
  15. connection.jingle.localVideo = stream;
  16. var localVideo = document.createElement('video');
  17. localVideo.id = 'localVideo_' + stream.id;
  18. localVideo.autoplay = true;
  19. localVideo.volume = 0; // is it required if audio is separated ?
  20. localVideo.oncontextmenu = function () { return false; };
  21. var localVideoContainer = document.getElementById('localVideoWrapper');
  22. localVideoContainer.appendChild(localVideo);
  23. AudioLevels.updateAudioLevelCanvas();
  24. var localVideoSelector = $('#' + localVideo.id);
  25. // Add click handler to both video and video wrapper elements in case
  26. // there's no video.
  27. localVideoSelector.click(function () {
  28. VideoLayout.handleVideoThumbClicked(localVideo.src);
  29. });
  30. $('#localVideoContainer').click(function () {
  31. VideoLayout.handleVideoThumbClicked(localVideo.src);
  32. });
  33. // Add hover handler
  34. $('#localVideoContainer').hover(
  35. function() {
  36. VideoLayout.showDisplayName('localVideoContainer', true);
  37. },
  38. function() {
  39. if (!VideoLayout.isLargeVideoVisible()
  40. || localVideo.src !== $('#largeVideo').attr('src'))
  41. VideoLayout.showDisplayName('localVideoContainer', false);
  42. }
  43. );
  44. // Add stream ended handler
  45. stream.onended = function () {
  46. localVideoContainer.removeChild(localVideo);
  47. VideoLayout.checkChangeLargeVideo(localVideo.src);
  48. };
  49. // Flip video x axis if needed
  50. flipXLocalVideo = flipX;
  51. if (flipX) {
  52. localVideoSelector.addClass("flipVideoX");
  53. }
  54. // Attach WebRTC stream
  55. RTC.attachMediaStream(localVideoSelector, stream);
  56. localVideoSrc = localVideo.src;
  57. VideoLayout.updateLargeVideo(localVideoSrc, 0);
  58. };
  59. /**
  60. * Checks if removed video is currently displayed and tries to display
  61. * another one instead.
  62. * @param removedVideoSrc src stream identifier of the video.
  63. */
  64. my.checkChangeLargeVideo = function(removedVideoSrc) {
  65. if (removedVideoSrc === $('#largeVideo').attr('src')) {
  66. // this is currently displayed as large
  67. // pick the last visible video in the row
  68. // if nobody else is left, this picks the local video
  69. var pick
  70. = $('#remoteVideos>span[id!="mixedstream"]:visible:last>video')
  71. .get(0);
  72. if (!pick) {
  73. console.info("Last visible video no longer exists");
  74. pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
  75. if (!pick) {
  76. // Try local video
  77. console.info("Fallback to local video...");
  78. pick = $('#remoteVideos>span>span>video').get(0);
  79. }
  80. }
  81. // mute if localvideo
  82. if (pick) {
  83. VideoLayout.updateLargeVideo(pick.src, pick.volume);
  84. } else {
  85. console.warn("Failed to elect large video");
  86. }
  87. }
  88. };
  89. /**
  90. * Updates the large video with the given new video source.
  91. */
  92. my.updateLargeVideo = function(newSrc, vol) {
  93. console.log('hover in', newSrc);
  94. if ($('#largeVideo').attr('src') != newSrc) {
  95. var isVisible = $('#largeVideo').is(':visible');
  96. $('#largeVideo').fadeOut(300, function () {
  97. var oldSrc = $(this).attr('src');
  98. $(this).attr('src', newSrc);
  99. // Screen stream is already rotated
  100. var flipX = (newSrc === localVideoSrc) && flipXLocalVideo;
  101. var videoTransform = document.getElementById('largeVideo')
  102. .style.webkitTransform;
  103. if (flipX && videoTransform !== 'scaleX(-1)') {
  104. document.getElementById('largeVideo').style.webkitTransform
  105. = "scaleX(-1)";
  106. }
  107. else if (!flipX && videoTransform === 'scaleX(-1)') {
  108. document.getElementById('largeVideo').style.webkitTransform
  109. = "none";
  110. }
  111. // Change the way we'll be measuring and positioning large video
  112. var isDesktop = isVideoSrcDesktop(newSrc);
  113. getVideoSize = isDesktop
  114. ? getDesktopVideoSize
  115. : getCameraVideoSize;
  116. getVideoPosition = isDesktop
  117. ? getDesktopVideoPosition
  118. : getCameraVideoPosition;
  119. if (isVisible) {
  120. // Only if the large video is currently visible.
  121. // Disable previous dominant speaker video.
  122. var oldJid = getJidFromVideoSrc(oldSrc);
  123. if (oldJid) {
  124. var oldResourceJid = Strophe.getResourceFromJid(oldJid);
  125. VideoLayout.enableDominantSpeaker(oldResourceJid, false);
  126. }
  127. // Enable new dominant speaker in the remote videos section.
  128. var userJid = getJidFromVideoSrc(newSrc);
  129. if (userJid)
  130. {
  131. var resourceJid = Strophe.getResourceFromJid(userJid);
  132. VideoLayout.enableDominantSpeaker(resourceJid, true);
  133. }
  134. $(this).fadeIn(300);
  135. }
  136. });
  137. }
  138. };
  139. my.handleVideoThumbClicked = function(videoSrc) {
  140. // Restore style for previously focused video
  141. var focusJid = getJidFromVideoSrc(focusedVideoSrc);
  142. var oldContainer = getParticipantContainer(focusJid);
  143. if (oldContainer) {
  144. oldContainer.removeClass("videoContainerFocused");
  145. }
  146. // Unlock current focused.
  147. if (focusedVideoSrc === videoSrc)
  148. {
  149. focusedVideoSrc = null;
  150. var dominantSpeakerVideo = null;
  151. // Enable the currently set dominant speaker.
  152. if (currentDominantSpeaker) {
  153. dominantSpeakerVideo
  154. = $('#participant_' + currentDominantSpeaker + '>video')
  155. .get(0);
  156. if (dominantSpeakerVideo)
  157. VideoLayout.updateLargeVideo(dominantSpeakerVideo.src, 1);
  158. }
  159. return;
  160. }
  161. // Lock new video
  162. focusedVideoSrc = videoSrc;
  163. // Update focused/pinned interface.
  164. var userJid = getJidFromVideoSrc(videoSrc);
  165. if (userJid)
  166. {
  167. var container = getParticipantContainer(userJid);
  168. container.addClass("videoContainerFocused");
  169. }
  170. // Triggers a "video.selected" event. The "false" parameter indicates
  171. // this isn't a prezi.
  172. $(document).trigger("video.selected", [false]);
  173. VideoLayout.updateLargeVideo(videoSrc, 1);
  174. $('audio').each(function (idx, el) {
  175. if (el.id.indexOf('mixedmslabel') !== -1) {
  176. el.volume = 0;
  177. el.volume = 1;
  178. }
  179. });
  180. };
  181. /**
  182. * Positions the large video.
  183. *
  184. * @param videoWidth the stream video width
  185. * @param videoHeight the stream video height
  186. */
  187. my.positionLarge = function (videoWidth, videoHeight) {
  188. var videoSpaceWidth = $('#videospace').width();
  189. var videoSpaceHeight = window.innerHeight;
  190. var videoSize = getVideoSize(videoWidth,
  191. videoHeight,
  192. videoSpaceWidth,
  193. videoSpaceHeight);
  194. var largeVideoWidth = videoSize[0];
  195. var largeVideoHeight = videoSize[1];
  196. var videoPosition = getVideoPosition(largeVideoWidth,
  197. largeVideoHeight,
  198. videoSpaceWidth,
  199. videoSpaceHeight);
  200. var horizontalIndent = videoPosition[0];
  201. var verticalIndent = videoPosition[1];
  202. positionVideo($('#largeVideo'),
  203. largeVideoWidth,
  204. largeVideoHeight,
  205. horizontalIndent, verticalIndent);
  206. };
  207. /**
  208. * Shows/hides the large video.
  209. */
  210. my.setLargeVideoVisible = function(isVisible) {
  211. var largeVideoJid = getJidFromVideoSrc($('#largeVideo').attr('src'));
  212. var resourceJid = Strophe.getResourceFromJid(largeVideoJid);
  213. if (isVisible) {
  214. $('#largeVideo').css({visibility: 'visible'});
  215. $('.watermark').css({visibility: 'visible'});
  216. VideoLayout.enableDominantSpeaker(resourceJid, true);
  217. }
  218. else {
  219. $('#largeVideo').css({visibility: 'hidden'});
  220. $('.watermark').css({visibility: 'hidden'});
  221. VideoLayout.enableDominantSpeaker(resourceJid, false);
  222. }
  223. };
  224. /**
  225. * Indicates if the large video is currently visible.
  226. *
  227. * @return <tt>true</tt> if visible, <tt>false</tt> - otherwise
  228. */
  229. my.isLargeVideoVisible = function() {
  230. return $('#largeVideo').is(':visible');
  231. };
  232. /**
  233. * Checks if container for participant identified by given peerJid exists
  234. * in the document and creates it eventually.
  235. *
  236. * @param peerJid peer Jid to check.
  237. */
  238. my.ensurePeerContainerExists = function(peerJid) {
  239. var peerResource = Strophe.getResourceFromJid(peerJid);
  240. var videoSpanId = 'participant_' + peerResource;
  241. if ($('#' + videoSpanId).length > 0) {
  242. // If there's been a focus change, make sure we add focus related
  243. // interface!!
  244. if (focus && $('#remote_popupmenu_' + peerResource).length <= 0)
  245. addRemoteVideoMenu( peerJid,
  246. document.getElementById(videoSpanId));
  247. return;
  248. }
  249. var container
  250. = VideoLayout.addRemoteVideoContainer(peerJid, videoSpanId);
  251. var nickfield = document.createElement('span');
  252. nickfield.className = "nick";
  253. nickfield.appendChild(document.createTextNode(peerResource));
  254. container.appendChild(nickfield);
  255. VideoLayout.resizeThumbnails();
  256. };
  257. my.addRemoteVideoContainer = function(peerJid, spanId) {
  258. var container = document.createElement('span');
  259. container.id = spanId;
  260. container.className = 'videocontainer';
  261. var remotes = document.getElementById('remoteVideos');
  262. // If the peerJid is null then this video span couldn't be directly
  263. // associated with a participant (this could happen in the case of prezi).
  264. if (focus && peerJid != null)
  265. addRemoteVideoMenu(peerJid, container);
  266. remotes.appendChild(container);
  267. AudioLevels.updateAudioLevelCanvas(peerJid);
  268. return container;
  269. };
  270. /**
  271. * Shows the display name for the given video.
  272. */
  273. my.setDisplayName = function(videoSpanId, displayName, statusMsg) {
  274. var nameSpan = $('#' + videoSpanId + '>span.displayname');
  275. var defaultLocalDisplayName = "Me";
  276. var defaultRemoteDisplayName = "Speaker";
  277. var statusSpan = $('#' + videoSpanId + '>span.status');
  278. if (!statusSpan.length)
  279. {
  280. //Add status span
  281. statusSpan = document.createElement('span');
  282. statusSpan.className = 'status';
  283. statusSpan.id = videoSpanId + '_status';
  284. $('#' + videoSpanId)[0].appendChild(statusSpan);
  285. statusSpan = $('#' + videoSpanId + '>span.status');
  286. }
  287. // Display status
  288. if (statusMsg && statusMsg.length)
  289. {
  290. $('#' + videoSpanId + '_status').text(statusMsg);
  291. statusSpan.get(0).setAttribute("style", "display:inline-block;");
  292. }
  293. else
  294. {
  295. // Hide
  296. statusSpan.get(0).setAttribute("style", "display:none;");
  297. }
  298. // If we already have a display name for this video.
  299. if (nameSpan.length > 0) {
  300. var nameSpanElement = nameSpan.get(0);
  301. if (nameSpanElement.id === 'localDisplayName' &&
  302. $('#localDisplayName').text() !== displayName) {
  303. if (displayName)
  304. $('#localDisplayName').text(displayName + ' (me)');
  305. else
  306. $('#localDisplayName').text(defaultLocalDisplayName);
  307. } else {
  308. if (displayName)
  309. $('#' + videoSpanId + '_name').text(displayName);
  310. else
  311. $('#' + videoSpanId + '_name').text(defaultRemoteDisplayName);
  312. }
  313. } else {
  314. var editButton = null;
  315. nameSpan = document.createElement('span');
  316. nameSpan.className = 'displayname';
  317. $('#' + videoSpanId)[0].appendChild(nameSpan);
  318. if (videoSpanId === 'localVideoContainer') {
  319. editButton = createEditDisplayNameButton();
  320. nameSpan.innerText = defaultLocalDisplayName;
  321. }
  322. else {
  323. nameSpan.innerText = defaultRemoteDisplayName;
  324. }
  325. if (displayName && displayName.length) {
  326. nameSpan.innerText = displayName;
  327. }
  328. if (!editButton) {
  329. nameSpan.id = videoSpanId + '_name';
  330. } else {
  331. nameSpan.id = 'localDisplayName';
  332. $('#' + videoSpanId)[0].appendChild(editButton);
  333. var editableText = document.createElement('input');
  334. editableText.className = 'displayname';
  335. editableText.id = 'editDisplayName';
  336. if (displayName.length) {
  337. editableText.value
  338. = displayName.substring(0, displayName.indexOf(' (me)'));
  339. }
  340. editableText.setAttribute('style', 'display:none;');
  341. editableText.setAttribute('placeholder', 'ex. Jane Pink');
  342. $('#' + videoSpanId)[0].appendChild(editableText);
  343. $('#localVideoContainer .displayname')
  344. .bind("click", function (e) {
  345. e.preventDefault();
  346. $('#localDisplayName').hide();
  347. $('#editDisplayName').show();
  348. $('#editDisplayName').focus();
  349. $('#editDisplayName').select();
  350. var inputDisplayNameHandler = function (name) {
  351. if (nickname !== name) {
  352. nickname = name;
  353. window.localStorage.displayname = nickname;
  354. connection.emuc.addDisplayNameToPresence(nickname);
  355. connection.emuc.sendPresence();
  356. Chat.setChatConversationMode(true);
  357. }
  358. if (!$('#localDisplayName').is(":visible")) {
  359. if (nickname)
  360. $('#localDisplayName').text(nickname + " (me)");
  361. else
  362. $('#localDisplayName')
  363. .text(defaultLocalDisplayName);
  364. $('#localDisplayName').show();
  365. }
  366. $('#editDisplayName').hide();
  367. };
  368. $('#editDisplayName').one("focusout", function (e) {
  369. inputDisplayNameHandler(this.value);
  370. });
  371. $('#editDisplayName').on('keydown', function (e) {
  372. if (e.keyCode === 13) {
  373. e.preventDefault();
  374. inputDisplayNameHandler(this.value);
  375. }
  376. });
  377. });
  378. }
  379. }
  380. };
  381. /**
  382. * Shows/hides the display name on the remote video.
  383. * @param videoSpanId the identifier of the video span element
  384. * @param isShow indicates if the display name should be shown or hidden
  385. */
  386. my.showDisplayName = function(videoSpanId, isShow) {
  387. // FIX: need to use noConflict of jquery, because apparently we're
  388. // using another library that uses $, which conflics with jquery and
  389. // sometimes objects are null because of that!!!!!!!!!
  390. // http://api.jquery.com/jQuery.noConflict/
  391. var nameSpan = jQuery('#' + videoSpanId + '>span.displayname').get(0);
  392. if (isShow) {
  393. if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length)
  394. nameSpan.setAttribute("style", "display:inline-block;");
  395. }
  396. else {
  397. if (nameSpan)
  398. nameSpan.setAttribute("style", "display:none;");
  399. }
  400. };
  401. /**
  402. * Shows a visual indicator for the focus of the conference.
  403. * Currently if we're not the owner of the conference we obtain the focus
  404. * from the connection.jingle.sessions.
  405. */
  406. my.showFocusIndicator = function() {
  407. if (focus !== null) {
  408. var indicatorSpan = $('#localVideoContainer .focusindicator');
  409. if (indicatorSpan.children().length === 0)
  410. {
  411. createFocusIndicatorElement(indicatorSpan[0]);
  412. }
  413. }
  414. else if (Object.keys(connection.jingle.sessions).length > 0) {
  415. // If we're only a participant the focus will be the only session we have.
  416. var session
  417. = connection.jingle.sessions
  418. [Object.keys(connection.jingle.sessions)[0]];
  419. var focusId
  420. = 'participant_' + Strophe.getResourceFromJid(session.peerjid);
  421. var focusContainer = document.getElementById(focusId);
  422. if (!focusContainer) {
  423. console.error("No focus container!");
  424. return;
  425. }
  426. var indicatorSpan = $('#' + focusId + ' .focusindicator');
  427. if (!indicatorSpan || indicatorSpan.length === 0) {
  428. indicatorSpan = document.createElement('span');
  429. indicatorSpan.className = 'focusindicator';
  430. focusContainer.appendChild(indicatorSpan);
  431. createFocusIndicatorElement(indicatorSpan);
  432. }
  433. }
  434. };
  435. /**
  436. * Shows video muted indicator over small videos.
  437. */
  438. my.showVideoIndicator = function(videoSpanId, isMuted) {
  439. var videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted');
  440. if (isMuted === 'false') {
  441. if (videoMutedSpan.length > 0) {
  442. videoMutedSpan.remove();
  443. }
  444. }
  445. else {
  446. var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted');
  447. videoMutedSpan = document.createElement('span');
  448. videoMutedSpan.className = 'videoMuted';
  449. if (audioMutedSpan) {
  450. videoMutedSpan.right = '30px';
  451. }
  452. $('#' + videoSpanId)[0].appendChild(videoMutedSpan);
  453. var mutedIndicator = document.createElement('i');
  454. mutedIndicator.className = 'icon-camera-disabled';
  455. Util.setTooltip(mutedIndicator,
  456. "Participant has<br/>stopped the camera.",
  457. "top");
  458. videoMutedSpan.appendChild(mutedIndicator);
  459. }
  460. };
  461. /**
  462. * Shows audio muted indicator over small videos.
  463. */
  464. my.showAudioIndicator = function(videoSpanId, isMuted) {
  465. var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted');
  466. if (isMuted === 'false') {
  467. if (audioMutedSpan.length > 0) {
  468. audioMutedSpan.remove();
  469. }
  470. }
  471. else {
  472. var videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted');
  473. audioMutedSpan = document.createElement('span');
  474. audioMutedSpan.className = 'audioMuted';
  475. Util.setTooltip(audioMutedSpan,
  476. "Participant is muted",
  477. "top");
  478. if (videoMutedSpan) {
  479. audioMutedSpan.right = '30px';
  480. }
  481. $('#' + videoSpanId)[0].appendChild(audioMutedSpan);
  482. var mutedIndicator = document.createElement('i');
  483. mutedIndicator.className = 'icon-mic-disabled';
  484. audioMutedSpan.appendChild(mutedIndicator);
  485. }
  486. };
  487. /**
  488. * Resizes the large video container.
  489. */
  490. my.resizeLargeVideoContainer = function () {
  491. Chat.resizeChat();
  492. var availableHeight = window.innerHeight;
  493. var availableWidth = Util.getAvailableVideoWidth();
  494. if (availableWidth < 0 || availableHeight < 0) return;
  495. $('#videospace').width(availableWidth);
  496. $('#videospace').height(availableHeight);
  497. $('#largeVideoContainer').width(availableWidth);
  498. $('#largeVideoContainer').height(availableHeight);
  499. VideoLayout.resizeThumbnails();
  500. };
  501. /**
  502. * Resizes thumbnails.
  503. */
  504. my.resizeThumbnails = function() {
  505. var videoSpaceWidth = $('#remoteVideos').width();
  506. var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
  507. var width = thumbnailSize[0];
  508. var height = thumbnailSize[1];
  509. // size videos so that while keeping AR and max height, we have a
  510. // nice fit
  511. $('#remoteVideos').height(height);
  512. $('#remoteVideos>span').width(width);
  513. $('#remoteVideos>span').height(height);
  514. $(document).trigger("remotevideo.resized", [width, height]);
  515. };
  516. /**
  517. * Enables the dominant speaker UI.
  518. *
  519. * @param resourceJid the jid indicating the video element to
  520. * activate/deactivate
  521. * @param isEnable indicates if the dominant speaker should be enabled or
  522. * disabled
  523. */
  524. my.enableDominantSpeaker = function(resourceJid, isEnable) {
  525. var displayName = resourceJid;
  526. var nameSpan = $('#participant_' + resourceJid + '>span.displayname');
  527. if (nameSpan.length > 0)
  528. displayName = nameSpan.text();
  529. console.log("UI enable dominant speaker",
  530. displayName,
  531. resourceJid,
  532. isEnable);
  533. var videoSpanId = null;
  534. var videoContainerId = null;
  535. if (resourceJid
  536. === Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
  537. videoSpanId = 'localVideoWrapper';
  538. videoContainerId = 'localVideoContainer';
  539. }
  540. else {
  541. videoSpanId = 'participant_' + resourceJid;
  542. videoContainerId = videoSpanId;
  543. }
  544. videoSpan = document.getElementById(videoContainerId);
  545. if (!videoSpan) {
  546. console.error("No video element for jid", resourceJid);
  547. return;
  548. }
  549. var video = $('#' + videoSpanId + '>video');
  550. if (video && video.length > 0) {
  551. if (isEnable) {
  552. VideoLayout.showDisplayName(videoContainerId, true);
  553. if (!videoSpan.classList.contains("dominantspeaker"))
  554. videoSpan.classList.add("dominantspeaker");
  555. video.css({visibility: 'hidden'});
  556. }
  557. else {
  558. VideoLayout.showDisplayName(videoContainerId, false);
  559. if (videoSpan.classList.contains("dominantspeaker"))
  560. videoSpan.classList.remove("dominantspeaker");
  561. video.css({visibility: 'visible'});
  562. }
  563. }
  564. };
  565. /**
  566. * Gets the selector of video thumbnail container for the user identified by
  567. * given <tt>userJid</tt>
  568. * @param userJid user's Jid for whom we want to get the video container.
  569. */
  570. function getParticipantContainer(userJid)
  571. {
  572. if (!userJid)
  573. return null;
  574. if (userJid === connection.emuc.myroomjid)
  575. return $("#localVideoContainer");
  576. else
  577. return $("#participant_" + Strophe.getResourceFromJid(userJid));
  578. }
  579. /**
  580. * Sets the size and position of the given video element.
  581. *
  582. * @param video the video element to position
  583. * @param width the desired video width
  584. * @param height the desired video height
  585. * @param horizontalIndent the left and right indent
  586. * @param verticalIndent the top and bottom indent
  587. */
  588. function positionVideo(video,
  589. width,
  590. height,
  591. horizontalIndent,
  592. verticalIndent) {
  593. video.width(width);
  594. video.height(height);
  595. video.css({ top: verticalIndent + 'px',
  596. bottom: verticalIndent + 'px',
  597. left: horizontalIndent + 'px',
  598. right: horizontalIndent + 'px'});
  599. }
  600. /**
  601. * Calculates the thumbnail size.
  602. */
  603. my.calculateThumbnailSize = function (videoSpaceWidth) {
  604. // Calculate the available height, which is the inner window height minus
  605. // 39px for the header minus 2px for the delimiter lines on the top and
  606. // bottom of the large video, minus the 36px space inside the remoteVideos
  607. // container used for highlighting shadow.
  608. var availableHeight = 100;
  609. var numvids = $('#remoteVideos>span:visible').length;
  610. // Remove the 3px borders arround videos and border around the remote
  611. // videos area
  612. var availableWinWidth = videoSpaceWidth - 2 * 3 * numvids - 50;
  613. var availableWidth = availableWinWidth / numvids;
  614. var aspectRatio = 16.0 / 9.0;
  615. var maxHeight = Math.min(160, availableHeight);
  616. availableHeight = Math.min(maxHeight, availableWidth / aspectRatio);
  617. if (availableHeight < availableWidth / aspectRatio) {
  618. availableWidth = Math.floor(availableHeight * aspectRatio);
  619. }
  620. return [availableWidth, availableHeight];
  621. };
  622. /**
  623. * Returns an array of the video dimensions, so that it keeps it's aspect
  624. * ratio and fits available area with it's larger dimension. This method
  625. * ensures that whole video will be visible and can leave empty areas.
  626. *
  627. * @return an array with 2 elements, the video width and the video height
  628. */
  629. function getDesktopVideoSize(videoWidth,
  630. videoHeight,
  631. videoSpaceWidth,
  632. videoSpaceHeight) {
  633. if (!videoWidth)
  634. videoWidth = currentVideoWidth;
  635. if (!videoHeight)
  636. videoHeight = currentVideoHeight;
  637. var aspectRatio = videoWidth / videoHeight;
  638. var availableWidth = Math.max(videoWidth, videoSpaceWidth);
  639. var availableHeight = Math.max(videoHeight, videoSpaceHeight);
  640. videoSpaceHeight -= $('#remoteVideos').outerHeight();
  641. if (availableWidth / aspectRatio >= videoSpaceHeight)
  642. {
  643. availableHeight = videoSpaceHeight;
  644. availableWidth = availableHeight * aspectRatio;
  645. }
  646. if (availableHeight * aspectRatio >= videoSpaceWidth)
  647. {
  648. availableWidth = videoSpaceWidth;
  649. availableHeight = availableWidth / aspectRatio;
  650. }
  651. return [availableWidth, availableHeight];
  652. }
  653. /**
  654. * Creates the edit display name button.
  655. *
  656. * @returns the edit button
  657. */
  658. function createEditDisplayNameButton() {
  659. var editButton = document.createElement('a');
  660. editButton.className = 'displayname';
  661. Util.setTooltip(editButton,
  662. 'Click to edit your<br/>display name',
  663. "top");
  664. editButton.innerHTML = '<i class="fa fa-pencil"></i>';
  665. return editButton;
  666. }
  667. /**
  668. * Creates the element indicating the focus of the conference.
  669. *
  670. * @param parentElement the parent element where the focus indicator will
  671. * be added
  672. */
  673. function createFocusIndicatorElement(parentElement) {
  674. var focusIndicator = document.createElement('i');
  675. focusIndicator.className = 'fa fa-star';
  676. parentElement.appendChild(focusIndicator);
  677. Util.setTooltip(parentElement,
  678. "The owner of<br/>this conference",
  679. "top");
  680. }
  681. /**
  682. * Updates the remote video menu.
  683. *
  684. * @param jid the jid indicating the video for which we're adding a menu.
  685. * @param isMuted indicates the current mute state
  686. */
  687. my.updateRemoteVideoMenu = function(jid, isMuted) {
  688. var muteMenuItem
  689. = $('#remote_popupmenu_'
  690. + Strophe.getResourceFromJid(jid)
  691. + '>li>a.mutelink');
  692. var mutedIndicator = "<i class='icon-mic-disabled'></i>";
  693. if (muteMenuItem.length) {
  694. var muteLink = muteMenuItem.get(0);
  695. if (isMuted === 'true') {
  696. muteLink.innerHTML = mutedIndicator + ' Muted';
  697. muteLink.className = 'mutelink disabled';
  698. }
  699. else {
  700. muteLink.innerHTML = mutedIndicator + ' Mute';
  701. muteLink.className = 'mutelink';
  702. }
  703. }
  704. };
  705. /**
  706. * Returns the current dominant speaker resource jid.
  707. */
  708. my.getDominantSpeakerResourceJid = function () {
  709. return currentDominantSpeaker;
  710. };
  711. /**
  712. * Adds the remote video menu element for the given <tt>jid</tt> in the
  713. * given <tt>parentElement</tt>.
  714. *
  715. * @param jid the jid indicating the video for which we're adding a menu.
  716. * @param parentElement the parent element where this menu will be added
  717. */
  718. function addRemoteVideoMenu(jid, parentElement) {
  719. var spanElement = document.createElement('span');
  720. spanElement.className = 'remotevideomenu';
  721. parentElement.appendChild(spanElement);
  722. var menuElement = document.createElement('i');
  723. menuElement.className = 'fa fa-angle-down';
  724. menuElement.title = 'Remote user controls';
  725. spanElement.appendChild(menuElement);
  726. // <ul class="popupmenu">
  727. // <li><a href="#">Mute</a></li>
  728. // <li><a href="#">Eject</a></li>
  729. // </ul>
  730. var popupmenuElement = document.createElement('ul');
  731. popupmenuElement.className = 'popupmenu';
  732. popupmenuElement.id
  733. = 'remote_popupmenu_' + Strophe.getResourceFromJid(jid);
  734. spanElement.appendChild(popupmenuElement);
  735. var muteMenuItem = document.createElement('li');
  736. var muteLinkItem = document.createElement('a');
  737. var mutedIndicator = "<i class='icon-mic-disabled'></i>";
  738. if (!mutedAudios[jid]) {
  739. muteLinkItem.innerHTML = mutedIndicator + 'Mute';
  740. muteLinkItem.className = 'mutelink';
  741. }
  742. else {
  743. muteLinkItem.innerHTML = mutedIndicator + ' Muted';
  744. muteLinkItem.className = 'mutelink disabled';
  745. }
  746. muteLinkItem.onclick = function(){
  747. if ($(this).attr('disabled') != undefined) {
  748. event.preventDefault();
  749. }
  750. var isMute = !mutedAudios[jid];
  751. connection.moderate.setMute(jid, isMute);
  752. popupmenuElement.setAttribute('style', 'display:none;');
  753. if (isMute) {
  754. this.innerHTML = mutedIndicator + ' Muted';
  755. this.className = 'mutelink disabled';
  756. }
  757. else {
  758. this.innerHTML = mutedIndicator + ' Mute';
  759. this.className = 'mutelink';
  760. }
  761. };
  762. muteMenuItem.appendChild(muteLinkItem);
  763. popupmenuElement.appendChild(muteMenuItem);
  764. var ejectIndicator = "<i class='fa fa-eject'></i>";
  765. var ejectMenuItem = document.createElement('li');
  766. var ejectLinkItem = document.createElement('a');
  767. ejectLinkItem.innerHTML = ejectIndicator + ' Kick out';
  768. ejectLinkItem.onclick = function(){
  769. connection.moderate.eject(jid);
  770. popupmenuElement.setAttribute('style', 'display:none;');
  771. };
  772. ejectMenuItem.appendChild(ejectLinkItem);
  773. popupmenuElement.appendChild(ejectMenuItem);
  774. }
  775. /**
  776. * On audio muted event.
  777. */
  778. $(document).bind('audiomuted.muc', function (event, jid, isMuted) {
  779. var videoSpanId = null;
  780. if (jid === connection.emuc.myroomjid) {
  781. videoSpanId = 'localVideoContainer';
  782. } else {
  783. VideoLayout.ensurePeerContainerExists(jid);
  784. videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
  785. }
  786. if (focus) {
  787. mutedAudios[jid] = isMuted;
  788. VideoLayout.updateRemoteVideoMenu(jid, isMuted);
  789. }
  790. if (videoSpanId)
  791. VideoLayout.showAudioIndicator(videoSpanId, isMuted);
  792. });
  793. /**
  794. * On video muted event.
  795. */
  796. $(document).bind('videomuted.muc', function (event, jid, isMuted) {
  797. var videoSpanId = null;
  798. if (jid === connection.emuc.myroomjid) {
  799. videoSpanId = 'localVideoContainer';
  800. } else {
  801. VideoLayout.ensurePeerContainerExists(jid);
  802. videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
  803. }
  804. if (videoSpanId)
  805. VideoLayout.showVideoIndicator(videoSpanId, isMuted);
  806. });
  807. /**
  808. * On dominant speaker changed event.
  809. */
  810. $(document).bind('dominantspeakerchanged', function (event, resourceJid) {
  811. // We ignore local user events.
  812. if (resourceJid
  813. === Strophe.getResourceFromJid(connection.emuc.myroomjid))
  814. return;
  815. // Obtain container for new dominant speaker.
  816. var container = document.getElementById(
  817. 'participant_' + resourceJid);
  818. // Update the current dominant speaker.
  819. if (resourceJid !== currentDominantSpeaker)
  820. currentDominantSpeaker = resourceJid;
  821. else
  822. return;
  823. // Local video will not have container found, but that's ok
  824. // since we don't want to switch to local video.
  825. if (container && !focusedVideoSrc)
  826. {
  827. var video = container.getElementsByTagName("video");
  828. if (video.length)
  829. {
  830. VideoLayout.updateLargeVideo(video[0].src);
  831. }
  832. }
  833. });
  834. return my;
  835. }(VideoLayout || {}));