Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

LargeVideo.js 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. var Avatar = require("../avatar/Avatar");
  2. var RTCBrowserType = require("../../RTC/RTCBrowserType");
  3. var UIUtil = require("../util/UIUtil");
  4. var UIEvents = require("../../../service/UI/UIEvents");
  5. var xmpp = require("../../xmpp/xmpp");
  6. var ToolbarToggler = require("../toolbars/ToolbarToggler");
  7. // FIXME: With Temasys we have to re-select everytime
  8. //var video = $('#largeVideo');
  9. var currentVideoWidth = null;
  10. var currentVideoHeight = null;
  11. // By default we use camera
  12. var getVideoSize = getCameraVideoSize;
  13. var getVideoPosition = getCameraVideoPosition;
  14. /**
  15. * The small video instance that is displayed in the large video
  16. * @type {SmallVideo}
  17. */
  18. var currentSmallVideo = null;
  19. /**
  20. * Indicates whether the large video is enabled.
  21. * @type {boolean}
  22. */
  23. var isEnabled = true;
  24. /**
  25. * Current large video state.
  26. * Possible values - video, prezi or etherpad.
  27. * @type {string}
  28. */
  29. var state = "video";
  30. /**
  31. * Returns the html element associated with the passed state of large video
  32. * @param state the state.
  33. * @returns {JQuery|*|jQuery|HTMLElement} the container.
  34. */
  35. function getContainerByState(state)
  36. {
  37. var selector = null;
  38. switch (state)
  39. {
  40. case "video":
  41. selector = "#largeVideo";
  42. break;
  43. case "etherpad":
  44. selector = "#etherpad>iframe";
  45. break;
  46. case "prezi":
  47. selector = "#presentation>iframe";
  48. break;
  49. }
  50. return (selector !== null)? $(selector) : null;
  51. }
  52. /**
  53. * Sets the size and position of the given video element.
  54. *
  55. * @param video the video element to position
  56. * @param width the desired video width
  57. * @param height the desired video height
  58. * @param horizontalIndent the left and right indent
  59. * @param verticalIndent the top and bottom indent
  60. */
  61. function positionVideo(video,
  62. width,
  63. height,
  64. horizontalIndent,
  65. verticalIndent,
  66. animate) {
  67. if(animate)
  68. {
  69. video.animate({
  70. width: width,
  71. height: height,
  72. top: verticalIndent,
  73. bottom: verticalIndent,
  74. left: horizontalIndent,
  75. right: horizontalIndent
  76. },
  77. {
  78. queue: false,
  79. duration: 500
  80. });
  81. }
  82. else
  83. {
  84. video.width(width);
  85. video.height(height);
  86. video.css({ top: verticalIndent + 'px',
  87. bottom: verticalIndent + 'px',
  88. left: horizontalIndent + 'px',
  89. right: horizontalIndent + 'px'});
  90. }
  91. }
  92. /**
  93. * Returns an array of the video dimensions, so that it keeps it's aspect
  94. * ratio and fits available area with it's larger dimension. This method
  95. * ensures that whole video will be visible and can leave empty areas.
  96. *
  97. * @return an array with 2 elements, the video width and the video height
  98. */
  99. function getDesktopVideoSize(videoWidth,
  100. videoHeight,
  101. videoSpaceWidth,
  102. videoSpaceHeight) {
  103. if (!videoWidth)
  104. videoWidth = currentVideoWidth;
  105. if (!videoHeight)
  106. videoHeight = currentVideoHeight;
  107. var aspectRatio = videoWidth / videoHeight;
  108. var availableWidth = Math.max(videoWidth, videoSpaceWidth);
  109. var availableHeight = Math.max(videoHeight, videoSpaceHeight);
  110. videoSpaceHeight -= $('#remoteVideos').outerHeight();
  111. if (availableWidth / aspectRatio >= videoSpaceHeight)
  112. {
  113. availableHeight = videoSpaceHeight;
  114. availableWidth = availableHeight * aspectRatio;
  115. }
  116. if (availableHeight * aspectRatio >= videoSpaceWidth)
  117. {
  118. availableWidth = videoSpaceWidth;
  119. availableHeight = availableWidth / aspectRatio;
  120. }
  121. return [availableWidth, availableHeight];
  122. }
  123. /**
  124. * Returns an array of the video horizontal and vertical indents,
  125. * so that if fits its parent.
  126. *
  127. * @return an array with 2 elements, the horizontal indent and the vertical
  128. * indent
  129. */
  130. function getCameraVideoPosition(videoWidth,
  131. videoHeight,
  132. videoSpaceWidth,
  133. videoSpaceHeight) {
  134. // Parent height isn't completely calculated when we position the video in
  135. // full screen mode and this is why we use the screen height in this case.
  136. // Need to think it further at some point and implement it properly.
  137. var isFullScreen = document.fullScreen ||
  138. document.mozFullScreen ||
  139. document.webkitIsFullScreen;
  140. if (isFullScreen)
  141. videoSpaceHeight = window.innerHeight;
  142. var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
  143. var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
  144. return [horizontalIndent, verticalIndent];
  145. }
  146. /**
  147. * Returns an array of the video horizontal and vertical indents.
  148. * Centers horizontally and top aligns vertically.
  149. *
  150. * @return an array with 2 elements, the horizontal indent and the vertical
  151. * indent
  152. */
  153. function getDesktopVideoPosition(videoWidth,
  154. videoHeight,
  155. videoSpaceWidth,
  156. videoSpaceHeight) {
  157. var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
  158. var verticalIndent = 0;// Top aligned
  159. return [horizontalIndent, verticalIndent];
  160. }
  161. /**
  162. * Returns an array of the video dimensions, so that it covers the screen.
  163. * It leaves no empty areas, but some parts of the video might not be visible.
  164. *
  165. * @return an array with 2 elements, the video width and the video height
  166. */
  167. function getCameraVideoSize(videoWidth,
  168. videoHeight,
  169. videoSpaceWidth,
  170. videoSpaceHeight) {
  171. if (!videoWidth)
  172. videoWidth = currentVideoWidth;
  173. if (!videoHeight)
  174. videoHeight = currentVideoHeight;
  175. var aspectRatio = videoWidth / videoHeight;
  176. var availableWidth = Math.max(videoWidth, videoSpaceWidth);
  177. var availableHeight = Math.max(videoHeight, videoSpaceHeight);
  178. if (availableWidth / aspectRatio < videoSpaceHeight) {
  179. availableHeight = videoSpaceHeight;
  180. availableWidth = availableHeight * aspectRatio;
  181. }
  182. if (availableHeight * aspectRatio < videoSpaceWidth) {
  183. availableWidth = videoSpaceWidth;
  184. availableHeight = availableWidth / aspectRatio;
  185. }
  186. return [availableWidth, availableHeight];
  187. }
  188. /**
  189. * Updates the src of the active speaker avatar
  190. * @param jid of the current active speaker
  191. */
  192. function updateActiveSpeakerAvatarSrc() {
  193. var avatar = $("#activeSpeakerAvatar")[0];
  194. var jid = currentSmallVideo.peerJid;
  195. var url = Avatar.getActiveSpeakerUrl(jid);
  196. if (avatar.src === url)
  197. return;
  198. if (jid) {
  199. avatar.src = url;
  200. currentSmallVideo.showAvatar();
  201. }
  202. }
  203. /**
  204. * Change the video source of the large video.
  205. * @param isVisible
  206. */
  207. function changeVideo(isVisible) {
  208. if (!currentSmallVideo) {
  209. console.error("Unable to change large video - no 'currentSmallVideo'");
  210. return;
  211. }
  212. updateActiveSpeakerAvatarSrc();
  213. APP.RTC.setVideoSrc($('#largeVideo')[0], currentSmallVideo.getSrc());
  214. var videoTransform = document.getElementById('largeVideo')
  215. .style.webkitTransform;
  216. var flipX = currentSmallVideo.flipX;
  217. if (flipX && videoTransform !== 'scaleX(-1)') {
  218. document.getElementById('largeVideo').style.webkitTransform
  219. = "scaleX(-1)";
  220. }
  221. else if (!flipX && videoTransform === 'scaleX(-1)') {
  222. document.getElementById('largeVideo').style.webkitTransform
  223. = "none";
  224. }
  225. var isDesktop = APP.RTC.isVideoSrcDesktop(currentSmallVideo.peerJid);
  226. // Change the way we'll be measuring and positioning large video
  227. getVideoSize = isDesktop
  228. ? getDesktopVideoSize
  229. : getCameraVideoSize;
  230. getVideoPosition = isDesktop
  231. ? getDesktopVideoPosition
  232. : getCameraVideoPosition;
  233. // Only if the large video is currently visible.
  234. if (isVisible) {
  235. LargeVideo.VideoLayout.largeVideoUpdated(currentSmallVideo);
  236. $('#largeVideo').fadeIn(300);
  237. }
  238. }
  239. /**
  240. * Creates the html elements for the large video.
  241. */
  242. function createLargeVideoHTML()
  243. {
  244. var html = '<div id="largeVideoContainer" class="videocontainer">';
  245. html += '<div id="presentation"></div>' +
  246. '<div id="etherpad"></div>' +
  247. '<a target="_new"><div class="watermark leftwatermark"></div></a>' +
  248. '<a target="_new"><div class="watermark rightwatermark"></div></a>' +
  249. '<a class="poweredby" href="http://jitsi.org" target="_new" >' +
  250. '<span data-i18n="poweredby"></span> jitsi.org' +
  251. '</a>'+
  252. '<div id="activeSpeaker">' +
  253. '<img id="activeSpeakerAvatar" src=""/>' +
  254. '<canvas id="activeSpeakerAudioLevel"></canvas>' +
  255. '</div>' +
  256. '<video id="largeVideo" autoplay oncontextmenu="return false;"></video>';
  257. html += '</div>';
  258. $(html).prependTo("#videospace");
  259. if (interfaceConfig.SHOW_JITSI_WATERMARK) {
  260. var leftWatermarkDiv
  261. = $("#largeVideoContainer div[class='watermark leftwatermark']");
  262. leftWatermarkDiv.css({display: 'block'});
  263. leftWatermarkDiv.parent().get(0).href
  264. = interfaceConfig.JITSI_WATERMARK_LINK;
  265. }
  266. if (interfaceConfig.SHOW_BRAND_WATERMARK) {
  267. var rightWatermarkDiv
  268. = $("#largeVideoContainer div[class='watermark rightwatermark']");
  269. rightWatermarkDiv.css({display: 'block'});
  270. rightWatermarkDiv.parent().get(0).href
  271. = interfaceConfig.BRAND_WATERMARK_LINK;
  272. rightWatermarkDiv.get(0).style.backgroundImage
  273. = "url(images/rightwatermark.png)";
  274. }
  275. if (interfaceConfig.SHOW_POWERED_BY) {
  276. $("#largeVideoContainer>a[class='poweredby']").css({display: 'block'});
  277. }
  278. if (!RTCBrowserType.isIExplorer()) {
  279. $('#largeVideo').volume = 0;
  280. }
  281. }
  282. var LargeVideo = {
  283. init: function (VideoLayout, emitter) {
  284. if(!isEnabled)
  285. return;
  286. createLargeVideoHTML();
  287. this.VideoLayout = VideoLayout;
  288. this.eventEmitter = emitter;
  289. this.eventEmitter.emit(UIEvents.LARGEVIDEO_INIT);
  290. var self = this;
  291. // Listen for large video size updates
  292. var largeVideo = $('#largeVideo')[0];
  293. var onplaying = function (arg1, arg2, arg3) {
  294. // re-select
  295. if (RTCBrowserType.isTemasysPluginUsed())
  296. largeVideo = $('#largeVideo')[0];
  297. currentVideoWidth = largeVideo.videoWidth;
  298. currentVideoHeight = largeVideo.videoHeight;
  299. self.position(currentVideoWidth, currentVideoHeight);
  300. };
  301. largeVideo.onplaying = onplaying;
  302. },
  303. /**
  304. * Indicates if the large video is currently visible.
  305. *
  306. * @return <tt>true</tt> if visible, <tt>false</tt> - otherwise
  307. */
  308. isLargeVideoVisible: function() {
  309. return $('#largeVideo').is(':visible');
  310. },
  311. /**
  312. * Returns <tt>true</tt> if the user is currently displayed on large video.
  313. */
  314. isCurrentlyOnLarge: function (resourceJid) {
  315. return currentSmallVideo && resourceJid &&
  316. currentSmallVideo.getResourceJid() === resourceJid;
  317. },
  318. /**
  319. * Updates the large video with the given new video source.
  320. */
  321. updateLargeVideo: function (resourceJid, forceUpdate) {
  322. if(!isEnabled)
  323. return;
  324. var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid);
  325. console.log('hover in ' + resourceJid + ', video: ', newSmallVideo);
  326. if (!LargeVideo.isCurrentlyOnLarge(resourceJid) || forceUpdate) {
  327. $('#activeSpeaker').css('visibility', 'hidden');
  328. var oldSmallVideo = null;
  329. if (currentSmallVideo) {
  330. oldSmallVideo = currentSmallVideo;
  331. }
  332. currentSmallVideo = newSmallVideo;
  333. var oldJid = null;
  334. if (oldSmallVideo)
  335. oldJid = oldSmallVideo.peerJid;
  336. if (oldJid !== resourceJid) {
  337. // we want the notification to trigger even if userJid is undefined,
  338. // or null.
  339. this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, resourceJid);
  340. }
  341. if (RTCBrowserType.isSafari()) {
  342. // FIXME In Safari fadeOut works only for the first time
  343. changeVideo(this.isLargeVideoVisible());
  344. } else {
  345. $('#largeVideo').fadeOut(300,
  346. changeVideo.bind($('#largeVideo'), this.isLargeVideoVisible()));
  347. }
  348. } else {
  349. if (currentSmallVideo) {
  350. currentSmallVideo.showAvatar();
  351. }
  352. }
  353. },
  354. /**
  355. * Shows/hides the large video.
  356. */
  357. setLargeVideoVisible: function(isVisible) {
  358. if(!isEnabled)
  359. return;
  360. if (isVisible) {
  361. $('#largeVideo').css({visibility: 'visible'});
  362. $('.watermark').css({visibility: 'visible'});
  363. if(currentSmallVideo)
  364. currentSmallVideo.enableDominantSpeaker(true);
  365. }
  366. else {
  367. $('#largeVideo').css({visibility: 'hidden'});
  368. $('#activeSpeaker').css('visibility', 'hidden');
  369. $('.watermark').css({visibility: 'hidden'});
  370. if(currentSmallVideo)
  371. currentSmallVideo.enableDominantSpeaker(false);
  372. }
  373. },
  374. onVideoTypeChanged: function (jid) {
  375. if(!isEnabled)
  376. return;
  377. var resourceJid = Strophe.getResourceFromJid(jid);
  378. if (LargeVideo.isCurrentlyOnLarge(resourceJid))
  379. {
  380. var isDesktop = APP.RTC.isVideoSrcDesktop(jid);
  381. getVideoSize = isDesktop
  382. ? getDesktopVideoSize
  383. : getCameraVideoSize;
  384. getVideoPosition = isDesktop
  385. ? getDesktopVideoPosition
  386. : getCameraVideoPosition;
  387. this.position(null, null);
  388. }
  389. },
  390. /**
  391. * Positions the large video.
  392. *
  393. * @param videoWidth the stream video width
  394. * @param videoHeight the stream video height
  395. */
  396. position: function (videoWidth, videoHeight,
  397. videoSpaceWidth, videoSpaceHeight, animate) {
  398. if(!isEnabled)
  399. return;
  400. if(!videoSpaceWidth)
  401. videoSpaceWidth = $('#videospace').width();
  402. if(!videoSpaceHeight)
  403. videoSpaceHeight = window.innerHeight;
  404. var videoSize = getVideoSize(videoWidth,
  405. videoHeight,
  406. videoSpaceWidth,
  407. videoSpaceHeight);
  408. var largeVideoWidth = videoSize[0];
  409. var largeVideoHeight = videoSize[1];
  410. var videoPosition = getVideoPosition(largeVideoWidth,
  411. largeVideoHeight,
  412. videoSpaceWidth,
  413. videoSpaceHeight);
  414. var horizontalIndent = videoPosition[0];
  415. var verticalIndent = videoPosition[1];
  416. positionVideo($('#largeVideo'),
  417. largeVideoWidth,
  418. largeVideoHeight,
  419. horizontalIndent, verticalIndent, animate);
  420. },
  421. resize: function (animate, isVisible, completeFunction) {
  422. if(!isEnabled)
  423. return;
  424. var availableHeight = window.innerHeight;
  425. var availableWidth = UIUtil.getAvailableVideoWidth(isVisible);
  426. if (availableWidth < 0 || availableHeight < 0) return;
  427. var avatarSize = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE;
  428. var top = availableHeight / 2 - avatarSize / 4 * 3;
  429. $('#activeSpeaker').css('top', top);
  430. if(animate)
  431. {
  432. $('#videospace').animate({
  433. right: window.innerWidth - availableWidth,
  434. width: availableWidth,
  435. height: availableHeight
  436. },
  437. {
  438. queue: false,
  439. duration: 500,
  440. complete: completeFunction
  441. });
  442. $('#largeVideoContainer').animate({
  443. width: availableWidth,
  444. height: availableHeight
  445. },
  446. {
  447. queue: false,
  448. duration: 500
  449. });
  450. }
  451. else
  452. {
  453. $('#videospace').width(availableWidth);
  454. $('#videospace').height(availableHeight);
  455. $('#largeVideoContainer').width(availableWidth);
  456. $('#largeVideoContainer').height(availableHeight);
  457. }
  458. return [availableWidth, availableHeight];
  459. },
  460. resizeVideoAreaAnimated: function (isVisible, completeFunction) {
  461. if(!isEnabled)
  462. return;
  463. var size = this.resize(true, isVisible, completeFunction);
  464. this.position(null, null, size[0], size[1], true);
  465. },
  466. getResourceJid: function () {
  467. return currentSmallVideo ? currentSmallVideo.getResourceJid() : null;
  468. },
  469. updateAvatar: function (resourceJid) {
  470. if(!isEnabled)
  471. return;
  472. if (resourceJid === this.getResourceJid()) {
  473. updateActiveSpeakerAvatarSrc();
  474. }
  475. },
  476. showAvatar: function (resourceJid, show) {
  477. if(!isEnabled)
  478. return;
  479. if(this.getResourceJid() === resourceJid
  480. && state === "video")
  481. {
  482. $("#largeVideo").css("visibility", show ? "hidden" : "visible");
  483. $('#activeSpeaker').css("visibility", show ? "visible" : "hidden");
  484. return true;
  485. }
  486. return false;
  487. },
  488. /**
  489. * Disables the large video
  490. */
  491. disable: function () {
  492. isEnabled = false;
  493. },
  494. /**
  495. * Enables the large video
  496. */
  497. enable: function () {
  498. isEnabled = true;
  499. },
  500. /**
  501. * Returns true if the video is enabled.
  502. */
  503. isEnabled: function () {
  504. return isEnabled;
  505. },
  506. /**
  507. * Creates the iframe used by the etherpad
  508. * @param src the value for src attribute
  509. * @param onloadHandler handler executed when the iframe loads it content
  510. * @returns {HTMLElement} the iframe
  511. */
  512. createEtherpadIframe: function (src, onloadHandler) {
  513. if(!isEnabled)
  514. return;
  515. var etherpadIFrame = document.createElement('iframe');
  516. etherpadIFrame.src = src;
  517. etherpadIFrame.frameBorder = 0;
  518. etherpadIFrame.scrolling = "no";
  519. etherpadIFrame.width = $('#largeVideoContainer').width() || 640;
  520. etherpadIFrame.height = $('#largeVideoContainer').height() || 480;
  521. etherpadIFrame.setAttribute('style', 'visibility: hidden;');
  522. document.getElementById('etherpad').appendChild(etherpadIFrame);
  523. etherpadIFrame.onload = onloadHandler;
  524. return etherpadIFrame;
  525. },
  526. /**
  527. * Changes the state of the large video.
  528. * Possible values - video, prezi, etherpad.
  529. * @param newState - the new state
  530. */
  531. setState: function (newState) {
  532. if(state === newState)
  533. return;
  534. var currentContainer = getContainerByState(state);
  535. if(!currentContainer)
  536. return;
  537. var self = this;
  538. var oldState = state;
  539. switch (newState)
  540. {
  541. case "etherpad":
  542. $('#activeSpeaker').css('visibility', 'hidden');
  543. currentContainer.fadeOut(300, function () {
  544. if (oldState === "prezi") {
  545. currentContainer.css({opacity: '0'});
  546. $('#reloadPresentation').css({display: 'none'});
  547. }
  548. else {
  549. self.setLargeVideoVisible(false);
  550. }
  551. });
  552. $('#etherpad>iframe').fadeIn(300, function () {
  553. document.body.style.background = '#eeeeee';
  554. $('#etherpad>iframe').css({visibility: 'visible'});
  555. $('#etherpad').css({zIndex: 2});
  556. });
  557. break;
  558. case "prezi":
  559. var prezi = $('#presentation>iframe');
  560. currentContainer.fadeOut(300, function() {
  561. document.body.style.background = 'black';
  562. });
  563. prezi.fadeIn(300, function() {
  564. prezi.css({opacity:'1'});
  565. ToolbarToggler.dockToolbar(true);//fix that
  566. self.setLargeVideoVisible(false);
  567. $('#etherpad>iframe').css({visibility: 'hidden'});
  568. $('#etherpad').css({zIndex: 0});
  569. });
  570. $('#activeSpeaker').css('visibility', 'hidden');
  571. break;
  572. case "video":
  573. currentContainer.fadeOut(300, function () {
  574. $('#presentation>iframe').css({opacity:'0'});
  575. $('#reloadPresentation').css({display:'none'});
  576. $('#etherpad>iframe').css({visibility: 'hidden'});
  577. $('#etherpad').css({zIndex: 0});
  578. document.body.style.background = 'black';
  579. ToolbarToggler.dockToolbar(false);//fix that
  580. });
  581. $('#largeVideo').fadeIn(300, function () {
  582. self.setLargeVideoVisible(true);
  583. });
  584. break;
  585. }
  586. state = newState;
  587. },
  588. /**
  589. * Returns the current state of the large video.
  590. * @returns {string} the current state - video, prezi or etherpad.
  591. */
  592. getState: function () {
  593. return state;
  594. },
  595. /**
  596. * Sets hover handlers for the large video container div.
  597. * @param inHandler
  598. * @param outHandler
  599. */
  600. setHover: function(inHandler, outHandler)
  601. {
  602. $('#largeVideoContainer').hover(inHandler, outHandler);
  603. }
  604. };
  605. module.exports = LargeVideo;