Skip to content

Commit 6dbb1f7

Browse files
[WIP] recorder API
1 parent 9041566 commit 6dbb1f7

File tree

3 files changed

+54
-30
lines changed

3 files changed

+54
-30
lines changed

src/models/ffmpeg.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { EventEmitter } from "node:events";
22

33
export class FFMPEG extends EventEmitter {
4-
54
constructor() {
65
super();
76
}
7+
8+
async kill() {}
89
}

src/models/recorder.ts

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,75 +3,93 @@ import { getFolder, type Folder } from "#src/services/resources.ts";
33
import { Logger } from "#src/utils/utils.ts";
44

55
import type { Channel } from "./channel";
6+
import { FFMPEG } from "#src/models/ffmpeg.ts";
67

78
export enum RECORDER_STATE {
89
STARTED = "started",
10+
STOPPING = "stopping",
911
STOPPED = "stopped"
1012
}
1113
const logger = new Logger("RECORDER");
1214

1315
export class Recorder extends EventEmitter {
1416
channel: Channel;
1517
folder: Folder | undefined;
16-
ffmpeg = null;
18+
ffmpeg: FFMPEG | undefined;
1719
/** Path to which the final recording will be uploaded to */
1820
recordingAddress: string;
1921
private _state: RECORDER_STATE = RECORDER_STATE.STOPPED;
2022

23+
get isRecording(): boolean {
24+
return this.state === RECORDER_STATE.STARTED;
25+
}
26+
get state(): RECORDER_STATE {
27+
return this._state;
28+
}
29+
set state(state: RECORDER_STATE) {
30+
this._state = state;
31+
this.emit("stateChange", state);
32+
}
33+
2134
constructor(channel: Channel, recordingAddress: string) {
2235
super();
2336
this.channel = channel;
2437
this.recordingAddress = recordingAddress;
2538
}
2639

2740
async start() {
28-
if (this.state === RECORDER_STATE.STOPPED) {
29-
this.folder = getFolder();
30-
this.state = RECORDER_STATE.STARTED;
31-
logger.trace("TO IMPLEMENT");
32-
// TODO ffmpeg instance creation for recording to folder.path with proper name, start, build timestamps object
41+
if (!this.isRecording) {
42+
try {
43+
await this._start();
44+
} catch {
45+
await this._stop();
46+
}
3347
}
34-
this._record();
3548
return this.isRecording;
3649
}
3750

3851
async stop() {
39-
if (this.state === RECORDER_STATE.STARTED) {
40-
logger.trace("TO IMPLEMENT");
52+
if (this.isRecording) {
4153
try {
42-
await this.folder!.seal("test-name");
54+
await this._stop({ save: true });
4355
} catch {
4456
logger.verbose("failed to save the recording"); // TODO maybe warn and give more info
4557
}
46-
this.folder = undefined;
47-
// TODO ffmpeg instance stop, cleanup,
48-
// only resolve promise and switch state when completely ready to start a new recording.
49-
this.state = RECORDER_STATE.STOPPED;
5058
}
5159
return this.isRecording;
5260
}
5361

54-
get isRecording(): boolean {
55-
return this.state === RECORDER_STATE.STARTED;
56-
}
57-
58-
get state(): RECORDER_STATE {
59-
return this._state;
60-
}
61-
62-
set state(state: RECORDER_STATE) {
63-
this._state = state;
64-
this.emit("stateChange", state);
65-
}
66-
6762
/**
6863
* @param video whether we want to record videos or not (will always record audio)
6964
*/
70-
_record(video: boolean = false) {
65+
private async _start({ video = true }: { video?: boolean } = {}) {
66+
this.state = RECORDER_STATE.STARTED;
67+
this.folder = getFolder();
7168
logger.trace(`TO IMPLEMENT: recording channel ${this.channel.name}, video: ${video}`);
69+
this.ffmpeg = new FFMPEG();
7270
// iterate all producers on all sessions of the channel, create a ffmpeg for each,
7371
// save them on a map by session id+type.
7472
// check if recording for that session id+type is already in progress
7573
// add listener to the channel for producer creation (and closure).
7674
}
75+
76+
private async _stop({ save = false }: { save?: boolean } = {}) {
77+
this.state = RECORDER_STATE.STOPPING;
78+
// remove all listener from the channel
79+
let failure = false;
80+
try {
81+
await this.ffmpeg?.kill();
82+
} catch (error) {
83+
logger.error(`failed to kill ffmpeg: ${error}`);
84+
failure = true;
85+
}
86+
this.ffmpeg = undefined;
87+
if (save && !failure) {
88+
await this.folder?.seal("test-name");
89+
} else {
90+
await this.folder?.delete();
91+
}
92+
this.folder = undefined;
93+
this.state = RECORDER_STATE.STOPPED;
94+
}
7795
}

src/services/resources.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ export async function start(): Promise<void> {
3535
for (let i = config.dynamicPorts.min; i <= config.dynamicPorts.max; i++) {
3636
availablePorts.add(i);
3737
}
38-
logger.info(`${availablePorts.size} dynamic ports available [${config.dynamicPorts.min}-${config.dynamicPorts.max}]`);
38+
logger.info(
39+
`${availablePorts.size} dynamic ports available [${config.dynamicPorts.min}-${config.dynamicPorts.max}]`
40+
);
3941
}
4042

4143
export function close(): void {
@@ -104,6 +106,9 @@ export class Folder {
104106
this.path = destinationPath;
105107
logger.verbose(`Moved folder from ${this.path} to ${destinationPath}`);
106108
}
109+
async delete() {
110+
logger.trace(`TO IMPLEMENT`);
111+
}
107112
}
108113

109114
export function getFolder(): Folder {

0 commit comments

Comments
 (0)