/** * 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'; import type { JitsiApi } from '$lib/types/jitsi'; 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: JitsiApi; 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(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: JitsiApi): 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(); }