feat(media): add single-server SFU scaffolding and media mode config
This commit is contained in:
71
Backend/media/sfu/noop.ts
Normal file
71
Backend/media/sfu/noop.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
import { mediaConfig } from '../config';
|
||||
import type {
|
||||
SfuPublishTransportRequest,
|
||||
SfuPublishTransportResult,
|
||||
SfuService,
|
||||
SfuSessionDescriptor,
|
||||
SfuSessionStartInput,
|
||||
SfuSubscribeTransportRequest,
|
||||
SfuSubscribeTransportResult,
|
||||
} from './types';
|
||||
|
||||
const toIceServers = (): Array<{ urls: string; username?: string; credential?: string }> => {
|
||||
if (mediaConfig.turn.urls.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return mediaConfig.turn.urls.map((urls) => ({
|
||||
urls,
|
||||
...(mediaConfig.turn.username ? { username: mediaConfig.turn.username } : {}),
|
||||
...(mediaConfig.turn.credential ? { credential: mediaConfig.turn.credential } : {}),
|
||||
}));
|
||||
};
|
||||
|
||||
export class NoopSfuService implements SfuService {
|
||||
mode: 'single_server_sfu' = 'single_server_sfu';
|
||||
private readonly sessions = new Map<string, SfuSessionDescriptor>();
|
||||
|
||||
async startSession(input: SfuSessionStartInput): Promise<SfuSessionDescriptor> {
|
||||
const now = new Date().toISOString();
|
||||
const existing = this.sessions.get(input.streamSessionId);
|
||||
if (existing) return existing;
|
||||
|
||||
const descriptor: SfuSessionDescriptor = {
|
||||
streamSessionId: input.streamSessionId,
|
||||
ownerUserId: input.ownerUserId,
|
||||
cameraDeviceId: input.cameraDeviceId,
|
||||
requesterDeviceId: input.requesterDeviceId,
|
||||
state: 'starting',
|
||||
createdAt: now,
|
||||
};
|
||||
this.sessions.set(input.streamSessionId, descriptor);
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
async endSession(streamSessionId: string): Promise<void> {
|
||||
const existing = this.sessions.get(streamSessionId);
|
||||
if (!existing) return;
|
||||
this.sessions.set(streamSessionId, { ...existing, state: 'ended' });
|
||||
}
|
||||
|
||||
async getSession(streamSessionId: string): Promise<SfuSessionDescriptor | null> {
|
||||
return this.sessions.get(streamSessionId) ?? null;
|
||||
}
|
||||
|
||||
async createPublishTransport(_input: SfuPublishTransportRequest): Promise<SfuPublishTransportResult> {
|
||||
return {
|
||||
transportId: `pub_${randomUUID()}`,
|
||||
iceServers: toIceServers(),
|
||||
};
|
||||
}
|
||||
|
||||
async createSubscribeTransport(_input: SfuSubscribeTransportRequest): Promise<SfuSubscribeTransportResult> {
|
||||
return {
|
||||
transportId: `sub_${randomUUID()}`,
|
||||
iceServers: toIceServers(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
14
Backend/media/sfu/service.ts
Normal file
14
Backend/media/sfu/service.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { mediaMode } from '../config';
|
||||
import { NoopSfuService } from './noop';
|
||||
import type { SfuService } from './types';
|
||||
|
||||
const createSfuService = (): SfuService | null => {
|
||||
if (mediaMode !== 'single_server_sfu') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new NoopSfuService();
|
||||
};
|
||||
|
||||
export const sfuService = createSfuService();
|
||||
|
||||
47
Backend/media/sfu/types.ts
Normal file
47
Backend/media/sfu/types.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
export type SfuSessionState = 'idle' | 'starting' | 'live' | 'ending' | 'ended';
|
||||
|
||||
export type SfuSessionDescriptor = {
|
||||
streamSessionId: string;
|
||||
ownerUserId: string;
|
||||
cameraDeviceId: string;
|
||||
requesterDeviceId: string;
|
||||
state: SfuSessionState;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type SfuSessionStartInput = {
|
||||
streamSessionId: string;
|
||||
ownerUserId: string;
|
||||
cameraDeviceId: string;
|
||||
requesterDeviceId: string;
|
||||
};
|
||||
|
||||
export type SfuPublishTransportRequest = {
|
||||
streamSessionId: string;
|
||||
cameraDeviceId: string;
|
||||
};
|
||||
|
||||
export type SfuPublishTransportResult = {
|
||||
transportId: string;
|
||||
iceServers: Array<{ urls: string; username?: string; credential?: string }>;
|
||||
};
|
||||
|
||||
export type SfuSubscribeTransportRequest = {
|
||||
streamSessionId: string;
|
||||
viewerDeviceId: string;
|
||||
};
|
||||
|
||||
export type SfuSubscribeTransportResult = {
|
||||
transportId: string;
|
||||
iceServers: Array<{ urls: string; username?: string; credential?: string }>;
|
||||
};
|
||||
|
||||
export interface SfuService {
|
||||
mode: 'single_server_sfu';
|
||||
startSession(input: SfuSessionStartInput): Promise<SfuSessionDescriptor>;
|
||||
endSession(streamSessionId: string): Promise<void>;
|
||||
getSession(streamSessionId: string): Promise<SfuSessionDescriptor | null>;
|
||||
createPublishTransport(input: SfuPublishTransportRequest): Promise<SfuPublishTransportResult>;
|
||||
createSubscribeTransport(input: SfuSubscribeTransportRequest): Promise<SfuSubscribeTransportResult>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user