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.

Captions.tsx 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { Theme } from '@mui/material';
  2. import React, { ReactElement } from 'react';
  3. import { connect } from 'react-redux';
  4. import { withStyles } from 'tss-react/mui';
  5. import { IReduxState } from '../../../app/types';
  6. import { getLocalParticipant } from '../../../base/participants/functions';
  7. import { getVideospaceFloatingElementsBottomSpacing } from '../../../base/ui/functions.web';
  8. import { getStageParticipantNameLabelHeight } from '../../../display-name/components/web/styles';
  9. import { getLargeVideoParticipant } from '../../../large-video/functions';
  10. import { isLayoutTileView } from '../../../video-layout/functions.web';
  11. import { calculateSubtitlesFontSize } from '../../functions.web';
  12. import {
  13. AbstractCaptions,
  14. type IAbstractCaptionsProps,
  15. _abstractMapStateToProps
  16. } from '../AbstractCaptions';
  17. interface IProps extends IAbstractCaptionsProps {
  18. /**
  19. * The height of the visible area.
  20. */
  21. _clientHeight?: number;
  22. /**
  23. * Whether the subtitles container is lifted above the invite box.
  24. */
  25. _isLifted: boolean | undefined;
  26. /**
  27. * An object containing the CSS classes.
  28. */
  29. classes?: Partial<Record<keyof ReturnType<typeof styles>, string>>;
  30. }
  31. const styles = (theme: Theme, props: IProps) => {
  32. const { _isLifted = false, _clientHeight } = props;
  33. const fontSize = calculateSubtitlesFontSize(_clientHeight);
  34. const padding = Math.ceil(0.2 * fontSize);
  35. // Currently the subtitles position are not affected by the toolbar visibility.
  36. let bottom = getVideospaceFloatingElementsBottomSpacing(theme, true);
  37. // This is the case where we display the onstage participant display name
  38. // below the subtitles.
  39. if (_isLifted) {
  40. // 10px is the space between the onstage participant display name label and subtitles. We also need
  41. // to add the padding of the subtitles because it will decrease the gap between the label and subtitles.
  42. bottom += getStageParticipantNameLabelHeight(theme) + 10 + padding;
  43. }
  44. return {
  45. transcriptionSubtitles: {
  46. bottom,
  47. fontSize: `${fontSize}px`,
  48. left: '50%',
  49. maxWidth: '50vw',
  50. overflowWrap: 'break-word' as const,
  51. pointerEvents: 'none' as const,
  52. position: 'absolute' as const,
  53. textShadow: `
  54. 0px 0px 1px rgba(0,0,0,0.3),
  55. 0px 1px 1px rgba(0,0,0,0.3),
  56. 1px 0px 1px rgba(0,0,0,0.3),
  57. 0px 0px 1px rgba(0,0,0,0.3)`,
  58. transform: 'translateX(-50%)',
  59. zIndex: 7, // The popups are with z-index 8. This z-index has to be lower.
  60. lineHeight: 1.2,
  61. span: {
  62. color: '#fff',
  63. background: 'black',
  64. // without this when the text is wrapped on 2+ lines there will be a gap in the background:
  65. padding: `${padding}px 0px`
  66. }
  67. }
  68. };
  69. };
  70. /**
  71. * React {@code Component} which can display speech-to-text results from
  72. * Jigasi as subtitles.
  73. */
  74. class Captions extends AbstractCaptions<IProps> {
  75. /**
  76. * Renders the transcription text.
  77. *
  78. * @param {string} id - The ID of the transcript message from which the
  79. * {@code text} has been created.
  80. * @param {string} text - Subtitles text formatted with the participant's
  81. * name.
  82. * @protected
  83. * @returns {ReactElement} - The React element which displays the text.
  84. */
  85. _renderParagraph(id: string, text: string): ReactElement {
  86. return (
  87. <p key = { id }>
  88. <span>{ text }</span>
  89. </p>
  90. );
  91. }
  92. /**
  93. * Renders the subtitles container.
  94. *
  95. * @param {Array<ReactElement>} paragraphs - An array of elements created
  96. * for each subtitle using the {@link _renderParagraph} method.
  97. * @protected
  98. * @returns {ReactElement} - The subtitles container.
  99. */
  100. _renderSubtitlesContainer(paragraphs: Array<ReactElement>): ReactElement {
  101. const classes = withStyles.getClasses(this.props);
  102. return (
  103. <div className = { classes.transcriptionSubtitles } >
  104. { paragraphs }
  105. </div>
  106. );
  107. }
  108. }
  109. /**
  110. * Maps (parts of) the redux state to the associated {@code }'s
  111. * props.
  112. *
  113. * @param {Object} state - The redux state.
  114. * @private
  115. * @returns {Object}
  116. */
  117. function mapStateToProps(state: IReduxState) {
  118. const isTileView = isLayoutTileView(state);
  119. const largeVideoParticipant = getLargeVideoParticipant(state);
  120. const localParticipant = getLocalParticipant(state);
  121. const { clientHeight } = state['features/base/responsive-ui'];
  122. return {
  123. ..._abstractMapStateToProps(state),
  124. _isLifted: Boolean(largeVideoParticipant && largeVideoParticipant?.id !== localParticipant?.id && !isTileView),
  125. _clientHeight: clientHeight
  126. };
  127. }
  128. export default connect(mapStateToProps)(withStyles(Captions, styles));