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

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