@@ -55,6 +55,18 @@ export default class CallCard extends React.Component {
55
55
this . pptLiveFeature = this . call . feature ( Features . PPTLive ) ;
56
56
this . pptLiveHtml = React . createRef ( ) ;
57
57
}
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
+ }
58
70
this . isTeamsUser = props . isTeamsUser ;
59
71
this . dummyStreamTimeout = undefined ;
60
72
this . state = {
@@ -71,6 +83,8 @@ export default class CallCard extends React.Component {
71
83
canSpotlight : this . capabilities . spotlightParticipant ?. isPresent || this . capabilities . spotlightParticipant ?. reason === 'FeatureNotSupported' ,
72
84
canMuteOthers : this . capabilities . muteOthers ?. isPresent || this . capabilities . muteOthers ?. reason === 'FeatureNotSupported' ,
73
85
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' ,
74
88
videoOn : this . call . isLocalVideoStarted ,
75
89
screenSharingOn : this . call . isScreenSharingOn ,
76
90
micMuted : this . call . isMuted ,
@@ -109,7 +123,12 @@ export default class CallCard extends React.Component {
109
123
pptLiveActive : false ,
110
124
isRecordingActive : false ,
111
125
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
+ }
113
132
} ;
114
133
this . selectedRemoteParticipants = new Set ( ) ;
115
134
this . dataChannelRef = React . createRef ( ) ;
@@ -150,7 +169,11 @@ export default class CallCard extends React.Component {
150
169
this . call . feature ( Features . PPTLive ) . off ( 'isActiveChanged' , this . pptLiveChangedHandler ) ;
151
170
}
152
171
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
+ }
154
177
155
178
componentDidMount ( ) {
156
179
if ( this . call ) {
@@ -239,10 +262,14 @@ export default class CallCard extends React.Component {
239
262
if ( this . call . state === 'LocalHold' || this . call . state === 'RemoteHold' ) {
240
263
this . setState ( { canRaiseHands : false } ) ;
241
264
this . setState ( { canSpotlight : false } ) ;
265
+ this . setState ( { canForbidOthersAudio : false } ) ;
266
+ this . setState ( { canForbidOthersVideo : false } ) ;
242
267
}
243
268
if ( this . call . state === 'Connected' ) {
244
269
this . setState ( { canRaiseHands : this . capabilities . raiseHand ?. isPresent || this . capabilities . raiseHand ?. reason === 'FeatureNotSupported' } ) ;
245
270
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' } ) ;
246
273
}
247
274
}
248
275
callStateChanged ( ) ;
@@ -480,6 +507,10 @@ export default class CallCard extends React.Component {
480
507
this . transcriptionFeature . on ( 'isTranscriptionActiveChanged' , this . isTranscriptionActiveChangedHandler ) ;
481
508
this . lobby ?. on ( 'lobbyParticipantsUpdated' , this . lobbyParticipantsUpdatedHandler ) ;
482
509
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
+ }
483
514
}
484
515
}
485
516
@@ -541,6 +572,24 @@ export default class CallCard extends React.Component {
541
572
this . identifier , this . spotlightFeature . getSpotlightedParticipants ( ) ) } )
542
573
}
543
574
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
+
544
593
isRecordingActiveChangedHandler = ( event ) => {
545
594
this . setState ( { isRecordingActive : this . recordingFeature . isRecordingActive } )
546
595
}
@@ -630,11 +679,11 @@ export default class CallCard extends React.Component {
630
679
capabilitiesChangedHandler = ( capabilitiesChangeInfo ) => {
631
680
for ( const [ key , value ] of Object . entries ( capabilitiesChangeInfo . newValue ) ) {
632
681
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.' } ) ;
634
683
continue ;
635
684
}
636
685
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.' } ) ;
638
687
continue ;
639
688
}
640
689
if ( key === 'shareScreen' && value . reason != 'FeatureNotSupported' ) {
@@ -657,6 +706,14 @@ export default class CallCard extends React.Component {
657
706
( value . isPresent ) ? this . setState ( { canReact : true } ) : this . setState ( { canReact : false } ) ;
658
707
continue ;
659
708
}
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
+ }
660
717
}
661
718
this . capabilities = this . capabilitiesFeature . capabilities ;
662
719
}
@@ -1133,6 +1190,62 @@ export default class CallCard extends React.Component {
1133
1190
} catch ( e ) {
1134
1191
console . error ( e ) ;
1135
1192
}
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
+ }
1136
1249
}
1137
1250
}
1138
1251
}
@@ -1214,6 +1327,43 @@ export default class CallCard extends React.Component {
1214
1327
onClick : ( e ) => menuCallBacks . consentToBeingRecorded ( e )
1215
1328
} ) ;
1216
1329
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
+
1217
1367
return menuItems . filter ( item => item != 0 )
1218
1368
}
1219
1369
@@ -1240,6 +1390,7 @@ export default class CallCard extends React.Component {
1240
1390
render ( ) {
1241
1391
const emojis = [ '👍' , '❤️' , '😂' , '👏' , '😲' ] ;
1242
1392
const streamCount = this . state . allRemoteParticipantStreams . length ;
1393
+ const mediaAccessMap = this . state . mediaAccessMap || new Map ( ) ;
1243
1394
return (
1244
1395
< div className = "ms-Grid mt-2" >
1245
1396
< div className = "ms-Grid-row" >
@@ -1305,18 +1456,19 @@ export default class CallCard extends React.Component {
1305
1456
< p > No other participants currently in the call</ p >
1306
1457
}
1307
1458
< 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
1311
1462
key = { `${ utils . getIdentifierText ( remoteParticipant . identifier ) } ` }
1312
1463
remoteParticipant = { remoteParticipant }
1313
1464
call = { this . call }
1314
1465
menuOptionsHandler = { this . getParticipantMenuCallBacks ( ) }
1315
1466
onSelectionChanged = { ( identifier , isChecked ) => this . remoteParticipantSelectionChanged ( identifier , isChecked ) }
1316
1467
capabilitiesFeature = { this . capabilitiesFeature }
1317
- />
1318
- )
1319
- }
1468
+ mediaAccess = { participantMediaAccess }
1469
+ /> ) ;
1470
+ } ) }
1471
+
1320
1472
</ ul >
1321
1473
1322
1474
</ div >
@@ -1363,30 +1515,37 @@ export default class CallCard extends React.Component {
1363
1515
< div className = "ms-Grid-row" >
1364
1516
< div className = "text-center" >
1365
1517
< 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 '} `}
1367
1519
variant = "secondary"
1368
1520
onClick = { ( ) => this . handleVideoOnOff ( ) } >
1369
1521
{
1370
1522
this . state . canOnVideo && this . state . videoOn &&
1371
1523
< Icon iconName = "Video" />
1372
1524
}
1373
1525
{
1374
- ( ! this . state . canOnVideo || ! this . state . videoOn ) &&
1526
+ ( this . state . canOnVideo || ! this . state . videoOn ) &&
1527
+ < Icon iconName = "VideoOff2" />
1528
+ }
1529
+ {
1530
+ ( ! this . state . canOnVideo ) &&
1375
1531
< Icon iconName = "VideoOff" />
1376
1532
}
1377
1533
</ span >
1378
1534
< 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' } ` }
1380
1536
variant = "secondary"
1381
1537
onClick = { ( ) => this . handleMicOnOff ( ) } >
1382
1538
{
1383
1539
this . state . canUnMuteMic && ! this . state . micMuted &&
1384
1540
< Icon iconName = "Microphone" />
1385
1541
}
1386
1542
{
1387
- ( ! this . state . canUnMuteMic || this . state . micMuted ) &&
1543
+ ( this . state . canUnMuteMic && this . state . micMuted ) &&
1388
1544
< Icon iconName = "MicOff2" />
1389
1545
}
1546
+ {
1547
+ ! this . state . canUnMuteMic && < Icon iconName = "MicOff" />
1548
+ }
1390
1549
</ span >
1391
1550
< span className = "in-call-button"
1392
1551
onClick = { ( ) => this . call . hangUp ( ) } >
0 commit comments