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.js 6.7KB

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