@sorisdk/web-audio 0.6.4 → 0.6.7
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 +160 -4
- package/dist/generated/window.d.ts +1 -0
- package/dist/generated/window_bg.js +73 -11
- package/dist/generated/window_bg.wasm +0 -0
- package/dist/generated/window_bg.wasm.d.ts +5 -3
- package/dist/index.d.ts +39 -3
- package/dist/index.js +780 -48
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -181,6 +181,18 @@ function getBrowserMediaDevices() {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
// src/match-window.ts
|
|
184
|
+
var FINGERPRINT_LEVEL_BYTE_LENGTH = 8;
|
|
185
|
+
function getTypedArrayByteLengthGetter() {
|
|
186
|
+
const getter = Object.getOwnPropertyDescriptor(
|
|
187
|
+
Object.getPrototypeOf(Uint8Array.prototype),
|
|
188
|
+
"byteLength"
|
|
189
|
+
)?.get;
|
|
190
|
+
if (!getter) {
|
|
191
|
+
throw new Error("Uint8Array byteLength intrinsic is unavailable");
|
|
192
|
+
}
|
|
193
|
+
return getter;
|
|
194
|
+
}
|
|
195
|
+
var typedArrayByteLengthGetter = getTypedArrayByteLengthGetter();
|
|
184
196
|
var cachedBindings = null;
|
|
185
197
|
var initPromise = null;
|
|
186
198
|
var testInjectedWasmModule = null;
|
|
@@ -197,18 +209,46 @@ var defaultLoader = async () => {
|
|
|
197
209
|
const loaderModule = await import("./default-loader.js");
|
|
198
210
|
return loaderModule.default();
|
|
199
211
|
};
|
|
212
|
+
function calculateMaxAppendFingerprintByteLength(options) {
|
|
213
|
+
const hopSize = options.afpgenConfig.hopSize ?? 1e3;
|
|
214
|
+
const levelCount = options.afpgenConfig.levelCount ?? 2;
|
|
215
|
+
const hopDurationMs = hopSize / options.sampleRate * 1e3;
|
|
216
|
+
const hopsInWindow = Math.ceil(options.matchWindowMs / hopDurationMs);
|
|
217
|
+
return Math.max(1, hopsInWindow) * levelCount * FINGERPRINT_LEVEL_BYTE_LENGTH;
|
|
218
|
+
}
|
|
219
|
+
function appendLimitError(byteLength, maxByteLength) {
|
|
220
|
+
return new RangeError(
|
|
221
|
+
`Fingerprint append byteLength ${byteLength} exceeds the per-instance limit of ${maxByteLength} bytes`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
function intrinsicUint8ArrayByteLength(bytes) {
|
|
225
|
+
return typedArrayByteLengthGetter.call(bytes);
|
|
226
|
+
}
|
|
200
227
|
var WrappedFingerprintMatchWindow = class {
|
|
201
|
-
constructor(inner) {
|
|
228
|
+
constructor(inner, options) {
|
|
202
229
|
this.inner = inner;
|
|
230
|
+
const maxLengthFn = getCallable(this.inner, [
|
|
231
|
+
"maxAppendFingerprintByteLength",
|
|
232
|
+
"max_append_fingerprint_byte_length"
|
|
233
|
+
]);
|
|
234
|
+
this.maxAppendByteLength = maxLengthFn ? Number(maxLengthFn.call(this.inner)) : calculateMaxAppendFingerprintByteLength(options);
|
|
203
235
|
}
|
|
204
236
|
inner;
|
|
237
|
+
maxAppendByteLength;
|
|
205
238
|
appendFingerprint(bytes) {
|
|
239
|
+
const byteLength = intrinsicUint8ArrayByteLength(bytes);
|
|
240
|
+
if (byteLength > this.maxAppendByteLength) {
|
|
241
|
+
throw appendLimitError(byteLength, this.maxAppendByteLength);
|
|
242
|
+
}
|
|
206
243
|
const fn = getCallable(this.inner, ["appendFingerprint", "append_fingerprint"]);
|
|
207
244
|
if (!fn) {
|
|
208
245
|
throw new Error("web-audio wasm window is missing appendFingerprint");
|
|
209
246
|
}
|
|
210
247
|
fn.call(this.inner, bytes);
|
|
211
248
|
}
|
|
249
|
+
maxAppendFingerprintByteLength() {
|
|
250
|
+
return this.maxAppendByteLength;
|
|
251
|
+
}
|
|
212
252
|
hasReadyQuery() {
|
|
213
253
|
const fn = getCallable(this.inner, ["hasReadyQuery", "has_ready_query"]);
|
|
214
254
|
if (!fn) {
|
|
@@ -261,7 +301,8 @@ function createBindingsFromModule(moduleLike) {
|
|
|
261
301
|
options.afpgenConfig,
|
|
262
302
|
options.matchWindowMs,
|
|
263
303
|
options.matchStrideMs
|
|
264
|
-
)
|
|
304
|
+
),
|
|
305
|
+
options
|
|
265
306
|
);
|
|
266
307
|
}
|
|
267
308
|
};
|
|
@@ -314,6 +355,22 @@ var FingerprintMatchWindow = class {
|
|
|
314
355
|
};
|
|
315
356
|
|
|
316
357
|
// src/activity.ts
|
|
358
|
+
var ActivityReportingError = class extends Error {
|
|
359
|
+
status;
|
|
360
|
+
detail;
|
|
361
|
+
constructor(message, status = null, detail = null) {
|
|
362
|
+
super(message);
|
|
363
|
+
this.name = "ActivityReportingError";
|
|
364
|
+
this.status = status;
|
|
365
|
+
this.detail = detail;
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
var ActivityReportingCancelledError = class extends Error {
|
|
369
|
+
constructor() {
|
|
370
|
+
super("Activity reporting was cancelled before transport");
|
|
371
|
+
this.name = "ActivityReportingCancelledError";
|
|
372
|
+
}
|
|
373
|
+
};
|
|
317
374
|
function isRecord2(value) {
|
|
318
375
|
return typeof value === "object" && value !== null;
|
|
319
376
|
}
|
|
@@ -333,11 +390,23 @@ async function resolveToken(token) {
|
|
|
333
390
|
}
|
|
334
391
|
return resolved;
|
|
335
392
|
}
|
|
336
|
-
|
|
337
|
-
const
|
|
393
|
+
function defaultRequestPayload(match) {
|
|
394
|
+
const marker = match.audioMarker?.code;
|
|
395
|
+
return {
|
|
338
396
|
type: "material",
|
|
339
|
-
material_id: match.name
|
|
397
|
+
material_id: match.name,
|
|
398
|
+
...typeof marker === "string" && marker.length > 0 ? { trait: { marker } } : {}
|
|
340
399
|
};
|
|
400
|
+
}
|
|
401
|
+
function captureActivityRecognitionTiming(match) {
|
|
402
|
+
const recognizedAtMillis = Date.now();
|
|
403
|
+
const position = match.position;
|
|
404
|
+
return Object.freeze({
|
|
405
|
+
recognizedAt: new Date(recognizedAtMillis).toISOString(),
|
|
406
|
+
position
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
function validateRequestPayload(payload) {
|
|
341
410
|
if (payload === null) {
|
|
342
411
|
return null;
|
|
343
412
|
}
|
|
@@ -346,6 +415,40 @@ async function resolveRequestPayload(reporter, match) {
|
|
|
346
415
|
}
|
|
347
416
|
return payload;
|
|
348
417
|
}
|
|
418
|
+
async function resolveCreateRequestPayload(reporter, match) {
|
|
419
|
+
const payload = validateRequestPayload(
|
|
420
|
+
reporter.mapMatchToRequest ? await reporter.mapMatchToRequest(match) : defaultRequestPayload(match)
|
|
421
|
+
);
|
|
422
|
+
if (!payload) {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
const {
|
|
426
|
+
position: _ignoredPosition,
|
|
427
|
+
recognized_at: _ignoredRecognizedAt,
|
|
428
|
+
refinement_expected: _ignoredRefinementExpected,
|
|
429
|
+
...requestPayload
|
|
430
|
+
} = payload;
|
|
431
|
+
return requestPayload;
|
|
432
|
+
}
|
|
433
|
+
async function resolveRefineRequestPayload(reporter, match, context) {
|
|
434
|
+
const refinement = reporter.refinement;
|
|
435
|
+
if (!refinement) {
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
const payload = validateRequestPayload(
|
|
439
|
+
refinement.mapMatchToRequest ? await refinement.mapMatchToRequest(match, context) : reporter.mapMatchToRequest ? await reporter.mapMatchToRequest(match) : defaultRequestPayload(match)
|
|
440
|
+
);
|
|
441
|
+
if (!payload) {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
const {
|
|
445
|
+
position: _ignoredPosition,
|
|
446
|
+
recognized_at: _ignoredRecognizedAt,
|
|
447
|
+
refinement_expected: _ignoredRefinementExpected,
|
|
448
|
+
...requestPayload
|
|
449
|
+
} = payload;
|
|
450
|
+
return requestPayload;
|
|
451
|
+
}
|
|
349
452
|
function defaultCampaignMapper(payload, match) {
|
|
350
453
|
const campaign = payload.campaign;
|
|
351
454
|
if (!isRecord2(campaign)) {
|
|
@@ -358,42 +461,608 @@ function defaultCampaignMapper(payload, match) {
|
|
|
358
461
|
raw: payload
|
|
359
462
|
};
|
|
360
463
|
}
|
|
361
|
-
async function
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
464
|
+
async function resolveHeaders(reporter, refinement = false) {
|
|
465
|
+
const refinementHeaders = reporter.refinement && reporter.refinement.headers;
|
|
466
|
+
const baseHeaders = reporter.headers ? typeof reporter.headers === "function" ? await reporter.headers() : reporter.headers : void 0;
|
|
467
|
+
const headers = new Headers(baseHeaders);
|
|
468
|
+
if (refinement && refinementHeaders !== void 0) {
|
|
469
|
+
const overrides = new Headers(
|
|
470
|
+
typeof refinementHeaders === "function" ? await refinementHeaders() : refinementHeaders
|
|
471
|
+
);
|
|
472
|
+
overrides.forEach((value, name) => headers.set(name, value));
|
|
365
473
|
}
|
|
366
|
-
const headers = new Headers(
|
|
367
|
-
reporter.headers ? typeof reporter.headers === "function" ? await reporter.headers() : reporter.headers : void 0
|
|
368
|
-
);
|
|
369
474
|
headers.set("authorization", `Bearer ${await resolveToken(reporter.token)}`);
|
|
370
475
|
headers.set("content-type", "application/json");
|
|
476
|
+
return headers;
|
|
477
|
+
}
|
|
478
|
+
async function parseActivityResponse(response, operation) {
|
|
479
|
+
const responseText = await response.text();
|
|
480
|
+
let data = null;
|
|
481
|
+
if (responseText.trim().length > 0) {
|
|
482
|
+
try {
|
|
483
|
+
data = JSON.parse(responseText);
|
|
484
|
+
} catch {
|
|
485
|
+
throw new ActivityReportingError(
|
|
486
|
+
`Activity ${operation} failed: invalid JSON response`,
|
|
487
|
+
response.status
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
const succeeded = operation === "refine" ? response.status === 200 : response.ok;
|
|
492
|
+
if (!succeeded) {
|
|
493
|
+
const detail = isRecord2(data) && typeof data.detail === "string" ? data.detail : null;
|
|
494
|
+
throw new ActivityReportingError(
|
|
495
|
+
`Activity ${operation} failed: HTTP ${response.status}${detail ? ` (${detail})` : ""}`,
|
|
496
|
+
response.status,
|
|
497
|
+
detail
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
if (data === null) {
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
if (!isRecord2(data)) {
|
|
504
|
+
throw new ActivityReportingError(
|
|
505
|
+
`Activity ${operation} failed: expected an object response`,
|
|
506
|
+
response.status
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
return data;
|
|
510
|
+
}
|
|
511
|
+
function responseActivityId(data, campaign) {
|
|
512
|
+
if (data && typeof data.activity_id === "string" && data.activity_id.length > 0) {
|
|
513
|
+
return data.activity_id;
|
|
514
|
+
}
|
|
515
|
+
return campaign?.activityId && campaign.activityId.length > 0 ? campaign.activityId : null;
|
|
516
|
+
}
|
|
517
|
+
async function mapCreateResponse(reporter, data, match, refinementExpected) {
|
|
518
|
+
if (!data) {
|
|
519
|
+
return {
|
|
520
|
+
activityId: null,
|
|
521
|
+
campaign: null,
|
|
522
|
+
creationAttempted: true,
|
|
523
|
+
refinementExpected
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
const campaign = reporter.mapResponseToCampaign ? await reporter.mapResponseToCampaign(data, match) : defaultCampaignMapper(data, match);
|
|
527
|
+
return {
|
|
528
|
+
activityId: responseActivityId(data, campaign),
|
|
529
|
+
campaign,
|
|
530
|
+
creationAttempted: true,
|
|
531
|
+
refinementExpected
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
async function resolveRefinementEndpoint(reporter, activityId) {
|
|
535
|
+
const configured = reporter.refinement && reporter.refinement.endpoint;
|
|
536
|
+
if (typeof configured === "function") {
|
|
537
|
+
return configured(activityId);
|
|
538
|
+
}
|
|
539
|
+
const prefix = String(configured ?? reporter.endpoint);
|
|
540
|
+
return `${prefix.endsWith("/") ? prefix : `${prefix}/`}${encodeURIComponent(activityId)}`;
|
|
541
|
+
}
|
|
542
|
+
async function createMatchedMaterialActivity(reporter, match, refinementExpected = false, recognitionTiming = captureActivityRecognitionTiming(match)) {
|
|
543
|
+
const payload = await resolveCreateRequestPayload(reporter, match);
|
|
544
|
+
if (!payload) {
|
|
545
|
+
return {
|
|
546
|
+
activityId: null,
|
|
547
|
+
campaign: null,
|
|
548
|
+
creationAttempted: false,
|
|
549
|
+
refinementExpected: false
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
const headers = await resolveHeaders(reporter);
|
|
553
|
+
const actualMarker = match.audioMarker?.code;
|
|
554
|
+
const payloadMarker = payload.trait?.marker;
|
|
555
|
+
const hasMarker = typeof actualMarker === "string" && actualMarker.length > 0 || typeof payloadMarker === "string" && payloadMarker.length > 0;
|
|
556
|
+
const lifecycleOpen = typeof refinementExpected === "function" ? refinementExpected() : refinementExpected;
|
|
557
|
+
const requestRefinementExpected = Boolean(reporter.refinement && lifecycleOpen && !hasMarker);
|
|
558
|
+
const requestPayload = {
|
|
559
|
+
...payload,
|
|
560
|
+
recognized_at: recognitionTiming.recognizedAt,
|
|
561
|
+
position: recognitionTiming.position,
|
|
562
|
+
...requestRefinementExpected ? { refinement_expected: true } : {}
|
|
563
|
+
};
|
|
371
564
|
const response = await resolveFetch(reporter.fetch)(reporter.endpoint, {
|
|
372
565
|
method: "POST",
|
|
373
566
|
headers,
|
|
567
|
+
body: JSON.stringify(requestPayload)
|
|
568
|
+
});
|
|
569
|
+
return mapCreateResponse(
|
|
570
|
+
reporter,
|
|
571
|
+
await parseActivityResponse(response, "create"),
|
|
572
|
+
match,
|
|
573
|
+
requestRefinementExpected
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
async function refineMatchedMaterialActivity(reporter, match, context, isActive = () => true) {
|
|
577
|
+
const refinement = reporter.refinement;
|
|
578
|
+
if (!refinement) {
|
|
579
|
+
return { activityId: null, campaign: null };
|
|
580
|
+
}
|
|
581
|
+
const payload = await resolveRefineRequestPayload(reporter, match, context);
|
|
582
|
+
if (!payload) {
|
|
583
|
+
return { activityId: null, campaign: null };
|
|
584
|
+
}
|
|
585
|
+
const endpoint = await resolveRefinementEndpoint(reporter, context.activityId);
|
|
586
|
+
const headers = await resolveHeaders(reporter, true);
|
|
587
|
+
if (!isActive()) {
|
|
588
|
+
throw new ActivityReportingCancelledError();
|
|
589
|
+
}
|
|
590
|
+
const response = await resolveFetch(refinement.fetch ?? reporter.fetch)(endpoint, {
|
|
591
|
+
method: "PUT",
|
|
592
|
+
headers,
|
|
374
593
|
body: JSON.stringify(payload)
|
|
375
594
|
});
|
|
376
|
-
|
|
377
|
-
|
|
595
|
+
const data = await parseActivityResponse(response, "refine");
|
|
596
|
+
if (!data) {
|
|
597
|
+
throw new ActivityReportingError("Activity refine failed: empty response", response.status);
|
|
598
|
+
}
|
|
599
|
+
const mappedCampaign = refinement.mapResponseToCampaign ? await refinement.mapResponseToCampaign(data, match, context) : reporter.mapResponseToCampaign ? await reporter.mapResponseToCampaign(data, match) : defaultCampaignMapper(data, match);
|
|
600
|
+
const activityId = responseActivityId(data, mappedCampaign);
|
|
601
|
+
if (activityId !== context.activityId) {
|
|
602
|
+
throw new ActivityReportingError(
|
|
603
|
+
"Activity refine failed: response activity_id does not match the requested activity",
|
|
604
|
+
response.status
|
|
605
|
+
);
|
|
378
606
|
}
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
607
|
+
const campaign = mappedCampaign ? { ...mappedCampaign, activityId: context.activityId } : null;
|
|
608
|
+
return { activityId, campaign };
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/activity-refinement.ts
|
|
612
|
+
var ACTIVITY_REFINEMENT_WINDOW_MS = 3e4;
|
|
613
|
+
var ACTIVITY_CONTINUITY_GAP_MS = 3e4;
|
|
614
|
+
var DEFAULT_MAX_REFINEMENT_ATTEMPTS = 2;
|
|
615
|
+
var MAX_REFINEMENT_ATTEMPTS = 10;
|
|
616
|
+
function normalizeError(error) {
|
|
617
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
618
|
+
}
|
|
619
|
+
function statusAndDetail(error) {
|
|
620
|
+
return error instanceof ActivityReportingError ? { status: error.status, detail: error.detail } : { status: null, detail: null };
|
|
621
|
+
}
|
|
622
|
+
function defaultShouldRetry(context) {
|
|
623
|
+
return context.status === null || context.status >= 500 && context.status <= 599;
|
|
624
|
+
}
|
|
625
|
+
function isStableTerminalStatus(status) {
|
|
626
|
+
return status === 404 || status === 409 || status === 410;
|
|
627
|
+
}
|
|
628
|
+
var ActivityRefinementCoordinator = class {
|
|
629
|
+
reporter;
|
|
630
|
+
callbacks;
|
|
631
|
+
lifecycles = /* @__PURE__ */ new Map();
|
|
632
|
+
requestLifecycles = /* @__PURE__ */ new Map();
|
|
633
|
+
pendingMarkers = /* @__PURE__ */ new Map();
|
|
634
|
+
registeredRequests = /* @__PURE__ */ new Set();
|
|
635
|
+
settledRequests = /* @__PURE__ */ new Set();
|
|
636
|
+
activeLifecycle = null;
|
|
637
|
+
generation = 0;
|
|
638
|
+
constructor(reporter, callbacks) {
|
|
639
|
+
this.reporter = reporter;
|
|
640
|
+
this.callbacks = callbacks;
|
|
641
|
+
}
|
|
642
|
+
registerRequest(requestId) {
|
|
643
|
+
if (this.reporter.refinement) {
|
|
644
|
+
this.registeredRequests.add(requestId);
|
|
645
|
+
}
|
|
382
646
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
647
|
+
report(recognitionId, match, captureGeneration, requestId = null) {
|
|
648
|
+
const coordinatorGeneration = this.generation;
|
|
649
|
+
const hasMarker = typeof match.audioMarker?.code === "string" && match.audioMarker.code.length > 0;
|
|
650
|
+
const refinement = this.reporter.refinement;
|
|
651
|
+
const requestCanRefine = requestId !== null && !this.settledRequests.has(requestId);
|
|
652
|
+
const pendingMarker = requestId === null ? void 0 : this.pendingMarkers.get(requestId);
|
|
653
|
+
if (requestId !== null) {
|
|
654
|
+
this.pendingMarkers.delete(requestId);
|
|
655
|
+
this.registeredRequests.delete(requestId);
|
|
656
|
+
this.settledRequests.delete(requestId);
|
|
657
|
+
}
|
|
658
|
+
if (refinement && this.activeLifecycle) {
|
|
659
|
+
const now = this.now();
|
|
660
|
+
const activeLifecycle = this.activeLifecycle;
|
|
661
|
+
if (this.isContinuousMatch(activeLifecycle, match, captureGeneration, now)) {
|
|
662
|
+
this.renewContinuity(activeLifecycle, now);
|
|
663
|
+
if (requestId !== null) {
|
|
664
|
+
if (requestCanRefine && this.canAcceptMarker(activeLifecycle)) {
|
|
665
|
+
this.requestLifecycles.set(requestId, activeLifecycle);
|
|
666
|
+
} else {
|
|
667
|
+
this.requestLifecycles.delete(requestId);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (pendingMarker) {
|
|
671
|
+
this.claimMarker(activeLifecycle, pendingMarker);
|
|
672
|
+
}
|
|
673
|
+
if (hasMarker && match.audioMarker) {
|
|
674
|
+
this.claimMarker(activeLifecycle, match.audioMarker);
|
|
675
|
+
}
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
this.sealLifecycle(activeLifecycle);
|
|
679
|
+
}
|
|
680
|
+
const refinementExpected = Boolean(refinement && !hasMarker && requestCanRefine);
|
|
681
|
+
let lifecycle = null;
|
|
682
|
+
if (refinement) {
|
|
683
|
+
const now = this.now();
|
|
684
|
+
lifecycle = {
|
|
685
|
+
recognitionId,
|
|
686
|
+
captureGeneration,
|
|
687
|
+
match,
|
|
688
|
+
deadline: now + ACTIVITY_REFINEMENT_WINDOW_MS,
|
|
689
|
+
lastObservedAt: now,
|
|
690
|
+
activityId: null,
|
|
691
|
+
markerMatch: hasMarker ? match : null,
|
|
692
|
+
markerCode: hasMarker ? match.audioMarker?.code ?? null : null,
|
|
693
|
+
refinementTimer: null,
|
|
694
|
+
continuityTimer: null,
|
|
695
|
+
createSettled: false,
|
|
696
|
+
refinementStarted: false,
|
|
697
|
+
refinementInFlight: false,
|
|
698
|
+
refinementClosed: !refinementExpected,
|
|
699
|
+
sealed: false
|
|
700
|
+
};
|
|
701
|
+
const createdLifecycle = lifecycle;
|
|
702
|
+
if (refinementExpected) {
|
|
703
|
+
lifecycle.refinementTimer = setTimeout(() => {
|
|
704
|
+
this.closeRefinement(createdLifecycle);
|
|
705
|
+
}, ACTIVITY_REFINEMENT_WINDOW_MS);
|
|
706
|
+
}
|
|
707
|
+
this.scheduleContinuityExpiry(lifecycle);
|
|
708
|
+
this.lifecycles.set(recognitionId, lifecycle);
|
|
709
|
+
if (requestId !== null && refinementExpected) {
|
|
710
|
+
this.requestLifecycles.set(requestId, lifecycle);
|
|
711
|
+
}
|
|
712
|
+
this.activeLifecycle = lifecycle;
|
|
713
|
+
if (pendingMarker && refinementExpected) {
|
|
714
|
+
this.claimMarker(lifecycle, pendingMarker);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
const recognitionTiming = captureActivityRecognitionTiming(match);
|
|
718
|
+
void createMatchedMaterialActivity(
|
|
719
|
+
this.reporter,
|
|
720
|
+
match,
|
|
721
|
+
() => Boolean(lifecycle && this.isRefinementActive(lifecycle)),
|
|
722
|
+
recognitionTiming
|
|
723
|
+
).then(
|
|
724
|
+
(result) => {
|
|
725
|
+
if (coordinatorGeneration === this.generation && result.campaign && this.callbacks.isCurrentCapture(captureGeneration)) {
|
|
726
|
+
this.callbacks.emitCampaign(result.campaign);
|
|
727
|
+
}
|
|
728
|
+
if (coordinatorGeneration !== this.generation || !lifecycle || this.lifecycles.get(recognitionId) !== lifecycle) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
lifecycle.createSettled = true;
|
|
732
|
+
lifecycle.activityId = result.activityId;
|
|
733
|
+
if (!result.creationAttempted) {
|
|
734
|
+
this.deleteLifecycle(lifecycle);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (!result.refinementExpected) {
|
|
738
|
+
this.closeRefinement(lifecycle);
|
|
739
|
+
this.maybeDeleteSealed(lifecycle);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (lifecycle.markerMatch) {
|
|
743
|
+
if (lifecycle.activityId) {
|
|
744
|
+
this.startRefinement(lifecycle);
|
|
745
|
+
} else {
|
|
746
|
+
this.failMissingActivityId(lifecycle);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
this.maybeDeleteSealed(lifecycle);
|
|
750
|
+
},
|
|
751
|
+
(error) => {
|
|
752
|
+
if (lifecycle) {
|
|
753
|
+
lifecycle.createSettled = true;
|
|
754
|
+
this.deleteLifecycle(lifecycle);
|
|
755
|
+
}
|
|
756
|
+
if (coordinatorGeneration === this.generation && this.callbacks.isCurrentCapture(captureGeneration)) {
|
|
757
|
+
this.callbacks.emitError("activity", error);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
);
|
|
388
761
|
}
|
|
389
|
-
|
|
390
|
-
|
|
762
|
+
associate(requestId, match, captureGeneration) {
|
|
763
|
+
const lifecycle = this.activeLifecycle;
|
|
764
|
+
this.registeredRequests.delete(requestId);
|
|
765
|
+
if (!lifecycle || !match) {
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
const now = this.now();
|
|
769
|
+
if (!this.isContinuousMatch(lifecycle, match, captureGeneration, now)) {
|
|
770
|
+
this.sealLifecycle(lifecycle);
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
this.renewContinuity(lifecycle, now);
|
|
774
|
+
const pendingMarker = this.pendingMarkers.get(requestId);
|
|
775
|
+
if (this.canAcceptMarker(lifecycle)) {
|
|
776
|
+
this.requestLifecycles.set(requestId, lifecycle);
|
|
777
|
+
} else {
|
|
778
|
+
this.requestLifecycles.delete(requestId);
|
|
779
|
+
}
|
|
780
|
+
if (pendingMarker && this.canAcceptMarker(lifecycle)) {
|
|
781
|
+
this.pendingMarkers.delete(requestId);
|
|
782
|
+
this.claimMarker(lifecycle, pendingMarker);
|
|
783
|
+
} else if (pendingMarker) {
|
|
784
|
+
this.pendingMarkers.delete(requestId);
|
|
785
|
+
}
|
|
786
|
+
return true;
|
|
391
787
|
}
|
|
392
|
-
|
|
393
|
-
|
|
788
|
+
discardRequest(requestId) {
|
|
789
|
+
this.registeredRequests.delete(requestId);
|
|
790
|
+
this.pendingMarkers.delete(requestId);
|
|
791
|
+
this.requestLifecycles.delete(requestId);
|
|
792
|
+
this.settledRequests.delete(requestId);
|
|
793
|
+
}
|
|
794
|
+
settleRequest(requestId) {
|
|
795
|
+
const awaitingAssociation = this.registeredRequests.delete(requestId);
|
|
796
|
+
this.pendingMarkers.delete(requestId);
|
|
797
|
+
this.requestLifecycles.delete(requestId);
|
|
798
|
+
if (awaitingAssociation) {
|
|
799
|
+
this.settledRequests.add(requestId);
|
|
800
|
+
}
|
|
394
801
|
}
|
|
395
|
-
|
|
396
|
-
|
|
802
|
+
clearActive(captureGeneration) {
|
|
803
|
+
if (this.activeLifecycle?.captureGeneration === captureGeneration) {
|
|
804
|
+
this.sealLifecycle(this.activeLifecycle);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
refine(recognitionId, marker, captureGeneration) {
|
|
808
|
+
if (!this.reporter.refinement) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
const lifecycle = this.requestLifecycles.get(recognitionId);
|
|
812
|
+
const markerCode = marker.code;
|
|
813
|
+
if (typeof markerCode !== "string" || markerCode.length === 0) {
|
|
814
|
+
this.settleRequest(recognitionId);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
if (!lifecycle || lifecycle.captureGeneration !== captureGeneration || lifecycle.markerCode !== null || !this.callbacks.isCurrentCapture(captureGeneration)) {
|
|
818
|
+
if (!lifecycle && this.registeredRequests.has(recognitionId) && typeof markerCode === "string" && markerCode.length > 0 && this.callbacks.isCurrentCapture(captureGeneration)) {
|
|
819
|
+
this.pendingMarkers.set(recognitionId, marker);
|
|
820
|
+
}
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (!this.canAcceptMarker(lifecycle)) {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
this.claimMarker(lifecycle, marker);
|
|
827
|
+
}
|
|
828
|
+
reset() {
|
|
829
|
+
this.generation += 1;
|
|
830
|
+
for (const lifecycle of this.lifecycles.values()) {
|
|
831
|
+
if (lifecycle.refinementTimer) {
|
|
832
|
+
clearTimeout(lifecycle.refinementTimer);
|
|
833
|
+
}
|
|
834
|
+
if (lifecycle.continuityTimer) {
|
|
835
|
+
clearTimeout(lifecycle.continuityTimer);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
this.lifecycles.clear();
|
|
839
|
+
this.requestLifecycles.clear();
|
|
840
|
+
this.pendingMarkers.clear();
|
|
841
|
+
this.registeredRequests.clear();
|
|
842
|
+
this.settledRequests.clear();
|
|
843
|
+
this.activeLifecycle = null;
|
|
844
|
+
}
|
|
845
|
+
startRefinement(lifecycle) {
|
|
846
|
+
if (lifecycle.refinementStarted || lifecycle.refinementClosed || !lifecycle.activityId || !lifecycle.markerMatch) {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
lifecycle.refinementStarted = true;
|
|
850
|
+
lifecycle.refinementInFlight = true;
|
|
851
|
+
void this.runRefinement(lifecycle).finally(() => {
|
|
852
|
+
lifecycle.refinementInFlight = false;
|
|
853
|
+
this.maybeDeleteSealed(lifecycle);
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
failMissingActivityId(lifecycle) {
|
|
857
|
+
if (!this.isRefinementActive(lifecycle)) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
this.closeRefinement(lifecycle);
|
|
861
|
+
this.callbacks.emitError(
|
|
862
|
+
"activity-refinement",
|
|
863
|
+
new Error("Activity refinement requires a non-empty activity_id from the create response")
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
async runRefinement(lifecycle) {
|
|
867
|
+
const refinement = this.reporter.refinement;
|
|
868
|
+
if (!refinement || !lifecycle.activityId || !lifecycle.markerMatch) {
|
|
869
|
+
this.closeRefinement(lifecycle);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
const maxAttempts = Math.min(
|
|
873
|
+
MAX_REFINEMENT_ATTEMPTS,
|
|
874
|
+
Math.max(
|
|
875
|
+
1,
|
|
876
|
+
Number.isSafeInteger(refinement.maxAttempts) ? refinement.maxAttempts ?? DEFAULT_MAX_REFINEMENT_ATTEMPTS : DEFAULT_MAX_REFINEMENT_ATTEMPTS
|
|
877
|
+
)
|
|
878
|
+
);
|
|
879
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
880
|
+
if (!this.isRefinementActive(lifecycle)) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
const now = this.now();
|
|
884
|
+
if (now > lifecycle.deadline) {
|
|
885
|
+
this.closeRefinement(lifecycle);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
try {
|
|
889
|
+
const result = await refineMatchedMaterialActivity(
|
|
890
|
+
this.reporter,
|
|
891
|
+
lifecycle.markerMatch,
|
|
892
|
+
{
|
|
893
|
+
operation: "refine",
|
|
894
|
+
activityId: lifecycle.activityId,
|
|
895
|
+
attempt,
|
|
896
|
+
deadline: lifecycle.deadline
|
|
897
|
+
},
|
|
898
|
+
() => this.isRefinementActive(lifecycle)
|
|
899
|
+
);
|
|
900
|
+
if (!this.isRefinementActive(lifecycle)) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
this.closeRefinement(lifecycle);
|
|
904
|
+
if (result.campaign) {
|
|
905
|
+
this.callbacks.emitCampaign(result.campaign);
|
|
906
|
+
}
|
|
907
|
+
return;
|
|
908
|
+
} catch (caught) {
|
|
909
|
+
if (caught instanceof ActivityReportingCancelledError) {
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
if (!this.isRefinementActive(lifecycle)) {
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
const error = normalizeError(caught);
|
|
916
|
+
const { status, detail } = statusAndDetail(error);
|
|
917
|
+
const failureContext = {
|
|
918
|
+
operation: "refine",
|
|
919
|
+
activityId: lifecycle.activityId,
|
|
920
|
+
attempt,
|
|
921
|
+
deadline: lifecycle.deadline,
|
|
922
|
+
error,
|
|
923
|
+
status,
|
|
924
|
+
detail
|
|
925
|
+
};
|
|
926
|
+
let retry = false;
|
|
927
|
+
try {
|
|
928
|
+
retry = !isStableTerminalStatus(status) && attempt < maxAttempts && (refinement.now?.() ?? Date.now()) <= lifecycle.deadline && (refinement.shouldRetry ? await refinement.shouldRetry(failureContext) : defaultShouldRetry(failureContext));
|
|
929
|
+
} catch (policyError) {
|
|
930
|
+
if (!this.isRefinementActive(lifecycle)) {
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
this.closeRefinement(lifecycle);
|
|
934
|
+
this.callbacks.emitError("activity-refinement", policyError);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
if (!this.isRefinementActive(lifecycle)) {
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
if (retry) {
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
this.closeRefinement(lifecycle);
|
|
944
|
+
this.callbacks.emitError("activity-refinement", error);
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
isTransportActive(lifecycle) {
|
|
950
|
+
return this.lifecycles.get(lifecycle.recognitionId) === lifecycle && this.callbacks.isCurrentCapture(lifecycle.captureGeneration);
|
|
951
|
+
}
|
|
952
|
+
isRefinementActive(lifecycle) {
|
|
953
|
+
if (!this.isTransportActive(lifecycle) || lifecycle.refinementClosed) {
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
const now = this.now();
|
|
957
|
+
if (now > lifecycle.deadline) {
|
|
958
|
+
this.closeRefinement(lifecycle);
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
return true;
|
|
962
|
+
}
|
|
963
|
+
canAcceptMarker(lifecycle) {
|
|
964
|
+
return lifecycle.markerCode === null && this.isRefinementActive(lifecycle);
|
|
965
|
+
}
|
|
966
|
+
claimMarker(lifecycle, marker) {
|
|
967
|
+
const markerCode = marker.code;
|
|
968
|
+
if (typeof markerCode !== "string" || markerCode.length === 0 || !this.canAcceptMarker(lifecycle)) {
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
lifecycle.markerCode = markerCode;
|
|
972
|
+
lifecycle.markerMatch = { ...lifecycle.match, audioMarker: marker };
|
|
973
|
+
if (lifecycle.activityId) {
|
|
974
|
+
this.startRefinement(lifecycle);
|
|
975
|
+
} else if (lifecycle.createSettled) {
|
|
976
|
+
this.failMissingActivityId(lifecycle);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
isContinuousMatch(lifecycle, match, captureGeneration, now) {
|
|
980
|
+
return !lifecycle.sealed && this.activeLifecycle === lifecycle && lifecycle.captureGeneration === captureGeneration && lifecycle.match.name === match.name && lifecycle.match.afpType === match.afpType && this.isTransportActive(lifecycle) && now - lifecycle.lastObservedAt <= ACTIVITY_CONTINUITY_GAP_MS;
|
|
981
|
+
}
|
|
982
|
+
renewContinuity(lifecycle, now) {
|
|
983
|
+
lifecycle.lastObservedAt = now;
|
|
984
|
+
this.scheduleContinuityExpiry(lifecycle);
|
|
985
|
+
}
|
|
986
|
+
scheduleContinuityExpiry(lifecycle) {
|
|
987
|
+
if (lifecycle.continuityTimer) {
|
|
988
|
+
clearTimeout(lifecycle.continuityTimer);
|
|
989
|
+
}
|
|
990
|
+
const remainingMs = lifecycle.lastObservedAt + ACTIVITY_CONTINUITY_GAP_MS - this.now();
|
|
991
|
+
lifecycle.continuityTimer = setTimeout(() => {
|
|
992
|
+
lifecycle.continuityTimer = null;
|
|
993
|
+
if (this.activeLifecycle !== lifecycle || lifecycle.sealed) {
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
const now = this.now();
|
|
997
|
+
if (now - lifecycle.lastObservedAt <= ACTIVITY_CONTINUITY_GAP_MS) {
|
|
998
|
+
this.scheduleContinuityExpiry(lifecycle);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
this.sealLifecycle(lifecycle);
|
|
1002
|
+
}, Math.max(1, remainingMs + 1));
|
|
1003
|
+
}
|
|
1004
|
+
sealLifecycle(lifecycle) {
|
|
1005
|
+
if (lifecycle.sealed) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
lifecycle.sealed = true;
|
|
1009
|
+
if (lifecycle.continuityTimer) {
|
|
1010
|
+
clearTimeout(lifecycle.continuityTimer);
|
|
1011
|
+
lifecycle.continuityTimer = null;
|
|
1012
|
+
}
|
|
1013
|
+
if (this.activeLifecycle === lifecycle) {
|
|
1014
|
+
this.activeLifecycle = null;
|
|
1015
|
+
}
|
|
1016
|
+
this.removeRequestAssociations(lifecycle);
|
|
1017
|
+
this.maybeDeleteSealed(lifecycle);
|
|
1018
|
+
}
|
|
1019
|
+
closeRefinement(lifecycle) {
|
|
1020
|
+
if (lifecycle.refinementClosed) {
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
lifecycle.refinementClosed = true;
|
|
1024
|
+
if (lifecycle.refinementTimer) {
|
|
1025
|
+
clearTimeout(lifecycle.refinementTimer);
|
|
1026
|
+
lifecycle.refinementTimer = null;
|
|
1027
|
+
}
|
|
1028
|
+
this.removeRequestAssociations(lifecycle);
|
|
1029
|
+
this.maybeDeleteSealed(lifecycle);
|
|
1030
|
+
}
|
|
1031
|
+
removeRequestAssociations(lifecycle) {
|
|
1032
|
+
for (const [requestId, requestLifecycle] of this.requestLifecycles) {
|
|
1033
|
+
if (requestLifecycle === lifecycle) {
|
|
1034
|
+
this.requestLifecycles.delete(requestId);
|
|
1035
|
+
this.pendingMarkers.delete(requestId);
|
|
1036
|
+
this.registeredRequests.delete(requestId);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
maybeDeleteSealed(lifecycle) {
|
|
1041
|
+
if (lifecycle.sealed && lifecycle.createSettled && !lifecycle.refinementInFlight) {
|
|
1042
|
+
this.deleteLifecycle(lifecycle);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
now() {
|
|
1046
|
+
return this.reporter.refinement && this.reporter.refinement.now ? this.reporter.refinement.now() : Date.now();
|
|
1047
|
+
}
|
|
1048
|
+
deleteLifecycle(lifecycle) {
|
|
1049
|
+
if (lifecycle.refinementTimer) {
|
|
1050
|
+
clearTimeout(lifecycle.refinementTimer);
|
|
1051
|
+
lifecycle.refinementTimer = null;
|
|
1052
|
+
}
|
|
1053
|
+
if (lifecycle.continuityTimer) {
|
|
1054
|
+
clearTimeout(lifecycle.continuityTimer);
|
|
1055
|
+
lifecycle.continuityTimer = null;
|
|
1056
|
+
}
|
|
1057
|
+
if (this.lifecycles.get(lifecycle.recognitionId) === lifecycle) {
|
|
1058
|
+
this.lifecycles.delete(lifecycle.recognitionId);
|
|
1059
|
+
}
|
|
1060
|
+
this.removeRequestAssociations(lifecycle);
|
|
1061
|
+
if (this.activeLifecycle === lifecycle) {
|
|
1062
|
+
this.activeLifecycle = null;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
397
1066
|
|
|
398
1067
|
// src/microphone.ts
|
|
399
1068
|
var WORKLET_PROCESSOR_NAME = "sori-microphone-capture";
|
|
@@ -518,11 +1187,11 @@ var defaultAudioGraphFactory = async (options) => {
|
|
|
518
1187
|
};
|
|
519
1188
|
|
|
520
1189
|
// src/microphone-matcher.ts
|
|
521
|
-
function
|
|
1190
|
+
function normalizeError2(error) {
|
|
522
1191
|
return error instanceof Error ? error : new Error(String(error));
|
|
523
1192
|
}
|
|
524
1193
|
function emitError(emitter, phase, error) {
|
|
525
|
-
const normalized =
|
|
1194
|
+
const normalized = normalizeError2(error);
|
|
526
1195
|
emitter.emit("error", { phase, error: normalized });
|
|
527
1196
|
return normalized;
|
|
528
1197
|
}
|
|
@@ -540,6 +1209,7 @@ var MicrophoneMatcher = class {
|
|
|
540
1209
|
captureSampleRate;
|
|
541
1210
|
audioGraphFactory;
|
|
542
1211
|
packSource;
|
|
1212
|
+
activityRefinement;
|
|
543
1213
|
options;
|
|
544
1214
|
extractor = null;
|
|
545
1215
|
graphController = null;
|
|
@@ -558,6 +1228,7 @@ var MicrophoneMatcher = class {
|
|
|
558
1228
|
latestAudioMarker = null;
|
|
559
1229
|
latestAudioMarkerDetection = null;
|
|
560
1230
|
audioMarkerRequestId = 0;
|
|
1231
|
+
activityRecognitionId = 0;
|
|
561
1232
|
hopSize = 0;
|
|
562
1233
|
diagnostics = {
|
|
563
1234
|
sampleCallbacks: 0,
|
|
@@ -579,6 +1250,11 @@ var MicrophoneMatcher = class {
|
|
|
579
1250
|
this.suspendContextOnStop = options.suspendContextOnStop ?? true;
|
|
580
1251
|
this.audioGraphFactory = defaultAudioGraphFactory;
|
|
581
1252
|
this.packSource = options.packSource;
|
|
1253
|
+
this.activityRefinement = options.activityReporter ? new ActivityRefinementCoordinator(options.activityReporter, {
|
|
1254
|
+
isCurrentCapture: (captureGeneration) => captureGeneration === this.captureGeneration && this.running,
|
|
1255
|
+
emitCampaign: (campaign) => this.events.emit("campaign", campaign),
|
|
1256
|
+
emitError: (phase, error) => emitError(this.events, phase, error)
|
|
1257
|
+
}) : null;
|
|
582
1258
|
}
|
|
583
1259
|
on(eventName, listener) {
|
|
584
1260
|
this.events.on(eventName, listener);
|
|
@@ -662,6 +1338,7 @@ var MicrophoneMatcher = class {
|
|
|
662
1338
|
try {
|
|
663
1339
|
this.ensureNotDestroyed();
|
|
664
1340
|
await this.prepare();
|
|
1341
|
+
this.activityRefinement?.reset();
|
|
665
1342
|
this.matchWindowOrThrow().reset();
|
|
666
1343
|
await this.matcherSession.clear();
|
|
667
1344
|
} catch (error) {
|
|
@@ -799,6 +1476,8 @@ var MicrophoneMatcher = class {
|
|
|
799
1476
|
this.diagnostics.readyQueries += 1;
|
|
800
1477
|
let matchedBest = null;
|
|
801
1478
|
let shouldEmitNoMatch = false;
|
|
1479
|
+
let audioMarkerRequestId = null;
|
|
1480
|
+
let associatedActivity = false;
|
|
802
1481
|
const handleMatch = (event) => {
|
|
803
1482
|
if (!event.best) {
|
|
804
1483
|
return;
|
|
@@ -815,8 +1494,23 @@ var MicrophoneMatcher = class {
|
|
|
815
1494
|
try {
|
|
816
1495
|
this.diagnostics.matchRequests += 1;
|
|
817
1496
|
const bestMatchPromise = this.matcherSession.bestMatch(query, this.matchConfig());
|
|
818
|
-
|
|
819
|
-
|
|
1497
|
+
audioMarkerRequestId = this.beginAudioMarkerDetection(captureGeneration);
|
|
1498
|
+
let observedBest;
|
|
1499
|
+
try {
|
|
1500
|
+
observedBest = await bestMatchPromise;
|
|
1501
|
+
} catch (error) {
|
|
1502
|
+
if (audioMarkerRequestId !== null) {
|
|
1503
|
+
this.activityRefinement?.discardRequest(audioMarkerRequestId);
|
|
1504
|
+
}
|
|
1505
|
+
throw error;
|
|
1506
|
+
}
|
|
1507
|
+
if (audioMarkerRequestId !== null) {
|
|
1508
|
+
associatedActivity = this.activityRefinement?.associate(
|
|
1509
|
+
audioMarkerRequestId,
|
|
1510
|
+
observedBest,
|
|
1511
|
+
captureGeneration
|
|
1512
|
+
) ?? false;
|
|
1513
|
+
}
|
|
820
1514
|
const distinctBest = this.withAudioMarkerForRequest(matchedBest, audioMarkerRequestId);
|
|
821
1515
|
matchedBest = distinctBest;
|
|
822
1516
|
} finally {
|
|
@@ -828,9 +1522,21 @@ var MicrophoneMatcher = class {
|
|
|
828
1522
|
}
|
|
829
1523
|
if (matchedBest) {
|
|
830
1524
|
this.events.emit("match", { best: matchedBest });
|
|
831
|
-
|
|
1525
|
+
this.activityRecognitionId += 1;
|
|
1526
|
+
this.reportCampaign(
|
|
1527
|
+
this.activityRecognitionId,
|
|
1528
|
+
matchedBest,
|
|
1529
|
+
captureGeneration,
|
|
1530
|
+
audioMarkerRequestId
|
|
1531
|
+
);
|
|
832
1532
|
} else if (shouldEmitNoMatch) {
|
|
1533
|
+
if (audioMarkerRequestId !== null && !associatedActivity) {
|
|
1534
|
+
this.activityRefinement?.discardRequest(audioMarkerRequestId);
|
|
1535
|
+
}
|
|
1536
|
+
this.activityRefinement?.clearActive(captureGeneration);
|
|
833
1537
|
this.events.emit("nomatch", {});
|
|
1538
|
+
} else if (audioMarkerRequestId !== null && !associatedActivity) {
|
|
1539
|
+
this.activityRefinement?.discardRequest(audioMarkerRequestId);
|
|
834
1540
|
}
|
|
835
1541
|
} while (this.pendingMatch || this.matchWindowOrThrow().hasReadyQuery());
|
|
836
1542
|
} catch (error) {
|
|
@@ -850,6 +1556,7 @@ var MicrophoneMatcher = class {
|
|
|
850
1556
|
this.latestAudioMarker = null;
|
|
851
1557
|
this.latestAudioMarkerDetection = null;
|
|
852
1558
|
this.audioMarkerRequestId += 1;
|
|
1559
|
+
this.activityRefinement?.reset();
|
|
853
1560
|
this.matchWindowOrThrow().reset();
|
|
854
1561
|
await this.extractor?.reset();
|
|
855
1562
|
}
|
|
@@ -867,6 +1574,7 @@ var MicrophoneMatcher = class {
|
|
|
867
1574
|
}
|
|
868
1575
|
const requestId = this.audioMarkerRequestId + 1;
|
|
869
1576
|
this.audioMarkerRequestId = requestId;
|
|
1577
|
+
this.activityRefinement?.registerRequest(requestId);
|
|
870
1578
|
const markerPcm = float32ToPcm16Le(this.markerPcmBuffer);
|
|
871
1579
|
void this.detectAudioMarkerForRequest(captureGeneration, requestId, markerPcm);
|
|
872
1580
|
return requestId;
|
|
@@ -882,8 +1590,10 @@ var MicrophoneMatcher = class {
|
|
|
882
1590
|
}
|
|
883
1591
|
this.latestAudioMarkerDetection = { requestId, detection };
|
|
884
1592
|
this.emitAudioMarkerIfChanged(detection);
|
|
1593
|
+
this.activityRefinement?.refine(requestId, detection, captureGeneration);
|
|
885
1594
|
return detection;
|
|
886
1595
|
} catch (error) {
|
|
1596
|
+
this.activityRefinement?.settleRequest(requestId);
|
|
887
1597
|
emitError(this.events, "audiomarker", error);
|
|
888
1598
|
return null;
|
|
889
1599
|
}
|
|
@@ -976,19 +1686,17 @@ var MicrophoneMatcher = class {
|
|
|
976
1686
|
}
|
|
977
1687
|
return this.matchWindow;
|
|
978
1688
|
}
|
|
979
|
-
|
|
1689
|
+
reportCampaign(recognitionId, match, captureGeneration, audioMarkerRequestId) {
|
|
980
1690
|
const reporter = this.options.activityReporter;
|
|
981
1691
|
if (!reporter) {
|
|
982
1692
|
return;
|
|
983
1693
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
emitError(this.events, "activity", error);
|
|
991
|
-
}
|
|
1694
|
+
this.activityRefinement?.report(
|
|
1695
|
+
recognitionId,
|
|
1696
|
+
match,
|
|
1697
|
+
captureGeneration,
|
|
1698
|
+
audioMarkerRequestId
|
|
1699
|
+
);
|
|
992
1700
|
}
|
|
993
1701
|
};
|
|
994
1702
|
function appendFloat32(left, right) {
|
|
@@ -1073,6 +1781,8 @@ function createLocalStorageSessionManager(options) {
|
|
|
1073
1781
|
|
|
1074
1782
|
// src/audio-recognizer.ts
|
|
1075
1783
|
var DEFAULT_SORI_API_ENDPOINT = "https://console.soriapi.com/api";
|
|
1784
|
+
var MAX_API_ENDPOINT_LENGTH = 32 * 1024;
|
|
1785
|
+
var ASCII_SLASH_CODE_UNIT = 47;
|
|
1076
1786
|
var FORWARDED_EVENTS = [
|
|
1077
1787
|
"ready",
|
|
1078
1788
|
"microphoneready",
|
|
@@ -1100,12 +1810,24 @@ function ensureNonEmptyString(name, value) {
|
|
|
1100
1810
|
}
|
|
1101
1811
|
return value;
|
|
1102
1812
|
}
|
|
1103
|
-
function
|
|
1104
|
-
const value = String(endpoint)
|
|
1813
|
+
function stringifyAndValidateApiEndpoint(endpoint) {
|
|
1814
|
+
const value = String(endpoint);
|
|
1815
|
+
if (value.length > MAX_API_ENDPOINT_LENGTH) {
|
|
1816
|
+
throw new Error(
|
|
1817
|
+
`AudioRecognizer apiEndpoint exceeds maximum length of ${MAX_API_ENDPOINT_LENGTH} UTF-16 code units`
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1105
1820
|
return value;
|
|
1106
1821
|
}
|
|
1822
|
+
function normalizeValidatedEndpointPrefix(value) {
|
|
1823
|
+
let end = value.length;
|
|
1824
|
+
while (end > 0 && value.charCodeAt(end - 1) === ASCII_SLASH_CODE_UNIT) {
|
|
1825
|
+
end -= 1;
|
|
1826
|
+
}
|
|
1827
|
+
return end === value.length ? value : value.slice(0, end);
|
|
1828
|
+
}
|
|
1107
1829
|
function joinEndpoint(prefix, path) {
|
|
1108
|
-
return `${
|
|
1830
|
+
return `${prefix}${path.startsWith("/") ? path : `/${path}`}`;
|
|
1109
1831
|
}
|
|
1110
1832
|
function createEphemeralKeyResolver(endpoint, fetcher, requestInit) {
|
|
1111
1833
|
return async () => {
|
|
@@ -1133,8 +1855,15 @@ function resolveRecognizerOptions(options) {
|
|
|
1133
1855
|
const activityReportingDisabled = options.activityReporter === false;
|
|
1134
1856
|
const customActivityReporter = activityReportingDisabled ? void 0 : options.activityReporter;
|
|
1135
1857
|
const appId = legacyAuth?.appId ?? options.appId;
|
|
1136
|
-
const
|
|
1137
|
-
const
|
|
1858
|
+
const configuredAuthEndpoint = legacyAuth?.endpoint ?? options.authEndpoint;
|
|
1859
|
+
const configuredActivityEndpoint = activityReportingDisabled ? void 0 : customActivityReporter?.endpoint ?? options.activityEndpoint;
|
|
1860
|
+
const derivesAuthEndpoint = configuredAuthEndpoint == null;
|
|
1861
|
+
const derivesActivityEndpoint = !activityReportingDisabled && configuredActivityEndpoint == null;
|
|
1862
|
+
const derivesApiEndpoint = derivesAuthEndpoint || derivesActivityEndpoint;
|
|
1863
|
+
const validatedApiEndpoint = options.apiEndpoint !== void 0 || derivesApiEndpoint ? stringifyAndValidateApiEndpoint(apiEndpoint) : void 0;
|
|
1864
|
+
const normalizedApiEndpoint = derivesApiEndpoint ? normalizeValidatedEndpointPrefix(validatedApiEndpoint) : void 0;
|
|
1865
|
+
const authEndpoint = configuredAuthEndpoint ?? joinEndpoint(normalizedApiEndpoint, "/auth");
|
|
1866
|
+
const activityEndpoint = activityReportingDisabled ? void 0 : configuredActivityEndpoint ?? joinEndpoint(normalizedApiEndpoint, "/activity/");
|
|
1138
1867
|
const ephemeralKey = legacyAuth?.ephemeralKey ?? options.ephemeralKey ?? options.ephemeraKey ?? (legacyAuth?.ephemeralKeyEndpoint ?? options.ephemeralKeyEndpoint ? createEphemeralKeyResolver(
|
|
1139
1868
|
legacyAuth?.ephemeralKeyEndpoint ?? options.ephemeralKeyEndpoint,
|
|
1140
1869
|
legacyAuth?.ephemeralKeyFetch ?? options.ephemeralKeyFetch ?? legacyAuth?.fetch ?? sharedFetch,
|
|
@@ -1143,7 +1872,8 @@ function resolveRecognizerOptions(options) {
|
|
|
1143
1872
|
const activityReporter = activityEndpoint === void 0 ? void 0 : {
|
|
1144
1873
|
...customActivityReporter,
|
|
1145
1874
|
endpoint: activityEndpoint,
|
|
1146
|
-
fetch: customActivityReporter?.fetch ?? sharedFetch
|
|
1875
|
+
fetch: customActivityReporter?.fetch ?? sharedFetch,
|
|
1876
|
+
refinement: customActivityReporter ? customActivityReporter.refinement : {}
|
|
1147
1877
|
};
|
|
1148
1878
|
return {
|
|
1149
1879
|
...options,
|
|
@@ -1316,10 +2046,12 @@ var AudioRecognizer = class {
|
|
|
1316
2046
|
// src/index.ts
|
|
1317
2047
|
import { AudioFingerprintType as AudioFingerprintType3 } from "@sorisdk/matcher";
|
|
1318
2048
|
export {
|
|
2049
|
+
ACTIVITY_REFINEMENT_WINDOW_MS,
|
|
1319
2050
|
AudioFingerprintType3 as AudioFingerprintType,
|
|
1320
2051
|
AudioRecognizer,
|
|
1321
2052
|
DEFAULT_BROWSER_AUDIOPACK_VERSION,
|
|
1322
2053
|
DEFAULT_SORI_API_ENDPOINT,
|
|
2054
|
+
MAX_API_ENDPOINT_LENGTH,
|
|
1323
2055
|
MicrophoneMatcher,
|
|
1324
2056
|
authenticateAndLoadAudioPack,
|
|
1325
2057
|
createLocalStorageAudioPackStore,
|