@mentra/engine 3.2.0-dev.221 → 3.2.0-dev.222
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/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/services/AcsMeetingService.d.ts +360 -10
- package/build/services/AcsMeetingService.d.ts.map +1 -1
- package/build/services/AcsMeetingService.js +836 -19
- package/build/services/AcsMeetingService.js.map +1 -1
- package/build/services/AudioPlaybackService.d.ts +6 -0
- package/build/services/AudioPlaybackService.d.ts.map +1 -1
- package/build/services/AudioPlaybackService.js +4 -3
- package/build/services/AudioPlaybackService.js.map +1 -1
- package/build/services/GlassesMicProbe.d.ts +78 -0
- package/build/services/GlassesMicProbe.d.ts.map +1 -0
- package/build/services/GlassesMicProbe.js +258 -0
- package/build/services/GlassesMicProbe.js.map +1 -0
- package/build/services/LocalMiniappRuntime.d.ts +83 -0
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +714 -39
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MentraJSLogPipeline.d.ts +39 -0
- package/build/services/MentraJSLogPipeline.d.ts.map +1 -1
- package/build/services/MentraJSLogPipeline.js +59 -3
- package/build/services/MentraJSLogPipeline.js.map +1 -1
- package/build/services/MentraJSRouter.d.ts +1 -1
- package/build/services/MentraJSRouter.d.ts.map +1 -1
- package/build/services/MentraJSRouter.js +2 -2
- package/build/services/MentraJSRouter.js.map +1 -1
- package/build/services/MicStateCoordinator.d.ts +22 -0
- package/build/services/MicStateCoordinator.d.ts.map +1 -1
- package/build/services/MicStateCoordinator.js +34 -3
- package/build/services/MicStateCoordinator.js.map +1 -1
- package/build/services/PhoneStreamCoordinator.d.ts +8 -0
- package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
- package/build/services/PhoneStreamCoordinator.js +2 -0
- package/build/services/PhoneStreamCoordinator.js.map +1 -1
- package/build/services/SoftapCallTransport.d.ts +365 -0
- package/build/services/SoftapCallTransport.d.ts.map +1 -0
- package/build/services/SoftapCallTransport.js +722 -0
- package/build/services/SoftapCallTransport.js.map +1 -0
- package/build/services/SoftapCleanupBarrier.d.ts +45 -0
- package/build/services/SoftapCleanupBarrier.d.ts.map +1 -0
- package/build/services/SoftapCleanupBarrier.js +51 -0
- package/build/services/SoftapCleanupBarrier.js.map +1 -0
- package/build/utils/pcm16.d.ts +35 -0
- package/build/utils/pcm16.d.ts.map +1 -0
- package/build/utils/pcm16.js +85 -0
- package/build/utils/pcm16.js.map +1 -0
- package/build/utils/softapTrace.d.ts +39 -0
- package/build/utils/softapTrace.d.ts.map +1 -0
- package/build/utils/softapTrace.js +124 -0
- package/build/utils/softapTrace.js.map +1 -0
- package/package.json +7 -7
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/index.ts +2 -0
- package/src/services/AcsMeetingService.ts +956 -22
- package/src/services/AudioPlaybackService.ts +12 -3
- package/src/services/GlassesMicProbe.ts +300 -0
- package/src/services/LocalMiniappRuntime.ts +775 -42
- package/src/services/MentraJSLogPipeline.ts +70 -3
- package/src/services/MentraJSRouter.ts +2 -2
- package/src/services/MicStateCoordinator.ts +35 -3
- package/src/services/PhoneStreamCoordinator.ts +10 -0
- package/src/services/SoftapCallTransport.ts +928 -0
- package/src/services/SoftapCleanupBarrier.ts +72 -0
- package/src/utils/pcm16.ts +93 -0
- package/src/utils/softapTrace.ts +133 -0
|
@@ -73,6 +73,34 @@ export interface ThrottleOptions {
|
|
|
73
73
|
now?: () => number
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/** A per-package override of the shared budget. Both fields are absolute, not multipliers. */
|
|
77
|
+
export interface PackageLogBudget {
|
|
78
|
+
tokensPerSecond: number
|
|
79
|
+
bucketCapacity: number
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Budgets for miniapps under active diagnosis.
|
|
84
|
+
*
|
|
85
|
+
* The default ceiling protects the host from a miniapp that logs in a render loop, and it is the
|
|
86
|
+
* right default. It is the wrong ceiling for a miniapp someone is actively debugging: a single
|
|
87
|
+
* instrumented call join emits several hundred lines in its first few seconds, which exhausts the
|
|
88
|
+
* burst and then drops everything that follows — and the drops land exactly where the interesting
|
|
89
|
+
* part is. A log with silent holes in the failure window is worse than no log, because it is read
|
|
90
|
+
* as complete.
|
|
91
|
+
*
|
|
92
|
+
* So the ceiling is raised for named packages rather than lowered for everyone. Anything not
|
|
93
|
+
* listed here keeps the protective default.
|
|
94
|
+
*/
|
|
95
|
+
export const DIAGNOSTIC_LOG_BUDGETS: Readonly<Record<string, PackageLogBudget>> = {
|
|
96
|
+
// Mentra Call is instrumented end to end — UI, background, and the host call path — so the
|
|
97
|
+
// whole of a join, a cancel, and a teardown can be read back from one capture.
|
|
98
|
+
"com.mentra.call": {tokensPerSecond: 200, bucketCapacity: 5_000},
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Ring-buffer window for a package in {@link DIAGNOSTIC_LOG_BUDGETS}. See `capacityFor`. */
|
|
102
|
+
export const DIAGNOSTIC_RING_CAPACITY = 2_000
|
|
103
|
+
|
|
76
104
|
interface PackageBucket {
|
|
77
105
|
tokens: number
|
|
78
106
|
lastRefillAtMs: number
|
|
@@ -85,6 +113,7 @@ export class MentraJSLogThrottle {
|
|
|
85
113
|
private readonly bucketCapacity: number
|
|
86
114
|
private readonly now: () => number
|
|
87
115
|
private readonly buckets: Map<string, PackageBucket> = new Map()
|
|
116
|
+
private readonly budgets: Map<string, PackageLogBudget> = new Map(Object.entries(DIAGNOSTIC_LOG_BUDGETS))
|
|
88
117
|
|
|
89
118
|
constructor(opts: ThrottleOptions = {}) {
|
|
90
119
|
this.tokensPerSecond = opts.tokensPerSecond ?? 100 / 60
|
|
@@ -92,6 +121,29 @@ export class MentraJSLogThrottle {
|
|
|
92
121
|
this.now = opts.now ?? (() => Date.now())
|
|
93
122
|
}
|
|
94
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Raise (or lower) one package's budget at runtime.
|
|
126
|
+
*
|
|
127
|
+
* Exists so a support session can widen the pipe for the miniapp being investigated without a
|
|
128
|
+
* rebuild, and so tests can exercise a budget without depending on the shipped table.
|
|
129
|
+
*/
|
|
130
|
+
setPackageBudget(packageName: string, budget: PackageLogBudget | null): void {
|
|
131
|
+
if (budget) this.budgets.set(packageName, budget)
|
|
132
|
+
else this.budgets.delete(packageName)
|
|
133
|
+
// The bucket holds a capacity snapshot, so a stale one would keep enforcing the old ceiling
|
|
134
|
+
// until it happened to refill. Dropping it re-derives from the new budget on the next line.
|
|
135
|
+
this.buckets.delete(packageName)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private budgetFor(packageName: string): PackageLogBudget {
|
|
139
|
+
return (
|
|
140
|
+
this.budgets.get(packageName) ?? {
|
|
141
|
+
tokensPerSecond: this.tokensPerSecond,
|
|
142
|
+
bucketCapacity: this.bucketCapacity,
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
95
147
|
/**
|
|
96
148
|
* Try to consume one token for the named package. Returns either:
|
|
97
149
|
* - `{allowed: true}` — caller emits the original log line.
|
|
@@ -102,17 +154,18 @@ export class MentraJSLogThrottle {
|
|
|
102
154
|
*/
|
|
103
155
|
consume(packageName: string): {allowed: true} | {allowed: false; throttledLine?: string} {
|
|
104
156
|
const at = this.now()
|
|
157
|
+
const budget = this.budgetFor(packageName)
|
|
105
158
|
let bucket = this.buckets.get(packageName)
|
|
106
159
|
if (!bucket) {
|
|
107
160
|
bucket = {
|
|
108
|
-
tokens:
|
|
161
|
+
tokens: budget.bucketCapacity,
|
|
109
162
|
lastRefillAtMs: at,
|
|
110
163
|
pendingDrops: 0,
|
|
111
164
|
}
|
|
112
165
|
this.buckets.set(packageName, bucket)
|
|
113
166
|
} else {
|
|
114
167
|
const elapsedSec = (at - bucket.lastRefillAtMs) / 1000
|
|
115
|
-
bucket.tokens = Math.min(
|
|
168
|
+
bucket.tokens = Math.min(budget.bucketCapacity, bucket.tokens + elapsedSec * budget.tokensPerSecond)
|
|
116
169
|
bucket.lastRefillAtMs = at
|
|
117
170
|
}
|
|
118
171
|
if (bucket.tokens >= 1) {
|
|
@@ -146,6 +199,19 @@ export class MentraJSLogRingBuffer {
|
|
|
146
199
|
this.capacity = capacityPerPackage
|
|
147
200
|
}
|
|
148
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Capacity for a package under diagnosis.
|
|
204
|
+
*
|
|
205
|
+
* This buffer is the miniapp's last words in a crash report, and 200 lines of a heavily
|
|
206
|
+
* instrumented miniapp is a couple of seconds — so the report would arrive holding the
|
|
207
|
+
* aftermath and none of the cause. A package with a raised log budget needs a window long
|
|
208
|
+
* enough to contain whatever produced the crash.
|
|
209
|
+
*/
|
|
210
|
+
private capacityFor(packageName: string): number {
|
|
211
|
+
const budget = DIAGNOSTIC_LOG_BUDGETS[packageName]
|
|
212
|
+
return budget ? Math.max(this.capacity, DIAGNOSTIC_RING_CAPACITY) : this.capacity
|
|
213
|
+
}
|
|
214
|
+
|
|
149
215
|
push(packageName: string, line: string): void {
|
|
150
216
|
let buf = this.buffers.get(packageName)
|
|
151
217
|
if (!buf) {
|
|
@@ -153,7 +219,8 @@ export class MentraJSLogRingBuffer {
|
|
|
153
219
|
this.buffers.set(packageName, buf)
|
|
154
220
|
}
|
|
155
221
|
buf.push(line)
|
|
156
|
-
|
|
222
|
+
const capacity = this.capacityFor(packageName)
|
|
223
|
+
while (buf.length > capacity) buf.shift()
|
|
157
224
|
}
|
|
158
225
|
|
|
159
226
|
snapshot(packageName: string): string[] {
|
|
@@ -379,9 +379,9 @@ export class MentraJSRouter {
|
|
|
379
379
|
* router gates this to registered packages and keeps LocalMiniappView from
|
|
380
380
|
* reaching into runtime internals.
|
|
381
381
|
*/
|
|
382
|
-
probeForegroundLiveness(packageName: string, reason = "foreground-open"): void {
|
|
382
|
+
probeForegroundLiveness(packageName: string, reason = "foreground-open", timeoutMs?: number): void {
|
|
383
383
|
if (!this.registered.has(packageName)) return
|
|
384
|
-
this.runtime.probeForegroundLiveness(packageName, reason)
|
|
384
|
+
this.runtime.probeForegroundLiveness(packageName, reason, timeoutMs)
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
// ----------------------------------------------------------------
|
|
@@ -48,6 +48,14 @@ class MicStateCoordinator {
|
|
|
48
48
|
// Local miniapp requirements (set when miniapps subscribe to audio streams)
|
|
49
49
|
private localWantsPcm = false
|
|
50
50
|
private localWantsLc3 = false
|
|
51
|
+
/**
|
|
52
|
+
* A live ACS call taking the wearer's voice off the glasses over BLE LC3.
|
|
53
|
+
*
|
|
54
|
+
* Tracked separately from the miniapp requirement because the two have independent lifetimes:
|
|
55
|
+
* the call miniapp does not subscribe to `audio_chunk`, and a captions miniapp that stops mid-call
|
|
56
|
+
* must not take the call's microphone with it.
|
|
57
|
+
*/
|
|
58
|
+
private callWantsPcm = false
|
|
51
59
|
private configuredVad: boolean | undefined
|
|
52
60
|
private configuredLoudnessGate: boolean | undefined
|
|
53
61
|
private readonly miniappVadOverrides = new Map<string, GateOverride>()
|
|
@@ -75,6 +83,29 @@ class MicStateCoordinator {
|
|
|
75
83
|
this.applyUnion()
|
|
76
84
|
}
|
|
77
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Claim or release raw PCM on behalf of an active call.
|
|
88
|
+
*
|
|
89
|
+
* Called by AcsMeetingService around a call whose uplink is the glasses microphone over BLE LC3.
|
|
90
|
+
* Releasing is a claim release, not a mic shutdown: if a captions miniapp still wants PCM the
|
|
91
|
+
* microphone stays on, which is the whole reason this is a separate flag rather than a setter on
|
|
92
|
+
* the local requirement.
|
|
93
|
+
*/
|
|
94
|
+
public setCallRequirement(pcm: boolean): void {
|
|
95
|
+
if (this.callWantsPcm === pcm) return
|
|
96
|
+
this.callWantsPcm = pcm
|
|
97
|
+
console.log(`${LOG_TAG}: call requirement updated — pcm=${pcm}`)
|
|
98
|
+
this.applyUnion()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Whether anything on this device needs a continuous raw-PCM timeline. Also the condition that
|
|
103
|
+
* forces hardware VAD off: a gate that drops silence turns a call into clipped half-words.
|
|
104
|
+
*/
|
|
105
|
+
private get wantsRawPcm(): boolean {
|
|
106
|
+
return this.localWantsPcm || this.callWantsPcm
|
|
107
|
+
}
|
|
108
|
+
|
|
78
109
|
/**
|
|
79
110
|
* Apply a miniapp-owned gate override without changing the OS preference.
|
|
80
111
|
* Overrides are lifecycle-scoped and last-live-owner-wins independently for
|
|
@@ -159,7 +190,7 @@ class MicStateCoordinator {
|
|
|
159
190
|
const vadOverride = this.latestOverride(this.miniappVadOverrides)
|
|
160
191
|
const loudnessOverride = this.latestOverride(this.miniappLoudnessGateOverrides)
|
|
161
192
|
|
|
162
|
-
if (this.
|
|
193
|
+
if (this.wantsRawPcm) {
|
|
163
194
|
runtimeSettings.voice_activity_detection_enabled = false
|
|
164
195
|
} else if (vadOverride) {
|
|
165
196
|
runtimeSettings.voice_activity_detection_enabled = vadOverride.enabled
|
|
@@ -176,7 +207,7 @@ class MicStateCoordinator {
|
|
|
176
207
|
* on-device PCM consumers; cloud audio uses LC3 through AudioCloudUplink.
|
|
177
208
|
*/
|
|
178
209
|
private applyUnion(): void {
|
|
179
|
-
const shouldSendPcm = this.
|
|
210
|
+
const shouldSendPcm = this.wantsRawPcm
|
|
180
211
|
const shouldSendLc3 = this.localWantsLc3
|
|
181
212
|
|
|
182
213
|
// console.log(
|
|
@@ -225,7 +256,7 @@ class MicStateCoordinator {
|
|
|
225
256
|
|
|
226
257
|
// Hardware VAD suppresses silence. Raw-audio consumers need a continuous
|
|
227
258
|
// timeline, so their requirement wins over both OS and miniapp VAD values.
|
|
228
|
-
if (this.
|
|
259
|
+
if (this.wantsRawPcm) patch.voice_activity_detection_enabled = false
|
|
229
260
|
else if (vadOverride) patch.voice_activity_detection_enabled = vadOverride.enabled
|
|
230
261
|
else if (this.configuredVad !== undefined) patch.voice_activity_detection_enabled = this.configuredVad
|
|
231
262
|
|
|
@@ -243,6 +274,7 @@ class MicStateCoordinator {
|
|
|
243
274
|
public reset(): void {
|
|
244
275
|
this.localWantsPcm = false
|
|
245
276
|
this.localWantsLc3 = false
|
|
277
|
+
this.callWantsPcm = false
|
|
246
278
|
this.applyUnion()
|
|
247
279
|
}
|
|
248
280
|
|
|
@@ -102,6 +102,14 @@ export interface StartUnmanagedOptions {
|
|
|
102
102
|
/** Optional Bearer token for WHIP Authorization (custom authenticated endpoints). */
|
|
103
103
|
authToken?: string
|
|
104
104
|
captureAudio?: boolean
|
|
105
|
+
/**
|
|
106
|
+
* ICE configuration for the glasses' publisher. SoftAP passes `{stun: ""}`, which puts the
|
|
107
|
+
* glasses in host-only mode: there is no route from the hotspot to a STUN server, so a
|
|
108
|
+
* configured one would only add doomed gathering to every call.
|
|
109
|
+
*/
|
|
110
|
+
ice?: StreamStartRequest["ice"]
|
|
111
|
+
/** Correlation id echoed by the glasses into their own logs. See softapTrace. */
|
|
112
|
+
traceId?: string
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
export interface StartManagedOptions {
|
|
@@ -364,6 +372,8 @@ export class PhoneStreamCoordinator {
|
|
|
364
372
|
...(opts.audio !== undefined ? {audio: opts.audio} : {}),
|
|
365
373
|
...(opts.authToken ? {authToken: opts.authToken} : {}),
|
|
366
374
|
...(typeof opts.captureAudio === "boolean" ? {captureAudio: opts.captureAudio} : {}),
|
|
375
|
+
...(opts.ice !== undefined ? {ice: opts.ice} : {}),
|
|
376
|
+
...(opts.traceId ? {traceId: opts.traceId} : {}),
|
|
367
377
|
})
|
|
368
378
|
const result = publisherStartResult(streamId, event)
|
|
369
379
|
this.startLifecycle(streamId)
|