Files
zep/src/chat/services/webrtc/webrtc.svelte.ts
T
2026-03-31 18:55:57 -04:00

118 lines
4.0 KiB
TypeScript

import { Identity } from "spacetimedb";
import { tables, reducers } from "../../../module_bindings";
import { useTable, useReducer } from "spacetimedb/svelte";
import { LocalMediaService } from "./local-media.svelte";
import { ChannelAudioWebRTCService } from "./channel-audio-webrtc.svelte";
import { ScreenSharingWebRTCService } from "./screen-sharing-webrtc.svelte";
import * as Types from "../../../module_bindings/types";
export class WebRTCService {
identity = $state<Identity | null>(null);
connectedChannelId = $state<bigint | undefined>();
watching = $state<readonly Types.Watching[]>([]);
#startWatchingReducer = useReducer(reducers.startWatching);
#stopWatchingReducer = useReducer(reducers.stopWatching);
#setMuteReducer = useReducer(reducers.setMute);
#setDeafenReducer = useReducer(reducers.setDeafen);
localMedia: LocalMediaService;
voice: ChannelAudioWebRTCService;
screen: ScreenSharingWebRTCService;
constructor(identity: Identity | null, connectedChannelId: bigint | undefined) {
this.identity = identity;
this.connectedChannelId = connectedChannelId;
const [wStore] = useTable(tables.watching);
wStore.subscribe(v => this.watching = v);
this.localMedia = new LocalMediaService(connectedChannelId);
// Pass localMedia's stream and deafen state to voice
this.voice = new ChannelAudioWebRTCService(
identity,
connectedChannelId,
this.localMedia.localStream,
this.localMedia.isDeafened
);
this.screen = new ScreenSharingWebRTCService(
identity,
connectedChannelId,
this.localMedia.localScreenStream
);
// Sync state to sub-services
$effect(() => {
this.localMedia.connectedChannelId = this.connectedChannelId;
this.voice.identity = this.identity;
this.voice.connectedChannelId = this.connectedChannelId;
this.voice.localStream = this.localMedia.localStream;
this.voice.isDeafened = this.localMedia.isDeafened;
this.screen.identity = this.identity;
this.screen.connectedChannelId = this.connectedChannelId;
this.screen.localScreenStream = this.localMedia.localScreenStream;
});
// Sync mute/deafen to DB
$effect(() => {
if (this.connectedChannelId) {
this.#setMuteReducer({ muted: this.localMedia.isMuted });
}
});
$effect(() => {
if (this.connectedChannelId) {
this.#setDeafenReducer({ deafened: this.localMedia.isDeafened });
}
});
// Orchestration
$effect(() => {
if (this.connectedChannelId && this.identity) {
console.log(`[WebRTC] Joined channel ${this.connectedChannelId}, requesting mic...`);
this.localMedia.requestMic();
} else {
console.log("[WebRTC] Left channel, releasing mic.");
this.localMedia.releaseMic();
}
});
}
startScreenShare = () => {
this.localMedia.startScreenShare(() => {});
};
stopScreenShare = () => {
this.localMedia.stopScreenShare(() => {});
};
startWatching = (peerIdentity: Identity) => {
if (this.connectedChannelId) {
this.#startWatchingReducer({
watchee: peerIdentity,
channelId: this.connectedChannelId,
});
}
};
stopWatching = (peerIdentity: Identity) => {
this.#stopWatchingReducer({ watchee: peerIdentity });
};
// Facade getters
get localStream() { return this.localMedia.localStream; }
get localScreenStream() { return this.localMedia.localScreenStream; }
get peerStatuses() { return this.voice.peerManager.peerStatuses; }
get peers() { return this.screen.peerManager.peers; }
get isSharingScreen() { return this.localMedia.isSharingScreen; }
get isMuted() { return this.localMedia.isMuted; }
get isDeafened() { return this.localMedia.isDeafened; }
get peerStats() { return this.voice.peerManager.peerStats; }
toggleMute = () => this.localMedia.toggleMute();
toggleDeafen = () => this.localMedia.toggleDeafen();
setPeerAudioPreference = (peerIdHex: string, pref: any) => this.voice.peerManager.setPeerAudioPreference(peerIdHex, pref);
}