Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

reducer.ts 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. import { AnyAction, combineReducers } from 'redux';
  2. import { CONFERENCE_FAILED, CONFERENCE_LEFT } from '../conference/actionTypes';
  3. import ReducerRegistry from '../redux/ReducerRegistry';
  4. import { TRACK_REMOVED } from '../tracks/actionTypes';
  5. import {
  6. GUM_PENDING,
  7. SET_AUDIO_AVAILABLE,
  8. SET_AUDIO_MUTED,
  9. SET_AUDIO_UNMUTE_PERMISSIONS,
  10. SET_CAMERA_FACING_MODE,
  11. SET_SCREENSHARE_MUTED,
  12. SET_VIDEO_AVAILABLE,
  13. SET_VIDEO_MUTED,
  14. SET_VIDEO_UNMUTE_PERMISSIONS,
  15. STORE_VIDEO_TRANSFORM,
  16. TOGGLE_CAMERA_FACING_MODE
  17. } from './actionTypes';
  18. import { CAMERA_FACING_MODE, MEDIA_TYPE, SCREENSHARE_MUTISM_AUTHORITY } from './constants';
  19. import { IGUMPendingState } from './types';
  20. /**
  21. * Media state object for local audio.
  22. *
  23. * @typedef {Object} AudioMediaState
  24. * @property {boolean} muted=false - Audio muted state.
  25. */
  26. // FIXME Technically, _AUDIO_INITIAL_MEDIA_STATE is a constant internal to the
  27. // feature base/media and used in multiple files so it should be in
  28. // constants.js. Practically though, AudioMediaState would then be used in
  29. // multiple files as well so I don't know where and how to move it.
  30. /**
  31. * Initial state for local audio.
  32. *
  33. * @type {AudioMediaState}
  34. */
  35. export const _AUDIO_INITIAL_MEDIA_STATE = {
  36. available: true,
  37. gumPending: IGUMPendingState.NONE,
  38. unmuteBlocked: false,
  39. muted: false
  40. };
  41. /**
  42. * Reducer for audio media state.
  43. *
  44. * @param {AudioMediaState} state - Media state of local audio.
  45. * @param {Object} action - Action object.
  46. * @param {string} action.type - Type of action.
  47. * @private
  48. * @returns {AudioMediaState}
  49. */
  50. function _audio(state: IAudioState = _AUDIO_INITIAL_MEDIA_STATE, action: AnyAction) {
  51. switch (action.type) {
  52. case SET_AUDIO_AVAILABLE:
  53. return {
  54. ...state,
  55. available: action.available
  56. };
  57. case GUM_PENDING:
  58. if (action.mediaTypes.includes(MEDIA_TYPE.AUDIO)) {
  59. return {
  60. ...state,
  61. gumPending: action.status
  62. };
  63. }
  64. return state;
  65. case SET_AUDIO_MUTED:
  66. return {
  67. ...state,
  68. muted: action.muted
  69. };
  70. case SET_AUDIO_UNMUTE_PERMISSIONS:
  71. return {
  72. ...state,
  73. unmuteBlocked: action.blocked
  74. };
  75. default:
  76. return state;
  77. }
  78. }
  79. /**
  80. * Media state object for local screenshare.
  81. *
  82. * @typedef {Object} ScreenshareMediaState
  83. * @property {boolean} available=true - Screenshare available state.
  84. * @property {boolean} muted=true - Screenshare muted state.
  85. * @property {boolean} unmuteBlocked=false - Screenshare unmute blocked state.
  86. */
  87. /**
  88. * Initial state for video.
  89. *
  90. * @type {ScreenshareMediaState}
  91. */
  92. export const _SCREENSHARE_INITIAL_MEDIA_STATE = {
  93. available: true,
  94. muted: SCREENSHARE_MUTISM_AUTHORITY.USER,
  95. unmuteBlocked: false
  96. };
  97. /**
  98. * Reducer for screenshare media state.
  99. *
  100. * @param {VideoMediaState} state - Media state of local screenshare.
  101. * @param {Object} action - Action object.
  102. * @param {string} action.type - Type of action.
  103. * @private
  104. * @returns {ScreenshareMediaState}
  105. */
  106. function _screenshare(state: IScreenshareState = _SCREENSHARE_INITIAL_MEDIA_STATE, action: AnyAction) {
  107. switch (action.type) {
  108. case SET_SCREENSHARE_MUTED:
  109. return {
  110. ...state,
  111. muted: action.muted
  112. };
  113. case SET_VIDEO_UNMUTE_PERMISSIONS:
  114. return {
  115. ...state,
  116. unmuteBlocked: action.blocked
  117. };
  118. default:
  119. return state;
  120. }
  121. }
  122. /**
  123. * Media state object for local video.
  124. *
  125. * @typedef {Object} VideoMediaState
  126. * @property {CAMERA_FACING_MODE} facingMode='user' - Camera facing mode.
  127. * @property {boolean} muted=false - Video muted state.
  128. */
  129. // FIXME Technically, _VIDEO_INITIAL_MEDIA_STATE is a constant internal to the
  130. // feature base/media and used in multiple files so it should be in
  131. // constants.js. Practically though, VideoMediaState would then be used in
  132. // multiple files as well so I don't know where and how to move it.
  133. /**
  134. * Initial state for video.
  135. *
  136. * @type {VideoMediaState}
  137. */
  138. export const _VIDEO_INITIAL_MEDIA_STATE = {
  139. available: true,
  140. gumPending: IGUMPendingState.NONE,
  141. unmuteBlocked: false,
  142. facingMode: CAMERA_FACING_MODE.USER,
  143. muted: 0,
  144. /**
  145. * The video {@link Transform}s applied to {@code MediaStream}s by
  146. * {@code id} i.e. "pinch to zoom".
  147. */
  148. transforms: {}
  149. };
  150. /**
  151. * Reducer for camera media state.
  152. *
  153. * @param {VideoMediaState} state - Media state of local video.
  154. * @param {Object} action - Action object.
  155. * @param {string} action.type - Type of action.
  156. * @private
  157. * @returns {VideoMediaState}
  158. */
  159. function _video(state: IVideoState = _VIDEO_INITIAL_MEDIA_STATE, action: any) {
  160. switch (action.type) {
  161. case CONFERENCE_FAILED:
  162. case CONFERENCE_LEFT:
  163. return _clearAllVideoTransforms(state);
  164. case GUM_PENDING:
  165. if (action.mediaTypes.includes(MEDIA_TYPE.VIDEO)) {
  166. return {
  167. ...state,
  168. gumPending: action.status
  169. };
  170. }
  171. return state;
  172. case SET_CAMERA_FACING_MODE:
  173. return {
  174. ...state,
  175. facingMode: action.cameraFacingMode
  176. };
  177. case SET_VIDEO_AVAILABLE:
  178. return {
  179. ...state,
  180. available: action.available
  181. };
  182. case SET_VIDEO_MUTED:
  183. return {
  184. ...state,
  185. muted: action.muted
  186. };
  187. case SET_VIDEO_UNMUTE_PERMISSIONS:
  188. return {
  189. ...state,
  190. unmuteBlocked: action.blocked
  191. };
  192. case STORE_VIDEO_TRANSFORM:
  193. return _storeVideoTransform(state, action);
  194. case TOGGLE_CAMERA_FACING_MODE: {
  195. let cameraFacingMode = state.facingMode;
  196. cameraFacingMode
  197. = cameraFacingMode === CAMERA_FACING_MODE.USER
  198. ? CAMERA_FACING_MODE.ENVIRONMENT
  199. : CAMERA_FACING_MODE.USER;
  200. return {
  201. ...state,
  202. facingMode: cameraFacingMode
  203. };
  204. }
  205. case TRACK_REMOVED:
  206. return _trackRemoved(state, action);
  207. default:
  208. return state;
  209. }
  210. }
  211. interface IAudioState {
  212. available: boolean;
  213. gumPending: IGUMPendingState;
  214. muted: boolean;
  215. unmuteBlocked: boolean;
  216. }
  217. interface IScreenshareState {
  218. available: boolean;
  219. muted: number;
  220. unmuteBlocked: boolean;
  221. }
  222. interface IVideoState {
  223. available: boolean;
  224. facingMode: string;
  225. gumPending: IGUMPendingState;
  226. muted: number;
  227. transforms: Object;
  228. unmuteBlocked: boolean;
  229. }
  230. export interface IMediaState {
  231. audio: IAudioState;
  232. screenshare: IScreenshareState;
  233. video: IVideoState;
  234. }
  235. /**
  236. * Listen for various actions related to media devices.
  237. *
  238. * @param {Object} state - State of media devices.
  239. * @param {Object} action - Action object.
  240. * @param {string} action.type - Type of action.
  241. * @param {Object} action.media - Information about media devices to be
  242. * modified.
  243. * @returns {Object}
  244. */
  245. ReducerRegistry.register<IMediaState>('features/base/media', combineReducers({
  246. audio: _audio,
  247. screenshare: _screenshare,
  248. video: _video
  249. }));
  250. /**
  251. * Removes all stored video {@link Transform}s.
  252. *
  253. * @param {Object} state - The {@code video} state of the feature base/media.
  254. * @private
  255. * @returns {Object}
  256. */
  257. function _clearAllVideoTransforms(state: IVideoState) {
  258. return {
  259. ...state,
  260. transforms: _VIDEO_INITIAL_MEDIA_STATE.transforms
  261. };
  262. }
  263. /**
  264. * Stores the last applied transform to a stream.
  265. *
  266. * @param {Object} state - The {@code video} state of the feature base/media.
  267. * @param {Object} action - The redux action {@link STORE_VIDEO_TRANSFORM}.
  268. * @private
  269. * @returns {Object}
  270. */
  271. function _storeVideoTransform(state: IVideoState, { streamId, transform }: { streamId: string; transform: string; }) {
  272. return {
  273. ...state,
  274. transforms: {
  275. ...state.transforms,
  276. [streamId]: transform
  277. }
  278. };
  279. }
  280. /**
  281. * Removes the stored video {@link Transform} associated with a
  282. * {@code MediaStream} when its respective track is removed.
  283. *
  284. * @param {Object} state - The {@code video} state of the feature base/media.
  285. * @param {Object} action - The redux action {@link TRACK_REMOVED}.
  286. * @private
  287. * @returns {Object}
  288. */
  289. function _trackRemoved(state: IVideoState, { track: { jitsiTrack } }: { track: { jitsiTrack: any; }; }) {
  290. if (jitsiTrack) {
  291. const streamId = jitsiTrack.getStreamId();
  292. if (streamId && streamId in state.transforms) {
  293. const nextTransforms: any = {
  294. ...state.transforms
  295. };
  296. delete nextTransforms[streamId];
  297. return {
  298. ...state,
  299. transforms: nextTransforms
  300. };
  301. }
  302. }
  303. return state;
  304. }