@@ -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