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 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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. updateActiveSpeakerAvatarSrc();
  177. APP.RTC.setVideoSrc($('#largeVideo')[0], currentSmallVideo.getSrc());
  178. var videoTransform = document.getElementById('largeVideo')
  179. .style.webkitTransform;
  180. var flipX = currentSmallVideo.flipX;
  181. if (flipX && videoTransform !== 'scaleX(-1)') {
  182. document.getElementById('largeVideo').style.webkitTransform
  183. = "scaleX(-1)";
  184. }
  185. else if (!flipX && videoTransform === 'scaleX(-1)') {
  186. document.getElementById('largeVideo').style.webkitTransform
  187. = "none";
  188. }
  189. var isDesktop = APP.RTC.isVideoSrcDesktop(currentSmallVideo.peerJid);
  190. // Change the way we'll be measuring and positioning large video
  191. getVideoSize = isDesktop
  192. ? getDesktopVideoSize
  193. : getCameraVideoSize;
  194. getVideoPosition = isDesktop
  195. ? getDesktopVideoPosition
  196. : getCameraVideoPosition;
  197. // Only if the large video is currently visible.
  198. if (isVisible) {
  199. LargeVideo.VideoLayout.largeVideoUpdated(currentSmallVideo);
  200. $('#largeVideo').fadeIn(300);
  201. }
  202. }
  203. var LargeVideo = {
  204. init: function (VideoLayout, emitter) {
  205. this.VideoLayout = VideoLayout;
  206. this.eventEmitter = emitter;
  207. var self = this;
  208. // Listen for large video size updates
  209. var largeVideo = $('#largeVideo')[0];
  210. var onplaying = function (arg1, arg2, arg3) {
  211. // re-select
  212. if (RTCBrowserType.isTemasysPluginUsed())
  213. largeVideo = $('#largeVideo')[0];
  214. currentVideoWidth = largeVideo.videoWidth;
  215. currentVideoHeight = largeVideo.videoHeight;
  216. self.position(currentVideoWidth, currentVideoHeight);
  217. };
  218. largeVideo.onplaying = onplaying;
  219. },
  220. /**
  221. * Indicates if the large video is currently visible.
  222. *
  223. * @return <tt>true</tt> if visible, <tt>false</tt> - otherwise
  224. */
  225. isLargeVideoVisible: function() {
  226. return $('#largeVideo').is(':visible');
  227. },
  228. /**
  229. * Returns <tt>true</tt> if the user is currently displayed on large video.
  230. */
  231. isCurrentlyOnLarge: function (resourceJid) {
  232. return currentSmallVideo && resourceJid &&
  233. currentSmallVideo.getResourceJid() === resourceJid;
  234. },
  235. /**
  236. * Updates the large video with the given new video source.
  237. */
  238. updateLargeVideo: function (resourceJid, forceUpdate) {
  239. var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid);
  240. console.log('hover in ' + resourceJid + ', video: ', newSmallVideo);
  241. if (!LargeVideo.isCurrentlyOnLarge(resourceJid) || forceUpdate) {
  242. $('#activeSpeaker').css('visibility', 'hidden');
  243. var oldSmallVideo = null;
  244. if (currentSmallVideo) {
  245. oldSmallVideo = currentSmallVideo;
  246. }
  247. currentSmallVideo = newSmallVideo;
  248. var oldJid = null;
  249. if (oldSmallVideo)
  250. oldJid = oldSmallVideo.peerJid;
  251. if (oldJid !== resourceJid) {
  252. // we want the notification to trigger even if userJid is undefined,
  253. // or null.
  254. this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, resourceJid);
  255. }
  256. $('#largeVideo').fadeOut(300,
  257. changeVideo.bind($('#largeVideo'), this.isLargeVideoVisible()));
  258. } else {
  259. if (currentSmallVideo) {
  260. currentSmallVideo.showAvatar();
  261. }
  262. }
  263. },
  264. /**
  265. * Shows/hides the large video.
  266. */
  267. setLargeVideoVisible: function(isVisible) {
  268. if (isVisible) {
  269. $('#largeVideo').css({visibility: 'visible'});
  270. $('.watermark').css({visibility: 'visible'});
  271. if(currentSmallVideo)
  272. currentSmallVideo.enableDominantSpeaker(true);
  273. }
  274. else {
  275. $('#largeVideo').css({visibility: 'hidden'});
  276. $('#activeSpeaker').css('visibility', 'hidden');
  277. $('.watermark').css({visibility: 'hidden'});
  278. if(currentSmallVideo)
  279. currentSmallVideo.enableDominantSpeaker(false);
  280. }
  281. },
  282. onVideoTypeChanged: function (jid) {
  283. var resourceJid = Strophe.getResourceFromJid(jid);
  284. if (LargeVideo.isCurrentlyOnLarge(resourceJid))
  285. {
  286. var isDesktop = APP.RTC.isVideoSrcDesktop(jid);
  287. getVideoSize = isDesktop
  288. ? getDesktopVideoSize
  289. : getCameraVideoSize;
  290. getVideoPosition = isDesktop
  291. ? getDesktopVideoPosition
  292. : getCameraVideoPosition;
  293. this.position(null, null);
  294. }
  295. },
  296. /**
  297. * Positions the large video.
  298. *
  299. * @param videoWidth the stream video width
  300. * @param videoHeight the stream video height
  301. */
  302. position: function (videoWidth, videoHeight,
  303. videoSpaceWidth, videoSpaceHeight, animate) {
  304. if(!videoSpaceWidth)
  305. videoSpaceWidth = $('#videospace').width();
  306. if(!videoSpaceHeight)
  307. videoSpaceHeight = window.innerHeight;
  308. var videoSize = getVideoSize(videoWidth,
  309. videoHeight,
  310. videoSpaceWidth,
  311. videoSpaceHeight);
  312. var largeVideoWidth = videoSize[0];
  313. var largeVideoHeight = videoSize[1];
  314. var videoPosition = getVideoPosition(largeVideoWidth,
  315. largeVideoHeight,
  316. videoSpaceWidth,
  317. videoSpaceHeight);
  318. var horizontalIndent = videoPosition[0];
  319. var verticalIndent = videoPosition[1];
  320. positionVideo($('#largeVideo'),
  321. largeVideoWidth,
  322. largeVideoHeight,
  323. horizontalIndent, verticalIndent, animate);
  324. },
  325. isLargeVideoOnTop: function () {
  326. var Etherpad = require("../etherpad/Etherpad");
  327. var Prezi = require("../prezi/Prezi");
  328. return !Prezi.isPresentationVisible() && !Etherpad.isVisible();
  329. },
  330. resize: function (animate, isVisible, completeFunction) {
  331. var availableHeight = window.innerHeight;
  332. var availableWidth = UIUtil.getAvailableVideoWidth(isVisible);
  333. if (availableWidth < 0 || availableHeight < 0) return;
  334. var avatarSize = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE;
  335. var top = availableHeight / 2 - avatarSize / 4 * 3;
  336. $('#activeSpeaker').css('top', top);
  337. if(animate)
  338. {
  339. $('#videospace').animate({
  340. right: window.innerWidth - availableWidth,
  341. width: availableWidth,
  342. height: availableHeight
  343. },
  344. {
  345. queue: false,
  346. duration: 500,
  347. complete: completeFunction
  348. });
  349. $('#largeVideoContainer').animate({
  350. width: availableWidth,
  351. height: availableHeight
  352. },
  353. {
  354. queue: false,
  355. duration: 500
  356. });
  357. }
  358. else
  359. {
  360. $('#videospace').width(availableWidth);
  361. $('#videospace').height(availableHeight);
  362. $('#largeVideoContainer').width(availableWidth);
  363. $('#largeVideoContainer').height(availableHeight);
  364. }
  365. return [availableWidth, availableHeight];
  366. },
  367. resizeVideoAreaAnimated: function (isVisible, completeFunction) {
  368. var size = this.resize(true, isVisible, completeFunction);
  369. this.position(null, null, size[0], size[1], true);
  370. },
  371. getResourceJid: function () {
  372. return currentSmallVideo ? currentSmallVideo.getResourceJid() : null;
  373. },
  374. updateAvatar: function (resourceJid) {
  375. if (resourceJid === this.getResourceJid()) {
  376. updateActiveSpeakerAvatarSrc();
  377. }
  378. },
  379. showAvatar: function (resourceJid, show) {
  380. if(this.getResourceJid() === resourceJid
  381. && LargeVideo.isLargeVideoOnTop())
  382. {
  383. $("#largeVideo").css("visibility", show ? "hidden" : "visible");
  384. $('#activeSpeaker').css("visibility", show ? "visible" : "hidden");
  385. return true;
  386. }
  387. return false;
  388. }
  389. }
  390. module.exports = LargeVideo;