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.

middleware.ts 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import i18next from 'i18next';
  2. import { AnyAction } from 'redux';
  3. import { IStore } from '../app/types';
  4. import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
  5. import {
  6. ENDPOINT_MESSAGE_RECEIVED,
  7. SET_REQUESTING_SUBTITLES,
  8. TOGGLE_REQUESTING_SUBTITLES
  9. } from './actionTypes';
  10. import {
  11. removeTranscriptMessage,
  12. updateTranscriptMessage
  13. } from './actions';
  14. import logger from './logger';
  15. /**
  16. * The type of json-message which indicates that json carries a
  17. * transcription result.
  18. */
  19. const JSON_TYPE_TRANSCRIPTION_RESULT = 'transcription-result';
  20. /**
  21. * The type of json-message which indicates that json carries a
  22. * translation result.
  23. */
  24. const JSON_TYPE_TRANSLATION_RESULT = 'translation-result';
  25. /**
  26. * The local participant property which is used to set whether the local
  27. * participant wants to have a transcriber in the room.
  28. */
  29. const P_NAME_REQUESTING_TRANSCRIPTION = 'requestingTranscription';
  30. /**
  31. * The local participant property which is used to store the language
  32. * preference for translation for a participant.
  33. */
  34. const P_NAME_TRANSLATION_LANGUAGE = 'translation_language';
  35. /**
  36. * Time after which the rendered subtitles will be removed.
  37. */
  38. const REMOVE_AFTER_MS = 3000;
  39. /**
  40. * Middleware that catches actions related to transcript messages to be rendered
  41. * in {@link Captions}.
  42. *
  43. * @param {Store} store - The redux store.
  44. * @returns {Function}
  45. */
  46. MiddlewareRegistry.register(store => next => action => {
  47. switch (action.type) {
  48. case ENDPOINT_MESSAGE_RECEIVED:
  49. return _endpointMessageReceived(store, next, action);
  50. case TOGGLE_REQUESTING_SUBTITLES:
  51. _requestingSubtitlesChange(store);
  52. break;
  53. case SET_REQUESTING_SUBTITLES:
  54. _requestingSubtitlesChange(store);
  55. _requestingSubtitlesSet(store, action.enabled);
  56. break;
  57. }
  58. return next(action);
  59. });
  60. /**
  61. * Notifies the feature transcription that the action
  62. * {@code ENDPOINT_MESSAGE_RECEIVED} is being dispatched within a specific redux
  63. * store.
  64. *
  65. * @param {Store} store - The redux store in which the specified {@code action}
  66. * is being dispatched.
  67. * @param {Dispatch} next - The redux {@code dispatch} function to
  68. * dispatch the specified {@code action} to the specified {@code store}.
  69. * @param {Action} action - The redux action {@code ENDPOINT_MESSAGE_RECEIVED}
  70. * which is being dispatched in the specified {@code store}.
  71. * @private
  72. * @returns {Object} The value returned by {@code next(action)}.
  73. */
  74. function _endpointMessageReceived({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
  75. const { json } = action;
  76. if (!(json
  77. && (json.type === JSON_TYPE_TRANSCRIPTION_RESULT
  78. || json.type === JSON_TYPE_TRANSLATION_RESULT))) {
  79. return next(action);
  80. }
  81. const state = getState();
  82. const translationLanguage
  83. = state['features/base/conference'].conference
  84. ?.getLocalParticipantProperty(P_NAME_TRANSLATION_LANGUAGE);
  85. try {
  86. const transcriptMessageID = json.message_id;
  87. const participantName = json.participant.name;
  88. if (json.type === JSON_TYPE_TRANSLATION_RESULT
  89. && json.language === translationLanguage) {
  90. // Displays final results in the target language if translation is
  91. // enabled.
  92. const newTranscriptMessage = {
  93. clearTimeOut: undefined,
  94. final: json.text,
  95. participantName
  96. };
  97. _setClearerOnTranscriptMessage(dispatch,
  98. transcriptMessageID, newTranscriptMessage);
  99. dispatch(updateTranscriptMessage(transcriptMessageID,
  100. newTranscriptMessage));
  101. } else if (json.type === JSON_TYPE_TRANSCRIPTION_RESULT
  102. && i18next.language === translationLanguage) {
  103. // Displays interim and final results without any translation if
  104. // translations are disabled.
  105. const { text } = json.transcript[0];
  106. // We update the previous transcript message with the same
  107. // message ID or adds a new transcript message if it does not
  108. // exist in the map.
  109. const newTranscriptMessage: any = {
  110. ...state['features/subtitles']._transcriptMessages
  111. .get(transcriptMessageID)
  112. || { participantName }
  113. };
  114. _setClearerOnTranscriptMessage(dispatch,
  115. transcriptMessageID, newTranscriptMessage);
  116. // If this is final result, update the state as a final result
  117. // and start a count down to remove the subtitle from the state
  118. if (!json.is_interim) {
  119. newTranscriptMessage.final = text;
  120. } else if (json.stability > 0.85) {
  121. // If the message has a high stability, we can update the
  122. // stable field of the state and remove the previously
  123. // unstable results
  124. newTranscriptMessage.stable = text;
  125. newTranscriptMessage.unstable = undefined;
  126. } else {
  127. // Otherwise, this result has an unstable result, which we
  128. // add to the state. The unstable result will be appended
  129. // after the stable part.
  130. newTranscriptMessage.unstable = text;
  131. }
  132. dispatch(
  133. updateTranscriptMessage(
  134. transcriptMessageID,
  135. newTranscriptMessage));
  136. }
  137. } catch (error) {
  138. logger.error('Error occurred while updating transcriptions\n', error);
  139. }
  140. return next(action);
  141. }
  142. /**
  143. * Toggle the local property 'requestingTranscription'. This will cause Jicofo
  144. * and Jigasi to decide whether the transcriber needs to be in the room.
  145. *
  146. * @param {Store} store - The redux store.
  147. * @private
  148. * @returns {void}
  149. */
  150. function _requestingSubtitlesChange({ getState }: IStore) {
  151. const state = getState();
  152. const { _language } = state['features/subtitles'];
  153. const { conference } = state['features/base/conference'];
  154. const requestingSubtitles = _language !== 'transcribing.subtitlesOff';
  155. conference?.setLocalParticipantProperty(
  156. P_NAME_REQUESTING_TRANSCRIPTION,
  157. requestingSubtitles);
  158. if (requestingSubtitles) {
  159. conference?.setLocalParticipantProperty(
  160. P_NAME_TRANSLATION_LANGUAGE,
  161. _language.replace('translation-languages:', ''));
  162. }
  163. }
  164. /**
  165. * Set the local property 'requestingTranscription'. This will cause Jicofo
  166. * and Jigasi to decide whether the transcriber needs to be in the room.
  167. *
  168. * @param {Store} store - The redux store.
  169. * @param {boolean} enabled - The new state of the subtitles.
  170. * @private
  171. * @returns {void}
  172. */
  173. function _requestingSubtitlesSet({ getState }: IStore, enabled: boolean) {
  174. const state = getState();
  175. const { conference } = state['features/base/conference'];
  176. conference?.setLocalParticipantProperty(
  177. P_NAME_REQUESTING_TRANSCRIPTION,
  178. enabled);
  179. }
  180. /**
  181. * Set a timeout on a TranscriptMessage object so it clears itself when it's not
  182. * updated.
  183. *
  184. * @param {Function} dispatch - Dispatch remove action to store.
  185. * @param {string} transcriptMessageID - The id of the message to remove.
  186. * @param {Object} transcriptMessage - The message to remove.
  187. * @returns {void}
  188. */
  189. function _setClearerOnTranscriptMessage(
  190. dispatch: IStore['dispatch'],
  191. transcriptMessageID: string,
  192. transcriptMessage: { clearTimeOut?: number; }) {
  193. if (transcriptMessage.clearTimeOut) {
  194. clearTimeout(transcriptMessage.clearTimeOut);
  195. }
  196. transcriptMessage.clearTimeOut
  197. = window.setTimeout(
  198. () => dispatch(removeTranscriptMessage(transcriptMessageID)),
  199. REMOVE_AFTER_MS);
  200. }