fix screen sharing
This commit is contained in:
@@ -1,25 +1,34 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import type { ChatService } from "../services/chat.svelte";
|
import type { ChatService } from "../services/chat.svelte";
|
||||||
|
import type { WebRTCService } from "../services/webrtc/webrtc.svelte";
|
||||||
import type * as Types from "../../module_bindings/types";
|
import type * as Types from "../../module_bindings/types";
|
||||||
import Avatar from "./Avatar.svelte";
|
import Avatar from "./Avatar.svelte";
|
||||||
import Skeleton from "./Skeleton.svelte";
|
import Skeleton from "./Skeleton.svelte";
|
||||||
|
|
||||||
const chat = getContext<ChatService>("chat");
|
const chat = getContext<ChatService>("chat");
|
||||||
|
const webrtc = getContext<WebRTCService>("webrtc");
|
||||||
|
|
||||||
let onlineMembers = $derived(chat.activeServerMembers.filter((m) => m.online));
|
let onlineMembers = $derived(chat.activeServerMembers.filter((m) => m.online));
|
||||||
let offlineMembers = $derived(chat.activeServerMembers.filter((m) => !m.online));
|
let offlineMembers = $derived(chat.activeServerMembers.filter((m) => !m.online));
|
||||||
|
|
||||||
function isTalking(member: Types.ServerMember) {
|
function getVoiceState(member: Types.ServerMember) {
|
||||||
return chat.userStates.find((s) => s.identity.isEqual(member.identity))?.isTalking || false;
|
return chat.userStates.find((s) => s.identity.isEqual(member.identity));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSharing(member: Types.ServerMember) {
|
function getVoiceStatusColor(member: Types.ServerMember) {
|
||||||
return chat.userStates.find((s) => s.identity.isEqual(member.identity))?.isSharingScreen || false;
|
const state = getVoiceState(member);
|
||||||
}
|
if (!state || state.channelId === undefined) return null;
|
||||||
|
|
||||||
function isMe(member: Types.ServerMember) {
|
if (chat.identity?.isEqual(member.identity)) return "green";
|
||||||
return chat.identity?.isEqual(member.identity);
|
|
||||||
|
const status = webrtc.peerStatuses.get(member.identity.toHexString());
|
||||||
|
if (!status) return "yellow";
|
||||||
|
|
||||||
|
const [iceState] = status.toLowerCase().split("/");
|
||||||
|
if (iceState.includes("connected") || iceState.includes("completed")) return "green";
|
||||||
|
if (iceState.includes("connecting") || iceState.includes("checking") || iceState.includes("new")) return "yellow";
|
||||||
|
return "red";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatus(member: Types.ServerMember) {
|
function getStatus(member: Types.ServerMember) {
|
||||||
@@ -57,13 +66,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{#each onlineMembers as member (member.identity.toHexString())}
|
{#each onlineMembers as member (member.identity.toHexString())}
|
||||||
{@const status = getStatus(member)}
|
{@const status = getStatus(member)}
|
||||||
|
{@const voiceState = getVoiceState(member)}
|
||||||
|
{@const voiceColor = getVoiceStatusColor(member)}
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div class="member-item" oncontextmenu={(e) => handleContextMenu(e, member)}>
|
<div class="member-item" oncontextmenu={(e) => handleContextMenu(e, member)}>
|
||||||
<Avatar user={member} size="tiny" isTalking={isTalking(member)} />
|
<div class="avatar-container">
|
||||||
|
<Avatar user={member} size="small" isTalking={voiceState?.isTalking} />
|
||||||
|
<div class="status-dot green overlay"></div>
|
||||||
|
</div>
|
||||||
<div class="member-details">
|
<div class="member-details">
|
||||||
<span class="member-name {isTalking(member) ? 'talking' : ''}">
|
<span class="member-name {voiceState?.isTalking ? 'talking' : ''}">
|
||||||
{member.name || member.identity.toHexString().substring(0, 8)}
|
{member.name || member.identity.toHexString().substring(0, 8)}
|
||||||
{#if isMe(member)}
|
{#if chat.identity?.isEqual(member.identity)}
|
||||||
<span class="me-badge">(You)</span>
|
<span class="me-badge">(You)</span>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
@@ -72,10 +86,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div style="flex: 1;"></div>
|
<div style="flex: 1;"></div>
|
||||||
{#if isSharing(member)}
|
<div class="member-indicators">
|
||||||
|
{#if voiceState?.isSharingScreen}
|
||||||
<span class="sharing-badge">LIVE</span>
|
<span class="sharing-badge">LIVE</span>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="status-dot green"></div>
|
{#if voiceColor}
|
||||||
|
<div class="status-dot {voiceColor}" title="Voice connection status"></div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
@@ -88,11 +106,14 @@
|
|||||||
{@const status = getStatus(member)}
|
{@const status = getStatus(member)}
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div class="member-item offline" oncontextmenu={(e) => handleContextMenu(e, member)}>
|
<div class="member-item offline" oncontextmenu={(e) => handleContextMenu(e, member)}>
|
||||||
<Avatar user={member} size="tiny" />
|
<div class="avatar-container">
|
||||||
|
<Avatar user={member} size="small" />
|
||||||
|
<div class="status-dot grey overlay"></div>
|
||||||
|
</div>
|
||||||
<div class="member-details">
|
<div class="member-details">
|
||||||
<span class="member-name">
|
<span class="member-name">
|
||||||
{member.name || member.identity.toHexString().substring(0, 8)}
|
{member.name || member.identity.toHexString().substring(0, 8)}
|
||||||
{#if isMe(member)}
|
{#if chat.identity?.isEqual(member.identity)}
|
||||||
<span class="me-badge">(You)</span>
|
<span class="me-badge">(You)</span>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
@@ -100,8 +121,6 @@
|
|||||||
<div class="member-status" title={status}>{status}</div>
|
<div class="member-status" title={status}>{status}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div style="flex: 1;"></div>
|
|
||||||
<div class="status-dot grey"></div>
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
@@ -110,6 +129,27 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.avatar-container {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.overlay {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
right: -2px;
|
||||||
|
border: 3px solid var(--background-secondary);
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-indicators {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.me-badge {
|
.me-badge {
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
|||||||
@@ -146,7 +146,7 @@
|
|||||||
<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}
|
{/if}
|
||||||
|
|
||||||
{#if isLocalUserInThisChannel && (s.isDeafened || s.isMuted)}
|
{#if isLocalUserInThisChannel}
|
||||||
<div class="status-dot {voiceStatusColor}"></div>
|
<div class="status-dot {voiceStatusColor}"></div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -63,16 +63,13 @@ export class ChannelAudioWebRTCService {
|
|||||||
void this.peerManager.peerStatuses;
|
void this.peerManager.peerStatuses;
|
||||||
this.peerManager.peers.forEach(async (peer, peerIdHex) => {
|
this.peerManager.peers.forEach(async (peer, peerIdHex) => {
|
||||||
const transceivers = peer.pc.getTransceivers();
|
const transceivers = peer.pc.getTransceivers();
|
||||||
if (transceivers[0] && transceivers[0].sender.track !== audioTrack) {
|
const audioTransceiver = transceivers.find(t => t.receiver.track.kind === 'audio' || t.sender.track?.kind === 'audio');
|
||||||
try {
|
|
||||||
console.log(
|
if (audioTransceiver && audioTransceiver.sender.track !== audioTrack) {
|
||||||
`[WebRTC][voice] Syncing track for ${peerIdHex} (track: ${audioTrack?.id})`,
|
try {
|
||||||
);
|
console.log(`[WebRTC][voice] Syncing track for ${peerIdHex}`);
|
||||||
await transceivers[0].sender.replaceTrack(audioTrack);
|
await audioTransceiver.sender.replaceTrack(audioTrack);
|
||||||
|
|
||||||
// If we just attached a real track and we are stable,
|
|
||||||
// we might need to negotiate if the remote side didn't know we have a track.
|
|
||||||
// But replaceTrack usually doesn't need negotiation if the transceiver was already sendrecv.
|
|
||||||
if (audioTrack && peer.pc.signalingState === "stable") {
|
if (audioTrack && peer.pc.signalingState === "stable") {
|
||||||
this.onNegotiationNeeded(peerIdHex, peer.pc);
|
this.onNegotiationNeeded(peerIdHex, peer.pc);
|
||||||
}
|
}
|
||||||
@@ -129,9 +126,14 @@ export class ChannelAudioWebRTCService {
|
|||||||
peersToConnect.forEach((id) => {
|
peersToConnect.forEach((id) => {
|
||||||
if (!this.peerManager.peers.has(id)) {
|
if (!this.peerManager.peers.has(id)) {
|
||||||
console.log(`[WebRTC][voice] Initiating mesh connection to ${id}`);
|
console.log(`[WebRTC][voice] Initiating mesh connection to ${id}`);
|
||||||
this.peerManager.createPeerConnection(id, [
|
const pc = this.peerManager.createPeerConnection(id);
|
||||||
this.localStream?.getAudioTracks()[0] || null,
|
if (pc) {
|
||||||
]);
|
const audioTrack = this.localStream?.getAudioTracks()[0] || null;
|
||||||
|
const transceiver = pc.addTransceiver('audio', { direction: 'sendrecv' });
|
||||||
|
if (audioTrack) {
|
||||||
|
transceiver.sender.replaceTrack(audioTrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -270,6 +272,17 @@ export class ChannelAudioWebRTCService {
|
|||||||
await pc.setRemoteDescription(
|
await pc.setRemoteDescription(
|
||||||
new RTCSessionDescription(JSON.parse(signal.data)),
|
new RTCSessionDescription(JSON.parse(signal.data)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Map local track to the transceiver created by the offer
|
||||||
|
const transceivers = pc.getTransceivers();
|
||||||
|
const audioTrack = this.localStream?.getAudioTracks()[0];
|
||||||
|
const audioTransceiver = transceivers.find(t => t.receiver.track.kind === 'audio');
|
||||||
|
|
||||||
|
if (audioTransceiver && audioTrack) {
|
||||||
|
await audioTransceiver.sender.replaceTrack(audioTrack);
|
||||||
|
audioTransceiver.direction = 'sendrecv';
|
||||||
|
}
|
||||||
|
|
||||||
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)}`);
|
console.log(`[WebRTC][voice] Sending Answer to ${peerIdHex.substring(0,8)}`);
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export class PeerManagerService {
|
|||||||
|
|
||||||
createPeerConnection = (
|
createPeerConnection = (
|
||||||
peerIdHex: string,
|
peerIdHex: string,
|
||||||
initialTracks: (MediaStreamTrack | null)[] = [],
|
_initialTracks: (MediaStreamTrack | null)[] = [],
|
||||||
) => {
|
) => {
|
||||||
if (this.peers.has(peerIdHex)) return this.peers.get(peerIdHex)!.pc;
|
if (this.peers.has(peerIdHex)) return this.peers.get(peerIdHex)!.pc;
|
||||||
|
|
||||||
@@ -276,7 +276,10 @@ export class PeerManagerService {
|
|||||||
`[WebRTC][${this.mediaType}] Received track from ${peerIdHex}: ${event.track.kind} (id: ${event.track.id})`,
|
`[WebRTC][${this.mediaType}] Received track from ${peerIdHex}: ${event.track.kind} (id: ${event.track.id})`,
|
||||||
);
|
);
|
||||||
const nextPeers = new Map(this.peers);
|
const nextPeers = new Map(this.peers);
|
||||||
const existingPeer = { ...(nextPeers.get(peerIdHex) || { pc }) };
|
const existingPeer = nextPeers.get(peerIdHex);
|
||||||
|
if (!existingPeer) return;
|
||||||
|
|
||||||
|
const peerUpdate = { ...existingPeer };
|
||||||
|
|
||||||
if (event.track.kind === "audio") {
|
if (event.track.kind === "audio") {
|
||||||
if (this.mediaType === "voice") {
|
if (this.mediaType === "voice") {
|
||||||
@@ -289,12 +292,12 @@ export class PeerManagerService {
|
|||||||
const ctx = this.getAudioContext();
|
const ctx = this.getAudioContext();
|
||||||
const stream = new MediaStream([event.track]);
|
const stream = new MediaStream([event.track]);
|
||||||
|
|
||||||
if (!existingPeer.audio) {
|
if (!peerUpdate.audio) {
|
||||||
existingPeer.audio = new Audio();
|
peerUpdate.audio = new Audio();
|
||||||
existingPeer.audio.muted = true;
|
peerUpdate.audio.muted = true;
|
||||||
}
|
}
|
||||||
existingPeer.audio.srcObject = stream;
|
peerUpdate.audio.srcObject = stream;
|
||||||
existingPeer.audio.play().catch((err) => {
|
peerUpdate.audio.play().catch((err) => {
|
||||||
if (err.name !== "AbortError")
|
if (err.name !== "AbortError")
|
||||||
console.warn(
|
console.warn(
|
||||||
`[WebRTC][voice] Dummy audio play failed for ${peerIdHex}`,
|
`[WebRTC][voice] Dummy audio play failed for ${peerIdHex}`,
|
||||||
@@ -310,7 +313,7 @@ export class PeerManagerService {
|
|||||||
source.connect(gainNode);
|
source.connect(gainNode);
|
||||||
gainNode.connect(ctx.destination);
|
gainNode.connect(ctx.destination);
|
||||||
|
|
||||||
existingPeer.gainNode = gainNode;
|
peerUpdate.gainNode = gainNode;
|
||||||
console.log(
|
console.log(
|
||||||
`[WebRTC][voice] Web Audio graph connected for ${peerIdHex} (volume: ${pref.volume})`,
|
`[WebRTC][voice] Web Audio graph connected for ${peerIdHex} (volume: ${pref.volume})`,
|
||||||
);
|
);
|
||||||
@@ -319,21 +322,21 @@ export class PeerManagerService {
|
|||||||
`[WebRTC][voice] Failed to setup Web Audio for ${peerIdHex}, falling back to HTMLAudioElement`,
|
`[WebRTC][voice] Failed to setup Web Audio for ${peerIdHex}, falling back to HTMLAudioElement`,
|
||||||
e,
|
e,
|
||||||
);
|
);
|
||||||
if (!existingPeer.audio) {
|
if (!peerUpdate.audio) {
|
||||||
existingPeer.audio = new Audio();
|
peerUpdate.audio = new Audio();
|
||||||
existingPeer.audio.autoplay = true;
|
peerUpdate.audio.autoplay = true;
|
||||||
|
|
||||||
const pref = this.peerAudioPreferences.get(peerIdHex) || {
|
const pref = this.peerAudioPreferences.get(peerIdHex) || {
|
||||||
volume: 1,
|
volume: 1,
|
||||||
muted: false,
|
muted: false,
|
||||||
};
|
};
|
||||||
existingPeer.audio.volume = Math.min(1, pref.volume);
|
peerUpdate.audio.volume = Math.min(1, pref.volume);
|
||||||
existingPeer.audio.muted = this.isDeafened || pref.muted;
|
peerUpdate.audio.muted = this.isDeafened || pref.muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stream = new MediaStream([event.track]);
|
const stream = new MediaStream([event.track]);
|
||||||
existingPeer.audio.srcObject = stream;
|
peerUpdate.audio.srcObject = stream;
|
||||||
existingPeer.audio.play().catch((err) => {
|
peerUpdate.audio.play().catch((err) => {
|
||||||
if (err.name !== "AbortError")
|
if (err.name !== "AbortError")
|
||||||
console.error(
|
console.error(
|
||||||
`[WebRTC][voice] Error playing audio for ${peerIdHex}`,
|
`[WebRTC][voice] Error playing audio for ${peerIdHex}`,
|
||||||
@@ -342,44 +345,32 @@ export class PeerManagerService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const currentStream = existingPeer.videoStream || new MediaStream();
|
const currentStream = peerUpdate.videoStream || new MediaStream();
|
||||||
if (!currentStream.getTracks().find((t) => t.id === event.track.id)) {
|
if (!currentStream.getTracks().find((t) => t.id === event.track.id)) {
|
||||||
currentStream.addTrack(event.track);
|
currentStream.addTrack(event.track);
|
||||||
}
|
}
|
||||||
existingPeer.videoStream = new MediaStream(currentStream.getTracks());
|
peerUpdate.videoStream = new MediaStream(currentStream.getTracks());
|
||||||
}
|
}
|
||||||
} else if (event.track.kind === "video") {
|
} else if (event.track.kind === "video") {
|
||||||
const currentStream = existingPeer.videoStream || new MediaStream();
|
const currentStream = peerUpdate.videoStream || new MediaStream();
|
||||||
if (!currentStream.getTracks().find((t) => t.id === event.track.id)) {
|
if (!currentStream.getTracks().find((t) => t.id === event.track.id)) {
|
||||||
currentStream.addTrack(event.track);
|
currentStream.addTrack(event.track);
|
||||||
}
|
}
|
||||||
existingPeer.videoStream = new MediaStream(currentStream.getTracks());
|
peerUpdate.videoStream = new MediaStream(currentStream.getTracks());
|
||||||
}
|
}
|
||||||
|
|
||||||
nextPeers.set(peerIdHex, existingPeer);
|
nextPeers.set(peerIdHex, peerUpdate);
|
||||||
this.peers = nextPeers;
|
this.peers = nextPeers;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.mediaType === "voice") {
|
|
||||||
pc.addTransceiver("audio", { direction: "sendrecv" });
|
|
||||||
} else {
|
|
||||||
pc.addTransceiver("video", { direction: "sendrecv" });
|
|
||||||
pc.addTransceiver("audio", { direction: "sendrecv" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const transceivers = pc.getTransceivers();
|
|
||||||
initialTracks.forEach((track, i) => {
|
|
||||||
if (track && transceivers[i]) {
|
|
||||||
console.log(
|
|
||||||
`[WebRTC][${this.mediaType}] Attaching initial track ${i} to ${peerIdHex}`,
|
|
||||||
);
|
|
||||||
transceivers[i].sender.replaceTrack(track);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const nextPeers = new Map(this.peers);
|
const nextPeers = new Map(this.peers);
|
||||||
nextPeers.set(peerIdHex, { pc });
|
nextPeers.set(peerIdHex, { pc });
|
||||||
this.peers = nextPeers;
|
this.peers = nextPeers;
|
||||||
|
|
||||||
|
// We no longer add transceivers here.
|
||||||
|
// The calling service is responsible for adding them if it's the offerer,
|
||||||
|
// or setRemoteDescription will create them if it's the answerer.
|
||||||
|
|
||||||
return pc;
|
return pc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -61,20 +61,21 @@ export class ScreenSharingWebRTCService {
|
|||||||
this.peerManager.peers.forEach(async (peer, peerIdHex) => {
|
this.peerManager.peers.forEach(async (peer, peerIdHex) => {
|
||||||
const transceivers = peer.pc.getTransceivers();
|
const transceivers = peer.pc.getTransceivers();
|
||||||
let changed = false;
|
let changed = false;
|
||||||
if (transceivers[0] && transceivers[0].sender.track !== videoTrack) {
|
|
||||||
console.log(
|
const videoTransceiver = transceivers.find(t => t.receiver.track.kind === 'video' || t.sender.track?.kind === 'video');
|
||||||
`[WebRTC][screen] Syncing video track for ${peerIdHex} (track: ${videoTrack?.id})`,
|
const audioTransceiver = transceivers.find(t => t.receiver.track.kind === 'audio' || t.sender.track?.kind === 'audio');
|
||||||
);
|
|
||||||
await transceivers[0].sender.replaceTrack(videoTrack);
|
if (videoTransceiver && videoTransceiver.sender.track !== videoTrack) {
|
||||||
|
console.log(`[WebRTC][screen] Syncing video track for ${peerIdHex}`);
|
||||||
|
await videoTransceiver.sender.replaceTrack(videoTrack);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
if (transceivers[1] && transceivers[1].sender.track !== audioTrack) {
|
if (audioTransceiver && audioTransceiver.sender.track !== audioTrack) {
|
||||||
console.log(
|
console.log(`[WebRTC][screen] Syncing audio track for ${peerIdHex}`);
|
||||||
`[WebRTC][screen] Syncing audio track for ${peerIdHex} (track: ${audioTrack?.id})`,
|
await audioTransceiver.sender.replaceTrack(audioTrack);
|
||||||
);
|
|
||||||
await transceivers[1].sender.replaceTrack(audioTrack);
|
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed && peer.pc.signalingState === "stable") {
|
if (changed && peer.pc.signalingState === "stable") {
|
||||||
this.onNegotiationNeeded(peerIdHex, peer.pc);
|
this.onNegotiationNeeded(peerIdHex, peer.pc);
|
||||||
}
|
}
|
||||||
@@ -128,10 +129,19 @@ 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.substring(0,8)}`);
|
console.log(`[WebRTC][screen] Connecting to watched peer ${id.substring(0,8)}`);
|
||||||
this.peerManager.createPeerConnection(id, [
|
const pc = this.peerManager.createPeerConnection(id);
|
||||||
this.localScreenStream?.getVideoTracks()[0] || null,
|
if (pc) {
|
||||||
this.localScreenStream?.getAudioTracks()[0] || null,
|
// Initiate with desired transceivers
|
||||||
]);
|
pc.addTransceiver('video', { direction: 'sendrecv' });
|
||||||
|
pc.addTransceiver('audio', { direction: 'sendrecv' });
|
||||||
|
|
||||||
|
// Attach initial tracks if available
|
||||||
|
const transceivers = pc.getTransceivers();
|
||||||
|
const videoTrack = this.localScreenStream?.getVideoTracks()[0];
|
||||||
|
const audioTrack = this.localScreenStream?.getAudioTracks()[0];
|
||||||
|
if (videoTrack) transceivers[0].sender.replaceTrack(videoTrack);
|
||||||
|
if (audioTrack) transceivers[1].sender.replaceTrack(audioTrack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -271,6 +281,25 @@ export class ScreenSharingWebRTCService {
|
|||||||
await pc.setRemoteDescription(
|
await pc.setRemoteDescription(
|
||||||
new RTCSessionDescription(JSON.parse(signal.data)),
|
new RTCSessionDescription(JSON.parse(signal.data)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// After setting remote offer, the transceivers exist.
|
||||||
|
// We should attach our local tracks if we have them.
|
||||||
|
const transceivers = pc.getTransceivers();
|
||||||
|
const videoTrack = this.localScreenStream?.getVideoTracks()[0];
|
||||||
|
const audioTrack = this.localScreenStream?.getAudioTracks()[0];
|
||||||
|
|
||||||
|
const videoTransceiver = transceivers.find(t => t.receiver.track.kind === 'video');
|
||||||
|
const audioTransceiver = transceivers.find(t => t.receiver.track.kind === 'audio');
|
||||||
|
|
||||||
|
if (videoTransceiver && videoTrack) {
|
||||||
|
await videoTransceiver.sender.replaceTrack(videoTrack);
|
||||||
|
videoTransceiver.direction = 'sendrecv';
|
||||||
|
}
|
||||||
|
if (audioTransceiver && audioTrack) {
|
||||||
|
await audioTransceiver.sender.replaceTrack(audioTrack);
|
||||||
|
audioTransceiver.direction = 'sendrecv';
|
||||||
|
}
|
||||||
|
|
||||||
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)}`);
|
console.log(`[WebRTC][screen] Sending Answer to ${peerIdHex.substring(0,8)}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user