Skip to content

Commit 5e84ffc

Browse files
authored
Merge pull request #252 from Azure-Samples/fuyan/hard-mute
Hard mute feature support to sample app
2 parents 3fdc1de + 41c773f commit 5e84ffc

File tree

3 files changed

+226
-25
lines changed

3 files changed

+226
-25
lines changed

Project/src/App.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ ul {
218218
padding-top: 2em;
219219
padding-bottom: 2em;
220220
padding-left: 1em;
221+
white-space: nowrap;
221222
}
222223

223224
.participant-item:hover {

Project/src/MakeCall/CallCard.js

Lines changed: 173 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ export default class CallCard extends React.Component {
5555
this.pptLiveFeature = this.call.feature(Features.PPTLive);
5656
this.pptLiveHtml = React.createRef();
5757
}
58+
let meetingMediaAccess = undefined;
59+
let remoteParticipantsMediaAccess = undefined;
60+
let mediaAccessMap = undefined;
61+
if (Features.MediaAccess) {
62+
this.mediaAccessCallFeature = this.call.feature(Features.MediaAccess);
63+
meetingMediaAccess = this.call.feature(Features.MediaAccess).getMeetingMediaAccess();
64+
remoteParticipantsMediaAccess = this.call.feature(Features.MediaAccess).getAllOthersMediaAccess();
65+
mediaAccessMap = new Map();
66+
remoteParticipantsMediaAccess.forEach((mediaAccess) => {
67+
mediaAccessMap.set(mediaAccess.participant.rawId, mediaAccess);
68+
});
69+
}
5870
this.isTeamsUser = props.isTeamsUser;
5971
this.dummyStreamTimeout = undefined;
6072
this.state = {
@@ -71,6 +83,8 @@ export default class CallCard extends React.Component {
7183
canSpotlight: this.capabilities.spotlightParticipant?.isPresent || this.capabilities.spotlightParticipant?.reason === 'FeatureNotSupported',
7284
canMuteOthers: this.capabilities.muteOthers?.isPresent || this.capabilities.muteOthers?.reason === 'FeatureNotSupported',
7385
canReact: this.capabilities.useReactions?.isPresent || this.capabilities.useReactions?.reason === 'FeatureNotSupported',
86+
canForbidOthersAudio: this.capabilities.forbidOthersAudio?.isPresent || this.capabilities.forbidOthersAudio?.reason === 'FeatureNotSupported',
87+
canForbidOthersVideo: this.capabilities.forbidOthersVideo?.isPresent || this.capabilities.forbidOthersVideo?.reason === 'FeatureNotSupported',
7488
videoOn: this.call.isLocalVideoStarted,
7589
screenSharingOn: this.call.isScreenSharingOn,
7690
micMuted: this.call.isMuted,
@@ -109,7 +123,12 @@ export default class CallCard extends React.Component {
109123
pptLiveActive: false,
110124
isRecordingActive: false,
111125
isTranscriptionActive: false,
112-
lobbyParticipantsCount: this.lobby?.participants.length
126+
lobbyParticipantsCount: this.lobby?.participants.length,
127+
mediaAccessMap,
128+
meetingMediaAccess: {
129+
isAudioPermitted: meetingMediaAccess?.isAudioPermitted,
130+
isVideoPermitted: meetingMediaAccess?.isVideoPermitted,
131+
}
113132
};
114133
this.selectedRemoteParticipants = new Set();
115134
this.dataChannelRef = React.createRef();
@@ -150,7 +169,11 @@ export default class CallCard extends React.Component {
150169
this.call.feature(Features.PPTLive).off('isActiveChanged', this.pptLiveChangedHandler);
151170
}
152171
this.dominantSpeakersFeature.off('dominantSpeakersChanged', this.dominantSpeakersChanged);
153-
}
172+
if (Features.mediaAccess) {
173+
this.mediaAccessCallFeature.off('mediaAccessChanged', this.mediaAccessChangedHandler);
174+
this.mediaAccessCallFeature.off('meetingMediaAccessChanged', this.meetingMediaAccessChangedHandler);
175+
}
176+
}
154177

155178
componentDidMount() {
156179
if (this.call) {
@@ -239,10 +262,14 @@ export default class CallCard extends React.Component {
239262
if (this.call.state === 'LocalHold' || this.call.state === 'RemoteHold') {
240263
this.setState({ canRaiseHands: false });
241264
this.setState({ canSpotlight: false });
265+
this.setState({ canForbidOthersAudio: false });
266+
this.setState({ canForbidOthersVideo: false });
242267
}
243268
if (this.call.state === 'Connected') {
244269
this.setState({ canRaiseHands: this.capabilities.raiseHand?.isPresent || this.capabilities.raiseHand?.reason === 'FeatureNotSupported' });
245270
this.setState({ canSpotlight: this.capabilities.spotlightParticipant?.isPresent || this.capabilities.spotlightParticipant?.reason === 'FeatureNotSupported' });
271+
this.setState({ canForbidOthersAudio: this.capabilities.forbidOthersAudio?.isPresent || this.capabilities.forbidOthersAudio?.reason === 'FeatureNotSupported' });
272+
this.setState({ canForbidOthersVideo: this.capabilities.forbidOthersVideo?.isPresent || this.capabilities.forbidOthersVideo?.reason === 'FeatureNotSupported' });
246273
}
247274
}
248275
callStateChanged();
@@ -480,6 +507,10 @@ export default class CallCard extends React.Component {
480507
this.transcriptionFeature.on('isTranscriptionActiveChanged', this.isTranscriptionActiveChangedHandler);
481508
this.lobby?.on('lobbyParticipantsUpdated', this.lobbyParticipantsUpdatedHandler);
482509
this.realTimeTextFeature?.on('realTimeTextReceived', this.realTimeTextReceivedHandler);
510+
if (Features.MediaAccess) {
511+
this.mediaAccessCallFeature.on('mediaAccessChanged', this.mediaAccessChangedHandler);
512+
this.mediaAccessCallFeature.on('meetingMediaAccessChanged', this.meetingMediaAccessChangedHandler);
513+
}
483514
}
484515
}
485516

@@ -541,6 +572,24 @@ export default class CallCard extends React.Component {
541572
this.identifier, this.spotlightFeature.getSpotlightedParticipants())})
542573
}
543574

575+
mediaAccessChangedHandler = (event) => {
576+
const mediaAccessMap = new Map();
577+
event.mediaAccesses.forEach((mediaAccess) => {
578+
mediaAccessMap.set(mediaAccess.participant.rawId, mediaAccess);
579+
});
580+
581+
this.setState({mediaAccessMap});
582+
}
583+
584+
meetingMediaAccessChangedHandler = (event) => {
585+
if (event.meetingMediaAccess) {
586+
this.setState({meetingMediaAccess: {
587+
isAudioPermitted: event.meetingMediaAccess.isAudioPermitted,
588+
isVideoPermitted: event.meetingMediaAccess.isVideoPermitted,
589+
}});
590+
}
591+
}
592+
544593
isRecordingActiveChangedHandler = (event) => {
545594
this.setState({ isRecordingActive: this.recordingFeature.isRecordingActive })
546595
}
@@ -630,11 +679,11 @@ export default class CallCard extends React.Component {
630679
capabilitiesChangedHandler = (capabilitiesChangeInfo) => {
631680
for (const [key, value] of Object.entries(capabilitiesChangeInfo.newValue)) {
632681
if(key === 'turnVideoOn' && value.reason != 'FeatureNotSupported') {
633-
(value.isPresent) ? this.setState({ canOnVideo: true }) : this.setState({ canOnVideo: false });
682+
(value.isPresent) ? this.setState(prevState => ({ ...prevState, canOnVideo: true, callMessage: prevState.callMessage?.replace('Your camera has been disabled.','') })) : this.setState({ canOnVideo: false, callMessage: 'Your camera has been disabled.' });
634683
continue;
635684
}
636685
if(key === 'unmuteMic' && value.reason != 'FeatureNotSupported') {
637-
(value.isPresent) ? this.setState({ canUnMuteMic: true }) : this.setState({ canUnMuteMic: false });
686+
(value.isPresent) ? this.setState(prevState => ({...prevState, canUnMuteMic: true, callMessage: prevState.callMessage?.replace('Your mic has been disabled.','') })) : this.setState({ canUnMuteMic: false, callMessage: 'Your mic has been disabled.' });
638687
continue;
639688
}
640689
if(key === 'shareScreen' && value.reason != 'FeatureNotSupported') {
@@ -657,6 +706,14 @@ export default class CallCard extends React.Component {
657706
(value.isPresent) ? this.setState({ canReact: true }) : this.setState({ canReact: false });
658707
continue;
659708
}
709+
if(key === 'forbidOthersAudio' && value.reason != 'FeatureNotSupported') {
710+
(value.isPresent) ? this.setState({ canForbidOthersAudio: true }) : this.setState({ canForbidOthersAudio: false });
711+
continue;
712+
}
713+
if(key === 'forbidOthersVideo' && value.reason != 'FeatureNotSupported') {
714+
(value.isPresent) ? this.setState({ canForbidOthersVideo: true }) : this.setState({ canForbidOthersVideo: false });
715+
continue;
716+
}
660717
}
661718
this.capabilities = this.capabilitiesFeature.capabilities;
662719
}
@@ -1133,6 +1190,62 @@ export default class CallCard extends React.Component {
11331190
} catch(e) {
11341191
console.error(e);
11351192
}
1193+
},
1194+
forbidAudio: async (identifier) => {
1195+
try {
1196+
await this.mediaAccessCallFeature.forbidAudio([identifier]);
1197+
} catch(e) {
1198+
console.error(e);
1199+
}
1200+
},
1201+
permitAudio: async (identifier) => {
1202+
try {
1203+
await this.mediaAccessCallFeature.permitAudio([identifier]);
1204+
} catch(e) {
1205+
console.error(e);
1206+
}
1207+
},
1208+
forbidVideo: async (identifier) => {
1209+
try {
1210+
await this.mediaAccessCallFeature.forbidVideo([identifier]);
1211+
} catch(e) {
1212+
console.error(e);
1213+
}
1214+
},
1215+
permitVideo: async (identifier) => {
1216+
try {
1217+
await this.mediaAccessCallFeature.permitVideo([identifier]);
1218+
} catch(e) {
1219+
console.error(e);
1220+
}
1221+
},
1222+
forbidOthersAudio: async () => {
1223+
try {
1224+
await this.mediaAccessCallFeature.forbidOthersAudio();
1225+
} catch(e) {
1226+
console.error(e);
1227+
}
1228+
},
1229+
permitOthersAudio: async () => {
1230+
try {
1231+
await this.mediaAccessCallFeature.permitOthersAudio();
1232+
} catch(e) {
1233+
console.error(e);
1234+
}
1235+
},
1236+
forbidOthersVideo: async () => {
1237+
try {
1238+
await this.mediaAccessCallFeature.forbidOthersVideo();
1239+
} catch(e) {
1240+
console.error(e);
1241+
}
1242+
},
1243+
permitOthersVideo: async () => {
1244+
try {
1245+
await this.mediaAccessCallFeature.permitOthersVideo();
1246+
} catch(e) {
1247+
console.error(e);
1248+
}
11361249
}
11371250
}
11381251
}
@@ -1214,6 +1327,43 @@ export default class CallCard extends React.Component {
12141327
onClick: (e) => menuCallBacks.consentToBeingRecorded(e)
12151328
});
12161329

1330+
1331+
if (this.state.canForbidOthersAudio && this.state.meetingMediaAccess.isAudioPermitted){
1332+
menuItems.push({
1333+
key: 'Disable mic for all attendees',
1334+
iconProps: { iconName: 'Focus'},
1335+
text: 'Disable mic for all attendees',
1336+
onClick: () => menuCallBacks.forbidOthersAudio()
1337+
});
1338+
}
1339+
1340+
if (this.state.canForbidOthersAudio && !this.state.meetingMediaAccess.isAudioPermitted){
1341+
menuItems.push({
1342+
key: 'Enable mic for all attendees',
1343+
iconProps: { iconName: 'Focus'},
1344+
text: 'Enable mic for all attendees',
1345+
onClick: () => menuCallBacks.permitOthersAudio()
1346+
});
1347+
}
1348+
1349+
if (this.state.canForbidOthersVideo && this.state.meetingMediaAccess.isVideoPermitted){
1350+
menuItems.push({
1351+
key: 'Disable camera for all attendees',
1352+
iconProps: { iconName: 'Focus'},
1353+
text: 'Disable camera for all attendees',
1354+
onClick: () => menuCallBacks.forbidOthersVideo()
1355+
});
1356+
}
1357+
1358+
if (this.state.canForbidOthersVideo && !this.state.meetingMediaAccess.isVideoPermitted){
1359+
menuItems.push({
1360+
key: 'Enable camera for all attendees',
1361+
iconProps: { iconName: 'Focus'},
1362+
text: 'Enable camera for all attendees',
1363+
onClick: () => menuCallBacks.permitOthersVideo()
1364+
});
1365+
}
1366+
12171367
return menuItems.filter(item => item != 0)
12181368
}
12191369

@@ -1240,6 +1390,7 @@ export default class CallCard extends React.Component {
12401390
render() {
12411391
const emojis = ['👍', '❤️', '😂', '👏', '😲'];
12421392
const streamCount = this.state.allRemoteParticipantStreams.length;
1393+
const mediaAccessMap = this.state.mediaAccessMap || new Map();
12431394
return (
12441395
<div className="ms-Grid mt-2">
12451396
<div className="ms-Grid-row">
@@ -1305,18 +1456,19 @@ export default class CallCard extends React.Component {
13051456
<p>No other participants currently in the call</p>
13061457
}
13071458
<ul className="p-0 m-0">
1308-
{
1309-
this.state.remoteParticipants.map(remoteParticipant =>
1310-
<RemoteParticipantCard
1459+
{this.state.remoteParticipants.map(remoteParticipant => {
1460+
const participantMediaAccess = mediaAccessMap?.get(remoteParticipant.identifier.rawId);
1461+
return ( <RemoteParticipantCard
13111462
key={`${utils.getIdentifierText(remoteParticipant.identifier)}`}
13121463
remoteParticipant={remoteParticipant}
13131464
call={this.call}
13141465
menuOptionsHandler={this.getParticipantMenuCallBacks()}
13151466
onSelectionChanged={(identifier, isChecked) => this.remoteParticipantSelectionChanged(identifier, isChecked)}
13161467
capabilitiesFeature={this.capabilitiesFeature}
1317-
/>
1318-
)
1319-
}
1468+
mediaAccess={participantMediaAccess}
1469+
/>);
1470+
})}
1471+
13201472
</ul>
13211473

13221474
</div>
@@ -1363,30 +1515,37 @@ export default class CallCard extends React.Component {
13631515
<div className="ms-Grid-row">
13641516
<div className="text-center">
13651517
<span className="in-call-button"
1366-
title={`Turn your video ${this.state.videoOn ? 'off' : 'on'}`}
1518+
title = {`${this.state.canOnVideo ? (this.state.videoOn ? 'Turn your video off' : 'Turn your video on') : 'Video is disabled'}`}
13671519
variant="secondary"
13681520
onClick={() => this.handleVideoOnOff()}>
13691521
{
13701522
this.state.canOnVideo && this.state.videoOn &&
13711523
<Icon iconName="Video" />
13721524
}
13731525
{
1374-
(!this.state.canOnVideo || !this.state.videoOn) &&
1526+
(this.state.canOnVideo || !this.state.videoOn) &&
1527+
<Icon iconName="VideoOff2" />
1528+
}
1529+
{
1530+
(!this.state.canOnVideo) &&
13751531
<Icon iconName="VideoOff" />
13761532
}
13771533
</span>
13781534
<span className="in-call-button"
1379-
title={`${this.state.micMuted ? 'Unmute' : 'Mute'} your microphone`}
1535+
title={`${this.state.canUnMuteMic ? (this.state.micMuted ? 'Unmute your microphone' : 'Mute your microphone') : 'Microphone is disabled'}`}
13801536
variant="secondary"
13811537
onClick={() => this.handleMicOnOff()}>
13821538
{
13831539
this.state.canUnMuteMic && !this.state.micMuted &&
13841540
<Icon iconName="Microphone" />
13851541
}
13861542
{
1387-
(!this.state.canUnMuteMic || this.state.micMuted) &&
1543+
(this.state.canUnMuteMic && this.state.micMuted) &&
13881544
<Icon iconName="MicOff2" />
13891545
}
1546+
{
1547+
!this.state.canUnMuteMic && <Icon iconName="MicOff" />
1548+
}
13901549
</span>
13911550
<span className="in-call-button"
13921551
onClick={() => this.call.hangUp()}>

0 commit comments

Comments
 (0)