video call
This commit is contained in:
@@ -4,14 +4,14 @@ import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
|
||||
import { PeerVideoModalComponent } from './peer-video-modal.component';
|
||||
import { PeerCallModalComponent } from './peer-call-modal.component';
|
||||
import { ChatSessionService } from './chat-session.service';
|
||||
import { JsonFileViewerComponent } from './json-file-viewer.component';
|
||||
import type { ChatEntry, ConnectionState, PeerSummary } from './models';
|
||||
import type { CallMode, ChatEntry, ConnectionState, PeerSummary } from './models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-chat-page',
|
||||
imports: [CommonModule, FormsModule, RouterLink, JsonFileViewerComponent, PeerVideoModalComponent],
|
||||
imports: [CommonModule, FormsModule, RouterLink, JsonFileViewerComponent, PeerCallModalComponent],
|
||||
templateUrl: './chat-page.component.html',
|
||||
styleUrl: './chat-page.component.scss',
|
||||
})
|
||||
@@ -46,6 +46,7 @@ export class ChatPageComponent implements OnDestroy {
|
||||
|
||||
messageText = '';
|
||||
readonly forwardingEntryId = signal<string | null>(null);
|
||||
readonly callChoicePeerId = signal<string | null>(null);
|
||||
readonly emojiPickerOpen = signal(false);
|
||||
readonly isRecordingVoice = signal(false);
|
||||
readonly isDictating = signal(false);
|
||||
@@ -63,8 +64,19 @@ export class ChatPageComponent implements OnDestroy {
|
||||
readonly peerId = computed(() => this.routeParamMap().get('peerId') ?? '');
|
||||
readonly peer = computed(() => this.session.peers().find((item) => item.id === this.peerId()) ?? null);
|
||||
readonly currentUser = computed(() => this.session.currentUser());
|
||||
readonly incomingVoiceCallPeer = computed(() => {
|
||||
const peerId = this.session.incomingVoiceCallPeerId();
|
||||
readonly callModalPeerId = computed(() =>
|
||||
this.session.activeVoiceCallPeerId()
|
||||
?? this.session.incomingVoiceCallPeerId()
|
||||
?? this.session.outgoingVoiceCallPeerId()
|
||||
?? null,
|
||||
);
|
||||
readonly callModalPeer = computed(() => {
|
||||
const peerId = this.callModalPeerId();
|
||||
|
||||
return peerId ? this.session.peers().find((peer) => peer.id === peerId) ?? null : null;
|
||||
});
|
||||
readonly callChoicePeer = computed(() => {
|
||||
const peerId = this.callChoicePeerId();
|
||||
|
||||
return peerId ? this.session.peers().find((peer) => peer.id === peerId) ?? null : null;
|
||||
});
|
||||
@@ -73,13 +85,50 @@ export class ChatPageComponent implements OnDestroy {
|
||||
.messages()
|
||||
.filter((entry) => entry.peerId === this.peerId()),
|
||||
);
|
||||
readonly remoteVideoStream = computed(() => this.session.remoteVideoStreamForPeer(this.peerId()));
|
||||
readonly remoteCallAudioStream = computed(() =>
|
||||
this.session.remoteAudioStreamForPeer(this.session.activeVoiceCallPeerId() ?? ''),
|
||||
);
|
||||
readonly remoteVideoModalVisible = computed(
|
||||
() => this.session.remoteVideoModalPeerId() === this.peerId() && !!this.remoteVideoStream(),
|
||||
this.session.remoteAudioStreamForPeer(this.callModalPeerId() ?? ''),
|
||||
);
|
||||
readonly callModalMode = computed<CallMode>(() => this.session.callModeForPeer(this.callModalPeerId() ?? '') ?? 'video');
|
||||
readonly localCallStream = computed(() => this.session.localCallStreamForPeer(this.callModalPeerId() ?? ''));
|
||||
readonly remoteCallVideoStream = computed(() => this.session.remoteVideoStreamForPeer(this.callModalPeerId() ?? ''));
|
||||
readonly callModalVisible = computed(() => !!this.callModalPeer());
|
||||
readonly callModalState = computed<'incoming' | 'outgoing' | 'active'>(() => {
|
||||
const peerId = this.callModalPeerId();
|
||||
|
||||
if (!peerId) {
|
||||
return 'active';
|
||||
}
|
||||
|
||||
if (this.session.incomingVoiceCallPeerId() === peerId) {
|
||||
return 'incoming';
|
||||
}
|
||||
|
||||
if (this.session.outgoingVoiceCallPeerId() === peerId) {
|
||||
return 'outgoing';
|
||||
}
|
||||
|
||||
return 'active';
|
||||
});
|
||||
readonly callModalStatusText = computed(() => {
|
||||
const peer = this.callModalPeer();
|
||||
|
||||
if (!peer) {
|
||||
return '';
|
||||
}
|
||||
|
||||
switch (this.callModalState()) {
|
||||
case 'incoming':
|
||||
return `${peer.displayName} is calling you${this.callModalMode() === 'audio' ? ' with audio only.' : '.'}`;
|
||||
case 'outgoing':
|
||||
return this.callModalMode() === 'audio'
|
||||
? 'Calling… your microphone is ready.'
|
||||
: 'Calling… your camera and microphone are ready.';
|
||||
default:
|
||||
return this.callModalMode() === 'audio'
|
||||
? 'Connected with live audio.'
|
||||
: 'Connected with live video and audio.';
|
||||
}
|
||||
});
|
||||
readonly selectedPeerVoiceCallState = computed<'idle' | 'incoming' | 'outgoing' | 'active'>(() => {
|
||||
const peerId = this.peerId();
|
||||
|
||||
@@ -265,6 +314,29 @@ export class ChatPageComponent implements OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
openCallChoice(peerId: string): void {
|
||||
if (!peerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.callChoicePeerId.set(peerId);
|
||||
}
|
||||
|
||||
closeCallChoice(): void {
|
||||
this.callChoicePeerId.set(null);
|
||||
}
|
||||
|
||||
async startSelectedCall(mode: CallMode): Promise<void> {
|
||||
const peerId = this.callChoicePeerId() ?? this.peerId();
|
||||
|
||||
if (!peerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.callChoicePeerId.set(null);
|
||||
await this.session.startVoiceCall(peerId, mode);
|
||||
}
|
||||
|
||||
async sendFile(peerId: string, input: HTMLInputElement): Promise<void> {
|
||||
const file = input.files?.item(0);
|
||||
|
||||
@@ -474,19 +546,6 @@ export class ChatPageComponent implements OnDestroy {
|
||||
this.forwardingEntryId.set(null);
|
||||
}
|
||||
|
||||
async toggleCameraStream(peerId: string): Promise<void> {
|
||||
if (this.session.isStreamingCameraToPeer(peerId)) {
|
||||
await this.session.stopCameraStream(peerId);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.session.startCameraStream(peerId);
|
||||
}
|
||||
|
||||
async startVoiceCall(peerId: string): Promise<void> {
|
||||
await this.session.startVoiceCall(peerId);
|
||||
}
|
||||
|
||||
async endVoiceCall(peerId: string): Promise<void> {
|
||||
await this.session.endVoiceCall(peerId);
|
||||
}
|
||||
@@ -496,10 +555,6 @@ export class ChatPageComponent implements OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
if (peerId !== this.peerId()) {
|
||||
await this.router.navigate(['/chat', peerId]);
|
||||
}
|
||||
|
||||
await this.session.acceptVoiceCall(peerId);
|
||||
}
|
||||
|
||||
@@ -561,22 +616,6 @@ export class ChatPageComponent implements OnDestroy {
|
||||
return this.indicatorTone(this.webRtcState()) === 'offline';
|
||||
}
|
||||
|
||||
isStreamingCameraToSelectedPeer(): boolean {
|
||||
const peerId = this.peerId();
|
||||
|
||||
return !!peerId && this.session.isStreamingCameraToPeer(peerId);
|
||||
}
|
||||
|
||||
closeRemoteVideoModal(): void {
|
||||
const peerId = this.peerId();
|
||||
|
||||
if (!peerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.session.dismissRemoteVideoModal(peerId);
|
||||
}
|
||||
|
||||
async switchPeer(peerId: string): Promise<void> {
|
||||
if (!peerId || peerId === this.peerId()) {
|
||||
return;
|
||||
@@ -585,6 +624,7 @@ export class ChatPageComponent implements OnDestroy {
|
||||
await this.stopDictation(true);
|
||||
this.stopVoiceRecording(true);
|
||||
this.forwardingEntryId.set(null);
|
||||
this.callChoicePeerId.set(null);
|
||||
this.emojiPickerOpen.set(false);
|
||||
this.session.selectPeer(peerId);
|
||||
await this.router.navigate(['/chat', peerId]);
|
||||
|
||||
Reference in New Issue
Block a user