Skip to content

Commit 57e9a6c

Browse files
[IMP] recording
TODO
1 parent efd5385 commit 57e9a6c

File tree

7 files changed

+62
-5
lines changed

7 files changed

+62
-5
lines changed

src/config.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ export const HTTP_INTERFACE: string = process.env.HTTP_INTERFACE || "0.0.0.0";
6464
*/
6565
export const PORT: number = Number(process.env.PORT) || 8070;
6666

67+
/**
68+
* Whether the recording feature is enabled, true by default.
69+
*/
70+
export const RECORDING: boolean = !FALSY_INPUT.has(process.env.LOG_TIMESTAMP!);
71+
6772
/**
6873
* The number of workers to spawn (up to core limits) to manage RTC servers.
6974
* 0 < NUM_WORKERS <= os.availableParallelism()
@@ -197,6 +202,25 @@ export const timeouts: TimeoutConfig = Object.freeze({
197202
busBatch: process.env.JEST_WORKER_ID ? 10 : 300
198203
});
199204

205+
export const recording = Object.freeze({
206+
directory: os.tmpdir() + "/recordings",
207+
enabled: RECORDING,
208+
maxDuration: 1000 * 60 * 60, // 1 hour, could be a env-var.
209+
fileTTL: 1000 * 60 * 60 * 24, // 24 hours
210+
fileType: "mp4",
211+
videoCodec: "libx264",
212+
audioCodec: "aac",
213+
audioLimit: 20,
214+
cameraLimit: 4, // how many camera can be merged into one recording
215+
screenLimit: 1,
216+
});
217+
218+
export const dynamicPorts = Object.freeze({
219+
min: 50000,
220+
max: 59999,
221+
});
222+
223+
200224
// how many errors can occur before the session is closed, recovery attempts will be made until this limit is reached
201225
export const maxSessionErrors: number = 6;
202226

src/models/channel.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
type SessionId,
1313
type SessionInfo
1414
} from "#src/models/session.ts";
15+
import { Recorder } from "#src/models/recorder.ts";
1516
import { getWorker, type RtcWorker } from "#src/services/rtc.ts";
1617

1718
const logger = new Logger("CHANNEL");
@@ -53,6 +54,7 @@ interface ChannelCreateOptions {
5354
key?: string;
5455
/** Whether to enable WebRTC functionality */
5556
useWebRtc?: boolean;
57+
useRecording?: boolean;
5658
}
5759
interface JoinResult {
5860
/** The channel instance */
@@ -87,6 +89,8 @@ export class Channel extends EventEmitter {
8789
public readonly sessions = new Map<SessionId, Session>();
8890
/** mediasoup Worker handling this channel */
8991
private readonly _worker?: RtcWorker;
92+
/** Manages the recording of this channel, undefined if the feature is disabled */
93+
private recorder?: Recorder;
9094
/** Timeout for auto-closing empty channels */
9195
private _closeTimeout?: NodeJS.Timeout;
9296

@@ -102,7 +106,7 @@ export class Channel extends EventEmitter {
102106
issuer: string,
103107
options: ChannelCreateOptions = {}
104108
): Promise<Channel> {
105-
const { key, useWebRtc = true } = options;
109+
const { key, useWebRtc = true, useRecording = true } = options;
106110
const safeIssuer = `${remoteAddress}::${issuer}`;
107111
const oldChannel = Channel.recordsByIssuer.get(safeIssuer);
108112
if (oldChannel) {
@@ -112,7 +116,7 @@ export class Channel extends EventEmitter {
112116
const channelOptions: ChannelCreateOptions & {
113117
worker?: Worker;
114118
router?: Router;
115-
} = { key };
119+
} = { key, useRecording: useWebRtc && useRecording };
116120
if (useWebRtc) {
117121
channelOptions.worker = await getWorker();
118122
channelOptions.router = await channelOptions.worker.createRouter({
@@ -183,6 +187,8 @@ export class Channel extends EventEmitter {
183187
const now = new Date();
184188
this.createDate = now.toISOString();
185189
this.remoteAddress = remoteAddress;
190+
this.recorder = config.recording.enabled && options.useRecording ? new Recorder(this) : undefined;
191+
this.recorder?.todo();
186192
this.key = key ? Buffer.from(key, "base64") : undefined;
187193
this.uuid = crypto.randomUUID();
188194
this.name = `${remoteAddress}*${this.uuid.slice(-5)}`;

src/models/recorder.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {EventEmitter} from "node:events";
2+
import type { Channel } from "./channel";
3+
import {Logger} from "#src/utils/utils.ts";
4+
5+
const logger = new Logger("RECORDER");
6+
7+
export class Recorder extends EventEmitter {
8+
channel: Channel;
9+
10+
constructor(channel: Channel) {
11+
super();
12+
this.channel = channel;
13+
}
14+
15+
todo() {
16+
logger.warn("TODO: Everything");
17+
}
18+
}

src/models/session.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ export enum SESSION_CLOSE_CODE {
5656
KICKED = "kicked",
5757
ERROR = "error"
5858
}
59+
export interface SessionPermissions {
60+
recording?: boolean;
61+
}
5962
export interface TransportConfig {
6063
/** Transport identifier */
6164
id: string;
@@ -135,6 +138,9 @@ export class Session extends EventEmitter {
135138
camera: null,
136139
screen: null
137140
};
141+
public permissions: SessionPermissions = {
142+
recording: false
143+
};
138144
/** Parent channel containing this session */
139145
private readonly _channel: Channel;
140146
/** Recovery timeouts for failed consumers */

src/services/auth.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import crypto from "node:crypto";
33
import * as config from "#src/config.ts";
44
import { Logger } from "#src/utils/utils.ts";
55
import { AuthenticationError } from "#src/utils/errors.ts";
6-
import type { SessionId } from "#src/models/session.ts";
6+
import type { SessionId, SessionPermissions } from "#src/models/session.ts";
77
import type { StringLike } from "#src/shared/types.ts";
88

99
/**
@@ -43,6 +43,7 @@ interface PrivateJWTClaims {
4343
sfu_channel_uuid?: string;
4444
session_id?: SessionId;
4545
ice_servers?: object[];
46+
permissions: SessionPermissions,
4647
sessionIdsByChannel?: Record<string, SessionId[]>;
4748
/** If provided when requesting a channel, this key will be used instead of the global key to verify JWTs related to this channel */
4849
key?: string;

src/services/http.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ function setupRoutes(routeListener: RouteListener): void {
9898
}
9999
const channel = await Channel.create(remoteAddress, claims.iss, {
100100
key: claims.key,
101-
useWebRtc: searchParams.get("webRTC") !== "false"
101+
useWebRtc: searchParams.get("webRTC") !== "false",
102+
useRecording: searchParams.get("recording") !== "false"
102103
});
103104
res.setHeader("Content-Type", "application/json");
104105
res.statusCode = 200;

src/services/ws.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ function connect(webSocket: WebSocket, credentials: Credentials): Session {
112112
const { channelUUID, jwt } = credentials;
113113
let channel = channelUUID ? Channel.records.get(channelUUID) : undefined;
114114
const authResult = verify(jwt, channel?.key);
115-
const { sfu_channel_uuid, session_id } = authResult;
115+
const { sfu_channel_uuid, session_id, permissions } = authResult;
116116
if (!channelUUID && sfu_channel_uuid) {
117117
// Cases where the channelUUID is not provided in the credentials for backwards compatibility with version 1.1 and earlier.
118118
channel = Channel.records.get(sfu_channel_uuid);
@@ -131,6 +131,7 @@ function connect(webSocket: WebSocket, credentials: Credentials): Session {
131131
webSocket.send(""); // client can start using ws after this message.
132132
const bus = new Bus(webSocket, { batchDelay: config.timeouts.busBatch });
133133
const { session } = Channel.join(channel.uuid, session_id);
134+
session.permissions = permissions;
134135
session.once("close", ({ code }: { code: string }) => {
135136
let wsCloseCode = WS_CLOSE_CODE.CLEAN;
136137
switch (code) {

0 commit comments

Comments
 (0)