Merge pull request #18 from vector-im/feature/mute-indicators
Add mute indicators
This commit is contained in:
commit
117f6d2f9e
5 changed files with 110 additions and 35 deletions
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
16
src/Room.jsx
16
src/Room.jsx
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue