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