Merge pull request #18 from vector-im/feature/mute-indicators

Add mute indicators
This commit is contained in:
Robert Long 2021-08-23 15:45:04 -07:00 committed by GitHub
commit 117f6d2f9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 35 deletions

View file

@ -172,7 +172,7 @@ export class ConferenceCallManager extends EventEmitter {
this.localVideoStream = null;
this.localParticipant = null;
this.micMuted = false;
this.audioMuted = false;
this.videoMuted = false;
this.client.on("RoomState.members", this._onRoomStateMembers);
@ -245,6 +245,8 @@ export class ConferenceCallManager extends EventEmitter {
sessionId: this.sessionId,
call: null,
stream,
audioMuted: this.audioMuted,
videoMuted: this.videoMuted,
};
this.participants.push(this.localParticipant);
@ -312,7 +314,9 @@ export class ConferenceCallManager extends EventEmitter {
this.participants = [];
this.localParticipant.stream = null;
this.localParticipant.call = null;
this.micMuted = false;
this.localParticipant.audioMuted = false;
this.localParticipant.videoMuted = false;
this.audioMuted = false;
this.videoMuted = false;
clearTimeout(this._memberParticipantStateTimeout);
@ -332,15 +336,19 @@ export class ConferenceCallManager extends EventEmitter {
return stream;
}
setMicMuted(muted) {
this.micMuted = muted;
setAudioMuted(muted) {
this.audioMuted = muted;
if (this.localParticipant) {
this.localParticipant.audioMuted = muted;
}
const localStream = this.localVideoStream;
if (localStream) {
for (const track of localStream.getTracks()) {
if (track.kind === "audio") {
track.enabled = !this.micMuted;
track.enabled = !this.audioMuted;
}
}
}
@ -351,16 +359,22 @@ export class ConferenceCallManager extends EventEmitter {
if (
call &&
call.localUsermediaStream &&
call.isMicrophoneMuted() !== this.micMuted
call.isMicrophoneMuted() !== this.audioMuted
) {
call.setMicrophoneMuted(this.micMuted);
call.setMicrophoneMuted(this.audioMuted);
}
}
this.emit("participants_changed");
}
setVideoMuted(muted) {
this.videoMuted = muted;
if (this.localParticipant) {
this.localParticipant.videoMuted = muted;
}
const localStream = this.localVideoStream;
if (localStream) {
@ -382,6 +396,8 @@ export class ConferenceCallManager extends EventEmitter {
call.setLocalVideoMuted(this.videoMuted);
}
}
this.emit("participants_changed");
}
logout() {
@ -473,7 +489,16 @@ export class ConferenceCallManager extends EventEmitter {
}
// Get the remote video stream if it exists.
const stream = call.getRemoteFeeds()[0]?.stream;
const remoteFeed = call.getRemoteFeeds()[0];
const stream = remoteFeed && remoteFeed.stream;
const audioMuted = remoteFeed ? remoteFeed.isAudioMuted() : false;
const videoMuted = remoteFeed ? remoteFeed.isVideoMuted() : false;
if (remoteFeed) {
remoteFeed.on("mute_state_changed", () =>
this._onCallFeedMuteStateChanged(participant, remoteFeed)
);
}
const userId = call.opponentMember.userId;
@ -496,6 +521,8 @@ export class ConferenceCallManager extends EventEmitter {
existingParticipant.call.hangup("replaced", false);
existingParticipant.call = call;
existingParticipant.stream = stream;
existingParticipant.audioMuted = audioMuted;
existingParticipant.videoMuted = videoMuted;
existingParticipant.sessionId = sessionId;
} else {
participant = {
@ -504,6 +531,8 @@ export class ConferenceCallManager extends EventEmitter {
sessionId,
call,
stream,
audioMuted,
videoMuted,
};
this.participants.push(participant);
}
@ -592,6 +621,8 @@ export class ConferenceCallManager extends EventEmitter {
participant.sessionId = sessionId;
participant.call = call;
participant.stream = null;
participant.audioMuted = false;
participant.videoMuted = false;
} else {
participant = {
local: false,
@ -599,6 +630,8 @@ export class ConferenceCallManager extends EventEmitter {
sessionId,
call,
stream: null,
audioMuted: false,
videoMuted: false,
};
// TODO: Should we wait until the call has been answered to push the participant?
// Or do we hide the participant until their stream is live?
@ -630,9 +663,9 @@ export class ConferenceCallManager extends EventEmitter {
_onCallStateChanged = (participant, call, state) => {
if (
call.localUsermediaStream &&
call.isMicrophoneMuted() !== this.micMuted
call.isMicrophoneMuted() !== this.audioMuted
) {
call.setMicrophoneMuted(this.micMuted);
call.setMicrophoneMuted(this.audioMuted);
}
if (
@ -646,14 +679,28 @@ export class ConferenceCallManager extends EventEmitter {
};
_onCallFeedsChanged = (participant, call) => {
const feeds = call.getRemoteFeeds();
const remoteFeed = call.getRemoteFeeds()[0];
const stream = remoteFeed && remoteFeed.stream;
const audioMuted = remoteFeed ? remoteFeed.isAudioMuted() : false;
const videoMuted = remoteFeed ? remoteFeed.isVideoMuted() : false;
if (feeds.length > 0 && participant.stream !== feeds[0].stream) {
participant.stream = feeds[0].stream;
this.emit("participants_changed");
if (remoteFeed && participant.stream !== stream) {
participant.stream = stream;
participant.audioMuted = audioMuted;
participant.videoMuted = videoMuted;
remoteFeed.on("mute_state_changed", () =>
this._onCallFeedMuteStateChanged(participant, remoteFeed)
);
this._onCallFeedMuteStateChanged(participant, remoteFeed);
}
};
_onCallFeedMuteStateChanged = (participant, feed) => {
participant.audioMuted = feed.isAudioMuted();
participant.videoMuted = feed.isVideoMuted();
this.emit("participants_changed");
};
_onCallReplaced = (participant, call, newCall) => {
participant.call = newCall;
@ -668,10 +715,15 @@ export class ConferenceCallManager extends EventEmitter {
);
newCall.on("hangup", () => this._onCallHangup(participant, newCall));
const feeds = newCall.getRemoteFeeds();
const remoteFeed = newCall.getRemoteFeeds()[0];
participant.stream = remoteFeed ? remoteFeed.stream : null;
participant.audioMuted = remoteFeed ? remoteFeed.isAudioMuted() : false;
participant.videoMuted = remoteFeed ? remoteFeed.isVideoMuted() : false;
if (feeds.length > 0) {
participant.stream = feeds[0].stream;
if (remoteFeed) {
remoteFeed.on("mute_state_changed", () =>
this._onCallFeedMuteStateChanged(participant, remoteFeed)
);
}
this.emit("call", newCall);

View file

@ -148,7 +148,7 @@ export function useVideoRoom(manager, roomId, timeout = 5000) {
participants,
error,
videoMuted,
micMuted,
audioMuted,
},
setState,
] = useState({
@ -159,7 +159,7 @@ export function useVideoRoom(manager, roomId, timeout = 5000) {
participants: [],
error: undefined,
videoMuted: false,
micMuted: false,
audioMuted: false,
});
useEffect(() => {
@ -225,7 +225,7 @@ export function useVideoRoom(manager, roomId, timeout = 5000) {
setState((prevState) => ({
...prevState,
videoMuted: manager.videoMuted,
micMuted: manager.micMuted,
audioMuted: manager.audioMuted,
}));
}
@ -329,9 +329,9 @@ export function useVideoRoom(manager, roomId, timeout = 5000) {
};
}, [manager]);
const toggleMuteMic = useCallback(() => {
manager.setMicMuted(!manager.micMuted);
setState((prevState) => ({ ...prevState, micMuted: manager.micMuted }));
const toggleMuteAudio = useCallback(() => {
manager.setAudioMuted(!manager.audioMuted);
setState((prevState) => ({ ...prevState, audioMuted: manager.audioMuted }));
}, [manager]);
const toggleMuteVideo = useCallback(() => {
@ -349,9 +349,9 @@ export function useVideoRoom(manager, roomId, timeout = 5000) {
joinCall,
leaveCall,
toggleMuteVideo,
toggleMuteMic,
toggleMuteAudio,
videoMuted,
micMuted,
audioMuted,
};
}

View file

@ -47,9 +47,9 @@ export function Room({ manager }) {
joinCall,
leaveCall,
toggleMuteVideo,
toggleMuteMic,
toggleMuteAudio,
videoMuted,
micMuted,
audioMuted,
} = useVideoRoom(manager, roomId);
const debugStr = query.get("debug");
const [debug, setDebug] = useState(debugStr === "" || debugStr === "true");
@ -100,9 +100,9 @@ export function Room({ manager }) {
joining={joining}
joinCall={joinCall}
toggleMuteVideo={toggleMuteVideo}
toggleMuteMic={toggleMuteMic}
toggleMuteAudio={toggleMuteAudio}
videoMuted={videoMuted}
micMuted={micMuted}
audioMuted={audioMuted}
/>
)}
{!loading && room && joined && participants.length === 0 && (
@ -115,7 +115,7 @@ export function Room({ manager }) {
)}
{!loading && room && joined && (
<div className={styles.footer}>
<MicButton muted={micMuted} onClick={toggleMuteMic} />
<MicButton muted={audioMuted} onClick={toggleMuteAudio} />
<VideoButton enabled={videoMuted} onClick={toggleMuteVideo} />
<HangupButton onClick={leaveCall} />
</div>
@ -130,9 +130,9 @@ function JoinRoom({
joinCall,
manager,
toggleMuteVideo,
toggleMuteMic,
toggleMuteAudio,
videoMuted,
micMuted,
audioMuted,
}) {
const videoRef = useRef();
const [hasPermissions, setHasPermissions] = useState(false);
@ -167,7 +167,7 @@ function JoinRoom({
</div>
{hasPermissions && (
<div className={styles.previewButtons}>
<MicButton muted={micMuted} onClick={toggleMuteMic} />
<MicButton muted={audioMuted} onClick={toggleMuteAudio} />
<VideoButton enabled={videoMuted} onClick={toggleMuteVideo} />
</div>
)}

View file

@ -6,6 +6,8 @@ import styles from "./VideoGrid.module.css";
import useMeasure from "react-use-measure";
import moveArrItem from "lodash-move";
import { ReactComponent as MicIcon } from "./icons/Mic.svg";
import { ReactComponent as MuteMicIcon } from "./icons/MuteMic.svg";
import { ReactComponent as DisableVideoIcon } from "./icons/DisableVideo.svg";
function useIsMounted() {
const isMountedRef = useRef(false);
@ -356,9 +358,20 @@ function ParticipantTile({ style, participant, remove, ...rest }) {
[styles.speaking]: participant.speaking,
})}
>
{participant.speaking && <MicIcon />}
{participant.speaking ? (
<MicIcon />
) : participant.audioMuted ? (
<MuteMicIcon className={styles.muteMicIcon} />
) : null}
<span>{participant.userId}</span>
</div>
{participant.videoMuted && (
<DisableVideoIcon
className={styles.videoMuted}
width={48}
height={48}
/>
)}
<video ref={videoRef} playsInline disablePictureInPicture />
</animated.div>
);

View file

@ -62,7 +62,17 @@ limitations under the License.
line-height: 32px;
}
.muteMicIcon * {
fill: #FF5B55;
}
.videoMuted {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.participantTile:hover .participantName, .participantName.speaking {
display: flex;
}