@openvertice/bot-sdk 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -2
- package/dist/src/audio.d.ts +32 -0
- package/dist/src/audio.d.ts.map +1 -0
- package/dist/src/audio.js +47 -0
- package/dist/src/audio.js.map +1 -0
- package/dist/src/cliente.d.ts +213 -0
- package/dist/src/cliente.d.ts.map +1 -0
- package/dist/src/cliente.js +381 -0
- package/dist/src/cliente.js.map +1 -0
- package/dist/src/gateway.d.ts +76 -0
- package/dist/src/gateway.d.ts.map +1 -0
- package/dist/src/gateway.js +299 -0
- package/dist/src/gateway.js.map +1 -0
- package/dist/src/http.d.ts +119 -0
- package/dist/src/http.d.ts.map +1 -0
- package/dist/src/http.js +189 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +16 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/tipos.d.ts +243 -0
- package/dist/src/tipos.d.ts.map +1 -0
- package/dist/src/tipos.js +5 -0
- package/dist/src/tipos.js.map +1 -0
- package/dist/src/voz-wrtc.d.ts +20 -0
- package/dist/src/voz-wrtc.d.ts.map +1 -0
- package/dist/src/voz-wrtc.js +68 -0
- package/dist/src/voz-wrtc.js.map +1 -0
- package/dist/src/voz.d.ts +150 -0
- package/dist/src/voz.d.ts.map +1 -0
- package/dist/src/voz.js +311 -0
- package/dist/src/voz.js.map +1 -0
- package/dist/src/wav.d.ts +29 -0
- package/dist/src/wav.d.ts.map +1 -0
- package/dist/src/wav.js +54 -0
- package/dist/src/wav.js.map +1 -0
- package/package.json +58 -3
- package/src/audio.ts +88 -0
- package/src/cliente.ts +472 -0
- package/src/gateway.ts +354 -0
- package/src/http.ts +249 -0
- package/src/index.ts +41 -0
- package/src/tipos.ts +220 -0
- package/src/voz-wrtc.ts +100 -0
- package/src/voz.ts +407 -0
- package/src/wav.ts +71 -0
- package/src/wrtc.d.ts +4 -0
package/dist/src/voz.js
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/** How long {@link Voz.entrar} waits for the server's `voice.joined`. */
|
|
2
|
+
const ESPERA_DA_ENTRADA_MS = 15_000;
|
|
3
|
+
/**
|
|
4
|
+
* Voice client for a {@link Bot}.
|
|
5
|
+
*
|
|
6
|
+
* The bot is one more peer in the mesh: it offers to peers that are already in
|
|
7
|
+
* the room, answers offers, exchanges ICE candidates and publishes one audio
|
|
8
|
+
* track. Create it once, before `bot.start()`, so the listeners are in place
|
|
9
|
+
* when `voice.joined` arrives.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const transporte = await criarTransporteWrtc();
|
|
14
|
+
* const voz = new Voz(bot, { transporte });
|
|
15
|
+
* await bot.start();
|
|
16
|
+
* await voz.entrar("PATIO");
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export class Voz {
|
|
20
|
+
#bot;
|
|
21
|
+
#opcoes;
|
|
22
|
+
#conexoes = new Map();
|
|
23
|
+
#log;
|
|
24
|
+
#eu = null;
|
|
25
|
+
#canal = null;
|
|
26
|
+
#track = null;
|
|
27
|
+
#fonte = null;
|
|
28
|
+
#pendente = null;
|
|
29
|
+
/**
|
|
30
|
+
* Wires the voice client to a bot.
|
|
31
|
+
*
|
|
32
|
+
* It subscribes to the voice events right away, so the instance must exist
|
|
33
|
+
* before the bot starts.
|
|
34
|
+
*
|
|
35
|
+
* @param bot Bot whose gateway carries the signaling.
|
|
36
|
+
* @param opcoes Transport, optional track and logging.
|
|
37
|
+
*/
|
|
38
|
+
constructor(bot, opcoes) {
|
|
39
|
+
this.#bot = bot;
|
|
40
|
+
this.#opcoes = opcoes;
|
|
41
|
+
this.#log = opcoes.log ?? (() => { });
|
|
42
|
+
this.#track = opcoes.track ?? null;
|
|
43
|
+
bot.on("voice.joined", (entrada) => {
|
|
44
|
+
this.#eu = entrada.eu;
|
|
45
|
+
this.#canal = entrada.code;
|
|
46
|
+
this.#log(`voz: em ${entrada.code} com ${entrada.peers.length} par(es)`);
|
|
47
|
+
this.#concluirEntrada(entrada);
|
|
48
|
+
for (const peer of entrada.peers)
|
|
49
|
+
void this.#oferecer(peer.id);
|
|
50
|
+
});
|
|
51
|
+
bot.on("voice.left", ({ code, motivo }) => {
|
|
52
|
+
this.#log(`voz: saiu de ${code} (${motivo})`);
|
|
53
|
+
this.#canal = null;
|
|
54
|
+
this.#fecharTodas();
|
|
55
|
+
});
|
|
56
|
+
bot.on("voice.peer.joined", ({ id, nome }) => {
|
|
57
|
+
this.#log(`voz: ${nome} entrou`);
|
|
58
|
+
void this.#oferecer(id);
|
|
59
|
+
});
|
|
60
|
+
bot.on("voice.peer.left", ({ id }) => {
|
|
61
|
+
this.#conexoes.get(id)?.close();
|
|
62
|
+
this.#conexoes.delete(id);
|
|
63
|
+
});
|
|
64
|
+
bot.on("voice.signal", ({ de, signal }) => {
|
|
65
|
+
void this.#aoSinal(de, signal);
|
|
66
|
+
});
|
|
67
|
+
bot.on("error", ({ codigo, mensagem }) => {
|
|
68
|
+
if (codigo === "canal" || codigo === "voz")
|
|
69
|
+
this.#falharEntrada(mensagem);
|
|
70
|
+
});
|
|
71
|
+
// Reconnecting takes a new seat on the server: the previous room is released
|
|
72
|
+
// in the gateway's `aoFechar` and `voice.left` never arrives (the socket
|
|
73
|
+
// died). Without releasing the room here, the bot would believe it is
|
|
74
|
+
// somewhere it is not, and `entrar` would answer "already in …" forever.
|
|
75
|
+
bot.aoMudarEstado((estado) => {
|
|
76
|
+
if (estado !== "reconectando" && estado !== "encerrado" && estado !== "recusado")
|
|
77
|
+
return;
|
|
78
|
+
this.#canal = null;
|
|
79
|
+
this.#fecharTodas();
|
|
80
|
+
this.#falharEntrada("A conexão com o gateway caiu: a voz foi encerrada.");
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** Channel the bot is in (`null` = not in voice). */
|
|
84
|
+
get canal() {
|
|
85
|
+
return this.#canal;
|
|
86
|
+
}
|
|
87
|
+
/** The published track; created on demand when it was not passed in the options. */
|
|
88
|
+
get track() {
|
|
89
|
+
if (!this.#track) {
|
|
90
|
+
this.#fonte ??= this.#opcoes.transporte.criarFonteDeAudio();
|
|
91
|
+
this.#track = this.#fonte.createTrack();
|
|
92
|
+
}
|
|
93
|
+
return this.#track;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Joins a voice channel and offers to everyone already there.
|
|
97
|
+
*
|
|
98
|
+
* @param code Channel code (case-insensitive).
|
|
99
|
+
* @returns The room information, once the server confirms the join.
|
|
100
|
+
* @throws {Error} When the server rejects the join or does not confirm it
|
|
101
|
+
* within {@link ESPERA_DA_ENTRADA_MS}.
|
|
102
|
+
*/
|
|
103
|
+
async entrar(code) {
|
|
104
|
+
const promessa = new Promise((resolver, rejeitar) => {
|
|
105
|
+
const prazo = setTimeout(() => {
|
|
106
|
+
this.#pendente = null;
|
|
107
|
+
rejeitar(new Error("O servidor não confirmou a entrada na voz."));
|
|
108
|
+
}, ESPERA_DA_ENTRADA_MS);
|
|
109
|
+
prazo.unref?.();
|
|
110
|
+
this.#pendente = { resolver, rejeitar, prazo };
|
|
111
|
+
});
|
|
112
|
+
try {
|
|
113
|
+
this.#bot.agir("voz.entrar", { code: code.toUpperCase() });
|
|
114
|
+
}
|
|
115
|
+
catch (erro) {
|
|
116
|
+
// With no gateway connected there is nothing to wait for: fail now
|
|
117
|
+
// instead of leaving the caller hanging until the timeout.
|
|
118
|
+
this.#falharEntrada(erro instanceof Error ? erro.message : String(erro));
|
|
119
|
+
}
|
|
120
|
+
return promessa;
|
|
121
|
+
}
|
|
122
|
+
/** Leaves the voice room, if the bot is in one. */
|
|
123
|
+
sair() {
|
|
124
|
+
if (this.#canal)
|
|
125
|
+
this.#bot.agir("voz.sair");
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Resolves a pending {@link entrar} call with the room information.
|
|
129
|
+
*
|
|
130
|
+
* @param entrada Data from the `voice.joined` event.
|
|
131
|
+
*/
|
|
132
|
+
#concluirEntrada(entrada) {
|
|
133
|
+
const pendente = this.#pendente;
|
|
134
|
+
if (!pendente)
|
|
135
|
+
return;
|
|
136
|
+
this.#pendente = null;
|
|
137
|
+
clearTimeout(pendente.prazo);
|
|
138
|
+
pendente.resolver(entrada);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Rejects a pending {@link entrar} call (server refusal, timeout, dropped gateway).
|
|
142
|
+
*
|
|
143
|
+
* @param mensagem Reason shown to the caller.
|
|
144
|
+
*/
|
|
145
|
+
#falharEntrada(mensagem) {
|
|
146
|
+
const pendente = this.#pendente;
|
|
147
|
+
if (!pendente)
|
|
148
|
+
return;
|
|
149
|
+
this.#pendente = null;
|
|
150
|
+
clearTimeout(pendente.prazo);
|
|
151
|
+
pendente.rejeitar(new Error(mensagem));
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Returns the peer connection for `peerId`, creating it on first use.
|
|
155
|
+
*
|
|
156
|
+
* The offerer adds the three m-lines up front; the answerer reuses the ones
|
|
157
|
+
* from the received SDP. ICE candidates and remote tracks are wired here.
|
|
158
|
+
*
|
|
159
|
+
* @param peerId Peer id inside the room.
|
|
160
|
+
* @param options Whether this side is the offerer.
|
|
161
|
+
* @returns The existing or newly created connection.
|
|
162
|
+
*/
|
|
163
|
+
#conexao(peerId, { ofertante }) {
|
|
164
|
+
const existente = this.#conexoes.get(peerId);
|
|
165
|
+
if (existente)
|
|
166
|
+
return existente;
|
|
167
|
+
const conexao = this.#opcoes.transporte.criarConexao({
|
|
168
|
+
iceServers: this.#bot.iceServers,
|
|
169
|
+
});
|
|
170
|
+
// The offerer creates the three m-lines; the answerer reuses the ones from the SDP.
|
|
171
|
+
if (ofertante)
|
|
172
|
+
for (const tipo of ["audio", "audio", "video"])
|
|
173
|
+
conexao.addTransceiver(tipo, { direction: "sendrecv" });
|
|
174
|
+
conexao.onicecandidate = (evento) => {
|
|
175
|
+
if (evento.candidate)
|
|
176
|
+
this.#enviar(peerId, { type: "candidate", candidate: evento.candidate });
|
|
177
|
+
};
|
|
178
|
+
conexao.ontrack = (evento) => this.#opcoes.aoReceberTrack?.(peerId, evento.track);
|
|
179
|
+
this.#conexoes.set(peerId, conexao);
|
|
180
|
+
return conexao;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Sends an offer to one peer, publishing the bot's track on the first m-line.
|
|
184
|
+
*
|
|
185
|
+
* @param peerId Peer to negotiate with.
|
|
186
|
+
*/
|
|
187
|
+
async #oferecer(peerId) {
|
|
188
|
+
try {
|
|
189
|
+
const conexao = this.#conexao(peerId, { ofertante: true });
|
|
190
|
+
const primeiro = conexao.getTransceivers()[0];
|
|
191
|
+
if (primeiro && !primeiro.sender.track)
|
|
192
|
+
await primeiro.sender.replaceTrack(this.track);
|
|
193
|
+
const oferta = await conexao.createOffer();
|
|
194
|
+
await conexao.setLocalDescription(oferta);
|
|
195
|
+
this.#enviarSdp(peerId, "offer", oferta.sdp);
|
|
196
|
+
}
|
|
197
|
+
catch (erro) {
|
|
198
|
+
this.#log(`voz: oferta para ${peerId} falhou: ${mensagemDe(erro)}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Applies an incoming signal: offer, answer or ICE candidate.
|
|
203
|
+
*
|
|
204
|
+
* @param peerId Peer that sent the signal.
|
|
205
|
+
* @param sinal Raw WebRTC signal.
|
|
206
|
+
*/
|
|
207
|
+
async #aoSinal(peerId, sinal) {
|
|
208
|
+
try {
|
|
209
|
+
if (sinal.type === "offer")
|
|
210
|
+
return await this.#responder(peerId, sdpDe(sinal));
|
|
211
|
+
if (sinal.type === "answer") {
|
|
212
|
+
const sdp = sdpDe(sinal);
|
|
213
|
+
if (!sdp)
|
|
214
|
+
return this.#log(`voz: resposta de ${peerId} veio sem SDP`);
|
|
215
|
+
const conexao = this.#conexao(peerId, { ofertante: false });
|
|
216
|
+
await conexao.setRemoteDescription({ type: "answer", sdp });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (sinal.type === "candidate" && sinal.candidate) {
|
|
220
|
+
const conexao = this.#conexao(peerId, { ofertante: false });
|
|
221
|
+
await conexao.addIceCandidate(sinal.candidate);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (erro) {
|
|
225
|
+
this.#log(`voz: sinal de ${peerId} falhou: ${mensagemDe(erro)}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Answers an offer.
|
|
230
|
+
*
|
|
231
|
+
* In a glare (both sides offered), the larger id ignores the other one's
|
|
232
|
+
* offer — the rule holds on both sides, so nobody ends up with two open
|
|
233
|
+
* negotiations.
|
|
234
|
+
*
|
|
235
|
+
* @param peerId Peer that sent the offer.
|
|
236
|
+
* @param sdp The offer's session description.
|
|
237
|
+
*/
|
|
238
|
+
async #responder(peerId, sdp) {
|
|
239
|
+
if (!sdp)
|
|
240
|
+
return this.#log(`voz: oferta de ${peerId} veio sem SDP`);
|
|
241
|
+
const conexao = this.#conexao(peerId, { ofertante: false });
|
|
242
|
+
const emDisputa = conexao.signalingState === "have-local-offer" &&
|
|
243
|
+
conexao.localDescription?.type === "offer";
|
|
244
|
+
if (emDisputa) {
|
|
245
|
+
if (this.#eu && this.#eu > peerId)
|
|
246
|
+
return;
|
|
247
|
+
await conexao.setLocalDescription({ type: "rollback" });
|
|
248
|
+
}
|
|
249
|
+
await conexao.setRemoteDescription({ type: "offer", sdp });
|
|
250
|
+
// The SDP carries the three m-lines; forcing `sendrecv` is what lets the bot
|
|
251
|
+
// speak even when the offer arrived as `recvonly`.
|
|
252
|
+
for (const transceiver of conexao.getTransceivers())
|
|
253
|
+
transceiver.direction = "sendrecv";
|
|
254
|
+
const primeiro = conexao.getTransceivers()[0];
|
|
255
|
+
if (primeiro && !primeiro.sender.track)
|
|
256
|
+
await primeiro.sender.replaceTrack(this.track);
|
|
257
|
+
const resposta = await conexao.createAnswer();
|
|
258
|
+
await conexao.setLocalDescription(resposta);
|
|
259
|
+
this.#enviarSdp(peerId, "answer", resposta.sdp);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Sends an offer/answer with the SDP under **both** names the protocol knows:
|
|
263
|
+
* `sdp` (the bot contract's) and `description` (what the browser client
|
|
264
|
+
* reads). Sending only one left the mesh mute on one side — the symptom was a
|
|
265
|
+
* `setRemoteDescription(undefined)` swallowed by the client, with no error on
|
|
266
|
+
* the bot.
|
|
267
|
+
*
|
|
268
|
+
* @param peerId Destination peer.
|
|
269
|
+
* @param tipo `"offer"` or `"answer"`.
|
|
270
|
+
* @param sdp Session description to send.
|
|
271
|
+
*/
|
|
272
|
+
#enviarSdp(peerId, tipo, sdp) {
|
|
273
|
+
this.#enviar(peerId, { type: tipo, sdp, description: { type: tipo, sdp } });
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Sends a raw signal to a peer through the gateway.
|
|
277
|
+
*
|
|
278
|
+
* @param peerId Destination peer.
|
|
279
|
+
* @param signal Signal payload.
|
|
280
|
+
*/
|
|
281
|
+
#enviar(peerId, signal) {
|
|
282
|
+
this.#bot.agir("voice.signal", { para: peerId, signal });
|
|
283
|
+
}
|
|
284
|
+
/** Closes every peer connection the bot currently has open. */
|
|
285
|
+
#fecharTodas() {
|
|
286
|
+
for (const conexao of this.#conexoes.values())
|
|
287
|
+
conexao.close();
|
|
288
|
+
this.#conexoes.clear();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Extracts the SDP from a signal, accepting both the bot's name and the browser
|
|
293
|
+
* client's.
|
|
294
|
+
*
|
|
295
|
+
* @param sinal Incoming signal.
|
|
296
|
+
* @returns The SDP, or `""` when the signal carries none.
|
|
297
|
+
*/
|
|
298
|
+
const sdpDe = (sinal) => {
|
|
299
|
+
if (typeof sinal.sdp === "string" && sinal.sdp)
|
|
300
|
+
return sinal.sdp;
|
|
301
|
+
const descricao = sinal.description;
|
|
302
|
+
return typeof descricao?.sdp === "string" ? descricao.sdp : "";
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* Turns any thrown value into a printable message.
|
|
306
|
+
*
|
|
307
|
+
* @param erro Value thrown or rejected.
|
|
308
|
+
* @returns `erro.message` for `Error`s, `String(erro)` otherwise.
|
|
309
|
+
*/
|
|
310
|
+
const mensagemDe = (erro) => erro instanceof Error ? erro.message : String(erro);
|
|
311
|
+
//# sourceMappingURL=voz.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voz.js","sourceRoot":"","sources":["../../src/voz.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Shape of the generated WAV. Every field is optional. */
|
|
2
|
+
export interface OpcoesDoWav {
|
|
3
|
+
/** Duration in seconds (clamped to a 0.05 s minimum). */
|
|
4
|
+
segundos?: number;
|
|
5
|
+
/** Frequency in Hz. 440 is concert A. */
|
|
6
|
+
frequencia?: number;
|
|
7
|
+
/** 0…1. */
|
|
8
|
+
amplitude?: number;
|
|
9
|
+
/** Sample rate in Hz; 48 kHz matches the voice transport. */
|
|
10
|
+
sampleRate?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Builds an in-memory WAV file containing a sine wave.
|
|
14
|
+
*
|
|
15
|
+
* Output is 16-bit PCM, mono, little-endian — what any player and the client's
|
|
16
|
+
* `<audio>` element can play without conversion. Useful to post a quick voice
|
|
17
|
+
* message (`bot.enviarVoz`) or to test the upload path end to end.
|
|
18
|
+
*
|
|
19
|
+
* @param options Tone and container parameters; defaults to one second at 440 Hz.
|
|
20
|
+
* @returns The complete `.wav` file as bytes, header included.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const wav = wavDeTom({ segundos: 2, frequencia: 330, amplitude: 0.2 });
|
|
25
|
+
* await bot.enviarVoz("MURAL", wav, { nome: "recado.wav" });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function wavDeTom({ segundos, frequencia, amplitude, sampleRate, }?: OpcoesDoWav): Uint8Array;
|
|
29
|
+
//# sourceMappingURL=wav.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wav.d.ts","sourceRoot":"","sources":["../../src/wav.ts"],"names":[],"mappings":"AAMA,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,EACvB,QAAY,EACZ,UAAgB,EAChB,SAAe,EACf,UAAmB,GACpB,GAAE,WAAgB,GAAG,UAAU,CA+B/B"}
|
package/dist/src/wav.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// WAV of a pure tone — no sample file in the repository and no dependency.
|
|
2
|
+
//
|
|
3
|
+
// It serves two purposes: sending a voice "note" to the chat and exercising the
|
|
4
|
+
// upload path without needing a recorded audio file.
|
|
5
|
+
const CABECALHO = 44;
|
|
6
|
+
/**
|
|
7
|
+
* Builds an in-memory WAV file containing a sine wave.
|
|
8
|
+
*
|
|
9
|
+
* Output is 16-bit PCM, mono, little-endian — what any player and the client's
|
|
10
|
+
* `<audio>` element can play without conversion. Useful to post a quick voice
|
|
11
|
+
* message (`bot.enviarVoz`) or to test the upload path end to end.
|
|
12
|
+
*
|
|
13
|
+
* @param options Tone and container parameters; defaults to one second at 440 Hz.
|
|
14
|
+
* @returns The complete `.wav` file as bytes, header included.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const wav = wavDeTom({ segundos: 2, frequencia: 330, amplitude: 0.2 });
|
|
19
|
+
* await bot.enviarVoz("MURAL", wav, { nome: "recado.wav" });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function wavDeTom({ segundos = 1, frequencia = 440, amplitude = 0.2, sampleRate = 48_000, } = {}) {
|
|
23
|
+
const quadros = Math.max(1, Math.round(sampleRate * Math.max(0.05, segundos)));
|
|
24
|
+
const bytes = new Uint8Array(CABECALHO + quadros * 2);
|
|
25
|
+
const visao = new DataView(bytes.buffer);
|
|
26
|
+
const texto = (posicao, valor) => {
|
|
27
|
+
for (let i = 0; i < valor.length; i += 1)
|
|
28
|
+
visao.setUint8(posicao + i, valor.charCodeAt(i));
|
|
29
|
+
};
|
|
30
|
+
texto(0, "RIFF");
|
|
31
|
+
visao.setUint32(4, 36 + quadros * 2, true);
|
|
32
|
+
texto(8, "WAVE");
|
|
33
|
+
texto(12, "fmt ");
|
|
34
|
+
visao.setUint32(16, 16, true); // tamanho do bloco fmt
|
|
35
|
+
visao.setUint16(20, 1, true); // PCM
|
|
36
|
+
visao.setUint16(22, 1, true); // mono
|
|
37
|
+
visao.setUint32(24, sampleRate, true);
|
|
38
|
+
visao.setUint32(28, sampleRate * 2, true); // bytes por segundo
|
|
39
|
+
visao.setUint16(32, 2, true); // alinhamento do bloco
|
|
40
|
+
visao.setUint16(34, 16, true); // bits por amostra
|
|
41
|
+
texto(36, "data");
|
|
42
|
+
visao.setUint32(40, quadros * 2, true);
|
|
43
|
+
const limite = Math.max(0, Math.min(1, amplitude)) * 0x7fff;
|
|
44
|
+
const passo = (2 * Math.PI * frequencia) / sampleRate;
|
|
45
|
+
let fase = 0;
|
|
46
|
+
for (let i = 0; i < quadros; i += 1) {
|
|
47
|
+
visao.setInt16(CABECALHO + i * 2, Math.round(Math.sin(fase) * limite), true);
|
|
48
|
+
fase += passo;
|
|
49
|
+
if (fase > 2 * Math.PI)
|
|
50
|
+
fase -= 2 * Math.PI;
|
|
51
|
+
}
|
|
52
|
+
return bytes;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=wav.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wav.js","sourceRoot":"","sources":["../../src/wav.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openvertice/bot-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SDK para escrever bots do Vértice: gateway, mensagens e voz (WebRTC), sem dependência de runtime",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/src/index.js",
|
|
7
|
+
"types": "./dist/src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/src/index.d.ts",
|
|
11
|
+
"import": "./dist/src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/src",
|
|
17
|
+
"src",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -p tsconfig.json",
|
|
25
|
+
"prepack": "npm run build",
|
|
26
|
+
"eco": "node dist/exemplos/eco.js",
|
|
27
|
+
"radio": "node dist/exemplos/radio.js",
|
|
28
|
+
"recado": "node dist/exemplos/recado.js"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {},
|
|
31
|
+
"optionalDependencies": {
|
|
32
|
+
"@roamhq/wrtc": "^0.10.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.10.0",
|
|
36
|
+
"typescript": "^5.6.0"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/Vortex-SA/vertice-bot-sdk.git"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/Vortex-SA/vertice-bot-sdk#readme",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/Vortex-SA/vertice-bot-sdk/issues"
|
|
45
|
+
},
|
|
46
|
+
"author": "OpenVertice",
|
|
47
|
+
"license": "UNLICENSED",
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"keywords": [
|
|
52
|
+
"vertice",
|
|
53
|
+
"openvertice",
|
|
54
|
+
"bot",
|
|
55
|
+
"sdk",
|
|
56
|
+
"gateway",
|
|
57
|
+
"websocket",
|
|
58
|
+
"webrtc",
|
|
59
|
+
"voice"
|
|
60
|
+
]
|
|
6
61
|
}
|
package/src/audio.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// What the bot plays: a pure tone, with no file and no dependency.
|
|
2
|
+
//
|
|
3
|
+
// The source comes from the transport (`RTCAudioSource` in wrtc): it receives
|
|
4
|
+
// 10 ms blocks — 480 samples at 48 kHz, the only frame size wrtc accepts. This
|
|
5
|
+
// is the shortest possible "real audio" example; reading a file is just feeding
|
|
6
|
+
// the same source from somewhere else.
|
|
7
|
+
import type { FonteDeAudio } from "./voz.js";
|
|
8
|
+
|
|
9
|
+
/** Audio source that accepts raw PCM blocks (the shape `RTCAudioSource` has). */
|
|
10
|
+
interface FonteComDados extends FonteDeAudio {
|
|
11
|
+
/** Pushes one PCM block into the source. */
|
|
12
|
+
onData(dados: {
|
|
13
|
+
/** Interleaved signed 16-bit samples. */
|
|
14
|
+
samples: Int16Array;
|
|
15
|
+
/** Sample rate in Hz (48 kHz for wrtc). */
|
|
16
|
+
sampleRate: number;
|
|
17
|
+
/** Bits per sample (16). */
|
|
18
|
+
bitsPerSample: number;
|
|
19
|
+
/** Number of channels (1 = mono). */
|
|
20
|
+
channelCount: number;
|
|
21
|
+
/** Frames in this block (480 at 48 kHz, i.e. 10 ms). */
|
|
22
|
+
numberOfFrames: number;
|
|
23
|
+
}): void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Everything `tocarTom` needs in order to publish a sine wave. */
|
|
27
|
+
export interface OpcoesDoTom {
|
|
28
|
+
/** The transport source (`transporte.criarFonteDeAudio()`). */
|
|
29
|
+
fonte: FonteDeAudio;
|
|
30
|
+
/** Frequency in Hz. 440 is concert A. */
|
|
31
|
+
frequencia?: number;
|
|
32
|
+
/** 0…1. Anything above 0.5 starts to clip and distort. */
|
|
33
|
+
amplitude?: number;
|
|
34
|
+
/** 48 kHz: wrtc requires blocks of 480 samples. */
|
|
35
|
+
sampleRate?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Starts playing the tone on the given source.
|
|
40
|
+
*
|
|
41
|
+
* Keep the returned stop function for as long as the bot stays in the room:
|
|
42
|
+
* stopping mid-flight leaves the peer with silence, not with an error — which
|
|
43
|
+
* is exactly what a bot that stops speaking should do.
|
|
44
|
+
*
|
|
45
|
+
* @param options Tone parameters; every field has a sensible default.
|
|
46
|
+
* @returns A function that stops the tone and clears its timer.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const fonte = transporte.criarFonteDeAudio();
|
|
51
|
+
* const parar = tocarTom({ fonte, frequencia: 440, amplitude: 0.15 });
|
|
52
|
+
* // …later, on shutdown:
|
|
53
|
+
* parar();
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function tocarTom({
|
|
57
|
+
fonte,
|
|
58
|
+
frequencia = 440,
|
|
59
|
+
amplitude = 0.2,
|
|
60
|
+
sampleRate = 48_000,
|
|
61
|
+
}: OpcoesDoTom): () => void {
|
|
62
|
+
const comDados = fonte as FonteComDados;
|
|
63
|
+
// 10 ms per block: it is the frame size of wrtc's RTCAudioSource.
|
|
64
|
+
const quadros = Math.round(sampleRate / 100);
|
|
65
|
+
const passo = (2 * Math.PI * frequencia) / sampleRate;
|
|
66
|
+
const limite = Math.max(0, Math.min(1, amplitude)) * 0x7fff;
|
|
67
|
+
let fase = 0;
|
|
68
|
+
|
|
69
|
+
const timer = setInterval(() => {
|
|
70
|
+
const amostras = new Int16Array(quadros);
|
|
71
|
+
for (let i = 0; i < quadros; i += 1) {
|
|
72
|
+
amostras[i] = Math.round(Math.sin(fase) * limite);
|
|
73
|
+
fase += passo;
|
|
74
|
+
if (fase > 2 * Math.PI) fase -= 2 * Math.PI;
|
|
75
|
+
}
|
|
76
|
+
comDados.onData({
|
|
77
|
+
samples: amostras,
|
|
78
|
+
sampleRate,
|
|
79
|
+
bitsPerSample: 16,
|
|
80
|
+
channelCount: 1,
|
|
81
|
+
numberOfFrames: quadros,
|
|
82
|
+
});
|
|
83
|
+
}, 10);
|
|
84
|
+
// Without `unref` the process would not exit while the tone plays — and the
|
|
85
|
+
// bot must be able to leave the room and still shut down cleanly.
|
|
86
|
+
timer.unref?.();
|
|
87
|
+
return () => clearInterval(timer);
|
|
88
|
+
}
|