feat: Add webcam basic support
This commit is contained in:
@@ -201,11 +201,15 @@
|
||||
[title]="getPeerLatency(u) !== null ? getPeerLatency(u) + ' ms' : 'Measuring...'"
|
||||
></span>
|
||||
}
|
||||
@if (u.screenShareState?.isSharing || isUserSharing(u.id)) {
|
||||
@if (isUserStreaming(u.oderId || u.id)) {
|
||||
<button
|
||||
(click)="viewStream(u.oderId || u.id); $event.stopPropagation()"
|
||||
class="px-1.5 py-0.5 text-[10px] font-bold bg-red-500 text-white rounded animate-pulse hover:bg-red-600 transition-colors"
|
||||
class="inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-bold bg-red-500 text-white rounded animate-pulse hover:bg-red-600 transition-colors"
|
||||
>
|
||||
<ng-icon
|
||||
[name]="getUserLiveIconName(u.oderId || u.id)"
|
||||
class="w-2.5 h-2.5"
|
||||
/>
|
||||
LIVE
|
||||
</button>
|
||||
}
|
||||
@@ -261,13 +265,13 @@
|
||||
In voice
|
||||
</p>
|
||||
}
|
||||
@if (currentUser()?.screenShareState?.isSharing || (currentUser()?.id && isUserSharing(currentUser()!.id))) {
|
||||
@if (currentUser() && isUserStreaming(currentUser()!.oderId || currentUser()!.id)) {
|
||||
<button
|
||||
class="text-[10px] bg-red-500 text-white px-1.5 py-0.5 rounded-sm font-medium flex items-center gap-1 animate-pulse hover:bg-red-600 transition-colors"
|
||||
(click)="viewStream(currentUser()!.oderId || currentUser()!.id); $event.stopPropagation()"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideMonitor"
|
||||
[name]="getUserLiveIconName(currentUser()!.oderId || currentUser()!.id)"
|
||||
class="w-2.5 h-2.5"
|
||||
/>
|
||||
LIVE
|
||||
@@ -318,13 +322,13 @@
|
||||
In voice
|
||||
</p>
|
||||
}
|
||||
@if (user.screenShareState?.isSharing || isUserSharing(user.id)) {
|
||||
@if (isUserStreaming(user.oderId || user.id)) {
|
||||
<button
|
||||
(click)="viewStream(user.oderId || user.id); $event.stopPropagation()"
|
||||
class="text-[10px] bg-red-500 text-white px-1.5 py-0.5 rounded-sm font-medium hover:bg-red-600 transition-colors flex items-center gap-1 animate-pulse"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideMonitor"
|
||||
[name]="getUserLiveIconName(user.oderId || user.id)"
|
||||
class="w-2.5 h-2.5"
|
||||
/>
|
||||
LIVE
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
lucideMicOff,
|
||||
lucideChevronLeft,
|
||||
lucideMonitor,
|
||||
lucideVideo,
|
||||
lucideHash,
|
||||
lucideUsers,
|
||||
lucidePlus,
|
||||
@@ -40,10 +41,7 @@ import { VoiceActivityService, VoiceConnectionFacade } from '../../../domains/vo
|
||||
import { VoiceSessionFacade, VoiceWorkspaceService } from '../../../domains/voice-session';
|
||||
import { VoicePlaybackService } from '../../../domains/voice-connection/application/voice-playback.service';
|
||||
import { VoiceControlsComponent } from '../../../domains/voice-session/feature/voice-controls/voice-controls.component';
|
||||
import {
|
||||
isChannelNameTaken,
|
||||
normalizeChannelName
|
||||
} from '../../../store/rooms/room-channels.rules';
|
||||
import { isChannelNameTaken, normalizeChannelName } from '../../../store/rooms/room-channels.rules';
|
||||
import {
|
||||
ContextMenuComponent,
|
||||
UserAvatarComponent,
|
||||
@@ -81,6 +79,7 @@ type TabView = 'channels' | 'users';
|
||||
lucideMicOff,
|
||||
lucideChevronLeft,
|
||||
lucideMonitor,
|
||||
lucideVideo,
|
||||
lucideHash,
|
||||
lucideUsers,
|
||||
lucidePlus,
|
||||
@@ -274,6 +273,7 @@ export class RoomsSidePanelComponent {
|
||||
input.focus();
|
||||
input.select();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -334,7 +334,6 @@ export class RoomsSidePanelComponent {
|
||||
|
||||
confirmCreateChannel() {
|
||||
const name = normalizeChannelName(this.newChannelName);
|
||||
|
||||
const validationError = this.getChannelNameError(name);
|
||||
|
||||
if (validationError) {
|
||||
@@ -597,6 +596,13 @@ export class RoomsSidePanelComponent {
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.store.dispatch(
|
||||
UsersActions.updateCameraState({
|
||||
userId: current.id,
|
||||
cameraState: { isEnabled: false }
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
this.voiceConnection.broadcastMessage({
|
||||
@@ -620,11 +626,15 @@ export class RoomsSidePanelComponent {
|
||||
}
|
||||
|
||||
viewShare(userId: string) {
|
||||
this.voiceWorkspace.focusStream(userId, { connectRemoteShares: true });
|
||||
this.voiceWorkspace.focusStream(`screen:${userId}`, { connectRemoteShares: true });
|
||||
}
|
||||
|
||||
viewStream(userId: string) {
|
||||
this.voiceWorkspace.focusStream(userId, { connectRemoteShares: true });
|
||||
const focusTarget = this.isUserSharing(userId)
|
||||
? `screen:${userId}`
|
||||
: `camera:${userId}`;
|
||||
|
||||
this.voiceWorkspace.focusStream(focusTarget, { connectRemoteShares: true });
|
||||
}
|
||||
|
||||
canMoveVoiceUsers(): boolean {
|
||||
@@ -740,31 +750,65 @@ export class RoomsSidePanelComponent {
|
||||
return this.voicePlayback.isUserMuted(peerId);
|
||||
}
|
||||
|
||||
isUserSharing(userId: string): boolean {
|
||||
const me = this.currentUser();
|
||||
isUserOnCamera(userId: string): boolean {
|
||||
const user = this.findKnownUser(userId);
|
||||
|
||||
if (me?.id === userId) {
|
||||
if (!this.isUserInCurrentVoiceRoom(userId, user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const current = this.currentUser();
|
||||
|
||||
if (current && (current.id === userId || current.oderId === userId)) {
|
||||
return this.voiceConnection.isCameraEnabled();
|
||||
}
|
||||
|
||||
if (user?.cameraState?.isEnabled === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (user?.cameraState?.isEnabled === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.getPeerKeysForUser(user, userId)
|
||||
.some((peerKey) => this.hasActiveVideoStream(this.voiceConnection.getRemoteCameraStream(peerKey)));
|
||||
}
|
||||
|
||||
isUserSharing(userId: string): boolean {
|
||||
const user = this.findKnownUser(userId);
|
||||
|
||||
if (!this.isUserInCurrentVoiceRoom(userId, user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const current = this.currentUser();
|
||||
|
||||
if (current && (current.id === userId || current.oderId === userId)) {
|
||||
return this.screenShare.isScreenSharing();
|
||||
}
|
||||
|
||||
const user = this.onlineUsers().find((onlineUser) => onlineUser.id === userId || onlineUser.oderId === userId);
|
||||
if (user?.screenShareState?.isSharing === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (user?.screenShareState?.isSharing === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const peerKeys = [
|
||||
user?.oderId,
|
||||
user?.id,
|
||||
userId
|
||||
].filter(
|
||||
(candidate): candidate is string => !!candidate
|
||||
);
|
||||
const stream = peerKeys
|
||||
const stream = this.getPeerKeysForUser(user, userId)
|
||||
.map((peerKey) => this.screenShare.getRemoteScreenShareStream(peerKey))
|
||||
.find((candidate) => !!candidate && candidate.getVideoTracks().length > 0) || null;
|
||||
.find((candidate) => this.hasActiveVideoStream(candidate)) || null;
|
||||
|
||||
return !!stream && stream.getVideoTracks().length > 0;
|
||||
return this.hasActiveVideoStream(stream);
|
||||
}
|
||||
|
||||
isUserStreaming(userId: string): boolean {
|
||||
return this.isUserSharing(userId) || this.isUserOnCamera(userId);
|
||||
}
|
||||
|
||||
getUserLiveIconName(userId: string): string {
|
||||
return this.isUserSharing(userId) ? 'lucideMonitor' : 'lucideVideo';
|
||||
}
|
||||
|
||||
voiceUsersInRoom(roomId: string) {
|
||||
@@ -829,4 +873,45 @@ export class RoomsSidePanelComponent {
|
||||
|
||||
return 'bg-red-500';
|
||||
}
|
||||
|
||||
private findKnownUser(userId: string): User | null {
|
||||
const current = this.currentUser();
|
||||
|
||||
if (current && (current.id === userId || current.oderId === userId)) {
|
||||
return current;
|
||||
}
|
||||
|
||||
return this.onlineUsers().find((onlineUser) => onlineUser.id === userId || onlineUser.oderId === userId) ?? null;
|
||||
}
|
||||
|
||||
private isUserInCurrentVoiceRoom(userId: string, user: User | null): boolean {
|
||||
const currentVoiceState = this.currentUser()?.voiceState;
|
||||
const current = this.currentUser();
|
||||
|
||||
if (!currentVoiceState?.isConnected || !currentVoiceState.roomId || !currentVoiceState.serverId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current && (current.id === userId || current.oderId === userId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !!user?.voiceState?.isConnected
|
||||
&& user.voiceState.roomId === currentVoiceState.roomId
|
||||
&& user.voiceState.serverId === currentVoiceState.serverId;
|
||||
}
|
||||
|
||||
private getPeerKeysForUser(user: User | null, userId: string): string[] {
|
||||
return [
|
||||
user?.oderId,
|
||||
user?.id,
|
||||
userId
|
||||
].filter(
|
||||
(candidate): candidate is string => !!candidate
|
||||
);
|
||||
}
|
||||
|
||||
private hasActiveVideoStream(stream: MediaStream | null): boolean {
|
||||
return !!stream && stream.getVideoTracks().some((track) => track.readyState === 'live');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user