feat(backend): add SIMPLE_STREAMING WebRTC control-path streaming

This commit is contained in:
2026-03-05 13:30:00 +00:00
parent c458857f0a
commit 19baf76169
14 changed files with 448 additions and 189 deletions

View File

@@ -0,0 +1,22 @@
import { describe, expect, test } from 'bun:test';
import { parseFeatureFlag } from '../media/config';
describe('media config feature flags', () => {
test('parses enabled values', () => {
expect(parseFeatureFlag('true', false)).toBe(true);
expect(parseFeatureFlag('1', false)).toBe(true);
expect(parseFeatureFlag('yes', false)).toBe(true);
});
test('parses disabled values', () => {
expect(parseFeatureFlag('false', true)).toBe(false);
expect(parseFeatureFlag('0', true)).toBe(false);
expect(parseFeatureFlag('off', true)).toBe(false);
});
test('falls back to default value for unknown input', () => {
expect(parseFeatureFlag(undefined, true)).toBe(true);
expect(parseFeatureFlag('maybe', false)).toBe(false);
});
});

View File

@@ -0,0 +1,97 @@
import { describe, expect, test } from 'bun:test';
import {
canRelayWebrtcSignal,
createStreamEndedPayload,
createStreamRequestedPayload,
createStreamStartedPayload,
toSimpleStreamSessionResponse,
} from '../streaming/simple';
const buildSession = () => ({
id: 'stream-1',
ownerUserId: 'user-1',
cameraDeviceId: 'camera-1',
requesterDeviceId: 'client-1',
status: 'streaming',
reason: 'on_demand',
metadata: { quality: 'standard' },
startedAt: new Date('2026-04-06T10:00:00.000Z'),
endedAt: null,
createdAt: new Date('2026-04-06T09:59:00.000Z'),
updatedAt: new Date('2026-04-06T10:00:00.000Z'),
});
describe('simple streaming helpers', () => {
test('only relays WebRTC signals between stream participants', () => {
const session = buildSession();
expect(canRelayWebrtcSignal(session, 'camera-1', 'client-1')).toBe(true);
expect(canRelayWebrtcSignal(session, 'client-1', 'camera-1')).toBe(true);
expect(canRelayWebrtcSignal(session, 'camera-1', 'camera-1')).toBe(false);
expect(canRelayWebrtcSignal(session, 'camera-1', 'intruder-1')).toBe(false);
});
test('builds deterministic requested and started payloads', () => {
const session = buildSession();
expect(createStreamRequestedPayload(session)).toEqual({
streamSessionId: 'stream-1',
cameraDeviceId: 'camera-1',
requesterDeviceId: 'client-1',
status: 'streaming',
reason: 'on_demand',
});
expect(createStreamStartedPayload(session)).toEqual({
streamSessionId: 'stream-1',
cameraDeviceId: 'camera-1',
requesterDeviceId: 'client-1',
status: 'streaming',
startedAt: session.startedAt,
transport: 'webrtc',
});
});
test('normalizes ended payload and strips provider fields from API response', () => {
const session = {
...buildSession(),
mediaProvider: 'mock',
mediaSessionId: 'mock_stream-1',
streamKey: 'stream-key',
publishEndpoint: 'https://example.test/publish',
subscribeEndpoint: 'https://example.test/subscribe',
};
expect(
createStreamEndedPayload({
streamSessionId: session.id,
cameraDeviceId: session.cameraDeviceId,
requesterDeviceId: session.requesterDeviceId,
endedAt: new Date('2026-04-06T10:05:00.000Z'),
reason: 'completed',
}),
).toEqual({
streamSessionId: 'stream-1',
cameraDeviceId: 'camera-1',
requesterDeviceId: 'client-1',
status: 'ended',
endedAt: new Date('2026-04-06T10:05:00.000Z'),
reason: 'completed',
});
expect(toSimpleStreamSessionResponse(session)).toEqual({
id: 'stream-1',
ownerUserId: 'user-1',
cameraDeviceId: 'camera-1',
requesterDeviceId: 'client-1',
status: 'streaming',
reason: 'on_demand',
metadata: { quality: 'standard' },
startedAt: new Date('2026-04-06T10:00:00.000Z'),
endedAt: null,
createdAt: new Date('2026-04-06T09:59:00.000Z'),
updatedAt: new Date('2026-04-06T10:00:00.000Z'),
});
});
});