選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

actions.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. import { NOTIFICATION_TIMEOUT, showNotification } from '../../notifications';
  2. import { set } from '../redux';
  3. import {
  4. DOMINANT_SPEAKER_CHANGED,
  5. HIDDEN_PARTICIPANT_JOINED,
  6. HIDDEN_PARTICIPANT_LEFT,
  7. GRANT_MODERATOR,
  8. KICK_PARTICIPANT,
  9. LOCAL_PARTICIPANT_RAISE_HAND,
  10. MUTE_REMOTE_PARTICIPANT,
  11. PARTICIPANT_ID_CHANGED,
  12. PARTICIPANT_JOINED,
  13. PARTICIPANT_KICKED,
  14. PARTICIPANT_LEFT,
  15. PARTICIPANT_UPDATED,
  16. PIN_PARTICIPANT,
  17. SET_LOADABLE_AVATAR_URL
  18. } from './actionTypes';
  19. import {
  20. DISCO_REMOTE_CONTROL_FEATURE
  21. } from './constants';
  22. import {
  23. getLocalParticipant,
  24. getNormalizedDisplayName,
  25. getParticipantDisplayName,
  26. getParticipantById
  27. } from './functions';
  28. import logger from './logger';
  29. /**
  30. * Create an action for when dominant speaker changes.
  31. *
  32. * @param {string} id - Participant's ID.
  33. * @param {JitsiConference} conference - The {@code JitsiConference} associated
  34. * with the participant identified by the specified {@code id}. Only the local
  35. * participant is allowed to not specify an associated {@code JitsiConference}
  36. * instance.
  37. * @returns {{
  38. * type: DOMINANT_SPEAKER_CHANGED,
  39. * participant: {
  40. * conference: JitsiConference,
  41. * id: string
  42. * }
  43. * }}
  44. */
  45. export function dominantSpeakerChanged(id, conference) {
  46. return {
  47. type: DOMINANT_SPEAKER_CHANGED,
  48. participant: {
  49. conference,
  50. id
  51. }
  52. };
  53. }
  54. /**
  55. * Create an action for granting moderator to a participant.
  56. *
  57. * @param {string} id - Participant's ID.
  58. * @returns {{
  59. * type: GRANT_MODERATOR,
  60. * id: string
  61. * }}
  62. */
  63. export function grantModerator(id) {
  64. return {
  65. type: GRANT_MODERATOR,
  66. id
  67. };
  68. }
  69. /**
  70. * Create an action for removing a participant from the conference.
  71. *
  72. * @param {string} id - Participant's ID.
  73. * @returns {{
  74. * type: KICK_PARTICIPANT,
  75. * id: string
  76. * }}
  77. */
  78. export function kickParticipant(id) {
  79. return {
  80. type: KICK_PARTICIPANT,
  81. id
  82. };
  83. }
  84. /**
  85. * Creates an action to signal the connection status of the local participant
  86. * has changed.
  87. *
  88. * @param {string} connectionStatus - The current connection status of the local
  89. * participant, as enumerated by the library's participantConnectionStatus
  90. * constants.
  91. * @returns {Function}
  92. */
  93. export function localParticipantConnectionStatusChanged(connectionStatus) {
  94. return (dispatch, getState) => {
  95. const participant = getLocalParticipant(getState);
  96. if (participant) {
  97. return dispatch(participantConnectionStatusChanged(
  98. participant.id,
  99. connectionStatus));
  100. }
  101. };
  102. }
  103. /**
  104. * Action to signal that the ID of local participant has changed. It happens
  105. * when the local participant joins a new conference or leaves an existing
  106. * conference.
  107. *
  108. * @param {string} id - New ID for local participant.
  109. * @returns {Function}
  110. */
  111. export function localParticipantIdChanged(id) {
  112. return (dispatch, getState) => {
  113. const participant = getLocalParticipant(getState);
  114. if (participant) {
  115. return dispatch({
  116. type: PARTICIPANT_ID_CHANGED,
  117. // XXX A participant is identified by an id-conference pair.
  118. // Only the local participant is with an undefined conference.
  119. conference: undefined,
  120. newValue: id,
  121. oldValue: participant.id
  122. });
  123. }
  124. };
  125. }
  126. /**
  127. * Action to signal that a local participant has joined.
  128. *
  129. * @param {Participant} participant={} - Information about participant.
  130. * @returns {{
  131. * type: PARTICIPANT_JOINED,
  132. * participant: Participant
  133. * }}
  134. */
  135. export function localParticipantJoined(participant = {}) {
  136. return participantJoined(set(participant, 'local', true));
  137. }
  138. /**
  139. * Action to remove a local participant.
  140. *
  141. * @returns {Function}
  142. */
  143. export function localParticipantLeft() {
  144. return (dispatch, getState) => {
  145. const participant = getLocalParticipant(getState);
  146. if (participant) {
  147. return (
  148. dispatch(
  149. participantLeft(
  150. participant.id,
  151. // XXX Only the local participant is allowed to leave
  152. // without stating the JitsiConference instance because
  153. // the local participant is uniquely identified by the
  154. // very fact that there is only one local participant
  155. // (and the fact that the local participant "joins" at
  156. // the beginning of the app and "leaves" at the end of
  157. // the app).
  158. undefined)));
  159. }
  160. };
  161. }
  162. /**
  163. * Action to signal the role of the local participant has changed. It can happen
  164. * when the participant has joined a conference, even before a non-default local
  165. * id has been set, or after a moderator leaves.
  166. *
  167. * @param {string} role - The new role of the local participant.
  168. * @returns {Function}
  169. */
  170. export function localParticipantRoleChanged(role) {
  171. return (dispatch, getState) => {
  172. const participant = getLocalParticipant(getState);
  173. if (participant) {
  174. return dispatch(participantRoleChanged(participant.id, role));
  175. }
  176. };
  177. }
  178. /**
  179. * Create an action for muting another participant in the conference.
  180. *
  181. * @param {string} id - Participant's ID.
  182. * @param {MEDIA_TYPE} mediaType - The media to mute.
  183. * @returns {{
  184. * type: MUTE_REMOTE_PARTICIPANT,
  185. * id: string,
  186. * mediaType: MEDIA_TYPE
  187. * }}
  188. */
  189. export function muteRemoteParticipant(id, mediaType) {
  190. return {
  191. type: MUTE_REMOTE_PARTICIPANT,
  192. id,
  193. mediaType
  194. };
  195. }
  196. /**
  197. * Action to update a participant's connection status.
  198. *
  199. * @param {string} id - Participant's ID.
  200. * @param {string} connectionStatus - The new connection status of the
  201. * participant.
  202. * @returns {{
  203. * type: PARTICIPANT_UPDATED,
  204. * participant: {
  205. * connectionStatus: string,
  206. * id: string
  207. * }
  208. * }}
  209. */
  210. export function participantConnectionStatusChanged(id, connectionStatus) {
  211. return {
  212. type: PARTICIPANT_UPDATED,
  213. participant: {
  214. connectionStatus,
  215. id
  216. }
  217. };
  218. }
  219. /**
  220. * Action to signal that a participant has joined.
  221. *
  222. * @param {Participant} participant - Information about participant.
  223. * @returns {{
  224. * type: PARTICIPANT_JOINED,
  225. * participant: Participant
  226. * }}
  227. */
  228. export function participantJoined(participant) {
  229. // Only the local participant is not identified with an id-conference pair.
  230. if (participant.local) {
  231. return {
  232. type: PARTICIPANT_JOINED,
  233. participant
  234. };
  235. }
  236. // In other words, a remote participant is identified with an id-conference
  237. // pair.
  238. const { conference } = participant;
  239. if (!conference) {
  240. throw Error(
  241. 'A remote participant must be associated with a JitsiConference!');
  242. }
  243. return (dispatch, getState) => {
  244. // A remote participant is only expected to join in a joined or joining
  245. // conference. The following check is really necessary because a
  246. // JitsiConference may have moved into leaving but may still manage to
  247. // sneak a PARTICIPANT_JOINED in if its leave is delayed for any purpose
  248. // (which is not outragous given that leaving involves network
  249. // requests.)
  250. const stateFeaturesBaseConference
  251. = getState()['features/base/conference'];
  252. if (conference === stateFeaturesBaseConference.conference
  253. || conference === stateFeaturesBaseConference.joining) {
  254. return dispatch({
  255. type: PARTICIPANT_JOINED,
  256. participant
  257. });
  258. }
  259. };
  260. }
  261. /**
  262. * Updates the features of a remote participant.
  263. *
  264. * @param {JitsiParticipant} jitsiParticipant - The ID of the participant.
  265. * @returns {{
  266. * type: PARTICIPANT_UPDATED,
  267. * participant: Participant
  268. * }}
  269. */
  270. export function updateRemoteParticipantFeatures(jitsiParticipant) {
  271. return (dispatch, getState) => {
  272. if (!jitsiParticipant) {
  273. return;
  274. }
  275. const id = jitsiParticipant.getId();
  276. jitsiParticipant.getFeatures()
  277. .then(features => {
  278. const supportsRemoteControl = features.has(DISCO_REMOTE_CONTROL_FEATURE);
  279. const participant = getParticipantById(getState(), id);
  280. if (!participant || participant.local) {
  281. return;
  282. }
  283. if (participant?.supportsRemoteControl !== supportsRemoteControl) {
  284. return dispatch({
  285. type: PARTICIPANT_UPDATED,
  286. participant: {
  287. id,
  288. supportsRemoteControl
  289. }
  290. });
  291. }
  292. })
  293. .catch(error => {
  294. logger.error(`Failed to get participant features for ${id}!`, error);
  295. });
  296. };
  297. }
  298. /**
  299. * Action to signal that a hidden participant has joined the conference.
  300. *
  301. * @param {string} id - The id of the participant.
  302. * @param {string} displayName - The display name, or undefined when
  303. * unknown.
  304. * @returns {{
  305. * type: HIDDEN_PARTICIPANT_JOINED,
  306. * displayName: string,
  307. * id: string
  308. * }}
  309. */
  310. export function hiddenParticipantJoined(id, displayName) {
  311. return {
  312. type: HIDDEN_PARTICIPANT_JOINED,
  313. id,
  314. displayName
  315. };
  316. }
  317. /**
  318. * Action to signal that a hidden participant has left the conference.
  319. *
  320. * @param {string} id - The id of the participant.
  321. * @returns {{
  322. * type: HIDDEN_PARTICIPANT_LEFT,
  323. * id: string
  324. * }}
  325. */
  326. export function hiddenParticipantLeft(id) {
  327. return {
  328. type: HIDDEN_PARTICIPANT_LEFT,
  329. id
  330. };
  331. }
  332. /**
  333. * Action to signal that a participant has left.
  334. *
  335. * @param {string} id - Participant's ID.
  336. * @param {JitsiConference} conference - The {@code JitsiConference} associated
  337. * with the participant identified by the specified {@code id}. Only the local
  338. * participant is allowed to not specify an associated {@code JitsiConference}
  339. * instance.
  340. * @param {boolean} isReplaced - Whether the participant is to be replaced in the meeting.
  341. * @returns {{
  342. * type: PARTICIPANT_LEFT,
  343. * participant: {
  344. * conference: JitsiConference,
  345. * id: string
  346. * }
  347. * }}
  348. */
  349. export function participantLeft(id, conference, isReplaced) {
  350. return {
  351. type: PARTICIPANT_LEFT,
  352. participant: {
  353. conference,
  354. id,
  355. isReplaced
  356. }
  357. };
  358. }
  359. /**
  360. * Action to signal that a participant's presence status has changed.
  361. *
  362. * @param {string} id - Participant's ID.
  363. * @param {string} presence - Participant's new presence status.
  364. * @returns {{
  365. * type: PARTICIPANT_UPDATED,
  366. * participant: {
  367. * id: string,
  368. * presence: string
  369. * }
  370. * }}
  371. */
  372. export function participantPresenceChanged(id, presence) {
  373. return participantUpdated({
  374. id,
  375. presence
  376. });
  377. }
  378. /**
  379. * Action to signal that a participant's role has changed.
  380. *
  381. * @param {string} id - Participant's ID.
  382. * @param {PARTICIPANT_ROLE} role - Participant's new role.
  383. * @returns {{
  384. * type: PARTICIPANT_UPDATED,
  385. * participant: {
  386. * id: string,
  387. * role: PARTICIPANT_ROLE
  388. * }
  389. * }}
  390. */
  391. export function participantRoleChanged(id, role) {
  392. return participantUpdated({
  393. id,
  394. role
  395. });
  396. }
  397. /**
  398. * Action to signal that some of participant properties has been changed.
  399. *
  400. * @param {Participant} participant={} - Information about participant. To
  401. * identify the participant the object should contain either property id with
  402. * value the id of the participant or property local with value true (if the
  403. * local participant hasn't joined the conference yet).
  404. * @returns {{
  405. * type: PARTICIPANT_UPDATED,
  406. * participant: Participant
  407. * }}
  408. */
  409. export function participantUpdated(participant = {}) {
  410. const participantToUpdate = {
  411. ...participant
  412. };
  413. if (participant.name) {
  414. participantToUpdate.name = getNormalizedDisplayName(participant.name);
  415. }
  416. return {
  417. type: PARTICIPANT_UPDATED,
  418. participant: participantToUpdate
  419. };
  420. }
  421. /**
  422. * Action to signal that a participant has muted us.
  423. *
  424. * @param {JitsiParticipant} participant - Information about participant.
  425. * @param {JitsiLocalTrack} track - Information about the track that has been muted.
  426. * @returns {Promise}
  427. */
  428. export function participantMutedUs(participant, track) {
  429. return (dispatch, getState) => {
  430. if (!participant) {
  431. return;
  432. }
  433. const isAudio = track.isAudioTrack();
  434. dispatch(showNotification({
  435. descriptionKey: isAudio ? 'notify.mutedRemotelyDescription' : 'notify.videoMutedRemotelyDescription',
  436. titleKey: isAudio ? 'notify.mutedRemotelyTitle' : 'notify.videoMutedRemotelyTitle',
  437. titleArguments: {
  438. participantDisplayName:
  439. getParticipantDisplayName(getState, participant.getId())
  440. }
  441. }));
  442. };
  443. }
  444. /**
  445. * Action to signal that a participant had been kicked.
  446. *
  447. * @param {JitsiParticipant} kicker - Information about participant performing the kick.
  448. * @param {JitsiParticipant} kicked - Information about participant that was kicked.
  449. * @returns {Promise}
  450. */
  451. export function participantKicked(kicker, kicked) {
  452. return (dispatch, getState) => {
  453. dispatch({
  454. type: PARTICIPANT_KICKED,
  455. kicked: kicked.getId(),
  456. kicker: kicker?.getId()
  457. });
  458. if (kicked.isReplaced && kicked.isReplaced()) {
  459. return;
  460. }
  461. dispatch(showNotification({
  462. titleArguments: {
  463. kicked:
  464. getParticipantDisplayName(getState, kicked.getId()),
  465. kicker:
  466. getParticipantDisplayName(getState, kicker.getId())
  467. },
  468. titleKey: 'notify.kickParticipant'
  469. }, NOTIFICATION_TIMEOUT * 2)); // leave more time for this
  470. };
  471. }
  472. /**
  473. * Create an action which pins a conference participant.
  474. *
  475. * @param {string|null} id - The ID of the conference participant to pin or null
  476. * if none of the conference's participants are to be pinned.
  477. * @returns {{
  478. * type: PIN_PARTICIPANT,
  479. * participant: {
  480. * id: string
  481. * }
  482. * }}
  483. */
  484. export function pinParticipant(id) {
  485. return {
  486. type: PIN_PARTICIPANT,
  487. participant: {
  488. id
  489. }
  490. };
  491. }
  492. /**
  493. * Creates an action which notifies the app that the loadable URL of the avatar of a participant got updated.
  494. *
  495. * @param {string} participantId - The ID of the participant.
  496. * @param {string} url - The new URL.
  497. * @returns {{
  498. * type: SET_LOADABLE_AVATAR_URL,
  499. * participant: {
  500. * id: string,
  501. * loadableAvatarUrl: string
  502. * }
  503. * }}
  504. */
  505. export function setLoadableAvatarUrl(participantId, url) {
  506. return {
  507. type: SET_LOADABLE_AVATAR_URL,
  508. participant: {
  509. id: participantId,
  510. loadableAvatarUrl: url
  511. }
  512. };
  513. }
  514. /**
  515. * Raise hand for the local participant.
  516. *
  517. * @param {boolean} enabled - Raise or lower hand.
  518. * @returns {{
  519. * type: LOCAL_PARTICIPANT_RAISE_HAND,
  520. * enabled: boolean
  521. * }}
  522. */
  523. export function raiseHand(enabled) {
  524. return {
  525. type: LOCAL_PARTICIPANT_RAISE_HAND,
  526. enabled
  527. };
  528. }