Receive side of remote mute
This commit is contained in:
parent
0d89e7b01a
commit
a444790bf9
13 changed files with 540 additions and 16 deletions
|
@ -13529,7 +13529,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## libsignal-account-keys 0.1.0, libsignal-core 0.1.0, mrp 2.51.0, protobuf 2.51.0, ringrtc 2.51.0, regex-aot 0.1.0, partial-default-derive 0.1.0
|
## libsignal-account-keys 0.1.0, libsignal-core 0.1.0, mrp 2.52.0, protobuf 2.52.0, ringrtc 2.52.0, regex-aot 0.1.0, partial-default-derive 0.1.0
|
||||||
|
|
||||||
```
|
```
|
||||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
|
|
@ -4164,6 +4164,22 @@
|
||||||
"messageformat": "Mic on",
|
"messageformat": "Mic on",
|
||||||
"description": "Shown in a call when the user is muted and then unmutes their audio input using the Mute toggle button."
|
"description": "Shown in a call when the user is muted and then unmutes their audio input using the Mute toggle button."
|
||||||
},
|
},
|
||||||
|
"icu:CallControls__MutedBySomeoneToast": {
|
||||||
|
"messageformat": "{otherName} muted you.",
|
||||||
|
"description": "Shown in a call when another client mutes this user"
|
||||||
|
},
|
||||||
|
"icu:CallControls__SomeoneMutedSomeoneToast": {
|
||||||
|
"messageformat": "{name} muted {otherName}.",
|
||||||
|
"description": "Shown in a call when one client mutes another client"
|
||||||
|
},
|
||||||
|
"icu:CallControls__YouMutedSomeoneToast": {
|
||||||
|
"messageformat": "You muted {otherName}.",
|
||||||
|
"description": "Shown in a call when you mute another client"
|
||||||
|
},
|
||||||
|
"icu:CallControls__YouMutedYourselfToast": {
|
||||||
|
"messageformat": "You muted yourself from another device.",
|
||||||
|
"description": "Shown in a call when you joined a call on two devices and mute one from the other."
|
||||||
|
},
|
||||||
"icu:CallControls__LowerHandSuggestionToast": {
|
"icu:CallControls__LowerHandSuggestionToast": {
|
||||||
"messageformat": "Lower your hand?",
|
"messageformat": "Lower your hand?",
|
||||||
"description": "Shown in a call when the user has their hand raised but has been talking, next to a button to lower their hand."
|
"description": "Shown in a call when the user has their hand raised but has been talking, next to a button to lower their hand."
|
||||||
|
|
|
@ -121,7 +121,7 @@
|
||||||
"@react-types/shared": "3.27.0",
|
"@react-types/shared": "3.27.0",
|
||||||
"@signalapp/libsignal-client": "0.70.0",
|
"@signalapp/libsignal-client": "0.70.0",
|
||||||
"@signalapp/quill-cjs": "2.1.2",
|
"@signalapp/quill-cjs": "2.1.2",
|
||||||
"@signalapp/ringrtc": "2.51.0",
|
"@signalapp/ringrtc": "2.52.0",
|
||||||
"@signalapp/sqlcipher": "2.0.1",
|
"@signalapp/sqlcipher": "2.0.1",
|
||||||
"@tanstack/react-virtual": "3.11.2",
|
"@tanstack/react-virtual": "3.11.2",
|
||||||
"@types/dom-mediacapture-transform": "0.1.11",
|
"@types/dom-mediacapture-transform": "0.1.11",
|
||||||
|
|
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
|
@ -132,8 +132,8 @@ importers:
|
||||||
specifier: 2.1.2
|
specifier: 2.1.2
|
||||||
version: 2.1.2
|
version: 2.1.2
|
||||||
'@signalapp/ringrtc':
|
'@signalapp/ringrtc':
|
||||||
specifier: 2.51.0
|
specifier: 2.52.0
|
||||||
version: 2.51.0
|
version: 2.52.0
|
||||||
'@signalapp/sqlcipher':
|
'@signalapp/sqlcipher':
|
||||||
specifier: 2.0.1
|
specifier: 2.0.1
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
|
@ -2550,8 +2550,8 @@ packages:
|
||||||
resolution: {integrity: sha512-y2sgqdivlrG41J4Zvt/82xtH/PZjDlgItqlD2g/Cv3ZbjlR6cGhTNXbfNygCJB8nXj+C7I28pjt1Zm3k0pv2mg==}
|
resolution: {integrity: sha512-y2sgqdivlrG41J4Zvt/82xtH/PZjDlgItqlD2g/Cv3ZbjlR6cGhTNXbfNygCJB8nXj+C7I28pjt1Zm3k0pv2mg==}
|
||||||
engines: {npm: '>=8.2.3'}
|
engines: {npm: '>=8.2.3'}
|
||||||
|
|
||||||
'@signalapp/ringrtc@2.51.0':
|
'@signalapp/ringrtc@2.52.0':
|
||||||
resolution: {integrity: sha512-p0S7JLReO9NjfxB3Er3V6eydNB4IUfrIIimtlD7E9CerUWtejxvPNqEPxfKTCL/vVde/pqsIn0Qw9LjysA84xA==}
|
resolution: {integrity: sha512-s4mwnL4f6LBpDonIJ2TZKv1mD+zbAq6aPnPH3tlJZUhrdJj0GZtjTVY2i7Y6dmLG3zsho10a8hGtGjPAMyDmlw==}
|
||||||
|
|
||||||
'@signalapp/sqlcipher@2.0.1':
|
'@signalapp/sqlcipher@2.0.1':
|
||||||
resolution: {integrity: sha512-7dSgNnf/hrGZfVSGlhVH39f7BDNNOO61tg6Xu/Fa38TCeZj6/U5YILKQavBArCtkahUvzGBV9QIyRr0zereU7A==}
|
resolution: {integrity: sha512-7dSgNnf/hrGZfVSGlhVH39f7BDNNOO61tg6Xu/Fa38TCeZj6/U5YILKQavBArCtkahUvzGBV9QIyRr0zereU7A==}
|
||||||
|
@ -12072,7 +12072,7 @@ snapshots:
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
quill-delta: 5.1.0
|
quill-delta: 5.1.0
|
||||||
|
|
||||||
'@signalapp/ringrtc@2.51.0':
|
'@signalapp/ringrtc@2.52.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
https-proxy-agent: 7.0.6
|
https-proxy-agent: 7.0.6
|
||||||
tar: 6.2.1
|
tar: 6.2.1
|
||||||
|
|
|
@ -128,6 +128,7 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
setGroupCallVideoRequest: action('set-group-call-video-request'),
|
setGroupCallVideoRequest: action('set-group-call-video-request'),
|
||||||
setIsCallActive: action('set-is-call-active'),
|
setIsCallActive: action('set-is-call-active'),
|
||||||
setLocalAudio: action('set-local-audio'),
|
setLocalAudio: action('set-local-audio'),
|
||||||
|
setLocalAudioRemoteMuted: action('set-local-audio-remote-muted'),
|
||||||
setLocalPreviewContainer: action('set-local-preview-container'),
|
setLocalPreviewContainer: action('set-local-preview-container'),
|
||||||
setLocalVideo: action('set-local-video'),
|
setLocalVideo: action('set-local-video'),
|
||||||
setRendererCanvas: action('set-renderer-canvas'),
|
setRendererCanvas: action('set-renderer-canvas'),
|
||||||
|
|
|
@ -36,6 +36,7 @@ import type {
|
||||||
SendGroupCallReactionType,
|
SendGroupCallReactionType,
|
||||||
SetGroupCallVideoRequestType,
|
SetGroupCallVideoRequestType,
|
||||||
SetLocalAudioType,
|
SetLocalAudioType,
|
||||||
|
SetMutedByType,
|
||||||
SetLocalVideoType,
|
SetLocalVideoType,
|
||||||
SetRendererCanvasType,
|
SetRendererCanvasType,
|
||||||
StartCallType,
|
StartCallType,
|
||||||
|
@ -124,6 +125,7 @@ export type PropsType = {
|
||||||
setIsCallActive: (_: boolean) => void;
|
setIsCallActive: (_: boolean) => void;
|
||||||
setLocalAudio: SetLocalAudioType;
|
setLocalAudio: SetLocalAudioType;
|
||||||
setLocalVideo: SetLocalVideoType;
|
setLocalVideo: SetLocalVideoType;
|
||||||
|
setLocalAudioRemoteMuted: SetMutedByType;
|
||||||
setLocalPreviewContainer: (container: HTMLDivElement | null) => void;
|
setLocalPreviewContainer: (container: HTMLDivElement | null) => void;
|
||||||
setOutgoingRing: (_: boolean) => void;
|
setOutgoingRing: (_: boolean) => void;
|
||||||
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
||||||
|
@ -188,6 +190,7 @@ function ActiveCallManager({
|
||||||
sendGroupCallReaction,
|
sendGroupCallReaction,
|
||||||
setGroupCallVideoRequest,
|
setGroupCallVideoRequest,
|
||||||
setLocalAudio,
|
setLocalAudio,
|
||||||
|
setLocalAudioRemoteMuted,
|
||||||
setLocalPreviewContainer,
|
setLocalPreviewContainer,
|
||||||
setLocalVideo,
|
setLocalVideo,
|
||||||
setRendererCanvas,
|
setRendererCanvas,
|
||||||
|
@ -475,6 +478,7 @@ function ActiveCallManager({
|
||||||
setLocalPreviewContainer={setLocalPreviewContainer}
|
setLocalPreviewContainer={setLocalPreviewContainer}
|
||||||
setRendererCanvas={setRendererCanvas}
|
setRendererCanvas={setRendererCanvas}
|
||||||
setLocalAudio={setLocalAudio}
|
setLocalAudio={setLocalAudio}
|
||||||
|
setLocalAudioRemoteMuted={setLocalAudioRemoteMuted}
|
||||||
setLocalVideo={setLocalVideo}
|
setLocalVideo={setLocalVideo}
|
||||||
stickyControls={showParticipantsList}
|
stickyControls={showParticipantsList}
|
||||||
switchToPresentationView={switchToPresentationView}
|
switchToPresentationView={switchToPresentationView}
|
||||||
|
@ -567,6 +571,7 @@ export function CallManager({
|
||||||
setGroupCallVideoRequest,
|
setGroupCallVideoRequest,
|
||||||
setIsCallActive,
|
setIsCallActive,
|
||||||
setLocalAudio,
|
setLocalAudio,
|
||||||
|
setLocalAudioRemoteMuted,
|
||||||
setLocalPreviewContainer,
|
setLocalPreviewContainer,
|
||||||
setLocalVideo,
|
setLocalVideo,
|
||||||
setOutgoingRing,
|
setOutgoingRing,
|
||||||
|
@ -659,6 +664,7 @@ export function CallManager({
|
||||||
sendGroupCallReaction={sendGroupCallReaction}
|
sendGroupCallReaction={sendGroupCallReaction}
|
||||||
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
||||||
setLocalAudio={setLocalAudio}
|
setLocalAudio={setLocalAudio}
|
||||||
|
setLocalAudioRemoteMuted={setLocalAudioRemoteMuted}
|
||||||
setLocalPreviewContainer={setLocalPreviewContainer}
|
setLocalPreviewContainer={setLocalPreviewContainer}
|
||||||
setLocalVideo={setLocalVideo}
|
setLocalVideo={setLocalVideo}
|
||||||
setOutgoingRing={setOutgoingRing}
|
setOutgoingRing={setOutgoingRing}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
||||||
ActiveCallReactionsType,
|
ActiveCallReactionsType,
|
||||||
ActiveGroupCallType,
|
ActiveGroupCallType,
|
||||||
GroupCallRemoteParticipantType,
|
GroupCallRemoteParticipantType,
|
||||||
|
ObservedRemoteMuteType,
|
||||||
} from '../types/Calling';
|
} from '../types/Calling';
|
||||||
import {
|
import {
|
||||||
CallViewMode,
|
CallViewMode,
|
||||||
|
@ -19,6 +20,7 @@ import {
|
||||||
} from '../types/Calling';
|
} from '../types/Calling';
|
||||||
import { CallMode } from '../types/CallDisposition';
|
import { CallMode } from '../types/CallDisposition';
|
||||||
import { generateAci } from '../types/ServiceId';
|
import { generateAci } from '../types/ServiceId';
|
||||||
|
import type { AciString } from '../types/ServiceId';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import { AvatarColors } from '../types/Colors';
|
import { AvatarColors } from '../types/Colors';
|
||||||
import type { PropsType } from './CallScreen';
|
import type { PropsType } from './CallScreen';
|
||||||
|
@ -47,6 +49,7 @@ const conversation = getDefaultConversation({
|
||||||
name: 'Rick Sanchez',
|
name: 'Rick Sanchez',
|
||||||
phoneNumber: '3051234567',
|
phoneNumber: '3051234567',
|
||||||
profileName: 'Rick Sanchez',
|
profileName: 'Rick Sanchez',
|
||||||
|
isMe: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
type OverridePropsBase = {
|
type OverridePropsBase = {
|
||||||
|
@ -56,6 +59,7 @@ type OverridePropsBase = {
|
||||||
viewMode?: CallViewMode;
|
viewMode?: CallViewMode;
|
||||||
outgoingRing?: boolean;
|
outgoingRing?: boolean;
|
||||||
reactions?: ActiveCallReactionsType;
|
reactions?: ActiveCallReactionsType;
|
||||||
|
myAci?: AciString;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DirectCallOverrideProps = OverridePropsBase & {
|
type DirectCallOverrideProps = OverridePropsBase & {
|
||||||
|
@ -80,6 +84,9 @@ type GroupCallOverrideProps = OverridePropsBase & {
|
||||||
selfViewExpanded?: boolean;
|
selfViewExpanded?: boolean;
|
||||||
suggestLowerHand?: boolean;
|
suggestLowerHand?: boolean;
|
||||||
outgoingRing?: boolean;
|
outgoingRing?: boolean;
|
||||||
|
localMutedBy?: number;
|
||||||
|
observedRemoteMute?: ObservedRemoteMuteType;
|
||||||
|
forceIndex0IsMe?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createActiveDirectCallProp = (
|
const createActiveDirectCallProp = (
|
||||||
|
@ -112,10 +119,16 @@ const getConversationsByDemuxId = (overrideProps: GroupCallOverrideProps) => {
|
||||||
const conversationsByDemuxId = new Map<number, ConversationType>(
|
const conversationsByDemuxId = new Map<number, ConversationType>(
|
||||||
overrideProps.remoteParticipants?.map((participant, index) => [
|
overrideProps.remoteParticipants?.map((participant, index) => [
|
||||||
participant.demuxId,
|
participant.demuxId,
|
||||||
getDefaultConversationWithServiceId({
|
getDefaultConversationWithServiceId(
|
||||||
|
{
|
||||||
isBlocked: index === 10 || index === MAX_PARTICIPANTS - 1,
|
isBlocked: index === 10 || index === MAX_PARTICIPANTS - 1,
|
||||||
title: `Participant ${index + 1}`,
|
title: `Participant ${index + 1}`,
|
||||||
}),
|
isMe: index === 0 && (overrideProps.forceIndex0IsMe ?? false),
|
||||||
|
},
|
||||||
|
index === 0 && (overrideProps.forceIndex0IsMe ?? false)
|
||||||
|
? conversation.serviceId
|
||||||
|
: undefined
|
||||||
|
),
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
conversationsByDemuxId.set(LOCAL_DEMUX_ID, conversation);
|
conversationsByDemuxId.set(LOCAL_DEMUX_ID, conversation);
|
||||||
|
@ -164,6 +177,8 @@ const createActiveGroupCallProp = (overrideProps: GroupCallOverrideProps) => ({
|
||||||
),
|
),
|
||||||
reactions: overrideProps.reactions || [],
|
reactions: overrideProps.reactions || [],
|
||||||
suggestLowerHand: overrideProps.suggestLowerHand ?? false,
|
suggestLowerHand: overrideProps.suggestLowerHand ?? false,
|
||||||
|
mutedBy: overrideProps.localMutedBy ?? undefined,
|
||||||
|
observedRemoteMute: overrideProps.observedRemoteMute ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const createActiveCallProp = (
|
const createActiveCallProp = (
|
||||||
|
@ -221,7 +236,7 @@ const createProps = (
|
||||||
name: 'Morty Smith',
|
name: 'Morty Smith',
|
||||||
profileName: 'Morty Smith',
|
profileName: 'Morty Smith',
|
||||||
title: 'Morty Smith',
|
title: 'Morty Smith',
|
||||||
serviceId: generateAci(),
|
serviceId: overrideProps.myAci ?? generateAci(),
|
||||||
}),
|
}),
|
||||||
openSystemPreferencesAction: action('open-system-preferences-action'),
|
openSystemPreferencesAction: action('open-system-preferences-action'),
|
||||||
renderEmojiPicker: () => <>EmojiPicker</>,
|
renderEmojiPicker: () => <>EmojiPicker</>,
|
||||||
|
@ -231,6 +246,7 @@ const createProps = (
|
||||||
sendGroupCallReaction: action('send-group-call-reaction'),
|
sendGroupCallReaction: action('send-group-call-reaction'),
|
||||||
setGroupCallVideoRequest: action('set-group-call-video-request'),
|
setGroupCallVideoRequest: action('set-group-call-video-request'),
|
||||||
setLocalAudio: action('set-local-audio'),
|
setLocalAudio: action('set-local-audio'),
|
||||||
|
setLocalAudioRemoteMuted: action('set-local-audio-remote-muted'),
|
||||||
setLocalPreviewContainer: action('set-local-preview-container'),
|
setLocalPreviewContainer: action('set-local-preview-container'),
|
||||||
setLocalVideo: action('set-local-video'),
|
setLocalVideo: action('set-local-video'),
|
||||||
setRendererCanvas: action('set-renderer-canvas'),
|
setRendererCanvas: action('set-renderer-canvas'),
|
||||||
|
@ -997,3 +1013,131 @@ export function CallLinkUnknownContactMissingMediaKeys(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RemoteMuteYouRemoteMutedByOther(): JSX.Element {
|
||||||
|
// Should show you're muted by another
|
||||||
|
const [props, setProps] = React.useState(() =>
|
||||||
|
createProps({ callMode: CallMode.Group })
|
||||||
|
);
|
||||||
|
React.useEffect(() => {
|
||||||
|
setProps(
|
||||||
|
createProps({
|
||||||
|
callMode: CallMode.Group,
|
||||||
|
hasLocalAudio: false,
|
||||||
|
remoteParticipants: allRemoteParticipants,
|
||||||
|
localMutedBy: 3,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
return <CallScreen {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoteMuteYouRemoteMutedBySelf(): JSX.Element {
|
||||||
|
// Should show you're muted by yourself
|
||||||
|
const [props, setProps] = React.useState(() =>
|
||||||
|
createProps({ callMode: CallMode.Group })
|
||||||
|
);
|
||||||
|
const myAci = conversation.serviceId as AciString;
|
||||||
|
React.useEffect(() => {
|
||||||
|
setProps(
|
||||||
|
createProps({
|
||||||
|
callMode: CallMode.Group,
|
||||||
|
remoteParticipants: [
|
||||||
|
{
|
||||||
|
aci: myAci,
|
||||||
|
demuxId: 0,
|
||||||
|
hasRemoteAudio: true,
|
||||||
|
hasRemoteVideo: true,
|
||||||
|
isHandRaised: false,
|
||||||
|
mediaKeysReceived: true,
|
||||||
|
presenting: false,
|
||||||
|
sharingScreen: false,
|
||||||
|
videoAspectRatio: 1.3,
|
||||||
|
...getDefaultConversation({
|
||||||
|
isBlocked: false,
|
||||||
|
title: 'Note To Self',
|
||||||
|
serviceId: myAci,
|
||||||
|
isMe: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
localMutedBy: 0,
|
||||||
|
myAci,
|
||||||
|
forceIndex0IsMe: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [myAci]);
|
||||||
|
return <CallScreen {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoteMuteObserveMuteYouSent(): JSX.Element {
|
||||||
|
// Should show you muted someone else
|
||||||
|
const [props, setProps] = React.useState(() =>
|
||||||
|
createProps({ callMode: CallMode.Group })
|
||||||
|
);
|
||||||
|
React.useEffect(() => {
|
||||||
|
setProps(
|
||||||
|
createProps({
|
||||||
|
callMode: CallMode.Group,
|
||||||
|
remoteParticipants: allRemoteParticipants,
|
||||||
|
observedRemoteMute: { source: LOCAL_DEMUX_ID, target: 0 },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
return <CallScreen {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoteMuteObserveMuteOtherSent(): JSX.Element {
|
||||||
|
// Should show someone else muted a third person
|
||||||
|
const [props, setProps] = React.useState(() =>
|
||||||
|
createProps({ callMode: CallMode.Group })
|
||||||
|
);
|
||||||
|
React.useEffect(() => {
|
||||||
|
setProps(
|
||||||
|
createProps({
|
||||||
|
callMode: CallMode.Group,
|
||||||
|
remoteParticipants: allRemoteParticipants,
|
||||||
|
observedRemoteMute: { source: 3, target: 4 },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
return <CallScreen {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoteMuteObserveIgnoreSelfMute(): JSX.Element {
|
||||||
|
// Should show nothing because the ACIs match
|
||||||
|
const [props, setProps] = React.useState(() =>
|
||||||
|
createProps({ callMode: CallMode.Group })
|
||||||
|
);
|
||||||
|
const myAci = conversation.serviceId as AciString;
|
||||||
|
React.useEffect(() => {
|
||||||
|
setProps(
|
||||||
|
createProps({
|
||||||
|
callMode: CallMode.Group,
|
||||||
|
remoteParticipants: [
|
||||||
|
{
|
||||||
|
aci: myAci,
|
||||||
|
demuxId: 0,
|
||||||
|
hasRemoteAudio: true,
|
||||||
|
hasRemoteVideo: true,
|
||||||
|
isHandRaised: false,
|
||||||
|
mediaKeysReceived: true,
|
||||||
|
presenting: false,
|
||||||
|
sharingScreen: false,
|
||||||
|
videoAspectRatio: 1.3,
|
||||||
|
...getDefaultConversation({
|
||||||
|
isBlocked: false,
|
||||||
|
title: 'Note To Self',
|
||||||
|
serviceId: myAci,
|
||||||
|
isMe: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
observedRemoteMute: { source: LOCAL_DEMUX_ID, target: 0 },
|
||||||
|
myAci,
|
||||||
|
forceIndex0IsMe: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [myAci]);
|
||||||
|
return <CallScreen {...props} />;
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import type {
|
||||||
SetLocalAudioType,
|
SetLocalAudioType,
|
||||||
SetLocalVideoType,
|
SetLocalVideoType,
|
||||||
SetRendererCanvasType,
|
SetRendererCanvasType,
|
||||||
|
SetMutedByType,
|
||||||
} from '../state/ducks/calling';
|
} from '../state/ducks/calling';
|
||||||
import { Avatar, AvatarSize } from './Avatar';
|
import { Avatar, AvatarSize } from './Avatar';
|
||||||
import { CallingHeader, getCallViewIconClassname } from './CallingHeader';
|
import { CallingHeader, getCallViewIconClassname } from './CallingHeader';
|
||||||
|
@ -135,6 +136,7 @@ export type PropsType = {
|
||||||
toggleSelfViewExpanded: () => void;
|
toggleSelfViewExpanded: () => void;
|
||||||
toggleSettings: () => void;
|
toggleSettings: () => void;
|
||||||
changeCallView: (mode: CallViewMode) => void;
|
changeCallView: (mode: CallViewMode) => void;
|
||||||
|
setLocalAudioRemoteMuted: SetMutedByType;
|
||||||
} & Pick<ReactionPickerProps, 'renderEmojiPicker'>;
|
} & Pick<ReactionPickerProps, 'renderEmojiPicker'>;
|
||||||
|
|
||||||
export const isInSpeakerView = (
|
export const isInSpeakerView = (
|
||||||
|
@ -224,6 +226,7 @@ export function CallScreen({
|
||||||
toggleScreenRecordingPermissionsDialog,
|
toggleScreenRecordingPermissionsDialog,
|
||||||
toggleSelfViewExpanded,
|
toggleSelfViewExpanded,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
|
setLocalAudioRemoteMuted,
|
||||||
}: PropsType): JSX.Element {
|
}: PropsType): JSX.Element {
|
||||||
const {
|
const {
|
||||||
conversation,
|
conversation,
|
||||||
|
@ -980,7 +983,17 @@ export function CallScreen({
|
||||||
: false
|
: false
|
||||||
}
|
}
|
||||||
isHandRaised={localHandRaised}
|
isHandRaised={localHandRaised}
|
||||||
|
mutedBy={
|
||||||
|
isGroupOrAdhocActiveCall(activeCall) ? activeCall.mutedBy : undefined
|
||||||
|
}
|
||||||
|
observedRemoteMute={
|
||||||
|
isGroupOrAdhocActiveCall(activeCall)
|
||||||
|
? activeCall.observedRemoteMute
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
conversationsByDemuxId={conversationsByDemuxId}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
setLocalAudioRemoteMuted={setLocalAudioRemoteMuted}
|
||||||
/>
|
/>
|
||||||
{isCallLinkAdmin ? (
|
{isCallLinkAdmin ? (
|
||||||
<CallingPendingParticipants
|
<CallingPendingParticipants
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useEffect, useMemo, useRef } from 'react';
|
import React, { useEffect, useMemo, useRef } from 'react';
|
||||||
import type { ActiveCallType } from '../types/Calling';
|
import type { ActiveCallType, ObservedRemoteMuteType } from '../types/Calling';
|
||||||
import { CallMode } from '../types/CallDisposition';
|
import { CallMode } from '../types/CallDisposition';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
|
@ -12,6 +12,7 @@ import { difference as setDifference } from '../util/setUtil';
|
||||||
import { isMoreRecentThan } from '../util/timestamp';
|
import { isMoreRecentThan } from '../util/timestamp';
|
||||||
import { isGroupOrAdhocActiveCall } from '../util/isGroupOrAdhocCall';
|
import { isGroupOrAdhocActiveCall } from '../util/isGroupOrAdhocCall';
|
||||||
import { SECOND } from '../util/durations';
|
import { SECOND } from '../util/durations';
|
||||||
|
import type { SetMutedByType } from '../state/ducks/calling';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
activeCall: ActiveCallType;
|
activeCall: ActiveCallType;
|
||||||
|
@ -83,9 +84,11 @@ export function useScreenSharingStoppedToast({
|
||||||
|
|
||||||
function useMutedToast({
|
function useMutedToast({
|
||||||
hasLocalAudio,
|
hasLocalAudio,
|
||||||
|
mutedBy,
|
||||||
i18n,
|
i18n,
|
||||||
}: {
|
}: {
|
||||||
hasLocalAudio: boolean;
|
hasLocalAudio: boolean;
|
||||||
|
mutedBy: number | undefined;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
}): void {
|
}): void {
|
||||||
const previousHasLocalAudio = usePrevious(hasLocalAudio, hasLocalAudio);
|
const previousHasLocalAudio = usePrevious(hasLocalAudio, hasLocalAudio);
|
||||||
|
@ -95,7 +98,8 @@ function useMutedToast({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
previousHasLocalAudio !== undefined &&
|
previousHasLocalAudio !== undefined &&
|
||||||
hasLocalAudio !== previousHasLocalAudio
|
hasLocalAudio !== previousHasLocalAudio &&
|
||||||
|
mutedBy === undefined // skip this if we were muted by someone
|
||||||
) {
|
) {
|
||||||
hideToast(MUTED_TOAST_KEY);
|
hideToast(MUTED_TOAST_KEY);
|
||||||
showToast({
|
showToast({
|
||||||
|
@ -107,7 +111,14 @@ function useMutedToast({
|
||||||
dismissable: true,
|
dismissable: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [hasLocalAudio, previousHasLocalAudio, hideToast, showToast, i18n]);
|
}, [
|
||||||
|
hasLocalAudio,
|
||||||
|
previousHasLocalAudio,
|
||||||
|
hideToast,
|
||||||
|
showToast,
|
||||||
|
mutedBy,
|
||||||
|
i18n,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function useOutgoingRingToast({
|
function useOutgoingRingToast({
|
||||||
|
@ -321,6 +332,143 @@ function useLowerHandSuggestionToast({
|
||||||
}, [isHandRaised, hideToast]);
|
}, [isHandRaised, hideToast]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useMutedByToast({
|
||||||
|
mutedBy,
|
||||||
|
setLocalAudioRemoteMuted,
|
||||||
|
conversationsByDemuxId,
|
||||||
|
i18n,
|
||||||
|
}: {
|
||||||
|
mutedBy: number | undefined;
|
||||||
|
setLocalAudioRemoteMuted?: SetMutedByType;
|
||||||
|
conversationsByDemuxId?: Map<number, ConversationType>;
|
||||||
|
i18n: LocalizerType;
|
||||||
|
}): void {
|
||||||
|
const previousMutedBy = usePrevious(mutedBy, mutedBy);
|
||||||
|
|
||||||
|
const { showToast, hideToast } = useCallingToasts();
|
||||||
|
const MUTED_BY_TOAST_KEY = 'MUTED_BY_TOAST_KEY';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (setLocalAudioRemoteMuted === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
mutedBy === undefined ||
|
||||||
|
// if it's undefined, likely we just received a remote mute request
|
||||||
|
// and hadn't had one before.
|
||||||
|
(previousMutedBy !== undefined && previousMutedBy === mutedBy) ||
|
||||||
|
conversationsByDemuxId === undefined
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const otherConversation = conversationsByDemuxId.get(mutedBy);
|
||||||
|
const title = otherConversation?.title;
|
||||||
|
if (title === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLocalAudioRemoteMuted({ mutedBy });
|
||||||
|
let content;
|
||||||
|
if (otherConversation?.isMe) {
|
||||||
|
content = i18n('icu:CallControls__YouMutedYourselfToast');
|
||||||
|
} else {
|
||||||
|
content = i18n('icu:CallControls__MutedBySomeoneToast', {
|
||||||
|
otherName: title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
hideToast(MUTED_BY_TOAST_KEY);
|
||||||
|
showToast({
|
||||||
|
key: MUTED_BY_TOAST_KEY,
|
||||||
|
content,
|
||||||
|
dismissable: true,
|
||||||
|
autoClose: true,
|
||||||
|
lifetime: 10 * SECOND,
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
mutedBy,
|
||||||
|
previousMutedBy,
|
||||||
|
conversationsByDemuxId,
|
||||||
|
i18n,
|
||||||
|
showToast,
|
||||||
|
hideToast,
|
||||||
|
MUTED_BY_TOAST_KEY,
|
||||||
|
setLocalAudioRemoteMuted,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useObservedRemoteMuteToast({
|
||||||
|
observedRemoteMute,
|
||||||
|
conversationsByDemuxId,
|
||||||
|
i18n,
|
||||||
|
}: {
|
||||||
|
observedRemoteMute: ObservedRemoteMuteType | undefined;
|
||||||
|
conversationsByDemuxId?: Map<number, ConversationType>;
|
||||||
|
i18n: LocalizerType;
|
||||||
|
}): void {
|
||||||
|
const { showToast, hideToast } = useCallingToasts();
|
||||||
|
const OBSERVED_REMOTE_MUTE_TOAST_KEY = 'OBSERVED_REMOTE_MUTE_TOAST_KEY';
|
||||||
|
const previousObservedRemoteMute = usePrevious(
|
||||||
|
observedRemoteMute,
|
||||||
|
observedRemoteMute
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
observedRemoteMute === undefined ||
|
||||||
|
(previousObservedRemoteMute !== undefined &&
|
||||||
|
previousObservedRemoteMute === observedRemoteMute) ||
|
||||||
|
conversationsByDemuxId === undefined
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceConversation = conversationsByDemuxId.get(
|
||||||
|
observedRemoteMute.source
|
||||||
|
);
|
||||||
|
const targetConversation = conversationsByDemuxId.get(
|
||||||
|
observedRemoteMute.target
|
||||||
|
);
|
||||||
|
if (sourceConversation?.serviceId === targetConversation?.serviceId) {
|
||||||
|
// Ignore self-mutes.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const targetTitle = targetConversation?.title;
|
||||||
|
if (targetTitle === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let content;
|
||||||
|
if (sourceConversation?.isMe) {
|
||||||
|
content = i18n('icu:CallControls__YouMutedSomeoneToast', {
|
||||||
|
otherName: targetTitle,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const sourceTitle = sourceConversation?.title;
|
||||||
|
if (sourceTitle === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
content = i18n('icu:CallControls__SomeoneMutedSomeoneToast', {
|
||||||
|
name: sourceTitle,
|
||||||
|
otherName: targetTitle,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hideToast(OBSERVED_REMOTE_MUTE_TOAST_KEY);
|
||||||
|
showToast({
|
||||||
|
key: OBSERVED_REMOTE_MUTE_TOAST_KEY,
|
||||||
|
content,
|
||||||
|
dismissable: true,
|
||||||
|
autoClose: true,
|
||||||
|
lifetime: 10 * SECOND,
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
observedRemoteMute,
|
||||||
|
previousObservedRemoteMute,
|
||||||
|
conversationsByDemuxId,
|
||||||
|
i18n,
|
||||||
|
showToast,
|
||||||
|
hideToast,
|
||||||
|
OBSERVED_REMOTE_MUTE_TOAST_KEY,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
type CallingButtonToastsType = {
|
type CallingButtonToastsType = {
|
||||||
hasLocalAudio: boolean;
|
hasLocalAudio: boolean;
|
||||||
outgoingRing: boolean | undefined;
|
outgoingRing: boolean | undefined;
|
||||||
|
@ -331,7 +479,11 @@ type CallingButtonToastsType = {
|
||||||
suggestLowerHand?: boolean;
|
suggestLowerHand?: boolean;
|
||||||
isHandRaised?: boolean;
|
isHandRaised?: boolean;
|
||||||
handleLowerHand?: () => void;
|
handleLowerHand?: () => void;
|
||||||
|
mutedBy?: number;
|
||||||
|
observedRemoteMute?: ObservedRemoteMuteType;
|
||||||
|
conversationsByDemuxId?: Map<number, ConversationType>;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
setLocalAudioRemoteMuted?: SetMutedByType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CallingButtonToastsContainer(
|
export function CallingButtonToastsContainer(
|
||||||
|
@ -361,8 +513,12 @@ function CallingButtonToasts({
|
||||||
handleLowerHand,
|
handleLowerHand,
|
||||||
isHandRaised,
|
isHandRaised,
|
||||||
i18n,
|
i18n,
|
||||||
|
mutedBy,
|
||||||
|
observedRemoteMute,
|
||||||
|
conversationsByDemuxId,
|
||||||
|
setLocalAudioRemoteMuted,
|
||||||
}: CallingButtonToastsType) {
|
}: CallingButtonToastsType) {
|
||||||
useMutedToast({ hasLocalAudio, i18n });
|
useMutedToast({ hasLocalAudio, mutedBy, i18n });
|
||||||
useOutgoingRingToast({ outgoingRing, i18n });
|
useOutgoingRingToast({ outgoingRing, i18n });
|
||||||
useRaisedHandsToast({ raisedHands, renderRaisedHandsToast });
|
useRaisedHandsToast({ raisedHands, renderRaisedHandsToast });
|
||||||
useLowerHandSuggestionToast({
|
useLowerHandSuggestionToast({
|
||||||
|
@ -371,6 +527,17 @@ function CallingButtonToasts({
|
||||||
handleLowerHand,
|
handleLowerHand,
|
||||||
isHandRaised,
|
isHandRaised,
|
||||||
});
|
});
|
||||||
|
useMutedByToast({
|
||||||
|
mutedBy,
|
||||||
|
setLocalAudioRemoteMuted,
|
||||||
|
conversationsByDemuxId,
|
||||||
|
i18n,
|
||||||
|
});
|
||||||
|
useObservedRemoteMuteToast({
|
||||||
|
observedRemoteMute,
|
||||||
|
conversationsByDemuxId,
|
||||||
|
i18n,
|
||||||
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -221,6 +221,9 @@ type CallingReduxInterface = Pick<
|
||||||
| 'startCallLinkLobbyByRoomId'
|
| 'startCallLinkLobbyByRoomId'
|
||||||
| 'peekNotConnectedGroupCall'
|
| 'peekNotConnectedGroupCall'
|
||||||
| 'setSuggestLowerHand'
|
| 'setSuggestLowerHand'
|
||||||
|
| 'setLocalAudio'
|
||||||
|
| 'setMutedBy'
|
||||||
|
| 'onObservedRemoteMute'
|
||||||
> & {
|
> & {
|
||||||
areAnyCallsActiveOrRinging(): boolean;
|
areAnyCallsActiveOrRinging(): boolean;
|
||||||
};
|
};
|
||||||
|
@ -1637,6 +1640,21 @@ export class CallingClass {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onRemoteMute: (_groupCall: GroupCall, demuxId: number) => {
|
||||||
|
log.info('GroupCall#onRemoteMute');
|
||||||
|
this.#reduxInterface?.setMutedBy({ mutedBy: demuxId });
|
||||||
|
},
|
||||||
|
onObservedRemoteMute: (
|
||||||
|
_groupCall: GroupCall,
|
||||||
|
sourceDemuxId: number,
|
||||||
|
targetDemuxId: number
|
||||||
|
) => {
|
||||||
|
log.info('GroupCall#onObservedRemoteMute');
|
||||||
|
this.#reduxInterface?.onObservedRemoteMute({
|
||||||
|
source: sourceDemuxId,
|
||||||
|
target: targetDemuxId,
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2251,6 +2269,20 @@ export class CallingClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setOutgoingAudioRemoteMuted(conversationId: string, source: number): void {
|
||||||
|
const call = getOwn(this.#callsLookup, conversationId);
|
||||||
|
if (!call) {
|
||||||
|
log.warn('Trying to remote mute outgoing audio for a non-existent call');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (call instanceof GroupCall) {
|
||||||
|
call.setOutgoingAudioMutedRemotely(source);
|
||||||
|
} else {
|
||||||
|
log.warn('Trying to remote mute outgoing audio on a 1:1 call');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async setOutgoingVideo(
|
async setOutgoingVideo(
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
|
|
|
@ -29,6 +29,7 @@ import type {
|
||||||
ChangeIODevicePayloadType,
|
ChangeIODevicePayloadType,
|
||||||
GroupCallVideoRequest,
|
GroupCallVideoRequest,
|
||||||
MediaDeviceSettings,
|
MediaDeviceSettings,
|
||||||
|
ObservedRemoteMuteType,
|
||||||
PresentedSource,
|
PresentedSource,
|
||||||
PresentableSource,
|
PresentableSource,
|
||||||
} from '../../types/Calling';
|
} from '../../types/Calling';
|
||||||
|
@ -192,6 +193,8 @@ export type ActiveCallStateType = {
|
||||||
showNeedsScreenRecordingPermissionsWarning?: boolean;
|
showNeedsScreenRecordingPermissionsWarning?: boolean;
|
||||||
showParticipantsList: boolean;
|
showParticipantsList: boolean;
|
||||||
suggestLowerHand?: boolean;
|
suggestLowerHand?: boolean;
|
||||||
|
mutedBy?: number;
|
||||||
|
observedRemoteMute?: ObservedRemoteMuteType;
|
||||||
reactions?: ActiveCallReactionsType;
|
reactions?: ActiveCallReactionsType;
|
||||||
};
|
};
|
||||||
export type WaitingCallStateType = ReadonlyDeep<{
|
export type WaitingCallStateType = ReadonlyDeep<{
|
||||||
|
@ -370,6 +373,18 @@ export type SetLocalVideoType = (
|
||||||
}>
|
}>
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||||
|
export type SetMutedByType = (
|
||||||
|
payload: ReadonlyDeep<{
|
||||||
|
mutedBy: number;
|
||||||
|
}>
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export type ObservedRemoteMuteDucksType = ReadonlyDeep<{
|
||||||
|
source: number;
|
||||||
|
target: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type SetGroupCallVideoRequestType = ReadonlyDeep<{
|
export type SetGroupCallVideoRequestType = ReadonlyDeep<{
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
resolutions: Array<GroupCallVideoRequest>;
|
resolutions: Array<GroupCallVideoRequest>;
|
||||||
|
@ -658,6 +673,8 @@ const SELECT_PRESENTING_SOURCE = 'calling/SELECT_PRESENTING_SOURCE';
|
||||||
const SEND_GROUP_CALL_REACTION = 'calling/SEND_GROUP_CALL_REACTION';
|
const SEND_GROUP_CALL_REACTION = 'calling/SEND_GROUP_CALL_REACTION';
|
||||||
const SET_LOCAL_AUDIO_FULFILLED = 'calling/SET_LOCAL_AUDIO_FULFILLED';
|
const SET_LOCAL_AUDIO_FULFILLED = 'calling/SET_LOCAL_AUDIO_FULFILLED';
|
||||||
const SET_LOCAL_VIDEO_FULFILLED = 'calling/SET_LOCAL_VIDEO_FULFILLED';
|
const SET_LOCAL_VIDEO_FULFILLED = 'calling/SET_LOCAL_VIDEO_FULFILLED';
|
||||||
|
const SET_MUTED_BY = 'calling/SET_MUTED_BY';
|
||||||
|
const OBSERVED_REMOTE_MUTE = 'calling/OBSERVED_REMOTE_MUTE';
|
||||||
const SET_OUTGOING_RING = 'calling/SET_OUTGOING_RING';
|
const SET_OUTGOING_RING = 'calling/SET_OUTGOING_RING';
|
||||||
const SET_PRESENTING = 'calling/SET_PRESENTING';
|
const SET_PRESENTING = 'calling/SET_PRESENTING';
|
||||||
const SET_PRESENTING_SOURCES = 'calling/SET_PRESENTING_SOURCES';
|
const SET_PRESENTING_SOURCES = 'calling/SET_PRESENTING_SOURCES';
|
||||||
|
@ -915,6 +932,16 @@ type SetLocalVideoFulfilledActionType = ReadonlyDeep<{
|
||||||
payload: Parameters<SetLocalVideoType>[0];
|
payload: Parameters<SetLocalVideoType>[0];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
type SetMutedByActionType = ReadonlyDeep<{
|
||||||
|
type: 'calling/SET_MUTED_BY';
|
||||||
|
payload: Parameters<SetMutedByType>[0];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type ObservedRemoteMuteActionType = ReadonlyDeep<{
|
||||||
|
type: 'calling/OBSERVED_REMOTE_MUTE';
|
||||||
|
payload: ObservedRemoteMuteDucksType;
|
||||||
|
}>;
|
||||||
|
|
||||||
type SetPresentingFulfilledActionType = ReadonlyDeep<{
|
type SetPresentingFulfilledActionType = ReadonlyDeep<{
|
||||||
type: 'calling/SET_PRESENTING';
|
type: 'calling/SET_PRESENTING';
|
||||||
payload?: PresentedSource;
|
payload?: PresentedSource;
|
||||||
|
@ -1019,6 +1046,8 @@ export type CallingActionType =
|
||||||
| SetCapturerBatonActionType
|
| SetCapturerBatonActionType
|
||||||
| SetLocalAudioActionType
|
| SetLocalAudioActionType
|
||||||
| SetLocalVideoFulfilledActionType
|
| SetLocalVideoFulfilledActionType
|
||||||
|
| SetMutedByActionType
|
||||||
|
| ObservedRemoteMuteActionType
|
||||||
| SetPresentingSourcesActionType
|
| SetPresentingSourcesActionType
|
||||||
| SetOutgoingRingActionType
|
| SetOutgoingRingActionType
|
||||||
| StartDirectCallActionType
|
| StartDirectCallActionType
|
||||||
|
@ -1929,6 +1958,28 @@ function setLocalAudio(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setLocalAudioRemoteMuted(
|
||||||
|
payload: Parameters<SetMutedByType>[0]
|
||||||
|
): ThunkAction<void, RootStateType, unknown, SetLocalAudioActionType> {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const activeCall = getActiveCall(getState().calling);
|
||||||
|
if (!activeCall) {
|
||||||
|
log.warn('Trying to set local audio when no call is active');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
calling.setOutgoingAudioRemoteMuted(
|
||||||
|
activeCall.conversationId,
|
||||||
|
payload?.mutedBy
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: SET_LOCAL_AUDIO_FULFILLED,
|
||||||
|
payload: { enabled: false },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function setLocalVideo(
|
function setLocalVideo(
|
||||||
payload: Parameters<SetLocalVideoType>[0]
|
payload: Parameters<SetLocalVideoType>[0]
|
||||||
): ThunkAction<void, RootStateType, unknown, SetLocalVideoFulfilledActionType> {
|
): ThunkAction<void, RootStateType, unknown, SetLocalVideoFulfilledActionType> {
|
||||||
|
@ -1967,6 +2018,40 @@ function setLocalVideo(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setMutedBy(
|
||||||
|
payload: Parameters<SetMutedByType>[0]
|
||||||
|
): ThunkAction<void, RootStateType, unknown, SetMutedByActionType> {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const activeCall = getActiveCall(getState().calling);
|
||||||
|
if (!activeCall) {
|
||||||
|
log.warn('Trying to set muted by when no call is active');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: SET_MUTED_BY,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function onObservedRemoteMute(
|
||||||
|
payload: ObservedRemoteMuteDucksType
|
||||||
|
): ThunkAction<void, RootStateType, unknown, ObservedRemoteMuteActionType> {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const activeCall = getActiveCall(getState().calling);
|
||||||
|
if (!activeCall) {
|
||||||
|
log.warn('Trying to record remote mute when no call is active');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: OBSERVED_REMOTE_MUTE,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function setGroupCallVideoRequest(
|
function setGroupCallVideoRequest(
|
||||||
payload: SetGroupCallVideoRequestType
|
payload: SetGroupCallVideoRequestType
|
||||||
): ThunkAction<void, RootStateType, unknown, never> {
|
): ThunkAction<void, RootStateType, unknown, never> {
|
||||||
|
@ -2765,6 +2850,7 @@ export const actions = {
|
||||||
handleCallLinkDelete,
|
handleCallLinkDelete,
|
||||||
joinedAdhocCall,
|
joinedAdhocCall,
|
||||||
leaveCurrentCallAndStartCallingLobby,
|
leaveCurrentCallAndStartCallingLobby,
|
||||||
|
onObservedRemoteMute,
|
||||||
onOutgoingVideoCallInConversation,
|
onOutgoingVideoCallInConversation,
|
||||||
onOutgoingAudioCallInConversation,
|
onOutgoingAudioCallInConversation,
|
||||||
openSystemPreferencesAction,
|
openSystemPreferencesAction,
|
||||||
|
@ -2788,6 +2874,8 @@ export const actions = {
|
||||||
setIsCallActive,
|
setIsCallActive,
|
||||||
setLocalAudio,
|
setLocalAudio,
|
||||||
setLocalVideo,
|
setLocalVideo,
|
||||||
|
setLocalAudioRemoteMuted,
|
||||||
|
setMutedBy,
|
||||||
setOutgoingRing,
|
setOutgoingRing,
|
||||||
setRendererCanvas,
|
setRendererCanvas,
|
||||||
setSuggestLowerHand,
|
setSuggestLowerHand,
|
||||||
|
@ -3995,11 +4083,16 @@ export function reducer(
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newMutedBy = action.payload?.enabled
|
||||||
|
? undefined
|
||||||
|
: state.activeCallState.mutedBy;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
activeCallState: {
|
activeCallState: {
|
||||||
...state.activeCallState,
|
...state.activeCallState,
|
||||||
hasLocalAudio: Boolean(action.payload?.enabled),
|
hasLocalAudio: Boolean(action.payload?.enabled),
|
||||||
|
mutedBy: newMutedBy,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4019,6 +4112,47 @@ export function reducer(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action.type === SET_MUTED_BY) {
|
||||||
|
const { mutedBy } = action.payload;
|
||||||
|
const { activeCallState } = state;
|
||||||
|
|
||||||
|
if (activeCallState?.state !== 'Active') {
|
||||||
|
log.warn('Cannot set muted by with no active call');
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newMutedBy = activeCallState.hasLocalAudio ? mutedBy : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeCallState: {
|
||||||
|
...activeCallState,
|
||||||
|
mutedBy: newMutedBy,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === OBSERVED_REMOTE_MUTE) {
|
||||||
|
const { source, target } = action.payload;
|
||||||
|
const { activeCallState } = state;
|
||||||
|
|
||||||
|
if (activeCallState?.state !== 'Active') {
|
||||||
|
log.warn('Cannot observe muted by with no active call');
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeCallState: {
|
||||||
|
...activeCallState,
|
||||||
|
observedRemoteMute: {
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (action.type === CHANGE_IO_DEVICE_FULFILLED) {
|
if (action.type === CHANGE_IO_DEVICE_FULFILLED) {
|
||||||
const { selectedDevice } = action.payload;
|
const { selectedDevice } = action.payload;
|
||||||
const nextState = Object.create(null);
|
const nextState = Object.create(null);
|
||||||
|
|
|
@ -338,6 +338,8 @@ const mapStateToActiveCallProp = (
|
||||||
remoteParticipants,
|
remoteParticipants,
|
||||||
remoteAudioLevels: call.remoteAudioLevels || new Map<number, number>(),
|
remoteAudioLevels: call.remoteAudioLevels || new Map<number, number>(),
|
||||||
suggestLowerHand: Boolean(activeCallState.suggestLowerHand),
|
suggestLowerHand: Boolean(activeCallState.suggestLowerHand),
|
||||||
|
mutedBy: activeCallState.mutedBy,
|
||||||
|
observedRemoteMute: activeCallState.observedRemoteMute,
|
||||||
} satisfies ActiveGroupCallType;
|
} satisfies ActiveGroupCallType;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -460,6 +462,7 @@ export const SmartCallManager = memo(function SmartCallManager() {
|
||||||
setGroupCallVideoRequest,
|
setGroupCallVideoRequest,
|
||||||
setIsCallActive,
|
setIsCallActive,
|
||||||
setLocalAudio,
|
setLocalAudio,
|
||||||
|
setLocalAudioRemoteMuted,
|
||||||
setLocalVideo,
|
setLocalVideo,
|
||||||
setOutgoingRing,
|
setOutgoingRing,
|
||||||
setRendererCanvas,
|
setRendererCanvas,
|
||||||
|
@ -519,6 +522,7 @@ export const SmartCallManager = memo(function SmartCallManager() {
|
||||||
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
||||||
setIsCallActive={setIsCallActive}
|
setIsCallActive={setIsCallActive}
|
||||||
setLocalAudio={setLocalAudio}
|
setLocalAudio={setLocalAudio}
|
||||||
|
setLocalAudioRemoteMuted={setLocalAudioRemoteMuted}
|
||||||
setLocalPreviewContainer={setLocalPreviewContainer}
|
setLocalPreviewContainer={setLocalPreviewContainer}
|
||||||
setLocalVideo={setLocalVideo}
|
setLocalVideo={setLocalVideo}
|
||||||
setOutgoingRing={setOutgoingRing}
|
setOutgoingRing={setOutgoingRing}
|
||||||
|
|
|
@ -84,6 +84,11 @@ export type ActiveDirectCallType = ActiveCallBaseType & {
|
||||||
remoteAudioLevel: number;
|
remoteAudioLevel: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ObservedRemoteMuteType = {
|
||||||
|
source: number;
|
||||||
|
target: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ActiveGroupCallType = ActiveCallBaseType & {
|
export type ActiveGroupCallType = ActiveCallBaseType & {
|
||||||
callMode: CallMode.Group | CallMode.Adhoc;
|
callMode: CallMode.Group | CallMode.Adhoc;
|
||||||
connectionState: GroupCallConnectionState;
|
connectionState: GroupCallConnectionState;
|
||||||
|
@ -100,6 +105,8 @@ export type ActiveGroupCallType = ActiveCallBaseType & {
|
||||||
remoteParticipants: Array<GroupCallRemoteParticipantType>;
|
remoteParticipants: Array<GroupCallRemoteParticipantType>;
|
||||||
remoteAudioLevels: Map<number, number>;
|
remoteAudioLevels: Map<number, number>;
|
||||||
suggestLowerHand: boolean;
|
suggestLowerHand: boolean;
|
||||||
|
mutedBy?: number;
|
||||||
|
observedRemoteMute?: ObservedRemoteMuteType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ActiveCallType = ActiveDirectCallType | ActiveGroupCallType;
|
export type ActiveCallType = ActiveDirectCallType | ActiveGroupCallType;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue