-
Notifications
You must be signed in to change notification settings - Fork 110
Expand file tree
/
Copy pathindex.ts
More file actions
124 lines (104 loc) · 3.72 KB
/
index.ts
File metadata and controls
124 lines (104 loc) · 3.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { AudioFrame, AudioStream, Room, RoomEvent, TrackKind } from '@livekit/rtc-node';
import { Buffer } from 'buffer';
import { config } from 'dotenv';
import * as fs from 'fs';
import { AccessToken } from 'livekit-server-sdk';
config();
// Constants for WAV file
const BITS_PER_SAMPLE = 16;
const WAV_FILE = 'output.wav';
function writeWavHeader(writer: fs.WriteStream, frame: AudioFrame) {
const header = Buffer.alloc(44);
const byteRate = (frame.sampleRate * frame.channels * BITS_PER_SAMPLE) / 8;
const blockAlign = (frame.channels * BITS_PER_SAMPLE) / 8;
writer = fs.createWriteStream(WAV_FILE);
// Write the RIFF header
header.write('RIFF', 0); // ChunkID
header.writeUInt32LE(0, 4); // ChunkSize placeholder
header.write('WAVE', 8); // Format
// Write the fmt subchunk
header.write('fmt ', 12); // Subchunk1ID
header.writeUInt32LE(16, 16); // Subchunk1Size (PCM)
header.writeUInt16LE(1, 20); // AudioFormat (PCM = 1)
header.writeUInt16LE(frame.channels, 22); // NumChannels
header.writeUInt32LE(frame.sampleRate, 24); // SampleRate
header.writeUInt32LE(byteRate, 28); // ByteRate
header.writeUInt16LE(blockAlign, 32); // BlockAlign
header.writeUInt16LE(16, 34); // BitsPerSample
// Write the data subchunk
header.write('data', 36); // Subchunk2ID
header.writeUInt32LE(0, 40); // Subchunk2Size placeholder
// Write the header to the stream
writer.write(header);
}
function updateWavHeader(path: string) {
// Update the size of the audio data in the header
const stats = fs.statSync(path);
const fileSize = stats.size;
const chunkSize = fileSize - 8;
const subchunk2Size = fileSize - 44;
const header = Buffer.alloc(8);
header.writeUInt32LE(chunkSize, 0);
header.writeUInt32LE(subchunk2Size, 4);
// Reopen the file for updating the header
const fd = fs.openSync(path, 'r+');
fs.writeSync(fd, header, 0, 4, 4); // Update ChunkSize
fs.writeSync(fd, header, 4, 4, 40); // Update Subchunk2Size
fs.closeSync(fd);
}
// create access token from API credentials
const token = new AccessToken(process.env.LIVEKIT_API_KEY, process.env.LIVEKIT_API_SECRET, {
identity: 'example-participant',
});
token.addGrant({
room: 'test-room',
roomJoin: true,
roomCreate: true,
canPublish: true,
canPublishData: true,
});
const jwt = await token.toJwt();
// set up room
const room = new Room();
let trackToProcess: string | null = null;
let writer: fs.WriteStream | null = null;
room.on(RoomEvent.TrackSubscribed, async (track, publication, participant) => {
console.log('subscribed to track', track.sid, publication, participant.identity);
if (track.kind === TrackKind.KIND_AUDIO) {
const stream = new AudioStream(track);
trackToProcess = track.sid;
for await (const frame of stream) {
if (!trackToProcess) {
return;
}
if (writer == null) {
// create file on first frame
// also guard when track is unsubscribed
writer = fs.createWriteStream('output.wav');
writeWavHeader(writer, frame);
}
if (writer) {
const buf = Buffer.from(frame.data.buffer);
writer.write(buf);
}
}
}
});
const finishedPromise = new Promise<void>((resolve) => {
room.on(RoomEvent.TrackUnsubscribed, (_, publication, participant) => {
console.log('unsubscribed from track', publication.sid, participant.identity);
if (publication.sid === trackToProcess) {
trackToProcess = null;
if (writer) {
writer.close();
// update header
updateWavHeader(WAV_FILE);
}
resolve();
}
});
});
await room.connect(process.env.LIVEKIT_URL, jwt, { autoSubscribe: true, dynacast: true });
console.log('connected to room', room);
// stay in the room until publisher leaves
await finishedPromise;