Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Project/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ ul {
padding-top: 2em;
padding-bottom: 2em;
padding-left: 1em;
white-space: nowrap;
}

.participant-item:hover {
Expand Down
187 changes: 173 additions & 14 deletions Project/src/MakeCall/CallCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ export default class CallCard extends React.Component {
this.pptLiveFeature = this.call.feature(Features.PPTLive);
this.pptLiveHtml = React.createRef();
}
let meetingMediaAccess = undefined;
let remoteParticipantsMediaAccess = undefined;
let mediaAccessMap = undefined;
if (Features.MediaAccess) {
this.mediaAccessCallFeature = this.call.feature(Features.MediaAccess);
meetingMediaAccess = this.call.feature(Features.MediaAccess).getMeetingMediaAccess();
remoteParticipantsMediaAccess = this.call.feature(Features.MediaAccess).getAllOthersMediaAccess();
mediaAccessMap = new Map();
remoteParticipantsMediaAccess.forEach((mediaAccess) => {
mediaAccessMap.set(mediaAccess.participant.rawId, mediaAccess);
});
}
this.isTeamsUser = props.isTeamsUser;
this.dummyStreamTimeout = undefined;
this.state = {
Expand All @@ -71,6 +83,8 @@ export default class CallCard extends React.Component {
canSpotlight: this.capabilities.spotlightParticipant?.isPresent || this.capabilities.spotlightParticipant?.reason === 'FeatureNotSupported',
canMuteOthers: this.capabilities.muteOthers?.isPresent || this.capabilities.muteOthers?.reason === 'FeatureNotSupported',
canReact: this.capabilities.useReactions?.isPresent || this.capabilities.useReactions?.reason === 'FeatureNotSupported',
canForbidOthersAudio: this.capabilities.forbidOthersAudio?.isPresent || this.capabilities.forbidOthersAudio?.reason === 'FeatureNotSupported',
canForbidOthersVideo: this.capabilities.forbidOthersVideo?.isPresent || this.capabilities.forbidOthersVideo?.reason === 'FeatureNotSupported',
videoOn: this.call.isLocalVideoStarted,
screenSharingOn: this.call.isScreenSharingOn,
micMuted: this.call.isMuted,
Expand Down Expand Up @@ -109,7 +123,12 @@ export default class CallCard extends React.Component {
pptLiveActive: false,
isRecordingActive: false,
isTranscriptionActive: false,
lobbyParticipantsCount: this.lobby?.participants.length
lobbyParticipantsCount: this.lobby?.participants.length,
mediaAccessMap,
meetingMediaAccess: {
isAudioPermitted: meetingMediaAccess?.isAudioPermitted,
isVideoPermitted: meetingMediaAccess?.isVideoPermitted,
}
};
this.selectedRemoteParticipants = new Set();
this.dataChannelRef = React.createRef();
Expand Down Expand Up @@ -150,7 +169,11 @@ export default class CallCard extends React.Component {
this.call.feature(Features.PPTLive).off('isActiveChanged', this.pptLiveChangedHandler);
}
this.dominantSpeakersFeature.off('dominantSpeakersChanged', this.dominantSpeakersChanged);
}
if (Features.mediaAccess) {
this.mediaAccessCallFeature.off('mediaAccessChanged', this.mediaAccessChangedHandler);
this.mediaAccessCallFeature.off('meetingMediaAccessChanged', this.meetingMediaAccessChangedHandler);
}
}

componentDidMount() {
if (this.call) {
Expand Down Expand Up @@ -239,10 +262,14 @@ export default class CallCard extends React.Component {
if (this.call.state === 'LocalHold' || this.call.state === 'RemoteHold') {
this.setState({ canRaiseHands: false });
this.setState({ canSpotlight: false });
this.setState({ canForbidOthersAudio: false });
this.setState({ canForbidOthersVideo: false });
}
if (this.call.state === 'Connected') {
this.setState({ canRaiseHands: this.capabilities.raiseHand?.isPresent || this.capabilities.raiseHand?.reason === 'FeatureNotSupported' });
this.setState({ canSpotlight: this.capabilities.spotlightParticipant?.isPresent || this.capabilities.spotlightParticipant?.reason === 'FeatureNotSupported' });
this.setState({ canForbidOthersAudio: this.capabilities.forbidOthersAudio?.isPresent || this.capabilities.forbidOthersAudio?.reason === 'FeatureNotSupported' });
this.setState({ canForbidOthersVideo: this.capabilities.forbidOthersVideo?.isPresent || this.capabilities.forbidOthersVideo?.reason === 'FeatureNotSupported' });
}
}
callStateChanged();
Expand Down Expand Up @@ -480,6 +507,10 @@ export default class CallCard extends React.Component {
this.transcriptionFeature.on('isTranscriptionActiveChanged', this.isTranscriptionActiveChangedHandler);
this.lobby?.on('lobbyParticipantsUpdated', this.lobbyParticipantsUpdatedHandler);
this.realTimeTextFeature?.on('realTimeTextReceived', this.realTimeTextReceivedHandler);
if (Features.MediaAccess) {
this.mediaAccessCallFeature.on('mediaAccessChanged', this.mediaAccessChangedHandler);
this.mediaAccessCallFeature.on('meetingMediaAccessChanged', this.meetingMediaAccessChangedHandler);
}
}
}

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

mediaAccessChangedHandler = (event) => {
const mediaAccessMap = new Map();
event.mediaAccesses.forEach((mediaAccess) => {
mediaAccessMap.set(mediaAccess.participant.rawId, mediaAccess);
});

this.setState({mediaAccessMap});
}

meetingMediaAccessChangedHandler = (event) => {
if (event.meetingMediaAccess) {
this.setState({meetingMediaAccess: {
isAudioPermitted: event.meetingMediaAccess.isAudioPermitted,
isVideoPermitted: event.meetingMediaAccess.isVideoPermitted,
}});
}
}

isRecordingActiveChangedHandler = (event) => {
this.setState({ isRecordingActive: this.recordingFeature.isRecordingActive })
}
Expand Down Expand Up @@ -630,11 +679,11 @@ export default class CallCard extends React.Component {
capabilitiesChangedHandler = (capabilitiesChangeInfo) => {
for (const [key, value] of Object.entries(capabilitiesChangeInfo.newValue)) {
if(key === 'turnVideoOn' && value.reason != 'FeatureNotSupported') {
(value.isPresent) ? this.setState({ canOnVideo: true }) : this.setState({ canOnVideo: false });
(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.' });
continue;
}
if(key === 'unmuteMic' && value.reason != 'FeatureNotSupported') {
(value.isPresent) ? this.setState({ canUnMuteMic: true }) : this.setState({ canUnMuteMic: false });
(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.' });
continue;
}
if(key === 'shareScreen' && value.reason != 'FeatureNotSupported') {
Expand All @@ -657,6 +706,14 @@ export default class CallCard extends React.Component {
(value.isPresent) ? this.setState({ canReact: true }) : this.setState({ canReact: false });
continue;
}
if(key === 'forbidOthersAudio' && value.reason != 'FeatureNotSupported') {
(value.isPresent) ? this.setState({ canForbidOthersAudio: true }) : this.setState({ canForbidOthersAudio: false });
continue;
}
if(key === 'forbidOthersVideo' && value.reason != 'FeatureNotSupported') {
(value.isPresent) ? this.setState({ canForbidOthersVideo: true }) : this.setState({ canForbidOthersVideo: false });
continue;
}
}
this.capabilities = this.capabilitiesFeature.capabilities;
}
Expand Down Expand Up @@ -1133,6 +1190,62 @@ export default class CallCard extends React.Component {
} catch(e) {
console.error(e);
}
},
forbidAudio: async (identifier) => {
try {
await this.mediaAccessCallFeature.forbidAudio([identifier]);
} catch(e) {
console.error(e);
}
},
permitAudio: async (identifier) => {
try {
await this.mediaAccessCallFeature.permitAudio([identifier]);
} catch(e) {
console.error(e);
}
},
forbidVideo: async (identifier) => {
try {
await this.mediaAccessCallFeature.forbidVideo([identifier]);
} catch(e) {
console.error(e);
}
},
permitVideo: async (identifier) => {
try {
await this.mediaAccessCallFeature.permitVideo([identifier]);
} catch(e) {
console.error(e);
}
},
forbidOthersAudio: async () => {
try {
await this.mediaAccessCallFeature.forbidOthersAudio();
} catch(e) {
console.error(e);
}
},
permitOthersAudio: async () => {
try {
await this.mediaAccessCallFeature.permitOthersAudio();
} catch(e) {
console.error(e);
}
},
forbidOthersVideo: async () => {
try {
await this.mediaAccessCallFeature.forbidOthersVideo();
} catch(e) {
console.error(e);
}
},
permitOthersVideo: async () => {
try {
await this.mediaAccessCallFeature.permitOthersVideo();
} catch(e) {
console.error(e);
}
}
}
}
Expand Down Expand Up @@ -1214,6 +1327,43 @@ export default class CallCard extends React.Component {
onClick: (e) => menuCallBacks.consentToBeingRecorded(e)
});


if (this.state.canForbidOthersAudio && this.state.meetingMediaAccess.isAudioPermitted){
menuItems.push({
key: 'Disable mic for all attendees',
iconProps: { iconName: 'Focus'},
text: 'Disable mic for all attendees',
onClick: () => menuCallBacks.forbidOthersAudio()
});
}

if (this.state.canForbidOthersAudio && !this.state.meetingMediaAccess.isAudioPermitted){
menuItems.push({
key: 'Enable mic for all attendees',
iconProps: { iconName: 'Focus'},
text: 'Enable mic for all attendees',
onClick: () => menuCallBacks.permitOthersAudio()
});
}

if (this.state.canForbidOthersVideo && this.state.meetingMediaAccess.isVideoPermitted){
menuItems.push({
key: 'Disable camera for all attendees',
iconProps: { iconName: 'Focus'},
text: 'Disable camera for all attendees',
onClick: () => menuCallBacks.forbidOthersVideo()
});
}

if (this.state.canForbidOthersVideo && !this.state.meetingMediaAccess.isVideoPermitted){
menuItems.push({
key: 'Enable camera for all attendees',
iconProps: { iconName: 'Focus'},
text: 'Enable camera for all attendees',
onClick: () => menuCallBacks.permitOthersVideo()
});
}

return menuItems.filter(item => item != 0)
}

Expand All @@ -1240,6 +1390,7 @@ export default class CallCard extends React.Component {
render() {
const emojis = ['👍', '❤️', '😂', '👏', '😲'];
const streamCount = this.state.allRemoteParticipantStreams.length;
const mediaAccessMap = this.state.mediaAccessMap || new Map();
return (
<div className="ms-Grid mt-2">
<div className="ms-Grid-row">
Expand Down Expand Up @@ -1305,18 +1456,19 @@ export default class CallCard extends React.Component {
<p>No other participants currently in the call</p>
}
<ul className="p-0 m-0">
{
this.state.remoteParticipants.map(remoteParticipant =>
<RemoteParticipantCard
{this.state.remoteParticipants.map(remoteParticipant => {
const participantMediaAccess = mediaAccessMap?.get(remoteParticipant.identifier.rawId);
return ( <RemoteParticipantCard
key={`${utils.getIdentifierText(remoteParticipant.identifier)}`}
remoteParticipant={remoteParticipant}
call={this.call}
menuOptionsHandler={this.getParticipantMenuCallBacks()}
onSelectionChanged={(identifier, isChecked) => this.remoteParticipantSelectionChanged(identifier, isChecked)}
capabilitiesFeature={this.capabilitiesFeature}
/>
)
}
mediaAccess={participantMediaAccess}
/>);
})}

</ul>

</div>
Expand Down Expand Up @@ -1363,30 +1515,37 @@ export default class CallCard extends React.Component {
<div className="ms-Grid-row">
<div className="text-center">
<span className="in-call-button"
title={`Turn your video ${this.state.videoOn ? 'off' : 'on'}`}
title = {`${this.state.canOnVideo ? (this.state.videoOn ? 'Turn your video off' : 'Turn your video on') : 'Video is disabled'}`}
variant="secondary"
onClick={() => this.handleVideoOnOff()}>
{
this.state.canOnVideo && this.state.videoOn &&
<Icon iconName="Video" />
}
{
(!this.state.canOnVideo || !this.state.videoOn) &&
(this.state.canOnVideo || !this.state.videoOn) &&
<Icon iconName="VideoOff2" />
}
{
(!this.state.canOnVideo) &&
<Icon iconName="VideoOff" />
}
</span>
<span className="in-call-button"
title={`${this.state.micMuted ? 'Unmute' : 'Mute'} your microphone`}
title={`${this.state.canUnMuteMic ? (this.state.micMuted ? 'Unmute your microphone' : 'Mute your microphone') : 'Microphone is disabled'}`}
variant="secondary"
onClick={() => this.handleMicOnOff()}>
{
this.state.canUnMuteMic && !this.state.micMuted &&
<Icon iconName="Microphone" />
}
{
(!this.state.canUnMuteMic || this.state.micMuted) &&
(this.state.canUnMuteMic && this.state.micMuted) &&
<Icon iconName="MicOff2" />
}
{
!this.state.canUnMuteMic && <Icon iconName="MicOff" />
}
</span>
<span className="in-call-button"
onClick={() => this.call.hangUp()}>
Expand Down
Loading
Loading