Files
sgse-app/apps/web/src/lib/stores/callStore.ts
deyvisonwanderley 2792424454 feat: implement audio/video call functionality in chat
- Added a new schema for managing audio/video calls, including fields for call type, room name, and participant management.
- Enhanced ChatWindow component to support initiating audio and video calls with dynamic loading of the CallWindow component.
- Updated package dependencies to include 'lib-jitsi-meet' for call handling.
- Refactored existing code to accommodate new call features and improve user experience.
2025-11-21 13:17:44 -03:00

323 lines
6.1 KiB
TypeScript

/**
* Store para gerenciar estado das chamadas de áudio/vídeo
*/
import { writable, derived, get } from 'svelte/store';
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
export interface ParticipanteChamada {
usuarioId: Id<'usuarios'>;
nome: string;
avatar?: string;
audioHabilitado: boolean;
videoHabilitado: boolean;
forcadoPeloAnfitriao?: boolean;
participantId?: string; // ID do participante no Jitsi
}
export interface EstadoChamada {
chamadaId: Id<'chamadas'> | null;
conversaId: Id<'conversas'> | null;
tipo: 'audio' | 'video' | null;
roomName: string | null;
estaConectado: boolean;
audioHabilitado: boolean;
videoHabilitado: boolean;
gravando: boolean;
ehAnfitriao: boolean;
participantes: ParticipanteChamada[];
duracaoSegundos: number;
dispositivos: {
microphoneId: string | null;
cameraId: string | null;
speakerId: string | null;
};
jitsiApi: any | null;
streamLocal: MediaStream | null;
}
const estadoInicial: EstadoChamada = {
chamadaId: null,
conversaId: null,
tipo: null,
roomName: null,
estaConectado: false,
audioHabilitado: true,
videoHabilitado: false,
gravando: false,
ehAnfitriao: false,
participantes: [],
duracaoSegundos: 0,
dispositivos: {
microphoneId: null,
cameraId: null,
speakerId: null
},
jitsiApi: null,
streamLocal: null
};
// Store principal do estado da chamada
export const callState = writable<EstadoChamada>(estadoInicial);
// Store para indicar se há chamada ativa
export const chamadaAtiva = derived(
callState,
($state) => $state.chamadaId !== null
);
// Store para indicar se está conectado
export const estaConectado = derived(
callState,
($state) => $state.estaConectado
);
// Store para indicar se está gravando
export const gravando = derived(
callState,
($state) => $state.gravando
);
// Funções para atualizar o estado
/**
* Inicializar chamada
*/
export function inicializarChamada(
chamadaId: Id<'chamadas'>,
conversaId: Id<'conversas'>,
tipo: 'audio' | 'video',
roomName: string,
ehAnfitriao: boolean,
participantes: ParticipanteChamada[]
): void {
callState.set({
...estadoInicial,
chamadaId,
conversaId,
tipo,
roomName,
ehAnfitriao,
participantes,
videoHabilitado: tipo === 'video'
});
}
/**
* Finalizar chamada e limpar estado
*/
export function finalizarChamada(): void {
const estadoAtual = get(callState);
// Liberar recursos
if (estadoAtual.streamLocal) {
estadoAtual.streamLocal.getTracks().forEach((track) => track.stop());
}
callState.set(estadoInicial);
}
/**
* Atualizar status de conexão
*/
export function atualizarStatusConexao(estaConectado: boolean): void {
callState.update((state) => ({
...state,
estaConectado
}));
}
/**
* Toggle áudio
*/
export function toggleAudio(): void {
callState.update((state) => ({
...state,
audioHabilitado: !state.audioHabilitado
}));
}
/**
* Toggle vídeo
*/
export function toggleVideo(): void {
callState.update((state) => ({
...state,
videoHabilitado: !state.videoHabilitado
}));
}
/**
* Definir áudio habilitado/desabilitado
*/
export function setAudioHabilitado(habilitado: boolean): void {
callState.update((state) => ({
...state,
audioHabilitado: habilitado
}));
}
/**
* Definir vídeo habilitado/desabilitado
*/
export function setVideoHabilitado(habilitado: boolean): void {
callState.update((state) => ({
...state,
videoHabilitado: habilitado
}));
}
/**
* Atualizar lista de participantes
*/
export function atualizarParticipantes(participantes: ParticipanteChamada[]): void {
callState.update((state) => ({
...state,
participantes
}));
}
/**
* Adicionar participante
*/
export function adicionarParticipante(participante: ParticipanteChamada): void {
callState.update((state) => {
// Verificar se já existe
const existe = state.participantes.some(
(p) => p.usuarioId === participante.usuarioId
);
if (existe) {
return state;
}
return {
...state,
participantes: [...state.participantes, participante]
};
});
}
/**
* Remover participante
*/
export function removerParticipante(usuarioId: Id<'usuarios'>): void {
callState.update((state) => ({
...state,
participantes: state.participantes.filter(
(p) => p.usuarioId !== usuarioId
)
}));
}
/**
* Atualizar status de áudio/vídeo de participante
*/
export function atualizarParticipanteMidia(
usuarioId: Id<'usuarios'>,
audioHabilitado?: boolean,
videoHabilitado?: boolean
): void {
callState.update((state) => ({
...state,
participantes: state.participantes.map((p) =>
p.usuarioId === usuarioId
? {
...p,
audioHabilitado: audioHabilitado ?? p.audioHabilitado,
videoHabilitado: videoHabilitado ?? p.videoHabilitado
}
: p
)
}));
}
/**
* Iniciar gravação
*/
export function iniciarGravacao(): void {
callState.update((state) => ({
...state,
gravando: true
}));
}
/**
* Parar gravação
*/
export function pararGravacao(): void {
callState.update((state) => ({
...state,
gravando: false
}));
}
/**
* Atualizar duração da chamada
*/
export function atualizarDuracao(segundos: number): void {
callState.update((state) => ({
...state,
duracaoSegundos: segundos
}));
}
/**
* Atualizar dispositivos selecionados
*/
export function atualizarDispositivos(dispositivos: {
microphoneId?: string | null;
cameraId?: string | null;
speakerId?: string | null;
}): void {
callState.update((state) => ({
...state,
dispositivos: {
...state.dispositivos,
...dispositivos
}
}));
}
/**
* Definir API Jitsi
*/
export function setJitsiApi(api: any | null): void {
callState.update((state) => ({
...state,
jitsiApi: api
}));
}
/**
* Definir stream local
*/
export function setStreamLocal(stream: MediaStream | null): void {
callState.update((state) => {
// Parar stream anterior se existir
if (state.streamLocal) {
state.streamLocal.getTracks().forEach((track) => track.stop());
}
return {
...state,
streamLocal: stream
};
});
}
/**
* Obter estado atual (helper)
*/
export function obterEstadoAtual(): EstadoChamada {
return get(callState);
}
/**
* Resetar estado (para cleanup)
*/
export function resetarEstado(): void {
finalizarChamada();
}