feat(streams): add phase-1 single-server SFU session and transport APIs

This commit is contained in:
2026-02-08 11:45:00 +00:00
parent 20373f411f
commit 1c8256bf57
5 changed files with 257 additions and 58 deletions

View File

@@ -1,6 +1,7 @@
import { randomUUID } from 'crypto';
import { mediaConfig } from '../config';
import { SfuSessionRegistry } from './registry';
import type {
SfuPublishTransportRequest,
SfuPublishTransportResult,
@@ -25,11 +26,11 @@ const toIceServers = (): Array<{ urls: string; username?: string; credential?: s
export class NoopSfuService implements SfuService {
mode: 'single_server_sfu' = 'single_server_sfu';
private readonly sessions = new Map<string, SfuSessionDescriptor>();
private readonly registry = new SfuSessionRegistry();
async startSession(input: SfuSessionStartInput): Promise<SfuSessionDescriptor> {
const now = new Date().toISOString();
const existing = this.sessions.get(input.streamSessionId);
const existing = this.registry.get(input.streamSessionId);
if (existing) return existing;
const descriptor: SfuSessionDescriptor = {
@@ -40,18 +41,24 @@ export class NoopSfuService implements SfuService {
state: 'starting',
createdAt: now,
};
this.sessions.set(input.streamSessionId, descriptor);
return descriptor;
return this.registry.set(descriptor);
}
async setSessionState(streamSessionId: string, state: SfuSessionDescriptor['state']): Promise<void> {
this.registry.updateState(streamSessionId, state);
}
async endSession(streamSessionId: string): Promise<void> {
const existing = this.sessions.get(streamSessionId);
if (!existing) return;
this.sessions.set(streamSessionId, { ...existing, state: 'ended' });
this.registry.updateState(streamSessionId, 'ending');
this.registry.updateState(streamSessionId, 'ended');
}
async getSession(streamSessionId: string): Promise<SfuSessionDescriptor | null> {
return this.sessions.get(streamSessionId) ?? null;
return this.registry.get(streamSessionId);
}
async listSessions(): Promise<SfuSessionDescriptor[]> {
return this.registry.list();
}
async createPublishTransport(_input: SfuPublishTransportRequest): Promise<SfuPublishTransportResult> {
@@ -68,4 +75,3 @@ export class NoopSfuService implements SfuService {
};
}
}

View File

@@ -0,0 +1,41 @@
import type { SfuSessionDescriptor, SfuSessionState } from './types';
type StoredSfuSession = SfuSessionDescriptor & {
updatedAt: string;
};
export class SfuSessionRegistry {
private readonly sessions = new Map<string, StoredSfuSession>();
get(streamSessionId: string): SfuSessionDescriptor | null {
const found = this.sessions.get(streamSessionId);
if (!found) return null;
const { updatedAt: _updatedAt, ...descriptor } = found;
return descriptor;
}
set(session: SfuSessionDescriptor): SfuSessionDescriptor {
const now = new Date().toISOString();
this.sessions.set(session.streamSessionId, { ...session, updatedAt: now });
return session;
}
updateState(streamSessionId: string, state: SfuSessionState): SfuSessionDescriptor | null {
const existing = this.sessions.get(streamSessionId);
if (!existing) return null;
const next: StoredSfuSession = {
...existing,
state,
updatedAt: new Date().toISOString(),
};
this.sessions.set(streamSessionId, next);
const { updatedAt: _updatedAt, ...descriptor } = next;
return descriptor;
}
list(): SfuSessionDescriptor[] {
return Array.from(this.sessions.values()).map(({ updatedAt: _updatedAt, ...descriptor }) => descriptor);
}
}

View File

@@ -39,9 +39,10 @@ export type SfuSubscribeTransportResult = {
export interface SfuService {
mode: 'single_server_sfu';
startSession(input: SfuSessionStartInput): Promise<SfuSessionDescriptor>;
setSessionState(streamSessionId: string, state: SfuSessionState): Promise<void>;
endSession(streamSessionId: string): Promise<void>;
getSession(streamSessionId: string): Promise<SfuSessionDescriptor | null>;
listSessions(): Promise<SfuSessionDescriptor[]>;
createPublishTransport(input: SfuPublishTransportRequest): Promise<SfuPublishTransportResult>;
createSubscribeTransport(input: SfuSubscribeTransportRequest): Promise<SfuSubscribeTransportResult>;
}