Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

Filmstrip.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. /* global $, APP, interfaceConfig */
  2. import {
  3. LAYOUTS,
  4. getCurrentLayout,
  5. getMaxColumnCount,
  6. getTileViewGridDimensions,
  7. shouldDisplayTileView
  8. } from '../../../react/features/video-layout';
  9. import UIUtil from '../util/UIUtil';
  10. const Filmstrip = {
  11. /**
  12. * Caches jquery lookups of the filmstrip for future use.
  13. */
  14. init() {
  15. this.filmstripContainerClassName = 'filmstrip';
  16. this.filmstrip = $('#remoteVideos');
  17. this.filmstripRemoteVideos = $('#filmstripRemoteVideosContainer');
  18. },
  19. /**
  20. * Shows if filmstrip is visible
  21. * @returns {boolean}
  22. */
  23. isFilmstripVisible() {
  24. return APP.store.getState()['features/filmstrip'].visible;
  25. },
  26. /**
  27. * Returns the height of filmstrip
  28. * @returns {number} height
  29. */
  30. getFilmstripHeight() {
  31. // FIXME Make it more clear the getFilmstripHeight check is used in
  32. // horizontal film strip mode for calculating how tall large video
  33. // display should be.
  34. if (this.isFilmstripVisible() && !interfaceConfig.VERTICAL_FILMSTRIP) {
  35. return $(`.${this.filmstripContainerClassName}`).outerHeight();
  36. }
  37. return 0;
  38. },
  39. /**
  40. * Returns the width of filmstip
  41. * @returns {number} width
  42. */
  43. getFilmstripWidth() {
  44. return this.isFilmstripVisible()
  45. ? this.filmstrip.outerWidth()
  46. - parseInt(this.filmstrip.css('paddingLeft'), 10)
  47. - parseInt(this.filmstrip.css('paddingRight'), 10)
  48. : 0;
  49. },
  50. /**
  51. * Calculates the size for thumbnails: local and remote one
  52. * @returns {*|{localVideo, remoteVideo}}
  53. */
  54. calculateThumbnailSize() {
  55. if (shouldDisplayTileView(APP.store.getState())) {
  56. return this._calculateThumbnailSizeForTileView();
  57. }
  58. const availableSizes = this.calculateAvailableSize();
  59. const width = availableSizes.availableWidth;
  60. const height = availableSizes.availableHeight;
  61. return this.calculateThumbnailSizeFromAvailable(width, height);
  62. },
  63. /**
  64. * Calculates available size for one thumbnail according to
  65. * the current window size.
  66. *
  67. * @returns {{availableWidth: number, availableHeight: number}}
  68. */
  69. calculateAvailableSize() {
  70. const state = APP.store.getState();
  71. const currentLayout = getCurrentLayout(state);
  72. const isHorizontalFilmstripView
  73. = currentLayout === LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW;
  74. /**
  75. * If the videoAreaAvailableWidth is set we use this one to calculate
  76. * the filmstrip width, because we're probably in a state where the
  77. * filmstrip size hasn't been updated yet, but it will be.
  78. */
  79. const videoAreaAvailableWidth
  80. = UIUtil.getAvailableVideoWidth()
  81. - this._getFilmstripExtraPanelsWidth()
  82. - UIUtil.parseCssInt(this.filmstrip.css('right'), 10)
  83. - UIUtil.parseCssInt(this.filmstrip.css('paddingLeft'), 10)
  84. - UIUtil.parseCssInt(this.filmstrip.css('paddingRight'), 10)
  85. - UIUtil.parseCssInt(this.filmstrip.css('borderLeftWidth'), 10)
  86. - UIUtil.parseCssInt(this.filmstrip.css('borderRightWidth'), 10)
  87. - 5;
  88. let availableHeight = interfaceConfig.FILM_STRIP_MAX_HEIGHT;
  89. let availableWidth = videoAreaAvailableWidth;
  90. const thumbs = this.getThumbs(true);
  91. // If local thumb is not hidden
  92. if (thumbs.localThumb) {
  93. const localVideoContainer = $('#localVideoContainer');
  94. availableWidth = Math.floor(
  95. videoAreaAvailableWidth - (
  96. UIUtil.parseCssInt(
  97. localVideoContainer.css('borderLeftWidth'), 10)
  98. + UIUtil.parseCssInt(
  99. localVideoContainer.css('borderRightWidth'), 10)
  100. + UIUtil.parseCssInt(
  101. localVideoContainer.css('paddingLeft'), 10)
  102. + UIUtil.parseCssInt(
  103. localVideoContainer.css('paddingRight'), 10)
  104. + UIUtil.parseCssInt(
  105. localVideoContainer.css('marginLeft'), 10)
  106. + UIUtil.parseCssInt(
  107. localVideoContainer.css('marginRight'), 10))
  108. );
  109. }
  110. // If the number of videos is 0 or undefined or we're not in horizontal
  111. // filmstrip mode we don't need to calculate further any adjustments
  112. // to width based on the number of videos present.
  113. const numvids = thumbs.remoteThumbs.length;
  114. if (numvids && isHorizontalFilmstripView) {
  115. const remoteVideoContainer = thumbs.remoteThumbs.eq(0);
  116. availableWidth = Math.floor(
  117. videoAreaAvailableWidth - (numvids * (
  118. UIUtil.parseCssInt(
  119. remoteVideoContainer.css('borderLeftWidth'), 10)
  120. + UIUtil.parseCssInt(
  121. remoteVideoContainer.css('borderRightWidth'), 10)
  122. + UIUtil.parseCssInt(
  123. remoteVideoContainer.css('paddingLeft'), 10)
  124. + UIUtil.parseCssInt(
  125. remoteVideoContainer.css('paddingRight'), 10)
  126. + UIUtil.parseCssInt(
  127. remoteVideoContainer.css('marginLeft'), 10)
  128. + UIUtil.parseCssInt(
  129. remoteVideoContainer.css('marginRight'), 10)))
  130. );
  131. }
  132. const maxHeight
  133. // If the MAX_HEIGHT property hasn't been specified
  134. // we have the static value.
  135. = Math.min(interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120,
  136. availableHeight);
  137. availableHeight
  138. = Math.min(maxHeight, window.innerHeight - 18);
  139. return {
  140. availableHeight,
  141. availableWidth
  142. };
  143. },
  144. /**
  145. * Traverse all elements inside the filmstrip
  146. * and calculates the sum of all of them except
  147. * remote videos element. Used for calculation of
  148. * available width for video thumbnails.
  149. *
  150. * @returns {number} calculated width
  151. * @private
  152. */
  153. _getFilmstripExtraPanelsWidth() {
  154. const className = this.filmstripContainerClassName;
  155. let width = 0;
  156. $(`.${className}`)
  157. .children()
  158. .each(function() {
  159. /* eslint-disable no-invalid-this */
  160. if (this.id !== 'remoteVideos') {
  161. width += $(this).outerWidth();
  162. }
  163. /* eslint-enable no-invalid-this */
  164. });
  165. return width;
  166. },
  167. /**
  168. Calculate the thumbnail size in order to fit all the thumnails in passed
  169. * dimensions.
  170. * NOTE: Here we assume that the remote and local thumbnails are with the
  171. * same height.
  172. * @param {int} availableWidth the maximum width for all thumbnails
  173. * @param {int} availableHeight the maximum height for all thumbnails
  174. * @returns {{localVideo, remoteVideo}}
  175. */
  176. calculateThumbnailSizeFromAvailable(availableWidth, availableHeight) {
  177. /**
  178. * Let:
  179. * lW - width of the local thumbnail
  180. * rW - width of the remote thumbnail
  181. * h - the height of the thumbnails
  182. * remoteRatio - width:height for the remote thumbnail
  183. * localRatio - width:height for the local thumbnail
  184. * remoteThumbsInRow - number of remote thumbnails in a row (we have
  185. * only one local thumbnail) next to the local thumbnail. In vertical
  186. * filmstrip mode, this will always be 0.
  187. *
  188. * Since the height for local thumbnail = height for remote thumbnail
  189. * and we know the ratio (width:height) for the local and for the
  190. * remote thumbnail we can find rW/lW:
  191. * rW / remoteRatio = lW / localRatio then -
  192. * remoteLocalWidthRatio = rW / lW = remoteRatio / localRatio
  193. * and rW = lW * remoteRatio / localRatio = lW * remoteLocalWidthRatio
  194. * And the total width for the thumbnails is:
  195. * totalWidth = rW * remoteThumbsInRow + lW
  196. * = lW * remoteLocalWidthRatio * remoteThumbsInRow + lW =
  197. * lW * (remoteLocalWidthRatio * remoteThumbsInRow + 1)
  198. * and the h = lW/localRatio
  199. *
  200. * In order to fit all the thumbails in the area defined by
  201. * availableWidth * availableHeight we should check one of the
  202. * following options:
  203. * 1) if availableHeight == h - totalWidth should be less than
  204. * availableWidth
  205. * 2) if availableWidth == totalWidth - h should be less than
  206. * availableHeight
  207. *
  208. * 1) or 2) will be true and we are going to use it to calculate all
  209. * sizes.
  210. *
  211. * if 1) is true that means that
  212. * availableHeight/h > availableWidth/totalWidth otherwise 2) is true
  213. */
  214. const remoteThumbsInRow = interfaceConfig.VERTICAL_FILMSTRIP
  215. ? 0 : this.getThumbs(true).remoteThumbs.length;
  216. const remoteLocalWidthRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO
  217. / interfaceConfig.LOCAL_THUMBNAIL_RATIO;
  218. const lW = Math.min(availableWidth
  219. / ((remoteLocalWidthRatio * remoteThumbsInRow) + 1), availableHeight
  220. * interfaceConfig.LOCAL_THUMBNAIL_RATIO);
  221. const h = lW / interfaceConfig.LOCAL_THUMBNAIL_RATIO;
  222. const remoteVideoWidth = lW * remoteLocalWidthRatio;
  223. let localVideo;
  224. if (interfaceConfig.VERTICAL_FILMSTRIP) {
  225. localVideo = {
  226. thumbWidth: remoteVideoWidth,
  227. thumbHeight: h * remoteLocalWidthRatio
  228. };
  229. } else {
  230. localVideo = {
  231. thumbWidth: lW,
  232. thumbHeight: h
  233. };
  234. }
  235. return {
  236. localVideo,
  237. remoteVideo: {
  238. thumbWidth: remoteVideoWidth,
  239. thumbHeight: h
  240. }
  241. };
  242. },
  243. /**
  244. * Calculates the size for thumbnails when in tile view layout.
  245. *
  246. * @returns {{localVideo, remoteVideo}}
  247. */
  248. _calculateThumbnailSizeForTileView() {
  249. const tileAspectRatio = 16 / 9;
  250. // The distance from the top and bottom of the screen, as set by CSS, to
  251. // avoid overlapping UI elements.
  252. const topBottomPadding = 200;
  253. // Minimum space to keep between the sides of the tiles and the sides
  254. // of the window.
  255. const sideMargins = 30 * 2;
  256. const state = APP.store.getState();
  257. const viewWidth = document.body.clientWidth - sideMargins;
  258. const viewHeight = document.body.clientHeight - topBottomPadding;
  259. const {
  260. columns,
  261. visibleRows
  262. } = getTileViewGridDimensions(state, getMaxColumnCount());
  263. const initialWidth = viewWidth / columns;
  264. const aspectRatioHeight = initialWidth / tileAspectRatio;
  265. const heightOfEach = Math.min(
  266. aspectRatioHeight,
  267. viewHeight / visibleRows);
  268. const widthOfEach = tileAspectRatio * heightOfEach;
  269. return {
  270. localVideo: {
  271. thumbWidth: widthOfEach,
  272. thumbHeight: heightOfEach
  273. },
  274. remoteVideo: {
  275. thumbWidth: widthOfEach,
  276. thumbHeight: heightOfEach
  277. }
  278. };
  279. },
  280. /**
  281. * Resizes thumbnails
  282. * @param local
  283. * @param remote
  284. * @param forceUpdate
  285. * @returns {Promise}
  286. */
  287. // eslint-disable-next-line max-params
  288. resizeThumbnails(local, remote, forceUpdate = false) {
  289. const state = APP.store.getState();
  290. if (shouldDisplayTileView(state)) {
  291. // The size of the side margins for each tile as set in CSS.
  292. const sideMargins = 10 * 2;
  293. const {
  294. columns,
  295. rows
  296. } = getTileViewGridDimensions(state, getMaxColumnCount());
  297. const hasOverflow = rows > columns;
  298. // Width is set so that the flex layout can automatically wrap
  299. // tiles onto new rows.
  300. this.filmstripRemoteVideos.css({
  301. width: (local.thumbWidth * columns) + (columns * sideMargins)
  302. });
  303. this.filmstripRemoteVideos.toggleClass('has-overflow', hasOverflow);
  304. } else {
  305. this.filmstripRemoteVideos.css('width', '');
  306. }
  307. const thumbs = this.getThumbs(!forceUpdate);
  308. if (thumbs.localThumb) {
  309. // eslint-disable-next-line no-shadow
  310. thumbs.localThumb.css({
  311. display: 'inline-block',
  312. height: `${local.thumbHeight}px`,
  313. 'min-height': `${local.thumbHeight}px`,
  314. 'min-width': `${local.thumbWidth}px`,
  315. width: `${local.thumbWidth}px`
  316. });
  317. }
  318. if (thumbs.remoteThumbs) {
  319. thumbs.remoteThumbs.css({
  320. display: 'inline-block',
  321. height: `${remote.thumbHeight}px`,
  322. 'min-height': `${remote.thumbHeight}px`,
  323. 'min-width': `${remote.thumbWidth}px`,
  324. width: `${remote.thumbWidth}px`
  325. });
  326. }
  327. const currentLayout = getCurrentLayout(APP.store.getState());
  328. // Let CSS take care of height in vertical filmstrip mode.
  329. if (currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) {
  330. $('#filmstripLocalVideo').css({
  331. // adds 4 px because of small video 2px border
  332. width: `${local.thumbWidth + 4}px`
  333. });
  334. } else if (currentLayout === LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW) {
  335. this.filmstrip.css({
  336. // adds 4 px because of small video 2px border
  337. height: `${remote.thumbHeight + 4}px`
  338. });
  339. }
  340. const { localThumb } = this.getThumbs();
  341. const height = localThumb ? localThumb.height() : 0;
  342. const fontSize = UIUtil.getIndicatorFontSize(height);
  343. this.filmstrip.find('.indicator').css({
  344. 'font-size': `${fontSize}px`
  345. });
  346. },
  347. /**
  348. * Returns thumbnails of the filmstrip
  349. * @param onlyVisible
  350. * @returns {object} thumbnails
  351. */
  352. getThumbs(onlyVisible = false) {
  353. let selector = 'span';
  354. if (onlyVisible) {
  355. selector += ':visible';
  356. }
  357. const localThumb = $('#localVideoContainer');
  358. const remoteThumbs = this.filmstripRemoteVideos.children(selector);
  359. // Exclude the local video container if it has been hidden.
  360. if (localThumb.hasClass('hidden')) {
  361. return { remoteThumbs };
  362. }
  363. return { remoteThumbs,
  364. localThumb };
  365. }
  366. };
  367. export default Filmstrip;