You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LargeVideo.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  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. // FIXME: With Temasys we have to re-select everytime
  7. //var video = $('#largeVideo');
  8. var currentVideoWidth = null;
  9. var currentVideoHeight = null;
  10. // By default we use camera
  11. var getVideoSize = getCameraVideoSize;
  12. var getVideoPosition = getCameraVideoPosition;
  13. var currentSmallVideo = null;
  14. /**
  15. * Sets the size and position of the given video element.
  16. *
  17. * @param video the video element to position
  18. * @param width the desired video width
  19. * @param height the desired video height
  20. * @param horizontalIndent the left and right indent
  21. * @param verticalIndent the top and bottom indent
  22. */
  23. function positionVideo(video,
  24. width,
  25. height,
  26. horizontalIndent,
  27. verticalIndent,
  28. animate) {
  29. if(animate)
  30. {
  31. video.animate({
  32. width: width,
  33. height: height,
  34. top: verticalIndent,
  35. bottom: verticalIndent,
  36. left: horizontalIndent,
  37. right: horizontalIndent
  38. },
  39. {
  40. queue: false,
  41. duration: 500
  42. });
  43. }
  44. else
  45. {
  46. video.width(width);
  47. video.height(height);
  48. video.css({ top: verticalIndent + 'px',
  49. bottom: verticalIndent + 'px',
  50. left: horizontalIndent + 'px',
  51. right: horizontalIndent + 'px'});
  52. }
  53. }
  54. /**
  55. * Returns an array of the video dimensions, so that it keeps it's aspect
  56. * ratio and fits available area with it's larger dimension. This method
  57. * ensures that whole video will be visible and can leave empty areas.
  58. *
  59. * @return an array with 2 elements, the video width and the video height
  60. */
  61. function getDesktopVideoSize(videoWidth,
  62. videoHeight,
  63. videoSpaceWidth,
  64. videoSpaceHeight) {
  65. if (!videoWidth)
  66. videoWidth = currentVideoWidth;
  67. if (!videoHeight)
  68. videoHeight = currentVideoHeight;
  69. var aspectRatio = videoWidth / videoHeight;
  70. var availableWidth = Math.max(videoWidth, videoSpaceWidth);
  71. var availableHeight = Math.max(videoHeight, videoSpaceHeight);
  72. videoSpaceHeight -= $('#remoteVideos').outerHeight();
  73. if (availableWidth / aspectRatio >= videoSpaceHeight)
  74. {
  75. availableHeight = videoSpaceHeight;
  76. availableWidth = availableHeight * aspectRatio;
  77. }
  78. if (availableHeight * aspectRatio >= videoSpaceWidth)
  79. {
  80. availableWidth = videoSpaceWidth;
  81. availableHeight = availableWidth / aspectRatio;
  82. }
  83. return [availableWidth, availableHeight];
  84. }
  85. /**
  86. * Returns an array of the video horizontal and vertical indents,
  87. * so that if fits its parent.
  88. *
  89. * @return an array with 2 elements, the horizontal indent and the vertical
  90. * indent
  91. */
  92. function getCameraVideoPosition(videoWidth,
  93. videoHeight,
  94. videoSpaceWidth,
  95. videoSpaceHeight) {
  96. // Parent height isn't completely calculated when we position the video in
  97. // full screen mode and this is why we use the screen height in this case.
  98. // Need to think it further at some point and implement it properly.
  99. var isFullScreen = document.fullScreen ||
  100. document.mozFullScreen ||
  101. document.webkitIsFullScreen;
  102. if (isFullScreen)
  103. videoSpaceHeight = window.innerHeight;
  104. var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
  105. var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
  106. return [horizontalIndent, verticalIndent];
  107. }
  108. /**
  109. * Returns an array of the video horizontal and vertical indents.
  110. * Centers horizontally and top aligns vertically.
  111. *
  112. * @return an array with 2 elements, the horizontal indent and the vertical
  113. * indent
  114. */
  115. function getDesktopVideoPosition(videoWidth,
  116. videoHeight,
  117. videoSpaceWidth,
  118. videoSpaceHeight) {
  119. var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
  120. var verticalIndent = 0;// Top aligned
  121. return [horizontalIndent, verticalIndent];
  122. }
  123. /**
  124. * Returns an array of the video dimensions, so that it covers the screen.
  125. * It leaves no empty areas, but some parts of the video might not be visible.
  126. *
  127. * @return an array with 2 elements, the video width and the video height
  128. */
  129. function getCameraVideoSize(videoWidth,
  130. videoHeight,
  131. videoSpaceWidth,
  132. videoSpaceHeight) {
  133. if (!videoWidth)
  134. videoWidth = currentVideoWidth;
  135. if (!videoHeight)
  136. videoHeight = currentVideoHeight;
  137. var aspectRatio = videoWidth / videoHeight;
  138. var availableWidth = Math.max(videoWidth, videoSpaceWidth);
  139. var availableHeight = Math.max(videoHeight, videoSpaceHeight);
  140. if (availableWidth / aspectRatio < videoSpaceHeight) {
  141. availableHeight = videoSpaceHeight;
  142. availableWidth = availableHeight * aspectRatio;
  143. }
  144. if (availableHeight * aspectRatio < videoSpaceWidth) {
  145. availableWidth = videoSpaceWidth;
  146. availableHeight = availableWidth / aspectRatio;
  147. }
  148. return [availableWidth, availableHeight];
  149. }
  150. /**
  151. * Updates the src of the active speaker avatar
  152. * @param jid of the current active speaker
  153. */
  154. function updateActiveSpeakerAvatarSrc() {
  155. var avatar = $("#activeSpeakerAvatar")[0];
  156. var jid = currentSmallVideo.peerJid;
  157. var url = Avatar.getActiveSpeakerUrl(jid);
  158. if (avatar.src === url)
  159. return;
  160. var isMuted = null;
  161. if (!currentSmallVideo.isLocal &&
  162. !LargeVideo.VideoLayout.isInLastN(currentSmallVideo.getResourceJid())) {
  163. isMuted = true;
  164. }
  165. else
  166. {
  167. isMuted = APP.RTC.isVideoMuted(jid);
  168. }
  169. if (jid && isMuted !== null) {
  170. avatar.src = url;
  171. $("#largeVideo").css("visibility", isMuted ? "hidden" : "visible");
  172. currentSmallVideo.showAvatar(isMuted);
  173. }
  174. }
  175. function changeVideo(isVisible) {
  176. if (!currentSmallVideo) {
  177. console.error("Unable to change large video - no 'currentSmallVideo'");
  178. return;
  179. }
  180. updateActiveSpeakerAvatarSrc();
  181. APP.RTC.setVideoSrc($('#largeVideo')[0], currentSmallVideo.getSrc());
  182. var videoTransform = document.getElementById('largeVideo')
  183. .style.webkitTransform;
  184. var flipX = currentSmallVideo.flipX;
  185. if (flipX && videoTransform !== 'scaleX(-1)') {
  186. document.getElementById('largeVideo').style.webkitTransform
  187. = "scaleX(-1)";
  188. }
  189. else if (!flipX && videoTransform === 'scaleX(-1)') {
  190. document.getElementById('largeVideo').style.webkitTransform
  191. = "none";
  192. }
  193. var isDesktop = APP.RTC.isVideoSrcDesktop(currentSmallVideo.peerJid);
  194. // Change the way we'll be measuring and positioning large video
  195. getVideoSize = isDesktop
  196. ? getDesktopVideoSize
  197. : getCameraVideoSize;
  198. getVideoPosition = isDesktop
  199. ? getDesktopVideoPosition
  200. : getCameraVideoPosition;
  201. // Only if the large video is currently visible.
  202. if (isVisible) {
  203. LargeVideo.VideoLayout.largeVideoUpdated(currentSmallVideo);
  204. $('#largeVideo').fadeIn(300);
  205. }
  206. }
  207. var LargeVideo = {
  208. init: function (VideoLayout, emitter) {
  209. this.VideoLayout = VideoLayout;
  210. this.eventEmitter = emitter;
  211. var self = this;
  212. // Listen for large video size updates
  213. var largeVideo = $('#largeVideo')[0];
  214. var onplaying = function (arg1, arg2, arg3) {
  215. // re-select
  216. if (RTCBrowserType.isTemasysPluginUsed())
  217. largeVideo = $('#largeVideo')[0];
  218. currentVideoWidth = largeVideo.videoWidth;
  219. currentVideoHeight = largeVideo.videoHeight;
  220. self.position(currentVideoWidth, currentVideoHeight);
  221. };
  222. largeVideo.onplaying = onplaying;
  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. isLargeVideoVisible: function() {
  230. return $('#largeVideo').is(':visible');
  231. },
  232. /**
  233. * Returns <tt>true</tt> if the user is currently displayed on large video.
  234. */
  235. isCurrentlyOnLarge: function (resourceJid) {
  236. return currentSmallVideo && resourceJid &&
  237. currentSmallVideo.getResourceJid() === resourceJid;
  238. },
  239. /**
  240. * Updates the large video with the given new video source.
  241. */
  242. updateLargeVideo: function (resourceJid, forceUpdate) {
  243. var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid);
  244. console.log('hover in ' + resourceJid + ', video: ', newSmallVideo);
  245. if (!LargeVideo.isCurrentlyOnLarge(resourceJid) || forceUpdate) {
  246. $('#activeSpeaker').css('visibility', 'hidden');
  247. var oldSmallVideo = null;
  248. if (currentSmallVideo) {
  249. oldSmallVideo = currentSmallVideo;
  250. }
  251. currentSmallVideo = newSmallVideo;
  252. var oldJid = null;
  253. if (oldSmallVideo)
  254. oldJid = oldSmallVideo.peerJid;
  255. if (oldJid !== resourceJid) {
  256. // we want the notification to trigger even if userJid is undefined,
  257. // or null.
  258. this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, resourceJid);
  259. }
  260. if (RTCBrowserType.isSafari()) {
  261. // FIXME In Safari fadeOut works only for the first time
  262. changeVideo(this.isLargeVideoVisible());
  263. } else {
  264. $('#largeVideo').fadeOut(300,
  265. changeVideo.bind($('#largeVideo'), this.isLargeVideoVisible()));
  266. }
  267. } else {
  268. if (currentSmallVideo) {
  269. currentSmallVideo.showAvatar();
  270. }
  271. }
  272. },
  273. /**
  274. * Shows/hides the large video.
  275. */
  276. setLargeVideoVisible: function(isVisible) {
  277. if (isVisible) {
  278. $('#largeVideo').css({visibility: 'visible'});
  279. $('.watermark').css({visibility: 'visible'});
  280. if(currentSmallVideo)
  281. currentSmallVideo.enableDominantSpeaker(true);
  282. }
  283. else {
  284. $('#largeVideo').css({visibility: 'hidden'});
  285. $('#activeSpeaker').css('visibility', 'hidden');
  286. $('.watermark').css({visibility: 'hidden'});
  287. if(currentSmallVideo)
  288. currentSmallVideo.enableDominantSpeaker(false);
  289. }
  290. },
  291. onVideoTypeChanged: function (jid) {
  292. var resourceJid = Strophe.getResourceFromJid(jid);
  293. if (LargeVideo.isCurrentlyOnLarge(resourceJid))
  294. {
  295. var isDesktop = APP.RTC.isVideoSrcDesktop(jid);
  296. getVideoSize = isDesktop
  297. ? getDesktopVideoSize
  298. : getCameraVideoSize;
  299. getVideoPosition = isDesktop
  300. ? getDesktopVideoPosition
  301. : getCameraVideoPosition;
  302. this.position(null, null);
  303. }
  304. },
  305. /**
  306. * Positions the large video.
  307. *
  308. * @param videoWidth the stream video width
  309. * @param videoHeight the stream video height
  310. */
  311. position: function (videoWidth, videoHeight,
  312. videoSpaceWidth, videoSpaceHeight, animate) {
  313. if(!videoSpaceWidth)
  314. videoSpaceWidth = $('#videospace').width();
  315. if(!videoSpaceHeight)
  316. videoSpaceHeight = window.innerHeight;
  317. var videoSize = getVideoSize(videoWidth,
  318. videoHeight,
  319. videoSpaceWidth,
  320. videoSpaceHeight);
  321. var largeVideoWidth = videoSize[0];
  322. var largeVideoHeight = videoSize[1];
  323. var videoPosition = getVideoPosition(largeVideoWidth,
  324. largeVideoHeight,
  325. videoSpaceWidth,
  326. videoSpaceHeight);
  327. var horizontalIndent = videoPosition[0];
  328. var verticalIndent = videoPosition[1];
  329. positionVideo($('#largeVideo'),
  330. largeVideoWidth,
  331. largeVideoHeight,
  332. horizontalIndent, verticalIndent, animate);
  333. },
  334. isLargeVideoOnTop: function () {
  335. var Etherpad = require("../etherpad/Etherpad");
  336. var Prezi = require("../prezi/Prezi");
  337. return !Prezi.isPresentationVisible() && !Etherpad.isVisible();
  338. },
  339. resize: function (animate, isVisible, completeFunction) {
  340. var availableHeight = window.innerHeight;
  341. var availableWidth = UIUtil.getAvailableVideoWidth(isVisible);
  342. if (availableWidth < 0 || availableHeight < 0) return;
  343. var avatarSize = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE;
  344. var top = availableHeight / 2 - avatarSize / 4 * 3;
  345. $('#activeSpeaker').css('top', top);
  346. if(animate)
  347. {
  348. $('#videospace').animate({
  349. right: window.innerWidth - availableWidth,
  350. width: availableWidth,
  351. height: availableHeight
  352. },
  353. {
  354. queue: false,
  355. duration: 500,
  356. complete: completeFunction
  357. });
  358. $('#largeVideoContainer').animate({
  359. width: availableWidth,
  360. height: availableHeight
  361. },
  362. {
  363. queue: false,
  364. duration: 500
  365. });
  366. }
  367. else
  368. {
  369. $('#videospace').width(availableWidth);
  370. $('#videospace').height(availableHeight);
  371. $('#largeVideoContainer').width(availableWidth);
  372. $('#largeVideoContainer').height(availableHeight);
  373. }
  374. return [availableWidth, availableHeight];
  375. },
  376. resizeVideoAreaAnimated: function (isVisible, completeFunction) {
  377. var size = this.resize(true, isVisible, completeFunction);
  378. this.position(null, null, size[0], size[1], true);
  379. },
  380. getResourceJid: function () {
  381. return currentSmallVideo ? currentSmallVideo.getResourceJid() : null;
  382. },
  383. updateAvatar: function (resourceJid) {
  384. if (resourceJid === this.getResourceJid()) {
  385. updateActiveSpeakerAvatarSrc();
  386. }
  387. },
  388. showAvatar: function (resourceJid, show) {
  389. if(this.getResourceJid() === resourceJid
  390. && LargeVideo.isLargeVideoOnTop())
  391. {
  392. $("#largeVideo").css("visibility", show ? "hidden" : "visible");
  393. $('#activeSpeaker').css("visibility", show ? "visible" : "hidden");
  394. return true;
  395. }
  396. return false;
  397. }
  398. }
  399. module.exports = LargeVideo;