Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c7e535057 | |||
| 8f960be1e9 | |||
| 9a173792a4 | |||
| cb2c0495b9 |
@@ -8,18 +8,24 @@ interface WsMessage {
|
|||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeDisplayName(value: unknown, fallback = 'User'): string {
|
||||||
|
const normalized = typeof value === 'string' ? value.trim() : '';
|
||||||
|
|
||||||
|
return normalized || fallback;
|
||||||
|
}
|
||||||
|
|
||||||
/** Sends the current user list for a given server to a single connected user. */
|
/** Sends the current user list for a given server to a single connected user. */
|
||||||
function sendServerUsers(user: ConnectedUser, serverId: string): void {
|
function sendServerUsers(user: ConnectedUser, serverId: string): void {
|
||||||
const users = Array.from(connectedUsers.values())
|
const users = Array.from(connectedUsers.values())
|
||||||
.filter(cu => cu.serverIds.has(serverId) && cu.oderId !== user.oderId)
|
.filter(cu => cu.serverIds.has(serverId) && cu.oderId !== user.oderId)
|
||||||
.map(cu => ({ oderId: cu.oderId, displayName: cu.displayName ?? 'Anonymous' }));
|
.map(cu => ({ oderId: cu.oderId, displayName: normalizeDisplayName(cu.displayName) }));
|
||||||
|
|
||||||
user.ws.send(JSON.stringify({ type: 'server_users', serverId, users }));
|
user.ws.send(JSON.stringify({ type: 'server_users', serverId, users }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleIdentify(user: ConnectedUser, message: WsMessage, connectionId: string): void {
|
function handleIdentify(user: ConnectedUser, message: WsMessage, connectionId: string): void {
|
||||||
user.oderId = String(message['oderId'] || connectionId);
|
user.oderId = String(message['oderId'] || connectionId);
|
||||||
user.displayName = String(message['displayName'] || 'Anonymous');
|
user.displayName = normalizeDisplayName(message['displayName'], normalizeDisplayName(user.displayName));
|
||||||
connectedUsers.set(connectionId, user);
|
connectedUsers.set(connectionId, user);
|
||||||
console.log(`User identified: ${user.displayName} (${user.oderId})`);
|
console.log(`User identified: ${user.displayName} (${user.oderId})`);
|
||||||
}
|
}
|
||||||
@@ -47,7 +53,7 @@ async function handleJoinServer(user: ConnectedUser, message: WsMessage, connect
|
|||||||
user.serverIds.add(sid);
|
user.serverIds.add(sid);
|
||||||
user.viewedServerId = sid;
|
user.viewedServerId = sid;
|
||||||
connectedUsers.set(connectionId, user);
|
connectedUsers.set(connectionId, user);
|
||||||
console.log(`User ${user.displayName ?? 'Anonymous'} (${user.oderId}) joined server ${sid} (new=${isNew})`);
|
console.log(`User ${normalizeDisplayName(user.displayName)} (${user.oderId}) joined server ${sid} (new=${isNew})`);
|
||||||
|
|
||||||
sendServerUsers(user, sid);
|
sendServerUsers(user, sid);
|
||||||
|
|
||||||
@@ -55,7 +61,7 @@ async function handleJoinServer(user: ConnectedUser, message: WsMessage, connect
|
|||||||
broadcastToServer(sid, {
|
broadcastToServer(sid, {
|
||||||
type: 'user_joined',
|
type: 'user_joined',
|
||||||
oderId: user.oderId,
|
oderId: user.oderId,
|
||||||
displayName: user.displayName ?? 'Anonymous',
|
displayName: normalizeDisplayName(user.displayName),
|
||||||
serverId: sid
|
serverId: sid
|
||||||
}, user.oderId);
|
}, user.oderId);
|
||||||
}
|
}
|
||||||
@@ -66,7 +72,7 @@ function handleViewServer(user: ConnectedUser, message: WsMessage, connectionId:
|
|||||||
|
|
||||||
user.viewedServerId = viewSid;
|
user.viewedServerId = viewSid;
|
||||||
connectedUsers.set(connectionId, user);
|
connectedUsers.set(connectionId, user);
|
||||||
console.log(`User ${user.displayName ?? 'Anonymous'} (${user.oderId}) viewing server ${viewSid}`);
|
console.log(`User ${normalizeDisplayName(user.displayName)} (${user.oderId}) viewing server ${viewSid}`);
|
||||||
|
|
||||||
sendServerUsers(user, viewSid);
|
sendServerUsers(user, viewSid);
|
||||||
}
|
}
|
||||||
@@ -87,7 +93,7 @@ function handleLeaveServer(user: ConnectedUser, message: WsMessage, connectionId
|
|||||||
broadcastToServer(leaveSid, {
|
broadcastToServer(leaveSid, {
|
||||||
type: 'user_left',
|
type: 'user_left',
|
||||||
oderId: user.oderId,
|
oderId: user.oderId,
|
||||||
displayName: user.displayName ?? 'Anonymous',
|
displayName: normalizeDisplayName(user.displayName),
|
||||||
serverId: leaveSid,
|
serverId: leaveSid,
|
||||||
serverIds: Array.from(user.serverIds)
|
serverIds: Array.from(user.serverIds)
|
||||||
}, user.oderId);
|
}, user.oderId);
|
||||||
|
|||||||
@@ -464,23 +464,37 @@ export class WebRTCService implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const existing = this.peerManager.activePeerConnections.get(user.oderId);
|
const existing = this.peerManager.activePeerConnections.get(user.oderId);
|
||||||
const healthy = this.isPeerHealthy(existing);
|
|
||||||
|
|
||||||
if (existing && !healthy) {
|
if (this.canReusePeerConnection(existing)) {
|
||||||
this.logger.info('Removing stale peer before recreate', { oderId: user.oderId });
|
this.logger.info('Reusing active peer connection', {
|
||||||
|
connectionState: existing?.connection.connectionState ?? 'unknown',
|
||||||
|
dataChannelState: existing?.dataChannel?.readyState ?? 'missing',
|
||||||
|
oderId: user.oderId,
|
||||||
|
serverId: message.serverId,
|
||||||
|
signalUrl
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
this.logger.info('Removing failed peer before recreate', {
|
||||||
|
connectionState: existing.connection.connectionState,
|
||||||
|
dataChannelState: existing.dataChannel?.readyState ?? 'missing',
|
||||||
|
oderId: user.oderId,
|
||||||
|
serverId: message.serverId,
|
||||||
|
signalUrl
|
||||||
|
});
|
||||||
this.peerManager.removePeer(user.oderId);
|
this.peerManager.removePeer(user.oderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (healthy)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
this.logger.info('Create peer connection to existing user', {
|
this.logger.info('Create peer connection to existing user', {
|
||||||
oderId: user.oderId,
|
oderId: user.oderId,
|
||||||
serverId: message.serverId
|
serverId: message.serverId,
|
||||||
|
signalUrl
|
||||||
});
|
});
|
||||||
|
|
||||||
this.peerManager.createPeerConnection(user.oderId, true);
|
this.peerManager.createPeerConnection(user.oderId, true);
|
||||||
this.peerManager.createAndSendOffer(user.oderId);
|
void this.peerManager.createAndSendOffer(user.oderId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -811,13 +825,15 @@ export class WebRTCService implements OnDestroy {
|
|||||||
* @param displayName - The user's display name.
|
* @param displayName - The user's display name.
|
||||||
*/
|
*/
|
||||||
identify(oderId: string, displayName: string, signalUrl?: string): void {
|
identify(oderId: string, displayName: string, signalUrl?: string): void {
|
||||||
|
const normalizedDisplayName = displayName.trim() || DEFAULT_DISPLAY_NAME;
|
||||||
|
|
||||||
this.lastIdentifyCredentials = { oderId,
|
this.lastIdentifyCredentials = { oderId,
|
||||||
displayName };
|
displayName: normalizedDisplayName };
|
||||||
|
|
||||||
const identifyMessage = {
|
const identifyMessage = {
|
||||||
type: SIGNALING_TYPE_IDENTIFY,
|
type: SIGNALING_TYPE_IDENTIFY,
|
||||||
oderId,
|
oderId,
|
||||||
displayName
|
displayName: normalizedDisplayName
|
||||||
};
|
};
|
||||||
|
|
||||||
if (signalUrl) {
|
if (signalUrl) {
|
||||||
@@ -825,7 +841,15 @@ export class WebRTCService implements OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendRawMessage(identifyMessage);
|
const connectedManagers = this.getConnectedSignalingManagers();
|
||||||
|
|
||||||
|
if (connectedManagers.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { manager } of connectedManagers) {
|
||||||
|
manager.sendRawMessage(identifyMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1282,15 +1306,14 @@ export class WebRTCService implements OnDestroy {
|
|||||||
this._isDeafened.set(this.mediaManager.getIsSelfDeafened());
|
this._isDeafened.set(this.mediaManager.getIsSelfDeafened());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns true if a peer connection exists and its data channel is open. */
|
/** Returns true if a peer connection is still alive enough to finish negotiating. */
|
||||||
private isPeerHealthy(peer: import('./webrtc').PeerData | undefined): boolean {
|
private canReusePeerConnection(peer: import('./webrtc').PeerData | undefined): boolean {
|
||||||
if (!peer)
|
if (!peer)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const connState = peer.connection?.connectionState;
|
const connState = peer.connection?.connectionState;
|
||||||
const dcState = peer.dataChannel?.readyState;
|
|
||||||
|
|
||||||
return connState === 'connected' && dcState === 'open';
|
return connState !== 'closed' && connState !== 'failed';
|
||||||
}
|
}
|
||||||
|
|
||||||
private handlePeerControlMessage(event: ChatEvent): void {
|
private handlePeerControlMessage(event: ChatEvent): void {
|
||||||
|
|||||||
@@ -27,8 +27,14 @@ export class DesktopElectronScreenShareCapture {
|
|||||||
return !!this.dependencies.getElectronApi()?.getSources && !this.isLinuxElectron();
|
return !!this.dependencies.getElectronApi()?.getSources && !this.isLinuxElectron();
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldSuppressRemotePlaybackDuringShare(includeSystemAudio: boolean): boolean {
|
shouldSuppressRemotePlaybackDuringShare(
|
||||||
return includeSystemAudio && this.isWindowsElectron();
|
includeSystemAudio: boolean,
|
||||||
|
usingElectronDesktopCapture: boolean
|
||||||
|
): boolean {
|
||||||
|
// Chromium display-media capture can use own-audio suppression on modern
|
||||||
|
// builds. The Electron desktop-capturer fallback cannot, so keep the old
|
||||||
|
// Windows mute behavior only for that fallback path.
|
||||||
|
return includeSystemAudio && usingElectronDesktopCapture && this.isWindowsElectron();
|
||||||
}
|
}
|
||||||
|
|
||||||
async startCapture(
|
async startCapture(
|
||||||
|
|||||||
@@ -340,7 +340,10 @@ export class ScreenShareManager {
|
|||||||
includeSystemAudio: this.isScreenActive ? includeSystemAudio : false,
|
includeSystemAudio: this.isScreenActive ? includeSystemAudio : false,
|
||||||
stream: this.isScreenActive ? this.activeScreenStream : null,
|
stream: this.isScreenActive ? this.activeScreenStream : null,
|
||||||
suppressRemotePlayback: this.isScreenActive
|
suppressRemotePlayback: this.isScreenActive
|
||||||
&& this.desktopElectronScreenShareCapture.shouldSuppressRemotePlaybackDuringShare(includeSystemAudio),
|
&& this.desktopElectronScreenShareCapture.shouldSuppressRemotePlaybackDuringShare(
|
||||||
|
includeSystemAudio,
|
||||||
|
captureMethod === 'electron-desktop'
|
||||||
|
),
|
||||||
forceDefaultRemotePlaybackOutput: this.isScreenActive
|
forceDefaultRemotePlaybackOutput: this.isScreenActive
|
||||||
&& includeSystemAudio
|
&& includeSystemAudio
|
||||||
&& captureMethod === 'linux-electron'
|
&& captureMethod === 'linux-electron'
|
||||||
|
|||||||
@@ -261,11 +261,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<!-- Other Online Users -->
|
<!-- Other Online Users -->
|
||||||
@if (onlineUsersFiltered().length > 0) {
|
@if (onlineRoomUsers().length > 0) {
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h4 class="text-xs uppercase tracking-wide text-muted-foreground font-medium mb-2 px-1">Online - {{ onlineUsersFiltered().length }}</h4>
|
<h4 class="text-xs uppercase tracking-wide text-muted-foreground font-medium mb-2 px-1">Online - {{ onlineRoomUsers().length }}</h4>
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
@for (user of onlineUsersFiltered(); track user.id) {
|
@for (user of onlineRoomUsers(); track user.id) {
|
||||||
<div
|
<div
|
||||||
class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-secondary/40 group/user"
|
class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-secondary/40 group/user"
|
||||||
(contextmenu)="openUserContextMenu($event, user)"
|
(contextmenu)="openUserContextMenu($event, user)"
|
||||||
@@ -354,7 +354,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<!-- No other users message -->
|
<!-- No other users message -->
|
||||||
@if (onlineUsersFiltered().length === 0 && offlineRoomMembers().length === 0) {
|
@if (onlineRoomUsers().length === 0 && offlineRoomMembers().length === 0) {
|
||||||
<div class="text-center py-4 text-muted-foreground">
|
<div class="text-center py-4 text-muted-foreground">
|
||||||
<p class="text-sm">No other users in this server</p>
|
<p class="text-sm">No other users in this server</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -104,15 +104,30 @@ export class RoomsSidePanelComponent {
|
|||||||
textChannels = this.store.selectSignal(selectTextChannels);
|
textChannels = this.store.selectSignal(selectTextChannels);
|
||||||
voiceChannels = this.store.selectSignal(selectVoiceChannels);
|
voiceChannels = this.store.selectSignal(selectVoiceChannels);
|
||||||
roomMembers = computed(() => this.currentRoom()?.members ?? []);
|
roomMembers = computed(() => this.currentRoom()?.members ?? []);
|
||||||
offlineRoomMembers = computed(() => {
|
roomMemberIdentifiers = computed(() => {
|
||||||
const current = this.currentUser();
|
const identifiers = new Set<string>();
|
||||||
const onlineIds = new Set(this.onlineUsers().map((user) => user.oderId || user.id));
|
|
||||||
|
|
||||||
if (current) {
|
for (const member of this.roomMembers()) {
|
||||||
onlineIds.add(current.oderId || current.id);
|
this.addIdentifiers(identifiers, member);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.roomMembers().filter((member) => !onlineIds.has(this.roomMemberKey(member)));
|
return identifiers;
|
||||||
|
});
|
||||||
|
onlineRoomUsers = computed(() => {
|
||||||
|
const memberIdentifiers = this.roomMemberIdentifiers();
|
||||||
|
|
||||||
|
return this.onlineUsers().filter((user) => !this.isCurrentUserIdentity(user) && this.matchesIdentifiers(memberIdentifiers, user));
|
||||||
|
});
|
||||||
|
offlineRoomMembers = computed(() => {
|
||||||
|
const onlineIdentifiers = new Set<string>();
|
||||||
|
|
||||||
|
for (const user of this.onlineRoomUsers()) {
|
||||||
|
this.addIdentifiers(onlineIdentifiers, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addIdentifiers(onlineIdentifiers, this.currentUser());
|
||||||
|
|
||||||
|
return this.roomMembers().filter((member) => !this.matchesIdentifiers(onlineIdentifiers, member));
|
||||||
});
|
});
|
||||||
knownUserCount = computed(() => {
|
knownUserCount = computed(() => {
|
||||||
const memberIds = new Set(
|
const memberIds = new Set(
|
||||||
@@ -151,18 +166,36 @@ export class RoomsSidePanelComponent {
|
|||||||
volumeMenuPeerId = signal('');
|
volumeMenuPeerId = signal('');
|
||||||
volumeMenuDisplayName = signal('');
|
volumeMenuDisplayName = signal('');
|
||||||
|
|
||||||
onlineUsersFiltered() {
|
|
||||||
const current = this.currentUser();
|
|
||||||
const currentId = current?.id;
|
|
||||||
const currentOderId = current?.oderId;
|
|
||||||
|
|
||||||
return this.onlineUsers().filter((user) => user.id !== currentId && user.oderId !== currentOderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private roomMemberKey(member: RoomMember): string {
|
private roomMemberKey(member: RoomMember): string {
|
||||||
return member.oderId || member.id;
|
return member.oderId || member.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addIdentifiers(identifiers: Set<string>, entity: { id?: string; oderId?: string } | null | undefined): void {
|
||||||
|
if (!entity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (entity.id) {
|
||||||
|
identifiers.add(entity.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.oderId) {
|
||||||
|
identifiers.add(entity.oderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private matchesIdentifiers(identifiers: Set<string>, entity: { id?: string; oderId?: string }): boolean {
|
||||||
|
return !!((entity.id && identifiers.has(entity.id)) || (entity.oderId && identifiers.has(entity.oderId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private isCurrentUserIdentity(entity: { id?: string; oderId?: string }): boolean {
|
||||||
|
const current = this.currentUser();
|
||||||
|
|
||||||
|
return !!current && (
|
||||||
|
(typeof entity.id === 'string' && entity.id === current.id)
|
||||||
|
|| (typeof entity.oderId === 'string' && entity.oderId === current.oderId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
canManageChannels(): boolean {
|
canManageChannels(): boolean {
|
||||||
const room = this.currentRoom();
|
const room = this.currentRoom();
|
||||||
const user = this.currentUser();
|
const user = this.currentUser();
|
||||||
|
|||||||
@@ -69,18 +69,17 @@ export class FloatingVoiceControlsComponent implements OnInit {
|
|||||||
isConnected = computed(() => this.webrtcService.isVoiceConnected());
|
isConnected = computed(() => this.webrtcService.isVoiceConnected());
|
||||||
isMuted = signal(false);
|
isMuted = signal(false);
|
||||||
isDeafened = signal(false);
|
isDeafened = signal(false);
|
||||||
isScreenSharing = signal(false);
|
isScreenSharing = this.webrtcService.isScreenSharing;
|
||||||
includeSystemAudio = signal(false);
|
includeSystemAudio = signal(false);
|
||||||
screenShareQuality = signal<ScreenShareQuality>('balanced');
|
screenShareQuality = signal<ScreenShareQuality>('balanced');
|
||||||
askScreenShareQuality = signal(true);
|
askScreenShareQuality = signal(true);
|
||||||
showScreenShareQualityDialog = signal(false);
|
showScreenShareQualityDialog = signal(false);
|
||||||
|
|
||||||
/** Sync local mute/deafen/screen-share state from the WebRTC service on init. */
|
/** Sync local mute/deafen state from the WebRTC service on init. */
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
// Sync mute/deafen state from webrtc service
|
// Sync mute/deafen state from webrtc service
|
||||||
this.isMuted.set(this.webrtcService.isMuted());
|
this.isMuted.set(this.webrtcService.isMuted());
|
||||||
this.isDeafened.set(this.webrtcService.isDeafened());
|
this.isDeafened.set(this.webrtcService.isDeafened());
|
||||||
this.isScreenSharing.set(this.webrtcService.isScreenSharing());
|
|
||||||
this.syncScreenShareSettings();
|
this.syncScreenShareSettings();
|
||||||
|
|
||||||
const settings = loadVoiceSettingsFromStorage();
|
const settings = loadVoiceSettingsFromStorage();
|
||||||
@@ -145,7 +144,6 @@ export class FloatingVoiceControlsComponent implements OnInit {
|
|||||||
async toggleScreenShare(): Promise<void> {
|
async toggleScreenShare(): Promise<void> {
|
||||||
if (this.isScreenSharing()) {
|
if (this.isScreenSharing()) {
|
||||||
this.webrtcService.stopScreenShare();
|
this.webrtcService.stopScreenShare();
|
||||||
this.isScreenSharing.set(false);
|
|
||||||
} else {
|
} else {
|
||||||
this.syncScreenShareSettings();
|
this.syncScreenShareSettings();
|
||||||
|
|
||||||
@@ -214,7 +212,6 @@ export class FloatingVoiceControlsComponent implements OnInit {
|
|||||||
this.voiceSessionService.endSession();
|
this.voiceSessionService.endSession();
|
||||||
|
|
||||||
// Reset local state
|
// Reset local state
|
||||||
this.isScreenSharing.set(false);
|
|
||||||
this.isMuted.set(false);
|
this.isMuted.set(false);
|
||||||
this.isDeafened.set(false);
|
this.isDeafened.set(false);
|
||||||
}
|
}
|
||||||
@@ -288,8 +285,6 @@ export class FloatingVoiceControlsComponent implements OnInit {
|
|||||||
includeSystemAudio: this.includeSystemAudio(),
|
includeSystemAudio: this.includeSystemAudio(),
|
||||||
quality
|
quality
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isScreenSharing.set(true);
|
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
// Screen share request was denied or failed
|
// Screen share request was denied or failed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export class VoiceControlsComponent implements OnInit, OnDestroy {
|
|||||||
connectionErrorMessage = computed(() => this.webrtcService.connectionErrorMessage());
|
connectionErrorMessage = computed(() => this.webrtcService.connectionErrorMessage());
|
||||||
isMuted = signal(false);
|
isMuted = signal(false);
|
||||||
isDeafened = signal(false);
|
isDeafened = signal(false);
|
||||||
isScreenSharing = signal(false);
|
isScreenSharing = this.webrtcService.isScreenSharing;
|
||||||
showSettings = signal(false);
|
showSettings = signal(false);
|
||||||
|
|
||||||
inputDevices = signal<AudioDevice[]>([]);
|
inputDevices = signal<AudioDevice[]>([]);
|
||||||
@@ -286,7 +286,6 @@ export class VoiceControlsComponent implements OnInit, OnDestroy {
|
|||||||
// End voice session for floating controls
|
// End voice session for floating controls
|
||||||
this.voiceSessionService.endSession();
|
this.voiceSessionService.endSession();
|
||||||
|
|
||||||
this.isScreenSharing.set(false);
|
|
||||||
this.isMuted.set(false);
|
this.isMuted.set(false);
|
||||||
this.isDeafened.set(false);
|
this.isDeafened.set(false);
|
||||||
}
|
}
|
||||||
@@ -368,7 +367,6 @@ export class VoiceControlsComponent implements OnInit, OnDestroy {
|
|||||||
async toggleScreenShare(): Promise<void> {
|
async toggleScreenShare(): Promise<void> {
|
||||||
if (this.isScreenSharing()) {
|
if (this.isScreenSharing()) {
|
||||||
this.webrtcService.stopScreenShare();
|
this.webrtcService.stopScreenShare();
|
||||||
this.isScreenSharing.set(false);
|
|
||||||
} else {
|
} else {
|
||||||
this.syncScreenShareSettings();
|
this.syncScreenShareSettings();
|
||||||
|
|
||||||
@@ -539,8 +537,6 @@ export class VoiceControlsComponent implements OnInit, OnDestroy {
|
|||||||
includeSystemAudio: this.includeSystemAudio(),
|
includeSystemAudio: this.includeSystemAudio(),
|
||||||
quality
|
quality
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isScreenSharing.set(true);
|
|
||||||
} catch (_error) {}
|
} catch (_error) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ function buildSignalingUser(
|
|||||||
data: { oderId: string; displayName?: string },
|
data: { oderId: string; displayName?: string },
|
||||||
extras: Record<string, unknown> = {}
|
extras: Record<string, unknown> = {}
|
||||||
) {
|
) {
|
||||||
const displayName = data.displayName || 'User';
|
const displayName = data.displayName?.trim() || 'User';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
oderId: data.oderId,
|
oderId: data.oderId,
|
||||||
@@ -98,6 +98,16 @@ function isWrongServer(
|
|||||||
return !!(msgServerId && viewedServerId && msgServerId !== viewedServerId);
|
return !!(msgServerId && viewedServerId && msgServerId !== viewedServerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveUserDisplayName(user: Pick<User, 'displayName' | 'username'> | null | undefined): string {
|
||||||
|
const displayName = user?.displayName?.trim();
|
||||||
|
|
||||||
|
if (displayName) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user?.username?.trim() || 'User';
|
||||||
|
}
|
||||||
|
|
||||||
interface RoomPresenceSignalingMessage {
|
interface RoomPresenceSignalingMessage {
|
||||||
type: string;
|
type: string;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
@@ -1379,7 +1389,7 @@ export class RoomsEffects {
|
|||||||
sourceUrl: room.sourceUrl
|
sourceUrl: room.sourceUrl
|
||||||
});
|
});
|
||||||
const oderId = resolvedOderId || user?.oderId || this.webrtc.peerId();
|
const oderId = resolvedOderId || user?.oderId || this.webrtc.peerId();
|
||||||
const displayName = user?.displayName || 'Anonymous';
|
const displayName = resolveUserDisplayName(user);
|
||||||
const sameSignalRooms = this.getRoomsForSignalingUrl(this.includeRoom(savedRooms, room), wsUrl);
|
const sameSignalRooms = this.getRoomsForSignalingUrl(this.includeRoom(savedRooms, room), wsUrl);
|
||||||
const backgroundRooms = sameSignalRooms.filter((candidate) => candidate.id !== room.id);
|
const backgroundRooms = sameSignalRooms.filter((candidate) => candidate.id !== room.id);
|
||||||
const joinCurrentEndpointRooms = () => {
|
const joinCurrentEndpointRooms = () => {
|
||||||
|
|||||||
@@ -438,6 +438,25 @@ export class UsersEffects {
|
|||||||
{ dispatch: false }
|
{ dispatch: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** Keep signaling identity aligned with the current profile to avoid stale fallback names. */
|
||||||
|
syncSignalingIdentity$ = createEffect(
|
||||||
|
() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(
|
||||||
|
UsersActions.setCurrentUser,
|
||||||
|
UsersActions.loadCurrentUserSuccess
|
||||||
|
),
|
||||||
|
withLatestFrom(this.store.select(selectCurrentUser)),
|
||||||
|
tap(([, user]) => {
|
||||||
|
if (!user)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.webrtc.identify(user.oderId || user.id, this.resolveDisplayName(user));
|
||||||
|
})
|
||||||
|
),
|
||||||
|
{ dispatch: false }
|
||||||
|
);
|
||||||
|
|
||||||
private resolveRoom(roomId: string | undefined, currentRoom: Room | null, savedRooms: Room[]): Room | null {
|
private resolveRoom(roomId: string | undefined, currentRoom: Room | null, savedRooms: Room[]): Room | null {
|
||||||
if (!roomId)
|
if (!roomId)
|
||||||
return currentRoom;
|
return currentRoom;
|
||||||
@@ -448,6 +467,16 @@ export class UsersEffects {
|
|||||||
return savedRooms.find((room) => room.id === roomId) ?? null;
|
return savedRooms.find((room) => room.id === roomId) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private resolveDisplayName(user: Pick<User, 'displayName' | 'username'>): string {
|
||||||
|
const displayName = user.displayName?.trim();
|
||||||
|
|
||||||
|
if (displayName) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.username?.trim() || 'User';
|
||||||
|
}
|
||||||
|
|
||||||
private toSourceSelector(room: Room): { sourceId?: string; sourceUrl?: string } {
|
private toSourceSelector(room: Room): { sourceId?: string; sourceUrl?: string } {
|
||||||
return {
|
return {
|
||||||
sourceId: room.sourceId,
|
sourceId: room.sourceId,
|
||||||
|
|||||||
Reference in New Issue
Block a user