import {Room as LiveKitRoom} from "livekit-client" import {derived, writable} from "svelte/store" import {type Room} from "@app/core/state" export type VoiceSession = { url: string h: string room: LiveKitRoom cameraOn: boolean screenShareOn: boolean } /** Mic mute state is separate so toggling it does not re-render video tiles. */ export const voiceMicMuted = writable(true) export type Pubkey = string export type VoiceParticipant = {pubkey?: Pubkey; identity: string} export enum VoiceState { Joining = "joining", Connected = "connected", Disconnected = "disconnected", } export const currentVoiceSession = writable(undefined) export const voiceState = writable(VoiceState.Disconnected) export const currentVoiceRoom = writable(undefined) export const participantPubkeyMap = writable>(new Map()) export const pubkeyFromLiveKitIdentity = (identity: string): string | undefined => /^[a-f0-9]{64}$/.test(identity.slice(0, 64)) ? identity.slice(0, 64) : undefined export const participantFromLiveKitIdentity = (identity: string): VoiceParticipant => { const pk = pubkeyFromLiveKitIdentity(identity) return pk ? {pubkey: pk, identity} : {identity} } export const participantKey = (p: VoiceParticipant) => p.pubkey ?? p.identity export const speakingParticipants = writable([]) export const isParticipantSpeaking = derived( speakingParticipants, $participants => (p: VoiceParticipant) => $participants.some(sp => participantKey(sp) === participantKey(p)), ) export const isLocalSpeaking = derived( [currentVoiceSession, speakingParticipants], ([$session, $speaking]) => { if (!$session?.room) return false const local = participantFromLiveKitIdentity($session.room.localParticipant.identity) return $speaking.some(sp => participantKey(sp) === participantKey(local)) }, )