diff --git a/package-lock.json b/package-lock.json index 0442c6c..eaf8dbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "version": "0.0.0", "dependencies": { + "color-hash": "^2.0.1", "events": "^3.3.0", "matrix-js-sdk": "^12.0.1", "react": "^17.0.0", @@ -596,6 +597,11 @@ "color-name": "1.1.3" } }, + "node_modules/color-hash": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-hash/-/color-hash-2.0.1.tgz", + "integrity": "sha512-/wIYAQ3xL9ruURLmDbxAsXEsivaOfwWDUVy+zbWJZL3bnNQIDNSmmqbkNzeTOQvDdiz11Kb010UlJN7hUXLg/w==" + }, "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", @@ -2042,6 +2048,11 @@ "color-name": "1.1.3" } }, + "color-hash": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-hash/-/color-hash-2.0.1.tgz", + "integrity": "sha512-/wIYAQ3xL9ruURLmDbxAsXEsivaOfwWDUVy+zbWJZL3bnNQIDNSmmqbkNzeTOQvDdiz11Kb010UlJN7hUXLg/w==" + }, "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", diff --git a/package.json b/package.json index d69922a..b084fbc 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "serve": "vite preview" }, "dependencies": { + "color-hash": "^2.0.1", "events": "^3.3.0", "matrix-js-sdk": "^12.0.1", "react": "^17.0.0", diff --git a/src/ConferenceCallManager.js b/src/ConferenceCallManager.js index 74a05dc..bc97f97 100644 --- a/src/ConferenceCallManager.js +++ b/src/ConferenceCallManager.js @@ -142,21 +142,19 @@ export class ConferenceCallManager extends EventEmitter { muted: true, }; this.participants = [this.localParticipant]; - this.pendingCalls = []; - + this.debugState = new Map(); + this._setDebugState(client.getUserId(), "you"); + this.client.on("event", this._onEvent); this.client.on("RoomState.members", this._onMemberChanged); this.client.on("Call.incoming", this._onIncomingCall); } join(roomId) { - console.debug( - "join", - `Local user ${this.client.getUserId()} joining room ${this.roomId}` - ); - this.joined = true; + this._addDebugEvent(this.client.getUserId(), "joined call"); + this.roomId = roomId; this.room = this.client.getRoom(this.roomId); @@ -185,6 +183,27 @@ export class ConferenceCallManager extends EventEmitter { this._updateParticipantState(); } + _onEvent = (event) => { + const type = event.getType(); + + if (type.startsWith("m.call.") || type.startsWith("me.robertlong.conf")) { + const content = event.getContent(); + const details = {}; + + switch (type) { + case "m.call.invite": + case "m.call.candidates": + case "m.call.answer": + case "m.call.hangup": + case "m.call.select_answer": + details.callId = content.call_id; + break; + } + + this._addDebugEvent(event.getSender(), type, details); + } + }; + _updateParticipantState = () => { const userId = this.client.getUserId(); const currentMemberState = this.room.currentState.getStateEvents( @@ -221,20 +240,6 @@ export class ConferenceCallManager extends EventEmitter { // Don't process members until we've joined if (!this.joined) { - console.debug( - "_processMember", - `Ignored ${userId}. Local user has not joined conference yet.` - ); - return; - } - - // Only initiate a call with a user who has a userId that is lexicographically - // less than your own. Otherwise, that user will call you. - if (userId < localUserId) { - console.debug( - "_processMember", - `Ignored ${userId}. Local user will answer call instead.` - ); return; } @@ -256,23 +261,32 @@ export class ConferenceCallManager extends EventEmitter { new Date().getTime() - participantTimeout > PARTICIPANT_TIMEOUT ) { // Member is inactive so don't call them. - console.debug( - "_processMember", - `Ignored ${userId}. User is not active in conference.` - ); + this._setDebugState(userId, "inactive"); + return; + } + + // Only initiate a call with a user who has a userId that is lexicographically + // less than your own. Otherwise, that user will call you. + if (userId < localUserId) { + this._setDebugState(userId, "waiting for invite"); return; } const call = this.client.createCall(this.roomId, userId); this._addCall(call, userId); - console.debug( - "_processMember", - `Placing video call ${call.callId} to ${userId}.` - ); + this._setDebugState(userId, "calling"); + this._addDebugEvent(this.client.getUserId(), "placeVideoCall", { + callId: call.callId, + to: userId, + }); call.placeVideoCall(); } _onIncomingCall = (call) => { + this._addDebugEvent(call.opponentMember.userId, "incoming call", { + callId: call.callId, + }); + if (!this.joined) { const onHangup = (call) => { const index = this.pendingCalls.findIndex((p) => p.call === call); @@ -308,10 +322,8 @@ export class ConferenceCallManager extends EventEmitter { if (call.opponentMember) { const userId = call.opponentMember.userId; this._addCall(call, userId); - console.debug( - "_onIncomingCall", - `Answering incoming call ${call.callId} from ${userId}` - ); + this._setDebugState(userId, "answered"); + this._addDebugEvent(userId, "answer", { callId: call.callId }); call.answer(); return; } @@ -327,10 +339,6 @@ export class ConferenceCallManager extends EventEmitter { ); if (existingCall) { - console.debug( - "_addCall", - `Found existing call ${call.callId}. Ignoring.` - ); return; } @@ -340,7 +348,8 @@ export class ConferenceCallManager extends EventEmitter { call, }); - console.debug("_addCall", `Added new participant ${userId}`); + this._setDebugCallId(userId, call.callId); + this._addDebugEvent(userId, "add participant"); call.on("feeds_changed", () => this._onCallFeedsChanged(call)); call.on("hangup", () => this._onCallHangup(call)); @@ -363,6 +372,7 @@ export class ConferenceCallManager extends EventEmitter { if (!this.localParticipant.feed && localFeeds.length > 0) { this.localParticipant.call = call; + this._setDebugCallId(this.localParticipant.userId, call.callId); this.localParticipant.feed = localFeeds[0]; participantsChanged = true; } @@ -374,6 +384,7 @@ export class ConferenceCallManager extends EventEmitter { if (remoteFeeds.length > 0 && remoteParticipant.feed !== remoteFeeds[0]) { remoteParticipant.feed = remoteFeeds[0]; + this._setDebugState(call.opponentMember.userId, "streaming"); participantsChanged = true; } @@ -383,12 +394,17 @@ export class ConferenceCallManager extends EventEmitter { }; _onCallHangup = (call) => { - console.debug("_onCallHangup", `Hangup reason ${call.hangupReason}`); + this._addDebugEvent(call.opponentMember.userId, "hangup", { + callId: call.callId, + reason: call.hangupReason, + }); if (call.hangupReason === "replaced") { return; } + this._setDebugState(call.opponentMember.userId, "hungup"); + const participantIndex = this.participants.findIndex( (p) => !p.local && p.call === call ); @@ -409,13 +425,16 @@ export class ConferenceCallManager extends EventEmitter { if (localFeeds.length > 0) { this.localParticipant.call = call; + this._setDebugCallId(this.localParticipant.userId, call.callId); this.localParticipant.feed = localFeeds[0]; } else { this.localParticipant.call = null; + this._setDebugCallId(this.localParticipant.userId, null); this.localParticipant.feed = null; } } else { this.localParticipant.call = null; + this._setDebugCallId(this.localParticipant.userId, null); this.localParticipant.feed = null; } } @@ -424,16 +443,17 @@ export class ConferenceCallManager extends EventEmitter { }; _onCallReplaced = (call, newCall) => { - console.debug( - "_onCallReplaced", - `Call ${call.callId} replaced with ${newCall.callId}` - ); + this._addDebugEvent(call.opponentMember.userId, "replaced", { + callId: call.callId, + newCallId: newCall.callId, + }); const remoteParticipant = this.participants.find( (p) => !p.local && p.call === call ); remoteParticipant.call = newCall; + this._setDebugCallId(remoteParticipant.userId, newCall.callId); newCall.on("feeds_changed", () => this._onCallFeedsChanged(newCall)); newCall.on("hangup", () => this._onCallHangup(newCall)); @@ -472,7 +492,45 @@ export class ConferenceCallManager extends EventEmitter { this.participants = [this.localParticipant]; this.localParticipant.feed = null; this.localParticipant.call = null; + this._setDebugCallId(this.localParticipant.userId, null); this.emit("participants_changed"); } + + _addDebugEvent(sender, type, content) { + if (!this.debugState.has(sender)) { + this.debugState.set(sender, { + callId: null, + state: "unknown", + events: [{ type, ...content }], + }); + } else { + const { events } = this.debugState.get(sender); + events.push({ type, ...content }); + } + + this.emit("debug"); + } + + _setDebugState(userId, state) { + if (!this.debugState.has(userId)) { + this.debugState.set(userId, { state, callId: null, events: [] }); + } else { + const userState = this.debugState.get(userId); + userState.state = state; + } + + this.emit("debug"); + } + + _setDebugCallId(userId, callId) { + if (!this.debugState.has(userId)) { + this.debugState.set(userId, { state: "unknown", callId, events: [] }); + } else { + const userState = this.debugState.get(userId); + userState.callId = callId; + } + + this.emit("debug"); + } } diff --git a/src/ConferenceCallManagerHooks.js b/src/ConferenceCallManagerHooks.js index 8d89093..31121f9 100644 --- a/src/ConferenceCallManagerHooks.js +++ b/src/ConferenceCallManagerHooks.js @@ -28,7 +28,6 @@ export function useConferenceCallManager(homeserverUrl) { useEffect(() => { ConferenceCallManager.restore(homeserverUrl) .then((manager) => { - console.log(manager); setState({ manager, loading: false, diff --git a/src/DevTools.jsx b/src/DevTools.jsx new file mode 100644 index 0000000..49775bf --- /dev/null +++ b/src/DevTools.jsx @@ -0,0 +1,97 @@ +import React, { useCallback, useEffect, useRef, useState } from "react"; +import ColorHash from "color-hash"; +import styles from "./DevTools.module.css"; + +const colorHash = new ColorHash({ lightness: 0.8 }); + +function CallId({ callId, ...rest }) { + const shortId = callId.substr(callId.length - 16); + const color = colorHash.hex(shortId); + + return ( + + {shortId} + + ); +} + +export function DevTools({ manager }) { + const [debugState, setDebugState] = useState(manager.debugState); + + useEffect(() => { + function onRoomDebug() { + setDebugState(manager.debugState); + } + + manager.on("debug", onRoomDebug); + + return () => { + manager.removeListener("debug", onRoomDebug); + }; + }, [manager]); + + return ( +