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.

reducer.ts 9.3KB

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