- 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.
323 lines
6.1 KiB
TypeScript
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();
|
|
}
|
|
|