【m】修改为服务器录屏

This commit is contained in:
2026-06-02 02:34:40 +08:00
parent d74a0c8121
commit 66d6f92d1e
21 changed files with 4053 additions and 32 deletions

View File

@@ -7,6 +7,29 @@ import signaling from './signaling';
import { log, LogLevel } from './log';
import Options from './class/options';
import { reset as resetHandler } from './class/httphandler';
import {
broadcastRecordingPeerRequest,
broadcastRecordingStarted,
broadcastRecordingStopped,
onGetRooms as getWebSocketRooms
} from './class/websockethandler';
import {
getRecordingAgent,
startRecordingAgent,
stopRecordingAgent
} from './recording/agent';
import {
getRecordingSession,
listRecordingSessions,
startRecordingSession,
stopRecordingSession
} from './recording/session-manager';
import {
getRecordingCompositionJob,
listRecordingCompositionJobs,
startRecordingCompositionJob
} from './recording/composer';
import { stopRecordingPeer } from './recording/werift-adapter';
import { initSwagger } from './swagger';
const cors = require('cors');
@@ -139,7 +162,15 @@ function sanitizeMetadataString(value: any, maxLength = 200): string {
return '';
}
return String(value).replace(/[\u0000-\u001f\u007f]/g, '').trim().slice(0, maxLength);
return String(value)
.split('')
.filter((character) => {
const code = character.charCodeAt(0);
return code >= 32 && code !== 127;
})
.join('')
.trim()
.slice(0, maxLength);
}
function sanitizeRecordingPerson(value: any, fallbackRole: string): RecordingPerson | undefined {
@@ -285,6 +316,16 @@ function removeEmptyDirectory(directory: string): void {
}
}
function getActiveRecordingSession(connectionId: string) {
const sessions = listRecordingSessions(connectionId);
for (const session of sessions) {
if (session.status === 'recording') {
return session;
}
}
return null;
}
export const createServer = (config: Options): express.Express => {
const app: express.Express = express();
resetHandler(config.mode);
@@ -452,6 +493,120 @@ export const createServer = (config: Options): express.Express => {
}
});
app.get('/api/recording-sessions', (req: express.Request, res: express.Response) => {
const connectionId = typeof req.query.connectionId === 'string'
? sanitizeMetadataString(req.query.connectionId, 120)
: undefined;
const sessions = listRecordingSessions(connectionId);
res.json({ success: true, sessions, totalCount: sessions.length });
});
app.get('/api/recording-sessions/:recordingId', (req: express.Request, res: express.Response) => {
const session = getRecordingSession(req.params.recordingId);
if (!session) {
res.status(404).json({ success: false, message: 'Recording session not found' });
return;
}
res.json({ success: true, session, agent: getRecordingAgent(session.id) });
});
app.post('/api/recording-sessions', (req: express.Request, res: express.Response) => {
const connectionId = sanitizeMetadataString(req.body.connectionId, 120);
if (!connectionId) {
res.status(400).json({ success: false, message: 'connectionId is required' });
return;
}
if (config.type === 'websocket' && getWebSocketRooms(connectionId).length === 0) {
res.status(404).json({ success: false, message: 'Active WebSocket room not found' });
return;
}
const activeSession = getActiveRecordingSession(connectionId);
if (activeSession) {
res.status(409).json({ success: false, message: 'Recording is already running', session: activeSession });
return;
}
try {
const session = startRecordingSession({
connectionId,
layout: req.body.layout,
format: req.body.format
});
const agent = startRecordingAgent(session);
const notified = broadcastRecordingStarted(session);
const peerRequestNotified = broadcastRecordingPeerRequest(session);
res.status(201).json({ success: true, session, agent, notified, peerRequestNotified });
} catch (error) {
log(LogLevel.error, 'Failed to start recording session:', error);
res.status(500).json({ success: false, message: 'Failed to start recording session' });
}
});
app.delete('/api/recording-sessions/:recordingId', async (req: express.Request, res: express.Response) => {
const session = stopRecordingSession(req.params.recordingId);
if (!session) {
res.status(404).json({ success: false, message: 'Recording session not found' });
return;
}
const notified = broadcastRecordingStopped(session);
const agent = stopRecordingAgent(session.id);
try {
await stopRecordingPeer(session.id);
} catch (error) {
log(LogLevel.warn, 'Failed to stop recording peer:', error);
}
const shouldCompose = req.query.compose !== 'false';
const compositionJob = shouldCompose
? startRecordingCompositionJob({
meetingId: session.connectionId,
recordingId: session.id,
layout: session.layout,
format: session.format
})
: null;
res.json({ success: true, session, agent, notified, compositionJob });
});
app.get('/api/recording-compositions', (req: express.Request, res: express.Response) => {
const meetingId = typeof req.query.meetingId === 'string'
? sanitizePathSegment(req.query.meetingId, 'unknown')
: undefined;
const jobs = listRecordingCompositionJobs(meetingId);
res.json({ success: true, jobs, totalCount: jobs.length });
});
app.get('/api/recording-compositions/:compositionId', (req: express.Request, res: express.Response) => {
const job = getRecordingCompositionJob(req.params.compositionId);
if (!job) {
res.status(404).json({ success: false, message: 'Recording composition job not found' });
return;
}
res.json({ success: true, job });
});
app.post('/api/recording-compositions', (req: express.Request, res: express.Response) => {
const meetingId = sanitizeMetadataString(req.body.meetingId, 120);
const recordingId = sanitizeMetadataString(req.body.recordingId, 120);
if (!meetingId || !recordingId) {
res.status(400).json({ success: false, message: 'meetingId and recordingId are required' });
return;
}
const job = startRecordingCompositionJob({
meetingId,
recordingId,
layout: req.body.layout,
format: req.body.format
});
res.status(202).json({ success: true, job });
});
app.get('/api/recordings', (_req: express.Request, res: express.Response) => {
try {
const recordings = listRecordings(recordingRoot);