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.6KB

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