diff --git a/src/ConferenceCallManager.js b/src/ConferenceCallManager.js index 8613d87..d608b07 100644 --- a/src/ConferenceCallManager.js +++ b/src/ConferenceCallManager.js @@ -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); diff --git a/src/ConferenceCallManagerHooks.js b/src/ConferenceCallManagerHooks.js index 48ee2fe..09941b9 100644 --- a/src/ConferenceCallManagerHooks.js +++ b/src/ConferenceCallManagerHooks.js @@ -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, }; } diff --git a/src/Room.jsx b/src/Room.jsx index 32b0397..4ff6e34 100644 --- a/src/Room.jsx +++ b/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 && (
- +
@@ -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({ {hasPermissions && (
- +
)} diff --git a/src/VideoGrid.jsx b/src/VideoGrid.jsx index ab7b0f7..fa24fc2 100644 --- a/src/VideoGrid.jsx +++ b/src/VideoGrid.jsx @@ -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 && } + {participant.speaking ? ( + + ) : participant.audioMuted ? ( + + ) : null} {participant.userId} + {participant.videoMuted && ( + + )}