Fix bugs and clean noise reduction
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import {
|
||||
Component,
|
||||
inject,
|
||||
computed,
|
||||
signal
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
@@ -16,7 +17,8 @@ import {
|
||||
lucideMonitor,
|
||||
lucideHash,
|
||||
lucideUsers,
|
||||
lucidePlus
|
||||
lucidePlus,
|
||||
lucideVolumeX
|
||||
} from '@ng-icons/lucide';
|
||||
import {
|
||||
selectOnlineUsers,
|
||||
@@ -35,15 +37,18 @@ import { MessagesActions } from '../../../store/messages/messages.actions';
|
||||
import { WebRTCService } from '../../../core/services/webrtc.service';
|
||||
import { VoiceSessionService } from '../../../core/services/voice-session.service';
|
||||
import { VoiceActivityService } from '../../../core/services/voice-activity.service';
|
||||
import { VoicePlaybackService } from '../../voice/voice-controls/services/voice-playback.service';
|
||||
import { VoiceControlsComponent } from '../../voice/voice-controls/voice-controls.component';
|
||||
import {
|
||||
ContextMenuComponent,
|
||||
UserAvatarComponent,
|
||||
ConfirmDialogComponent
|
||||
ConfirmDialogComponent,
|
||||
UserVolumeMenuComponent
|
||||
} from '../../../shared';
|
||||
import {
|
||||
Channel,
|
||||
ChatEvent,
|
||||
RoomMember,
|
||||
Room,
|
||||
User
|
||||
} from '../../../core/models';
|
||||
@@ -54,7 +59,16 @@ type TabView = 'channels' | 'users';
|
||||
@Component({
|
||||
selector: 'app-rooms-side-panel',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, NgIcon, VoiceControlsComponent, ContextMenuComponent, UserAvatarComponent, ConfirmDialogComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
NgIcon,
|
||||
VoiceControlsComponent,
|
||||
ContextMenuComponent,
|
||||
UserVolumeMenuComponent,
|
||||
UserAvatarComponent,
|
||||
ConfirmDialogComponent
|
||||
],
|
||||
viewProviders: [
|
||||
provideIcons({
|
||||
lucideMessageSquare,
|
||||
@@ -64,7 +78,8 @@ type TabView = 'channels' | 'users';
|
||||
lucideMonitor,
|
||||
lucideHash,
|
||||
lucideUsers,
|
||||
lucidePlus
|
||||
lucidePlus,
|
||||
lucideVolumeX
|
||||
})
|
||||
],
|
||||
templateUrl: './rooms-side-panel.component.html'
|
||||
@@ -76,6 +91,7 @@ export class RoomsSidePanelComponent {
|
||||
private store = inject(Store);
|
||||
private webrtc = inject(WebRTCService);
|
||||
private voiceSessionService = inject(VoiceSessionService);
|
||||
private voicePlayback = inject(VoicePlaybackService);
|
||||
voiceActivity = inject(VoiceActivityService);
|
||||
|
||||
activeTab = signal<TabView>('channels');
|
||||
@@ -87,6 +103,31 @@ export class RoomsSidePanelComponent {
|
||||
activeChannelId = this.store.selectSignal(selectActiveChannelId);
|
||||
textChannels = this.store.selectSignal(selectTextChannels);
|
||||
voiceChannels = this.store.selectSignal(selectVoiceChannels);
|
||||
roomMembers = computed(() => this.currentRoom()?.members ?? []);
|
||||
offlineRoomMembers = computed(() => {
|
||||
const current = this.currentUser();
|
||||
const onlineIds = new Set(this.onlineUsers().map((user) => user.oderId || user.id));
|
||||
|
||||
if (current) {
|
||||
onlineIds.add(current.oderId || current.id);
|
||||
}
|
||||
|
||||
return this.roomMembers().filter((member) => !onlineIds.has(this.roomMemberKey(member)));
|
||||
});
|
||||
knownUserCount = computed(() => {
|
||||
const memberIds = new Set(
|
||||
this.roomMembers()
|
||||
.map((member) => this.roomMemberKey(member))
|
||||
.filter(Boolean)
|
||||
);
|
||||
const current = this.currentUser();
|
||||
|
||||
if (current) {
|
||||
memberIds.add(current.oderId || current.id);
|
||||
}
|
||||
|
||||
return memberIds.size;
|
||||
});
|
||||
|
||||
// Channel context menu state
|
||||
showChannelMenu = signal(false);
|
||||
@@ -108,6 +149,13 @@ export class RoomsSidePanelComponent {
|
||||
userMenuY = signal(0);
|
||||
contextMenuUser = signal<User | null>(null);
|
||||
|
||||
// Per-user volume context menu state
|
||||
showVolumeMenu = signal(false);
|
||||
volumeMenuX = signal(0);
|
||||
volumeMenuY = signal(0);
|
||||
volumeMenuPeerId = signal('');
|
||||
volumeMenuDisplayName = signal('');
|
||||
|
||||
/** Return online users excluding the current user. */
|
||||
// Filter out current user from online users list
|
||||
onlineUsersFiltered() {
|
||||
@@ -118,6 +166,10 @@ export class RoomsSidePanelComponent {
|
||||
return this.onlineUsers().filter((user) => user.id !== currentId && user.oderId !== currentOderId);
|
||||
}
|
||||
|
||||
private roomMemberKey(member: RoomMember): string {
|
||||
return member.oderId || member.id;
|
||||
}
|
||||
|
||||
/** Check whether the current user has permission to manage channels. */
|
||||
canManageChannels(): boolean {
|
||||
const room = this.currentRoom();
|
||||
@@ -287,9 +339,27 @@ export class RoomsSidePanelComponent {
|
||||
this.showUserMenu.set(false);
|
||||
}
|
||||
|
||||
/** Open the per-user volume context menu for a voice channel participant. */
|
||||
openVoiceUserVolumeMenu(evt: MouseEvent, user: User) {
|
||||
evt.preventDefault();
|
||||
|
||||
// Don't show volume menu for the local user
|
||||
const me = this.currentUser();
|
||||
|
||||
if (user.id === me?.id || user.oderId === me?.oderId)
|
||||
return;
|
||||
|
||||
this.volumeMenuPeerId.set(user.oderId || user.id);
|
||||
this.volumeMenuDisplayName.set(user.displayName);
|
||||
this.volumeMenuX.set(evt.clientX);
|
||||
this.volumeMenuY.set(evt.clientY);
|
||||
this.showVolumeMenu.set(true);
|
||||
}
|
||||
|
||||
/** Change a user's role and broadcast the update to connected peers. */
|
||||
changeUserRole(role: 'admin' | 'moderator' | 'member') {
|
||||
const user = this.contextMenuUser();
|
||||
const roomId = this.currentRoom()?.id;
|
||||
|
||||
this.closeUserMenu();
|
||||
|
||||
@@ -298,6 +368,7 @@ export class RoomsSidePanelComponent {
|
||||
// Broadcast role change to peers
|
||||
this.webrtc.broadcastMessage({
|
||||
type: 'role-change',
|
||||
roomId,
|
||||
targetUserId: user.id,
|
||||
role
|
||||
});
|
||||
@@ -377,11 +448,29 @@ export class RoomsSidePanelComponent {
|
||||
|
||||
private onVoiceJoinSucceeded(roomId: string, room: Room, current: User | null): void {
|
||||
this.updateVoiceStateStore(roomId, room, current);
|
||||
this.trackCurrentUserMic();
|
||||
this.startVoiceHeartbeat(roomId, room);
|
||||
this.broadcastVoiceConnected(roomId, room, current);
|
||||
this.startVoiceSession(roomId, room);
|
||||
}
|
||||
|
||||
private trackCurrentUserMic(): void {
|
||||
const userId = this.currentUser()?.oderId || this.currentUser()?.id;
|
||||
const micStream = this.webrtc.getRawMicStream();
|
||||
|
||||
if (userId && micStream) {
|
||||
this.voiceActivity.trackLocalMic(userId, micStream);
|
||||
}
|
||||
}
|
||||
|
||||
private untrackCurrentUserMic(): void {
|
||||
const userId = this.currentUser()?.oderId || this.currentUser()?.id;
|
||||
|
||||
if (userId) {
|
||||
this.voiceActivity.untrackLocalMic(userId);
|
||||
}
|
||||
}
|
||||
|
||||
private updateVoiceStateStore(roomId: string, room: Room, current: User | null): void {
|
||||
if (!current?.id)
|
||||
return;
|
||||
@@ -445,6 +534,8 @@ export class RoomsSidePanelComponent {
|
||||
// Stop voice heartbeat
|
||||
this.webrtc.stopVoiceHeartbeat();
|
||||
|
||||
this.untrackCurrentUserMic();
|
||||
|
||||
// Disable voice locally
|
||||
this.webrtc.disableVoice();
|
||||
|
||||
@@ -484,11 +575,7 @@ export class RoomsSidePanelComponent {
|
||||
|
||||
/** Count the number of users connected to a voice channel in the current room. */
|
||||
voiceOccupancy(roomId: string): number {
|
||||
const users = this.onlineUsers();
|
||||
const room = this.currentRoom();
|
||||
|
||||
return users.filter((user) => !!user.voiceState?.isConnected && user.voiceState?.roomId === roomId && user.voiceState?.serverId === room?.id)
|
||||
.length;
|
||||
return this.voiceUsersInRoom(roomId).length;
|
||||
}
|
||||
|
||||
/** Dispatch a viewer:focus event to display a remote user's screen share. */
|
||||
@@ -505,6 +592,13 @@ export class RoomsSidePanelComponent {
|
||||
window.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
/** Check whether the local user has muted a specific voice user. */
|
||||
isUserLocallyMuted(user: User): boolean {
|
||||
const peerId = user.oderId || user.id;
|
||||
|
||||
return this.voicePlayback.isUserMuted(peerId);
|
||||
}
|
||||
|
||||
/** Check whether a user is currently sharing their screen. */
|
||||
isUserSharing(userId: string): boolean {
|
||||
const me = this.currentUser();
|
||||
@@ -524,13 +618,33 @@ export class RoomsSidePanelComponent {
|
||||
return !!stream && stream.getVideoTracks().length > 0;
|
||||
}
|
||||
|
||||
/** Return all users currently connected to a specific voice channel. */
|
||||
/** Return all users currently connected to a specific voice channel, including the local user. */
|
||||
voiceUsersInRoom(roomId: string) {
|
||||
const room = this.currentRoom();
|
||||
|
||||
return this.onlineUsers().filter(
|
||||
const me = this.currentUser();
|
||||
const remoteUsers = this.onlineUsers().filter(
|
||||
(user) => !!user.voiceState?.isConnected && user.voiceState?.roomId === roomId && user.voiceState?.serverId === room?.id
|
||||
);
|
||||
|
||||
// Include the local user at the top if they are in this voice channel
|
||||
if (
|
||||
me?.voiceState?.isConnected &&
|
||||
me.voiceState?.roomId === roomId &&
|
||||
me.voiceState?.serverId === room?.id
|
||||
) {
|
||||
// Avoid duplicates if the current user is already in onlineUsers
|
||||
const meId = me.id;
|
||||
const meOderId = me.oderId;
|
||||
const alreadyIncluded = remoteUsers.some(
|
||||
(user) => user.id === meId || user.oderId === meOderId
|
||||
);
|
||||
|
||||
if (!alreadyIncluded) {
|
||||
return [me, ...remoteUsers];
|
||||
}
|
||||
}
|
||||
|
||||
return remoteUsers;
|
||||
}
|
||||
|
||||
/** Check whether the current user is connected to the specified voice channel. */
|
||||
|
||||
Reference in New Issue
Block a user