现在合成的有问题,时间轴错乱,在房间中有多个用户情况下

This commit is contained in:
2026-06-02 23:59:55 +08:00
parent 206a3ac91d
commit 3e161ff995
4 changed files with 298 additions and 22 deletions

View File

@@ -1,7 +1,14 @@
import { buildFfmpegCompositionArgs } from '../src/recording/composer';
import { ServerTrackRecordingFile } from '../src/recording/storage';
function file(filename: string, trackKind: string, participantId: string, role = 'participant'): ServerTrackRecordingFile {
function file(
filename: string,
trackKind: string,
participantId: string,
role = 'participant',
recordingStartedAt = '2026-06-01T00:00:00.000Z',
recordingEndedAt = '2026-06-01T00:00:10.000Z'
): ServerTrackRecordingFile {
return {
meetingId: 'room-1',
directory: 'recordings/room-1',
@@ -12,8 +19,10 @@ function file(filename: string, trackKind: string, participantId: string, role =
participantId,
trackId: `${participantId}-${trackKind}`,
trackKind,
uploadedAt: '2026-06-01T00:00:00.000Z',
metadata: { role }
uploadedAt: recordingStartedAt,
recordingStartedAt,
recordingEndedAt,
metadata: { role, recordingStartedAt, recordingEndedAt, updatedAt: recordingEndedAt }
};
}
@@ -40,6 +49,7 @@ describe('recording composer', () => {
expect(args.join(' ')).toContain('amix=inputs=2');
expect(args).toContain('libvpx-vp9');
expect(args).toContain('libopus');
expect(args).not.toContain('-shortest');
expect(args[args.length - 1]).toBe('recordings/room-1/output.webm');
});
@@ -82,4 +92,70 @@ describe('recording composer', () => {
expect(args).toContain('-pix_fmt');
expect(args).not.toContain('libopus');
});
test('pads late participant tracks to keep the room timeline aligned', () => {
const args = buildFfmpegCompositionArgs({
videoInputs: [
file('host-video.webm', 'video', 'host', 'host', '2026-06-01T00:00:00.000Z', '2026-06-01T00:00:10.000Z'),
file('p1-video.webm', 'video', 'p1', 'participant', '2026-06-01T00:00:02.500Z', '2026-06-01T00:00:10.000Z')
],
audioInputs: [
file('host-audio.webm', 'audio', 'host', 'host', '2026-06-01T00:00:00.000Z', '2026-06-01T00:00:10.000Z'),
file('p1-audio.webm', 'audio', 'p1', 'participant', '2026-06-01T00:00:02.500Z', '2026-06-01T00:00:10.000Z')
],
outputPath: 'recordings/room-1/output.webm',
format: 'webm'
});
const filter = args[args.indexOf('-filter_complex') + 1];
expect(filter).toContain('[0:v]split=2[vin0_0][vin0_1]');
expect(filter).toContain('[vin0_0]trim=start=0:duration=2.5');
expect(filter).toContain('[vin0_1]trim=start=2.5:duration=7.5');
expect(filter).toContain('[1:v]trim=start=0:duration=7.5');
expect(filter).toContain('concat=n=2:v=1:a=0[vout]');
expect(filter).toContain('[2:a]aresample=async=1:first_pts=0[a0]');
expect(filter).toContain('[3:a]aresample=async=1:first_pts=0,adelay=2500:all=1[a1]');
expect(filter).toContain('[a0][a1]amix=inputs=2:duration=longest');
});
test('changes the layout when participants join and leave without overlapping', () => {
const args = buildFfmpegCompositionArgs({
videoInputs: [
file('host-video.webm', 'video', 'host', 'host', '2026-06-01T00:00:00.000Z', '2026-06-01T00:00:12.000Z'),
file('p1-video.webm', 'video', 'p1', 'participant', '2026-06-01T00:00:00.000Z', '2026-06-01T00:00:05.000Z'),
file('p2-video.webm', 'video', 'p2', 'participant', '2026-06-01T00:00:05.000Z', '2026-06-01T00:00:12.000Z')
],
audioInputs: [],
outputPath: 'recordings/room-1/output.webm',
format: 'webm'
});
const filter = args[args.indexOf('-filter_complex') + 1];
expect(filter).toContain('xstack=inputs=2');
expect(filter).toContain('layout=0_0|0_540');
expect(filter).toContain('[0:v]split=2[vin0_0][vin0_1]');
expect(filter).toContain('[vin0_0]trim=start=0:duration=5');
expect(filter).toContain('[1:v]trim=start=0:duration=5');
expect(filter).toContain('[vin0_1]trim=start=5:duration=7');
expect(filter).toContain('[2:v]trim=start=0:duration=7');
expect(filter).toContain('concat=n=2:v=1:a=0[vout]');
expect(filter).not.toContain('xstack=inputs=3');
});
test('keeps separate viewports for participants whose video intervals overlap', () => {
const args = buildFfmpegCompositionArgs({
videoInputs: [
file('host-video.webm', 'video', 'host', 'host', '2026-06-01T00:00:00.000Z', '2026-06-01T00:00:12.000Z'),
file('p1-video.webm', 'video', 'p1', 'participant', '2026-06-01T00:00:00.000Z', '2026-06-01T00:00:08.000Z'),
file('p2-video.webm', 'video', 'p2', 'participant', '2026-06-01T00:00:05.000Z', '2026-06-01T00:00:12.000Z')
],
audioInputs: [],
outputPath: 'recordings/room-1/output.webm',
format: 'webm'
});
const filter = args[args.indexOf('-filter_complex') + 1];
expect(filter).toContain('xstack=inputs=3');
expect(filter).toContain('layout=0_0|0_540|640_540');
});
});

View File

@@ -64,7 +64,9 @@ describe('recording storage', () => {
userId: 'server-recorder',
recordingSource: 'server',
participantId: 'participant-1',
trackKind: 'video'
trackKind: 'video',
recordingStartedAt: expect.any(String),
recordingEndedAt: expect.any(String)
}));
const files = listServerTrackRecordingFiles({
meetingId: 'room_1',