@sorisdk/web-audio 0.6.7 → 0.6.8
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 +51 -11
- package/dist/generated/window_bg.wasm +0 -0
- package/dist/index.d.ts +9 -8
- package/dist/index.js +115 -24
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -13,6 +13,36 @@ Public integration guide: https://docs.soriapi.com/ko/integration/web
|
|
|
13
13
|
- An ephemeral key issued by your server
|
|
14
14
|
- Microphone permission granted by the user
|
|
15
15
|
|
|
16
|
+
## Unreleased: Console-resolved audio marker events
|
|
17
|
+
|
|
18
|
+
The public `audiomarker` event now contains only the marker identity managed in
|
|
19
|
+
SORI Console. The SDK emits it after a successful activity POST or refinement
|
|
20
|
+
PUT returns both a non-blank `audio_marker_id` and `audio_marker_name`:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
recognizer.on("audiomarker", ({ marker }) => {
|
|
24
|
+
console.log(marker.id, marker.name);
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The event is independent from campaign mapping and can be emitted when the
|
|
29
|
+
activity response has no campaign. Repeated responses within one continuous
|
|
30
|
+
material segment emit once; an intervening material, no-match, capture reset,
|
|
31
|
+
or expired continuity gap starts a new segment that may emit again.
|
|
32
|
+
|
|
33
|
+
Local WASM detection and labels such as `code_000` remain private inputs to the
|
|
34
|
+
activity request. They are no longer present on public `audiomarker` or `match`
|
|
35
|
+
events, `campaign.match`, the exported `DetectedMatch`, or the
|
|
36
|
+
`@sorisdk/web-audio` type exports. Custom request and campaign mappers receive
|
|
37
|
+
the public codebook-free match; the SDK adds its private `trait.marker` only to
|
|
38
|
+
the outgoing activity payload after request mapping.
|
|
39
|
+
|
|
40
|
+
This deliberately replaces the 0.6.7-and-earlier event shape. Migrate from
|
|
41
|
+
`event.marker` as a nullable string and `event.detection` as a raw detector
|
|
42
|
+
result to the non-null `event.marker.id` and `event.marker.name` fields above.
|
|
43
|
+
There is no legacy fallback: disabled activity reporting, failed transport, or
|
|
44
|
+
a response with a missing or malformed id/name pair emits no public marker.
|
|
45
|
+
|
|
16
46
|
## Standalone CDN
|
|
17
47
|
|
|
18
48
|
For a browser application that does not use npm or a bundler, import the
|
|
@@ -22,7 +52,7 @@ configuration, or Node-based asset server is required:
|
|
|
22
52
|
```html
|
|
23
53
|
<script type="module">
|
|
24
54
|
import { AudioRecognizer } from
|
|
25
|
-
"https://cdn.iplateia.com/web/sorisdk/v0.6.
|
|
55
|
+
"https://cdn.iplateia.com/web/sorisdk/v0.6.8/sori-web-audio.mjs";
|
|
26
56
|
|
|
27
57
|
const recognizer = new AudioRecognizer({
|
|
28
58
|
appId: "YOUR_APP_ID",
|
|
@@ -49,7 +79,7 @@ configuration, or Node-based asset server is required:
|
|
|
49
79
|
```
|
|
50
80
|
|
|
51
81
|
Static URL imports are valid inside `<script type="module">`. Dynamic
|
|
52
|
-
`await import("https://cdn.iplateia.com/web/sorisdk/v0.6.
|
|
82
|
+
`await import("https://cdn.iplateia.com/web/sorisdk/v0.6.8/sori-web-audio.mjs")`
|
|
53
83
|
is an optional alternative when the SDK should be loaded conditionally.
|
|
54
84
|
|
|
55
85
|
Pin an exact version in production. Do not construct a mutable `latest` URL.
|
|
@@ -119,7 +149,7 @@ An import map can give the standalone URL the npm package name:
|
|
|
119
149
|
<script type="importmap">
|
|
120
150
|
{
|
|
121
151
|
"imports": {
|
|
122
|
-
"@sorisdk/web-audio": "https://cdn.iplateia.com/web/sorisdk/v0.6.
|
|
152
|
+
"@sorisdk/web-audio": "https://cdn.iplateia.com/web/sorisdk/v0.6.8/sori-web-audio.mjs"
|
|
123
153
|
}
|
|
124
154
|
}
|
|
125
155
|
</script>
|
|
@@ -171,6 +201,10 @@ recognizer.on("campaign", (event) => {
|
|
|
171
201
|
console.log(event.activityId, event.campaign);
|
|
172
202
|
});
|
|
173
203
|
|
|
204
|
+
recognizer.on("audiomarker", ({ marker }) => {
|
|
205
|
+
console.log(marker.id, marker.name);
|
|
206
|
+
});
|
|
207
|
+
|
|
174
208
|
recognizer.on("error", ({ error }) => {
|
|
175
209
|
console.error(error);
|
|
176
210
|
});
|
|
@@ -200,10 +234,12 @@ server activity:
|
|
|
200
234
|
3. A marker-free match with an active marker-scan request also includes
|
|
201
235
|
top-level `refinement_expected: true`.
|
|
202
236
|
4. The returned `activity_id` is retained for 30 seconds.
|
|
203
|
-
5. If the same recognition gains
|
|
237
|
+
5. If the same recognition gains a private local marker detection, the SDK sends
|
|
204
238
|
`PUT /api/activity/{activity_id}` with the same material and
|
|
205
239
|
`trait.marker`.
|
|
206
|
-
6.
|
|
240
|
+
6. A valid response marker id/name pair emits one public structured
|
|
241
|
+
`audiomarker` event, even when the response has no campaign.
|
|
242
|
+
7. The refined `campaign` event keeps the same non-empty `activityId`, so an
|
|
207
243
|
application can replace the earlier campaign row deterministically.
|
|
208
244
|
|
|
209
245
|
A consecutive match for the same material and fingerprint type renews a local
|
|
@@ -228,10 +264,10 @@ refine the earlier A. Marker evidence arriving after a segment is sealed is
|
|
|
228
264
|
ignored for that segment; create or PUT transport that already started may
|
|
229
265
|
finish under the existing capture, deadline, and cancellation guards.
|
|
230
266
|
|
|
231
|
-
Ordinary fingerprint `match`
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
267
|
+
Ordinary fingerprint `match` events remain independently observable and never
|
|
268
|
+
contain the private detector result. Public `audiomarker` delivery is sourced
|
|
269
|
+
only from a completed activity response. It is independent from campaign
|
|
270
|
+
availability but does prove that the server resolved the marker id and name.
|
|
235
271
|
|
|
236
272
|
Recognition timing belongs only to the segment's first material POST. Later
|
|
237
273
|
same-material observations update only the internal `lastObservedAt` lease;
|
|
@@ -273,7 +309,10 @@ Existing `mapMatchToRequest` and `mapResponseToCampaign` hooks continue to
|
|
|
273
309
|
handle POST. Additive refinement hooks can override the PUT payload, response
|
|
274
310
|
mapping, endpoint, headers, fetcher, and retry policy. The stable terminal
|
|
275
311
|
statuses 404, 409, and 410 are never retried even if a custom policy returns
|
|
276
|
-
`true`.
|
|
312
|
+
`true`. Match arguments passed to these hooks omit private detector state. When
|
|
313
|
+
local detection succeeds, the SDK injects its private marker into the outgoing
|
|
314
|
+
`trait.marker` after request mapping, while resolved public identity is parsed
|
|
315
|
+
directly from the unmodified activity response before campaign delivery.
|
|
277
316
|
|
|
278
317
|
`recognized_at` and `position` are optional public request properties for
|
|
279
318
|
source compatibility, but they are SDK-owned transport values. After a custom
|
|
@@ -404,7 +443,8 @@ fingerprint chunks and the existing match-window cadence are unchanged.
|
|
|
404
443
|
|
|
405
444
|
1. Prepare your `appId` and ephemeral key flow.
|
|
406
445
|
2. Create an `AudioRecognizer`.
|
|
407
|
-
3. Subscribe to the events you need, such as `campaign`, `
|
|
446
|
+
3. Subscribe to the events you need, such as `campaign`, `audiomarker`, `match`,
|
|
447
|
+
and `error`.
|
|
408
448
|
4. Call `start()` to begin recognition.
|
|
409
449
|
5. Call `stop()` or `destroy()` when leaving the page or stopping capture.
|
|
410
450
|
|
|
Binary file
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { MatcherEphemeralAuthOptions, InitMatcherOptions, MatcherEphemeralAuthResult,
|
|
2
|
-
export { AudioFingerprintType, AudioMarkerConfig,
|
|
1
|
+
import { MatcherEphemeralAuthOptions, InitMatcherOptions, MatcherEphemeralAuthResult, PackSource, MatchResult, MatcherSessionOptions, AudioFingerprintType } from '@sorisdk/matcher';
|
|
2
|
+
export { AudioFingerprintType, AudioMarkerConfig, PackSource } from '@sorisdk/matcher';
|
|
3
3
|
import { InitAfpgenOptions } from '@sorisdk/afpgen';
|
|
4
4
|
|
|
5
5
|
declare const DEFAULT_BROWSER_AUDIOPACK_VERSION = "20220401000000";
|
|
@@ -53,11 +53,14 @@ interface InitMatchWindowWasmOptions {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
interface DetectedMatch extends Pick<MatchResult, "afpType" | "durationMillis" | "name" | "position" | "score"> {
|
|
56
|
-
|
|
56
|
+
}
|
|
57
|
+
/** SORI Console-managed identity resolved from a successful activity response. */
|
|
58
|
+
interface AudioMarkerIdentity {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly name: string;
|
|
57
61
|
}
|
|
58
62
|
interface AudioMarkerEvent {
|
|
59
|
-
marker:
|
|
60
|
-
detection: AudioMarkerDetection;
|
|
63
|
+
readonly marker: AudioMarkerIdentity;
|
|
61
64
|
}
|
|
62
65
|
interface MaterialActivityRequestPayload {
|
|
63
66
|
type: "material";
|
|
@@ -243,7 +246,6 @@ declare class MicrophoneMatcher {
|
|
|
243
246
|
private markerPcmBuffer;
|
|
244
247
|
private resampleBuffer;
|
|
245
248
|
private resampleOffset;
|
|
246
|
-
private latestAudioMarker;
|
|
247
249
|
private latestAudioMarkerDetection;
|
|
248
250
|
private audioMarkerRequestId;
|
|
249
251
|
private activityRecognitionId;
|
|
@@ -281,7 +283,6 @@ declare class MicrophoneMatcher {
|
|
|
281
283
|
private detectAudioMarkerForRequest;
|
|
282
284
|
private matchConfig;
|
|
283
285
|
private withAudioMarkerForRequest;
|
|
284
|
-
private emitAudioMarkerIfChanged;
|
|
285
286
|
private normalizeInputSamples;
|
|
286
287
|
private resampleToTargetRate;
|
|
287
288
|
private cleanupAfterStartFailure;
|
|
@@ -296,4 +297,4 @@ declare function requestMicrophoneStream(mediaDevices: MediaDevices, mediaConstr
|
|
|
296
297
|
preferredSampleRate?: number;
|
|
297
298
|
}): Promise<MediaStream>;
|
|
298
299
|
|
|
299
|
-
export { ACTIVITY_REFINEMENT_WINDOW_MS, type ActivityRefinementFailureContext, type ActivityRefinementRequestContext, type AudioMarkerEvent, AudioRecognizer, type AudioRecognizerAuthOptions, type AudioRecognizerOptions, type AuthenticateAndLoadAudioPackOptions, type AuthenticateAndLoadAudioPackResult, type BrowserAudioPackLoader, type BrowserAudioPackState, type BrowserAudioPackStateStore, type BrowserSessionManager, type CreateLocalStorageAudioPackStoreOptions, type CreateLocalStorageSessionManagerOptions, type CreateMicrophoneMatcherOptions, type CreateMicrophoneMatcherWasmOptions, DEFAULT_BROWSER_AUDIOPACK_VERSION, DEFAULT_SORI_API_ENDPOINT, type DetectedMatch, MAX_API_ENDPOINT_LENGTH, type MaterialActivityRequestPayload, MicrophoneMatcher, type MicrophoneMatcherActivityRefinementOptions, type MicrophoneMatcherActivityReporterOptions, type MicrophoneMatcherCampaignEvent, type MicrophoneMatcherEventMap, type MicrophoneMatcherEventName, type MicrophoneMatcherListener, authenticateAndLoadAudioPack, createLocalStorageAudioPackStore, createLocalStorageSessionManager, createMicrophoneMatcher, requestMicrophoneStream };
|
|
300
|
+
export { ACTIVITY_REFINEMENT_WINDOW_MS, type ActivityRefinementFailureContext, type ActivityRefinementRequestContext, type AudioMarkerEvent, type AudioMarkerIdentity, AudioRecognizer, type AudioRecognizerAuthOptions, type AudioRecognizerOptions, type AuthenticateAndLoadAudioPackOptions, type AuthenticateAndLoadAudioPackResult, type BrowserAudioPackLoader, type BrowserAudioPackState, type BrowserAudioPackStateStore, type BrowserSessionManager, type CreateLocalStorageAudioPackStoreOptions, type CreateLocalStorageSessionManagerOptions, type CreateMicrophoneMatcherOptions, type CreateMicrophoneMatcherWasmOptions, DEFAULT_BROWSER_AUDIOPACK_VERSION, DEFAULT_SORI_API_ENDPOINT, type DetectedMatch, MAX_API_ENDPOINT_LENGTH, type MaterialActivityRequestPayload, MicrophoneMatcher, type MicrophoneMatcherActivityRefinementOptions, type MicrophoneMatcherActivityReporterOptions, type MicrophoneMatcherCampaignEvent, type MicrophoneMatcherEventMap, type MicrophoneMatcherEventName, type MicrophoneMatcherListener, authenticateAndLoadAudioPack, createLocalStorageAudioPackStore, createLocalStorageSessionManager, createMicrophoneMatcher, requestMicrophoneStream };
|
package/dist/index.js
CHANGED
|
@@ -354,6 +354,12 @@ var FingerprintMatchWindow = class {
|
|
|
354
354
|
}
|
|
355
355
|
};
|
|
356
356
|
|
|
357
|
+
// src/detected-match.ts
|
|
358
|
+
function toPublicDetectedMatch(match) {
|
|
359
|
+
const { audioMarker: _internalAudioMarker, ...publicMatch } = match;
|
|
360
|
+
return publicMatch;
|
|
361
|
+
}
|
|
362
|
+
|
|
357
363
|
// src/activity.ts
|
|
358
364
|
var ActivityReportingError = class extends Error {
|
|
359
365
|
status;
|
|
@@ -417,7 +423,7 @@ function validateRequestPayload(payload) {
|
|
|
417
423
|
}
|
|
418
424
|
async function resolveCreateRequestPayload(reporter, match) {
|
|
419
425
|
const payload = validateRequestPayload(
|
|
420
|
-
reporter.mapMatchToRequest ? await reporter.mapMatchToRequest(match) : defaultRequestPayload(match)
|
|
426
|
+
reporter.mapMatchToRequest ? await reporter.mapMatchToRequest(toPublicDetectedMatch(match)) : defaultRequestPayload(match)
|
|
421
427
|
);
|
|
422
428
|
if (!payload) {
|
|
423
429
|
return null;
|
|
@@ -428,7 +434,7 @@ async function resolveCreateRequestPayload(reporter, match) {
|
|
|
428
434
|
refinement_expected: _ignoredRefinementExpected,
|
|
429
435
|
...requestPayload
|
|
430
436
|
} = payload;
|
|
431
|
-
return requestPayload;
|
|
437
|
+
return withInternalAudioMarker(requestPayload, match);
|
|
432
438
|
}
|
|
433
439
|
async function resolveRefineRequestPayload(reporter, match, context) {
|
|
434
440
|
const refinement = reporter.refinement;
|
|
@@ -436,7 +442,7 @@ async function resolveRefineRequestPayload(reporter, match, context) {
|
|
|
436
442
|
return null;
|
|
437
443
|
}
|
|
438
444
|
const payload = validateRequestPayload(
|
|
439
|
-
refinement.mapMatchToRequest ? await refinement.mapMatchToRequest(match, context) : reporter.mapMatchToRequest ? await reporter.mapMatchToRequest(match) : defaultRequestPayload(match)
|
|
445
|
+
refinement.mapMatchToRequest ? await refinement.mapMatchToRequest(toPublicDetectedMatch(match), context) : reporter.mapMatchToRequest ? await reporter.mapMatchToRequest(toPublicDetectedMatch(match)) : defaultRequestPayload(match)
|
|
440
446
|
);
|
|
441
447
|
if (!payload) {
|
|
442
448
|
return null;
|
|
@@ -447,7 +453,20 @@ async function resolveRefineRequestPayload(reporter, match, context) {
|
|
|
447
453
|
refinement_expected: _ignoredRefinementExpected,
|
|
448
454
|
...requestPayload
|
|
449
455
|
} = payload;
|
|
450
|
-
return requestPayload;
|
|
456
|
+
return withInternalAudioMarker(requestPayload, match);
|
|
457
|
+
}
|
|
458
|
+
function withInternalAudioMarker(payload, match) {
|
|
459
|
+
const marker = match.audioMarker?.code;
|
|
460
|
+
if (typeof marker !== "string" || marker.length === 0) {
|
|
461
|
+
return payload;
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
...payload,
|
|
465
|
+
trait: {
|
|
466
|
+
...payload.trait,
|
|
467
|
+
marker
|
|
468
|
+
}
|
|
469
|
+
};
|
|
451
470
|
}
|
|
452
471
|
function defaultCampaignMapper(payload, match) {
|
|
453
472
|
const campaign = payload.campaign;
|
|
@@ -455,12 +474,23 @@ function defaultCampaignMapper(payload, match) {
|
|
|
455
474
|
return null;
|
|
456
475
|
}
|
|
457
476
|
return {
|
|
458
|
-
match,
|
|
477
|
+
match: toPublicDetectedMatch(match),
|
|
459
478
|
campaign,
|
|
460
479
|
activityId: typeof payload.activity_id === "string" ? payload.activity_id : null,
|
|
461
480
|
raw: payload
|
|
462
481
|
};
|
|
463
482
|
}
|
|
483
|
+
function resolvedAudioMarker(payload) {
|
|
484
|
+
if (!payload) {
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
const id = payload.audio_marker_id;
|
|
488
|
+
const name = payload.audio_marker_name;
|
|
489
|
+
if (typeof id !== "string" || id.trim().length === 0 || typeof name !== "string" || name.trim().length === 0) {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
return Object.freeze({ id, name });
|
|
493
|
+
}
|
|
464
494
|
async function resolveHeaders(reporter, refinement = false) {
|
|
465
495
|
const refinementHeaders = reporter.refinement && reporter.refinement.headers;
|
|
466
496
|
const baseHeaders = reporter.headers ? typeof reporter.headers === "function" ? await reporter.headers() : reporter.headers : void 0;
|
|
@@ -518,17 +548,28 @@ async function mapCreateResponse(reporter, data, match, refinementExpected) {
|
|
|
518
548
|
if (!data) {
|
|
519
549
|
return {
|
|
520
550
|
activityId: null,
|
|
551
|
+
audioMarker: null,
|
|
521
552
|
campaign: null,
|
|
522
553
|
creationAttempted: true,
|
|
523
554
|
refinementExpected
|
|
524
555
|
};
|
|
525
556
|
}
|
|
526
|
-
const
|
|
557
|
+
const audioMarker = resolvedAudioMarker(data);
|
|
558
|
+
let campaign;
|
|
559
|
+
let campaignMappingFailure;
|
|
560
|
+
try {
|
|
561
|
+
campaign = reporter.mapResponseToCampaign ? await reporter.mapResponseToCampaign(data, toPublicDetectedMatch(match)) : defaultCampaignMapper(data, match);
|
|
562
|
+
} catch (error) {
|
|
563
|
+
campaign = null;
|
|
564
|
+
campaignMappingFailure = { error };
|
|
565
|
+
}
|
|
527
566
|
return {
|
|
528
567
|
activityId: responseActivityId(data, campaign),
|
|
568
|
+
audioMarker,
|
|
529
569
|
campaign,
|
|
530
570
|
creationAttempted: true,
|
|
531
|
-
refinementExpected
|
|
571
|
+
refinementExpected,
|
|
572
|
+
...campaignMappingFailure ? { campaignMappingFailure } : {}
|
|
532
573
|
};
|
|
533
574
|
}
|
|
534
575
|
async function resolveRefinementEndpoint(reporter, activityId) {
|
|
@@ -544,6 +585,7 @@ async function createMatchedMaterialActivity(reporter, match, refinementExpected
|
|
|
544
585
|
if (!payload) {
|
|
545
586
|
return {
|
|
546
587
|
activityId: null,
|
|
588
|
+
audioMarker: null,
|
|
547
589
|
campaign: null,
|
|
548
590
|
creationAttempted: false,
|
|
549
591
|
refinementExpected: false
|
|
@@ -576,11 +618,11 @@ async function createMatchedMaterialActivity(reporter, match, refinementExpected
|
|
|
576
618
|
async function refineMatchedMaterialActivity(reporter, match, context, isActive = () => true) {
|
|
577
619
|
const refinement = reporter.refinement;
|
|
578
620
|
if (!refinement) {
|
|
579
|
-
return { activityId: null, campaign: null };
|
|
621
|
+
return { activityId: null, audioMarker: null, campaign: null };
|
|
580
622
|
}
|
|
581
623
|
const payload = await resolveRefineRequestPayload(reporter, match, context);
|
|
582
624
|
if (!payload) {
|
|
583
|
-
return { activityId: null, campaign: null };
|
|
625
|
+
return { activityId: null, audioMarker: null, campaign: null };
|
|
584
626
|
}
|
|
585
627
|
const endpoint = await resolveRefinementEndpoint(reporter, context.activityId);
|
|
586
628
|
const headers = await resolveHeaders(reporter, true);
|
|
@@ -596,8 +638,16 @@ async function refineMatchedMaterialActivity(reporter, match, context, isActive
|
|
|
596
638
|
if (!data) {
|
|
597
639
|
throw new ActivityReportingError("Activity refine failed: empty response", response.status);
|
|
598
640
|
}
|
|
599
|
-
const
|
|
600
|
-
|
|
641
|
+
const audioMarker = resolvedAudioMarker(data);
|
|
642
|
+
let mappedCampaign;
|
|
643
|
+
let campaignMappingFailure;
|
|
644
|
+
try {
|
|
645
|
+
mappedCampaign = refinement.mapResponseToCampaign ? await refinement.mapResponseToCampaign(data, toPublicDetectedMatch(match), context) : reporter.mapResponseToCampaign ? await reporter.mapResponseToCampaign(data, toPublicDetectedMatch(match)) : defaultCampaignMapper(data, match);
|
|
646
|
+
} catch (error) {
|
|
647
|
+
mappedCampaign = null;
|
|
648
|
+
campaignMappingFailure = { error };
|
|
649
|
+
}
|
|
650
|
+
const activityId = responseActivityId(data, mappedCampaign) ?? (campaignMappingFailure ? context.activityId : null);
|
|
601
651
|
if (activityId !== context.activityId) {
|
|
602
652
|
throw new ActivityReportingError(
|
|
603
653
|
"Activity refine failed: response activity_id does not match the requested activity",
|
|
@@ -605,7 +655,12 @@ async function refineMatchedMaterialActivity(reporter, match, context, isActive
|
|
|
605
655
|
);
|
|
606
656
|
}
|
|
607
657
|
const campaign = mappedCampaign ? { ...mappedCampaign, activityId: context.activityId } : null;
|
|
608
|
-
return {
|
|
658
|
+
return {
|
|
659
|
+
activityId,
|
|
660
|
+
audioMarker,
|
|
661
|
+
campaign,
|
|
662
|
+
...campaignMappingFailure ? { campaignMappingFailure } : {}
|
|
663
|
+
};
|
|
609
664
|
}
|
|
610
665
|
|
|
611
666
|
// src/activity-refinement.ts
|
|
@@ -634,6 +689,7 @@ var ActivityRefinementCoordinator = class {
|
|
|
634
689
|
registeredRequests = /* @__PURE__ */ new Set();
|
|
635
690
|
settledRequests = /* @__PURE__ */ new Set();
|
|
636
691
|
activeLifecycle = null;
|
|
692
|
+
activeMarkerSegment = null;
|
|
637
693
|
generation = 0;
|
|
638
694
|
constructor(reporter, callbacks) {
|
|
639
695
|
this.reporter = reporter;
|
|
@@ -646,6 +702,7 @@ var ActivityRefinementCoordinator = class {
|
|
|
646
702
|
}
|
|
647
703
|
report(recognitionId, match, captureGeneration, requestId = null) {
|
|
648
704
|
const coordinatorGeneration = this.generation;
|
|
705
|
+
const markerSegment = this.resolveMarkerSegment(match, captureGeneration);
|
|
649
706
|
const hasMarker = typeof match.audioMarker?.code === "string" && match.audioMarker.code.length > 0;
|
|
650
707
|
const refinement = this.reporter.refinement;
|
|
651
708
|
const requestCanRefine = requestId !== null && !this.settledRequests.has(requestId);
|
|
@@ -690,6 +747,7 @@ var ActivityRefinementCoordinator = class {
|
|
|
690
747
|
activityId: null,
|
|
691
748
|
markerMatch: hasMarker ? match : null,
|
|
692
749
|
markerCode: hasMarker ? match.audioMarker?.code ?? null : null,
|
|
750
|
+
markerSegment,
|
|
693
751
|
refinementTimer: null,
|
|
694
752
|
continuityTimer: null,
|
|
695
753
|
createSettled: false,
|
|
@@ -722,9 +780,15 @@ var ActivityRefinementCoordinator = class {
|
|
|
722
780
|
recognitionTiming
|
|
723
781
|
).then(
|
|
724
782
|
(result) => {
|
|
783
|
+
if (coordinatorGeneration === this.generation && result.audioMarker && this.callbacks.isCurrentCapture(captureGeneration)) {
|
|
784
|
+
this.emitResolvedAudioMarker(markerSegment, result.audioMarker);
|
|
785
|
+
}
|
|
725
786
|
if (coordinatorGeneration === this.generation && result.campaign && this.callbacks.isCurrentCapture(captureGeneration)) {
|
|
726
787
|
this.callbacks.emitCampaign(result.campaign);
|
|
727
788
|
}
|
|
789
|
+
if (coordinatorGeneration === this.generation && result.campaignMappingFailure && this.callbacks.isCurrentCapture(captureGeneration)) {
|
|
790
|
+
this.callbacks.emitError("activity", result.campaignMappingFailure.error);
|
|
791
|
+
}
|
|
728
792
|
if (coordinatorGeneration !== this.generation || !lifecycle || this.lifecycles.get(recognitionId) !== lifecycle) {
|
|
729
793
|
return;
|
|
730
794
|
}
|
|
@@ -803,6 +867,9 @@ var ActivityRefinementCoordinator = class {
|
|
|
803
867
|
if (this.activeLifecycle?.captureGeneration === captureGeneration) {
|
|
804
868
|
this.sealLifecycle(this.activeLifecycle);
|
|
805
869
|
}
|
|
870
|
+
if (this.activeMarkerSegment?.captureGeneration === captureGeneration) {
|
|
871
|
+
this.activeMarkerSegment = null;
|
|
872
|
+
}
|
|
806
873
|
}
|
|
807
874
|
refine(recognitionId, marker, captureGeneration) {
|
|
808
875
|
if (!this.reporter.refinement) {
|
|
@@ -841,6 +908,7 @@ var ActivityRefinementCoordinator = class {
|
|
|
841
908
|
this.registeredRequests.clear();
|
|
842
909
|
this.settledRequests.clear();
|
|
843
910
|
this.activeLifecycle = null;
|
|
911
|
+
this.activeMarkerSegment = null;
|
|
844
912
|
}
|
|
845
913
|
startRefinement(lifecycle) {
|
|
846
914
|
if (lifecycle.refinementStarted || lifecycle.refinementClosed || !lifecycle.activityId || !lifecycle.markerMatch) {
|
|
@@ -901,9 +969,18 @@ var ActivityRefinementCoordinator = class {
|
|
|
901
969
|
return;
|
|
902
970
|
}
|
|
903
971
|
this.closeRefinement(lifecycle);
|
|
972
|
+
if (result.audioMarker) {
|
|
973
|
+
this.emitResolvedAudioMarker(lifecycle.markerSegment, result.audioMarker);
|
|
974
|
+
}
|
|
904
975
|
if (result.campaign) {
|
|
905
976
|
this.callbacks.emitCampaign(result.campaign);
|
|
906
977
|
}
|
|
978
|
+
if (result.campaignMappingFailure) {
|
|
979
|
+
this.callbacks.emitError(
|
|
980
|
+
"activity-refinement",
|
|
981
|
+
result.campaignMappingFailure.error
|
|
982
|
+
);
|
|
983
|
+
}
|
|
907
984
|
return;
|
|
908
985
|
} catch (caught) {
|
|
909
986
|
if (caught instanceof ActivityReportingCancelledError) {
|
|
@@ -979,6 +1056,30 @@ var ActivityRefinementCoordinator = class {
|
|
|
979
1056
|
isContinuousMatch(lifecycle, match, captureGeneration, now) {
|
|
980
1057
|
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
1058
|
}
|
|
1059
|
+
resolveMarkerSegment(match, captureGeneration) {
|
|
1060
|
+
const now = this.now();
|
|
1061
|
+
const active = this.activeMarkerSegment;
|
|
1062
|
+
if (active && active.captureGeneration === captureGeneration && active.materialName === match.name && active.afpType === match.afpType && now - active.lastObservedAt <= ACTIVITY_CONTINUITY_GAP_MS) {
|
|
1063
|
+
active.lastObservedAt = now;
|
|
1064
|
+
return active;
|
|
1065
|
+
}
|
|
1066
|
+
const segment = {
|
|
1067
|
+
captureGeneration,
|
|
1068
|
+
materialName: match.name,
|
|
1069
|
+
afpType: match.afpType,
|
|
1070
|
+
lastObservedAt: now,
|
|
1071
|
+
emitted: false
|
|
1072
|
+
};
|
|
1073
|
+
this.activeMarkerSegment = segment;
|
|
1074
|
+
return segment;
|
|
1075
|
+
}
|
|
1076
|
+
emitResolvedAudioMarker(segment, marker) {
|
|
1077
|
+
if (segment.emitted) {
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
segment.emitted = true;
|
|
1081
|
+
this.callbacks.emitAudioMarker(Object.freeze({ marker }));
|
|
1082
|
+
}
|
|
982
1083
|
renewContinuity(lifecycle, now) {
|
|
983
1084
|
lifecycle.lastObservedAt = now;
|
|
984
1085
|
this.scheduleContinuityExpiry(lifecycle);
|
|
@@ -1225,7 +1326,6 @@ var MicrophoneMatcher = class {
|
|
|
1225
1326
|
markerPcmBuffer = new Float32Array(0);
|
|
1226
1327
|
resampleBuffer = new Float32Array(0);
|
|
1227
1328
|
resampleOffset = 0;
|
|
1228
|
-
latestAudioMarker = null;
|
|
1229
1329
|
latestAudioMarkerDetection = null;
|
|
1230
1330
|
audioMarkerRequestId = 0;
|
|
1231
1331
|
activityRecognitionId = 0;
|
|
@@ -1252,6 +1352,7 @@ var MicrophoneMatcher = class {
|
|
|
1252
1352
|
this.packSource = options.packSource;
|
|
1253
1353
|
this.activityRefinement = options.activityReporter ? new ActivityRefinementCoordinator(options.activityReporter, {
|
|
1254
1354
|
isCurrentCapture: (captureGeneration) => captureGeneration === this.captureGeneration && this.running,
|
|
1355
|
+
emitAudioMarker: (event) => this.events.emit("audiomarker", event),
|
|
1255
1356
|
emitCampaign: (campaign) => this.events.emit("campaign", campaign),
|
|
1256
1357
|
emitError: (phase, error) => emitError(this.events, phase, error)
|
|
1257
1358
|
}) : null;
|
|
@@ -1521,7 +1622,7 @@ var MicrophoneMatcher = class {
|
|
|
1521
1622
|
return;
|
|
1522
1623
|
}
|
|
1523
1624
|
if (matchedBest) {
|
|
1524
|
-
this.events.emit("match", { best: matchedBest });
|
|
1625
|
+
this.events.emit("match", { best: toPublicDetectedMatch(matchedBest) });
|
|
1525
1626
|
this.activityRecognitionId += 1;
|
|
1526
1627
|
this.reportCampaign(
|
|
1527
1628
|
this.activityRecognitionId,
|
|
@@ -1553,7 +1654,6 @@ var MicrophoneMatcher = class {
|
|
|
1553
1654
|
this.markerPcmBuffer = new Float32Array(0);
|
|
1554
1655
|
this.resampleBuffer = new Float32Array(0);
|
|
1555
1656
|
this.resampleOffset = 0;
|
|
1556
|
-
this.latestAudioMarker = null;
|
|
1557
1657
|
this.latestAudioMarkerDetection = null;
|
|
1558
1658
|
this.audioMarkerRequestId += 1;
|
|
1559
1659
|
this.activityRefinement?.reset();
|
|
@@ -1589,7 +1689,6 @@ var MicrophoneMatcher = class {
|
|
|
1589
1689
|
return null;
|
|
1590
1690
|
}
|
|
1591
1691
|
this.latestAudioMarkerDetection = { requestId, detection };
|
|
1592
|
-
this.emitAudioMarkerIfChanged(detection);
|
|
1593
1692
|
this.activityRefinement?.refine(requestId, detection, captureGeneration);
|
|
1594
1693
|
return detection;
|
|
1595
1694
|
} catch (error) {
|
|
@@ -1616,14 +1715,6 @@ var MicrophoneMatcher = class {
|
|
|
1616
1715
|
audioMarker: this.latestAudioMarkerDetection.detection
|
|
1617
1716
|
};
|
|
1618
1717
|
}
|
|
1619
|
-
emitAudioMarkerIfChanged(detection) {
|
|
1620
|
-
const marker = detection.code ?? null;
|
|
1621
|
-
if (marker === this.latestAudioMarker) {
|
|
1622
|
-
return;
|
|
1623
|
-
}
|
|
1624
|
-
this.latestAudioMarker = marker;
|
|
1625
|
-
this.events.emit("audiomarker", { marker, detection });
|
|
1626
|
-
}
|
|
1627
1718
|
normalizeInputSamples(samples) {
|
|
1628
1719
|
if (this.audioContext.sampleRate === TARGET_SAMPLE_RATE) {
|
|
1629
1720
|
return samples;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sorisdk/web-audio",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"description": "Web SDK for browser-based audio recognition with SORI API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
35
35
|
"homepage": "https://docs.soriapi.com/ko/integration/web",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@sorisdk/
|
|
38
|
-
"@sorisdk/
|
|
37
|
+
"@sorisdk/afpgen": "0.6.8",
|
|
38
|
+
"@sorisdk/matcher": "0.6.8"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"tsup": "^8.5.1",
|