@audd/sdk 1.4.7 → 1.5.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 CHANGED
@@ -4,16 +4,9 @@
4
4
  [![Contract](https://github.com/AudDMusic/audd-node/actions/workflows/contract.yml/badge.svg)](https://github.com/AudDMusic/audd-node/actions/workflows/contract.yml)
5
5
  [![npm](https://img.shields.io/npm/v/@audd/sdk.svg)](https://www.npmjs.com/package/@audd/sdk)
6
6
 
7
- Official TypeScript / Node.js SDK for the [AudD](https://audd.io) music
8
- recognition API.
7
+ Official TypeScript / Node.js SDK for [AudD](https://audd.io) — music recognition from a short audio clip, a long audio file, or a live stream.
9
8
 
10
- AudD identifies music from a short audio clip, a URL, a long file, or a
11
- live stream. The HTTPS API is a plain form-POST — every endpoint is
12
- documented at **[docs.audd.io](https://docs.audd.io)** and you can call
13
- it from anywhere `fetch` works. This package adds typed result models
14
- with helpers for cover art, streaming-provider URLs, and previews;
15
- `AbortSignal`-aware async; ESM and CJS dual-build; and a separate
16
- browser-safe entry point for tokenless longpoll widgets.
9
+ The [API itself](https://docs.audd.io) is so simple that it can be easily used even without an SDK.
17
10
 
18
11
  ## Quickstart
19
12
 
@@ -21,12 +14,14 @@ browser-safe entry point for tokenless longpoll widgets.
21
14
  npm install @audd/sdk
22
15
  ```
23
16
 
17
+ Get your API token at [dashboard.audd.io](https://dashboard.audd.io).
18
+
24
19
  Recognize from a URL:
25
20
 
26
21
  ```ts
27
22
  import { AudD } from "@audd/sdk";
28
23
 
29
- const audd = new AudD("test"); // grab a real token at https://dashboard.audd.io
24
+ const audd = new AudD("test");
30
25
  const song = await audd.recognize("https://audd.tech/example.mp3");
31
26
  if (song) {
32
27
  console.log(`${song.artist} — ${song.title}`);
@@ -132,16 +127,18 @@ console.log(song.rawResponse); // the whole result object as the server returne
132
127
  flat array of matches:
133
128
 
134
129
  ```ts
135
- const matches = await audd.recognizeEnterprise("./show.mp3", {
136
- return: ["apple_music", "musicbrainz"],
137
- limit: 20,
138
- });
130
+ const matches = await audd.recognizeEnterprise("./show.mp3", { limit: 20 });
139
131
 
140
132
  for (const m of matches) {
141
- console.log(m.timecode, m.score, m.artist, m.title, m.isrc);
133
+ console.log(m.timecode, m.artist, m.title);
142
134
  }
143
135
  ```
144
136
 
137
+ `EnterpriseMatch` carries the same core tags plus `score`, `startOffset`,
138
+ `endOffset`, `isrc`, `upc`. Access to `isrc`, `upc`, and `score` requires
139
+ a Startup plan or higher — [contact us](mailto:api@audd.io) for enterprise
140
+ features.
141
+
145
142
  The default per-call timeout is **1 hour** for this endpoint (60s for
146
143
  standard recognition); override with `timeoutMs`.
147
144
 
@@ -243,40 +240,86 @@ await audd.streams.add({
243
240
  const streams = await audd.streams.list();
244
241
  ```
245
242
 
246
- Parse incoming callback POSTs into a typed payload:
243
+ ### Handling callback POSTs
244
+
245
+ Drop `handleCallback` into any HTTP handler — Express, Fastify, Hono,
246
+ or the bare `node:http` module. It duck-types the request: a Web
247
+ `Request`, a Node `IncomingMessage`, or a framework request whose body
248
+ has already been parsed all work without configuration.
247
249
 
248
250
  ```ts
249
- const payload = audd.streams.parseCallback(reqBodyJson);
250
- if (payload.isResult) {
251
- for (const r of payload.result!.results) {
252
- console.log(r.artist, r.title, r.score);
251
+ import express from "express";
252
+ import { handleCallback } from "@audd/sdk";
253
+
254
+ const app = express();
255
+ app.use(express.json());
256
+
257
+ app.post("/audd-callback", async (req, res) => {
258
+ const { match, notification } = await handleCallback(req);
259
+ if (match) {
260
+ console.log(`${match.song.artist} - ${match.song.title} score=${match.song.score}`);
261
+ for (const alt of match.alternatives) {
262
+ // alternatives are variant catalog releases — different artist/title is possible
263
+ console.log(` alt: ${alt.artist} - ${alt.title}`);
264
+ }
265
+ } else if (notification) {
266
+ console.log(`#${notification.notificationCode} ${notification.notificationMessage}`);
253
267
  }
254
- } else if (payload.isNotification) {
255
- console.log(payload.notification!.notificationCode,
256
- payload.notification!.notificationMessage);
257
- }
268
+ res.json({ ok: true });
269
+ });
258
270
  ```
259
271
 
272
+ If you already have the body bytes (queue consumer, replay tool), call
273
+ `parseCallback(body)` directly — it accepts a parsed JSON object or a
274
+ JSON string and returns the same `{ match, notification }` shape.
275
+
260
276
  ### Receiving events without a callback URL (longpoll)
261
277
 
262
- Useful when you can't expose a public HTTPS receiver. Before the first
263
- event, the SDK runs a one-time `getCallbackUrl` preflight — AudD
264
- silently discards events for accounts without any callback URL set, so
265
- this catches the trap early. Pass `skipCallbackCheck: true` to opt out.
278
+ Useful when you can't expose a public HTTPS receiver. The poll handle
279
+ exposes three async-iterables — `matches`, `notifications`, `errors` —
280
+ filled by a background loop. Iterate them independently, or in parallel
281
+ via `Promise.all`.
282
+
283
+ Before the first request the SDK runs a one-time `getCallbackUrl`
284
+ preflight: AudD silently discards events for accounts without any
285
+ callback URL set, and the preflight surfaces that as an actionable
286
+ error. Pass `skipCallbackCheck: true` to bypass.
266
287
 
267
288
  ```ts
268
- for await (const event of audd.streams.longpoll(category, { timeout: 30 })) {
269
- console.log(event);
289
+ const category = audd.streams.deriveLongpollCategory(12345); // pure, no network call
290
+ const poll = await audd.streams.longpoll(category, { timeout: 30 });
291
+
292
+ for await (const m of poll.matches) {
293
+ console.log(m.song.artist, m.song.title);
270
294
  }
271
295
  ```
272
296
 
273
- `category` is a 9-character string derived locally from your token and
274
- `radioId`:
297
+ Consume matches and notifications concurrently:
275
298
 
276
299
  ```ts
277
- const category = audd.streams.deriveLongpollCategory(12345);
300
+ await Promise.all([
301
+ (async () => {
302
+ for await (const m of poll.matches) {
303
+ console.log("match:", m.song.artist, m.song.title);
304
+ }
305
+ })(),
306
+ (async () => {
307
+ for await (const n of poll.notifications) {
308
+ console.log("notification:", n.notificationMessage);
309
+ }
310
+ })(),
311
+ (async () => {
312
+ for await (const err of poll.errors) {
313
+ console.error(err);
314
+ poll.close();
315
+ }
316
+ })(),
317
+ ]);
278
318
  ```
279
319
 
320
+ `poll.close()` (or the `await using` resource-management form) tears
321
+ down the background loop and completes all three iterables.
322
+
280
323
  ### Browser / widget consumers
281
324
 
282
325
  The `audd/longpoll` sub-entry exports a tokenless `LongpollConsumer` for
@@ -288,8 +331,9 @@ client out of the resulting bundle.
288
331
  import { LongpollConsumer } from "@audd/sdk/longpoll";
289
332
 
290
333
  const consumer = new LongpollConsumer("abc123def");
291
- for await (const event of consumer.iterate({ timeout: 30 })) {
292
- console.log(event);
334
+ const poll = consumer.iterate({ timeout: 30 });
335
+ for await (const m of poll.matches) {
336
+ console.log(m.song.artist, m.song.title);
293
337
  }
294
338
  ```
295
339