fix webrtc

This commit is contained in:
2026-04-11 15:33:55 -04:00
parent eacd25a6ef
commit b02f7b292c
4 changed files with 90 additions and 21 deletions
@@ -41,10 +41,10 @@
return `${bps.toFixed(0)} bps`;
};
function handleContextMenu(e: MouseEvent, vsIdentity: any) {
function handleContextMenu(e: MouseEvent, targetIdentity: any) {
e.preventDefault();
e.stopPropagation();
const user = chat.users.find((u) => u.identity.isEqual(vsIdentity));
const user = chat.users.find((u) => u.identity.isEqual(targetIdentity));
if (user) {
chat.userContextMenu = { x: e.clientX, y: e.clientY, user };
}
@@ -144,9 +144,10 @@
</span>
{:else if s.isMuted}
<span class="voice-indicator-icon" title="Muted"><i class="fas fa-microphone-slash"></i></span>
{#if isLocalUserInThisChannel}
<div class="status-dot {voiceStatusColor}"></div>
{/if}
{#if isLocalUserInThisChannel && (s.isDeafened || s.isMuted)}
<div class="status-dot {voiceStatusColor}"></div>
{/if}
</div>
</div>
@@ -87,9 +87,13 @@ export class ChannelAudioWebRTCService {
});
// Lifecycle Mesh Management
let lastChannelId: bigint | undefined = undefined;
$effect(() => {
if (!this.connectedChannelId || !this.identity) {
console.log(`[WebRTC][voice] Cleaning up channel state`);
const currentChannelId = this.connectedChannelId;
const currentIdentity = this.identity;
if (!currentChannelId || !currentIdentity) {
console.log(`[WebRTC][voice] Cleaning up channel state (Channel: ${currentChannelId}, Identity: ${currentIdentity?.toHexString().substring(0,8)})`);
this.peerManager.peers.forEach((_, id) =>
this.peerManager.closePeer(id),
);
@@ -97,15 +101,27 @@ export class ChannelAudioWebRTCService {
this.makingOffer.clear();
this.ignoreOffer.clear();
this.signalingQueue.clear();
lastChannelId = undefined;
return;
}
// If we switched channels, clear state for the new connection mesh
if (lastChannelId !== undefined && lastChannelId !== currentChannelId) {
console.log(`[WebRTC][voice] Channel switched ${lastChannelId} -> ${currentChannelId}, resetting mesh state`);
this.peerManager.peers.forEach((_, id) => this.peerManager.closePeer(id));
this.processedSignals.clear();
this.makingOffer.clear();
this.ignoreOffer.clear();
this.signalingQueue.clear();
}
lastChannelId = currentChannelId;
const peersToConnect = new Set(
this.userStates
.filter(
(s) =>
s.channelId === this.connectedChannelId &&
!s.identity.isEqual(this.identity!),
s.channelId === currentChannelId &&
!s.identity.isEqual(currentIdentity),
)
.map((s) => s.identity.toHexString()),
);
@@ -132,19 +148,26 @@ export class ChannelAudioWebRTCService {
// Signaling Processors
$effect(() => {
if (!this.connectedChannelId || !this.identity) return;
const currentChannelId = this.connectedChannelId;
const currentIdentity = this.identity;
if (!currentChannelId || !currentIdentity) return;
const mySignals = this.signals.filter(
(s) =>
s.channelId === this.connectedChannelId &&
s.receiver.isEqual(this.identity!) &&
s.channelId === currentChannelId &&
s.receiver.isEqual(currentIdentity) &&
s.mediaType.tag === "Voice"
);
if (mySignals.length > 0) {
console.log(`[WebRTC][voice] Found ${mySignals.length} signals for channel ${currentChannelId}`);
}
for (const signal of mySignals) {
if (this.processedSignals.has(signal.id)) continue;
this.processedSignals.add(signal.id);
console.log(`[WebRTC][voice] Processing ${signal.signalKind.tag} from ${signal.sender.toHexString().substring(0,8)} (Signal ID: ${signal.id})`);
switch (signal.signalKind.tag) {
case "Offer":
this.handleOffer(signal);
@@ -203,6 +226,7 @@ export class ChannelAudioWebRTCService {
try {
this.makingOffer.set(peerIdHex, true);
await pc.setLocalDescription();
console.log(`[WebRTC][voice] Sending Offer to ${peerIdHex.substring(0,8)}`);
this.#sendSignal({
receiver: Identity.fromString(peerIdHex),
signalKind: { tag: "Offer" },
@@ -218,6 +242,7 @@ export class ChannelAudioWebRTCService {
onIceCandidate(peerIdHex: string, candidate: RTCIceCandidate) {
if (this.connectedChannelId) {
console.log(`[WebRTC][voice] Sending ICE candidate to ${peerIdHex.substring(0,8)}`);
this.#sendSignal({
receiver: Identity.fromString(peerIdHex),
signalKind: { tag: "IceCandidate" },
@@ -247,6 +272,7 @@ export class ChannelAudioWebRTCService {
);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
console.log(`[WebRTC][voice] Sending Answer to ${peerIdHex.substring(0,8)}`);
this.#sendSignal({
receiver: signal.sender,
signalKind: { tag: "Answer" },
@@ -266,6 +292,7 @@ export class ChannelAudioWebRTCService {
handleAnswer(signal: Types.WebRtcSignal) {
const peerIdHex = signal.sender.toHexString();
console.log(`[WebRTC][voice] Received Answer from ${peerIdHex.substring(0,8)}`);
this.enqueueSignalingTask(peerIdHex, async () => {
const peer = this.peerManager.getPeer(peerIdHex);
if (!peer) return;
@@ -285,6 +312,7 @@ export class ChannelAudioWebRTCService {
handleIceCandidate(signal: Types.WebRtcSignal) {
const peerIdHex = signal.sender.toHexString();
console.log(`[WebRTC][voice] Received ICE candidate from ${peerIdHex.substring(0,8)}`);
this.enqueueSignalingTask(peerIdHex, async () => {
const pc = this.peerManager.createPeerConnection(peerIdHex);
if (!pc) return;
@@ -82,9 +82,13 @@ export class ScreenSharingWebRTCService {
});
// Lifecycle Mesh Management
let lastChannelId: bigint | undefined = undefined;
$effect(() => {
if (!this.connectedChannelId || !this.identity) {
console.log(`[WebRTC][screen] Cleaning up screen state`);
const currentChannelId = this.connectedChannelId;
const currentIdentity = this.identity;
if (!currentChannelId || !currentIdentity) {
console.log(`[WebRTC][screen] Cleaning up screen state (Channel: ${currentChannelId}, Identity: ${currentIdentity?.toHexString().substring(0,8)})`);
this.peerManager.peers.forEach((_, id) =>
this.peerManager.closePeer(id),
);
@@ -92,18 +96,30 @@ export class ScreenSharingWebRTCService {
this.makingOffer.clear();
this.ignoreOffer.clear();
this.signalingQueue.clear();
lastChannelId = undefined;
return;
}
// If we switched channels, clear state for the new connection mesh
if (lastChannelId !== undefined && lastChannelId !== currentChannelId) {
console.log(`[WebRTC][screen] Channel switched ${lastChannelId} -> ${currentChannelId}, resetting mesh state`);
this.peerManager.peers.forEach((_, id) => this.peerManager.closePeer(id));
this.processedSignals.clear();
this.makingOffer.clear();
this.ignoreOffer.clear();
this.signalingQueue.clear();
}
lastChannelId = currentChannelId;
const screenPeersToConnect = new Set<string>();
this.userStates.forEach((s) => {
if (s.channelId === this.connectedChannelId) {
if (s.channelId === currentChannelId) {
// If I am watching this user
if (s.identity.isEqual(this.identity!)) {
if (s.identity.isEqual(currentIdentity)) {
if (s.watching) screenPeersToConnect.add(s.watching.toHexString());
}
// If this user is watching me
if (s.watching?.isEqual(this.identity!)) {
if (s.watching?.isEqual(currentIdentity)) {
screenPeersToConnect.add(s.identity.toHexString());
}
}
@@ -111,7 +127,7 @@ export class ScreenSharingWebRTCService {
screenPeersToConnect.forEach((id) => {
if (!this.peerManager.peers.has(id)) {
console.log(`[WebRTC][screen] Connecting to watched peer ${id}`);
console.log(`[WebRTC][screen] Connecting to watched peer ${id.substring(0,8)}`);
this.peerManager.createPeerConnection(id, [
this.localScreenStream?.getVideoTracks()[0] || null,
this.localScreenStream?.getAudioTracks()[0] || null,
@@ -121,7 +137,7 @@ export class ScreenSharingWebRTCService {
this.peerManager.peers.forEach((_, id) => {
if (!screenPeersToConnect.has(id)) {
console.log(`[WebRTC][screen] Peer ${id} no longer watched, closing`);
console.log(`[WebRTC][screen] Peer ${id.substring(0,8)} no longer watched, closing`);
this.peerManager.closePeer(id);
this.makingOffer.delete(id);
this.ignoreOffer.delete(id);
@@ -132,19 +148,26 @@ export class ScreenSharingWebRTCService {
// Signaling Processors
$effect(() => {
if (!this.connectedChannelId || !this.identity) return;
const currentChannelId = this.connectedChannelId;
const currentIdentity = this.identity;
if (!currentChannelId || !currentIdentity) return;
const mySignals = this.signals.filter(
(s) =>
s.channelId === this.connectedChannelId &&
s.receiver.isEqual(this.identity!) &&
s.channelId === currentChannelId &&
s.receiver.isEqual(currentIdentity) &&
s.mediaType.tag === "Screen"
);
if (mySignals.length > 0) {
console.log(`[WebRTC][screen] Found ${mySignals.length} signals for channel ${currentChannelId}`);
}
for (const signal of mySignals) {
if (this.processedSignals.has(signal.id)) continue;
this.processedSignals.add(signal.id);
console.log(`[WebRTC][screen] Processing ${signal.signalKind.tag} from ${signal.sender.toHexString().substring(0,8)} (Signal ID: ${signal.id})`);
switch (signal.signalKind.tag) {
case "Offer":
this.handleOffer(signal);
@@ -203,6 +226,7 @@ export class ScreenSharingWebRTCService {
try {
this.makingOffer.set(peerIdHex, true);
await pc.setLocalDescription();
console.log(`[WebRTC][screen] Sending Offer to ${peerIdHex.substring(0,8)}`);
this.#sendSignal({
receiver: Identity.fromString(peerIdHex),
signalKind: { tag: "Offer" },
@@ -218,6 +242,7 @@ export class ScreenSharingWebRTCService {
onIceCandidate(peerIdHex: string, candidate: RTCIceCandidate) {
if (this.connectedChannelId) {
console.log(`[WebRTC][screen] Sending ICE candidate to ${peerIdHex.substring(0,8)}`);
this.#sendSignal({
receiver: Identity.fromString(peerIdHex),
signalKind: { tag: "IceCandidate" },
@@ -230,6 +255,7 @@ export class ScreenSharingWebRTCService {
handleOffer(signal: Types.WebRtcSignal) {
const peerIdHex = signal.sender.toHexString();
console.log(`[WebRTC][screen] Received Offer from ${peerIdHex.substring(0,8)}`);
this.enqueueSignalingTask(peerIdHex, async () => {
const pc = this.peerManager.createPeerConnection(peerIdHex);
if (!pc) return;
@@ -247,6 +273,7 @@ export class ScreenSharingWebRTCService {
);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
console.log(`[WebRTC][screen] Sending Answer to ${peerIdHex.substring(0,8)}`);
this.#sendSignal({
receiver: signal.sender,
signalKind: { tag: "Answer" },
@@ -266,6 +293,7 @@ export class ScreenSharingWebRTCService {
handleAnswer(signal: Types.WebRtcSignal) {
const peerIdHex = signal.sender.toHexString();
console.log(`[WebRTC][screen] Received Answer from ${peerIdHex.substring(0,8)}`);
this.enqueueSignalingTask(peerIdHex, async () => {
const peer = this.peerManager.getPeer(peerIdHex);
if (!peer) return;
@@ -285,6 +313,7 @@ export class ScreenSharingWebRTCService {
handleIceCandidate(signal: Types.WebRtcSignal) {
const peerIdHex = signal.sender.toHexString();
console.log(`[WebRTC][screen] Received ICE candidate from ${peerIdHex.substring(0,8)}`);
this.enqueueSignalingTask(peerIdHex, async () => {
const pc = this.peerManager.createPeerConnection(peerIdHex);
if (!pc) return;
+11
View File
@@ -67,6 +67,10 @@ export class WebRTCService {
this.localMedia = new LocalMediaService(connectedChannelId);
$effect(() => {
this.localMedia.connectedChannelId = this.connectedChannelId;
});
// Pass localMedia's stream and deafen state to voice
this.voice = new ChannelAudioWebRTCService(
identity,
@@ -81,6 +85,13 @@ export class WebRTCService {
this.localMedia.localScreenStream,
);
$effect(() => {
this.voice.connectedChannelId = this.connectedChannelId;
this.screen.connectedChannelId = this.connectedChannelId;
this.voice.localStream = this.localMedia.localStream;
this.screen.localScreenStream = this.localMedia.localScreenStream;
});
// Sound for streams I am watching
let lastWatched = new Set<string>();
$effect(() => {