@mentra/sdk 2.1.3 → 2.1.4
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/dist/app/session/layouts.d.ts +18 -21
- package/dist/app/session/layouts.d.ts.map +1 -1
- package/dist/app/session/layouts.js +44 -55
- package/dist/app/session/modules/audio.d.ts +2 -2
- package/dist/app/session/modules/audio.d.ts.map +1 -1
- package/dist/app/session/modules/audio.js +21 -17
- package/dist/app/session/modules/camera.d.ts +4 -4
- package/dist/app/session/modules/camera.d.ts.map +1 -1
- package/dist/app/session/modules/camera.js +28 -22
- package/dist/app/session/modules/location.d.ts +3 -3
- package/dist/app/session/modules/location.d.ts.map +1 -1
- package/dist/app/session/modules/location.js +8 -5
- package/dist/types/user-session.d.ts +8 -8
- package/dist/types/user-session.d.ts.map +1 -1
- package/dist/utils/animation-utils.d.ts +4 -4
- package/dist/utils/animation-utils.d.ts.map +1 -1
- package/dist/utils/animation-utils.js +25 -24
- package/dist/utils/bitmap-utils.d.ts +4 -2
- package/dist/utils/bitmap-utils.d.ts.map +1 -1
- package/dist/utils/bitmap-utils.js +172 -45
- package/package.json +2 -1
@@ -15,7 +15,7 @@
|
|
15
15
|
* layouts.showReferenceCard('Weather', 'Sunny and 75°F');
|
16
16
|
* ```
|
17
17
|
*/
|
18
|
-
import { DisplayRequest, ViewType } from
|
18
|
+
import { DisplayRequest, ViewType } from "../../types";
|
19
19
|
export declare class LayoutManager {
|
20
20
|
private packageName;
|
21
21
|
private sendMessage;
|
@@ -109,28 +109,25 @@ export declare class LayoutManager {
|
|
109
109
|
durationMs?: number;
|
110
110
|
}): void;
|
111
111
|
/**
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
112
|
+
* 📇 Shows a bitmap
|
113
|
+
*
|
114
|
+
* Uses the proven animation system internally for proper L/R eye synchronization.
|
115
|
+
* This ensures single bitmap displays work consistently with the same
|
116
|
+
* hardware-optimized timing as animations.
|
117
|
+
*
|
118
|
+
* @param data - hex or base64 encoded bitmap data
|
119
|
+
* @param options - Optional parameters (view, duration)
|
120
|
+
*
|
121
|
+
* @example
|
122
|
+
* ```typescript
|
123
|
+
* layouts.showBitmapView(
|
124
|
+
* yourHexOrBase64EncodedBitmapDataString
|
125
|
+
* );
|
126
|
+
* ```
|
127
|
+
*/
|
128
128
|
showBitmapView(data: string, options?: {
|
129
129
|
view?: ViewType;
|
130
|
-
|
131
|
-
}): {
|
132
|
-
stop: () => void;
|
133
|
-
};
|
130
|
+
}): void;
|
134
131
|
/**
|
135
132
|
* 📊 Shows a dashboard card with left and right text
|
136
133
|
*
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"layouts.d.ts","sourceRoot":"","sources":["../../../src/app/session/layouts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,cAAc,EAOd,QAAQ,EAKT,MAAM,aAAa,CAAC;AAErB,qBAAa,aAAa;IAQtB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW;IARrB;;;;;OAKG;gBAEO,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI;IAGxD;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;
|
1
|
+
{"version":3,"file":"layouts.d.ts","sourceRoot":"","sources":["../../../src/app/session/layouts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,cAAc,EAOd,QAAQ,EAKT,MAAM,aAAa,CAAC;AAErB,qBAAa,aAAa;IAQtB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW;IARrB;;;;;OAKG;gBAEO,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI;IAGxD;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAoH1B;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CACV,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;IAuCpD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,kBAAkB,CAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;IAYpD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,iBAAiB,CACf,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;IAYpD;;;;;;;;;;;;;;;;OAgBG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,QAAQ,CAAA;KAAE;IAQ1D;;;;;;;;;;;;;;;;OAgBG;IACH,iBAAiB,CACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;IAgBpD;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,QAAQ,CAAA;KAAE;IAOvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,mBAAmB,CACjB,eAAe,EAAE,MAAM,EAAE,EACzB,UAAU,GAAE,MAAa,EACzB,MAAM,GAAE,OAAe,EACvB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,QAAQ,CAAA;KAAE,GAC5B;QAAE,IAAI,EAAE,MAAM,IAAI,CAAA;KAAE;CAiCxB"}
|
@@ -51,7 +51,7 @@ class LayoutManager {
|
|
51
51
|
// Layout-specific validations
|
52
52
|
switch (layout.layoutType) {
|
53
53
|
case types_1.LayoutType.TEXT_WALL:
|
54
|
-
if (typeof layout.text !==
|
54
|
+
if (typeof layout.text !== "string") {
|
55
55
|
throw new Error("TextWall layout must have a text property");
|
56
56
|
}
|
57
57
|
// Ensure text is not too long (prevent performance issues)
|
@@ -61,38 +61,39 @@ class LayoutManager {
|
|
61
61
|
break;
|
62
62
|
case types_1.LayoutType.DOUBLE_TEXT_WALL:
|
63
63
|
const doubleText = layout;
|
64
|
-
if (typeof doubleText.topText !==
|
64
|
+
if (typeof doubleText.topText !== "string") {
|
65
65
|
throw new Error("DoubleTextWall layout must have a topText property");
|
66
66
|
}
|
67
|
-
if (typeof doubleText.bottomText !==
|
67
|
+
if (typeof doubleText.bottomText !== "string") {
|
68
68
|
throw new Error("DoubleTextWall layout must have a bottomText property");
|
69
69
|
}
|
70
70
|
break;
|
71
71
|
case types_1.LayoutType.REFERENCE_CARD:
|
72
72
|
const refCard = layout;
|
73
|
-
if (typeof refCard.title !==
|
73
|
+
if (typeof refCard.title !== "string") {
|
74
74
|
throw new Error("ReferenceCard layout must have a title property");
|
75
75
|
}
|
76
|
-
if (typeof refCard.text !==
|
76
|
+
if (typeof refCard.text !== "string") {
|
77
77
|
throw new Error("ReferenceCard layout must have a text property");
|
78
78
|
}
|
79
79
|
break;
|
80
80
|
case types_1.LayoutType.DASHBOARD_CARD:
|
81
81
|
const dashCard = layout;
|
82
|
-
if (typeof dashCard.leftText !==
|
82
|
+
if (typeof dashCard.leftText !== "string") {
|
83
83
|
throw new Error("DashboardCard layout must have a leftText property");
|
84
84
|
}
|
85
|
-
if (typeof dashCard.rightText !==
|
85
|
+
if (typeof dashCard.rightText !== "string") {
|
86
86
|
throw new Error("DashboardCard layout must have a rightText property");
|
87
87
|
}
|
88
88
|
break;
|
89
89
|
case types_1.LayoutType.BITMAP_VIEW:
|
90
90
|
const bitmapView = layout;
|
91
|
-
if (typeof bitmapView.data !==
|
91
|
+
if (typeof bitmapView.data !== "string") {
|
92
92
|
throw new Error("BitmapView layout must have a data property");
|
93
93
|
}
|
94
94
|
// Check if data is too large (prevent OOM errors)
|
95
|
-
if (bitmapView.data.length > 1000000) {
|
95
|
+
if (bitmapView.data.length > 1000000) {
|
96
|
+
// 1MB limit
|
96
97
|
throw new Error("Bitmap data is too large (>1MB), please reduce size");
|
97
98
|
}
|
98
99
|
break;
|
@@ -107,7 +108,7 @@ class LayoutManager {
|
|
107
108
|
}
|
108
109
|
// Validate duration if provided
|
109
110
|
if (durationMs !== undefined) {
|
110
|
-
if (typeof durationMs !==
|
111
|
+
if (typeof durationMs !== "number" || durationMs < 0) {
|
111
112
|
console.warn(`Invalid duration: ${durationMs}, ignoring`);
|
112
113
|
durationMs = undefined;
|
113
114
|
}
|
@@ -115,12 +116,12 @@ class LayoutManager {
|
|
115
116
|
// Create the display request with validated data
|
116
117
|
return {
|
117
118
|
timestamp: new Date(),
|
118
|
-
sessionId:
|
119
|
+
sessionId: "", // Will be filled by session
|
119
120
|
type: types_1.AppToCloudMessageType.DISPLAY_REQUEST,
|
120
121
|
packageName: this.packageName,
|
121
122
|
view,
|
122
123
|
layout,
|
123
|
-
durationMs
|
124
|
+
durationMs,
|
124
125
|
};
|
125
126
|
}
|
126
127
|
catch (error) {
|
@@ -154,14 +155,14 @@ class LayoutManager {
|
|
154
155
|
console.warn("showTextWall called with null/undefined text");
|
155
156
|
}
|
156
157
|
// Ensure text is a string
|
157
|
-
if (typeof text !==
|
158
|
+
if (typeof text !== "string") {
|
158
159
|
text = String(text); // Convert to string
|
159
160
|
console.warn("showTextWall: Non-string input converted to string");
|
160
161
|
}
|
161
162
|
// Create layout with validated text
|
162
163
|
const layout = {
|
163
164
|
layoutType: types_1.LayoutType.TEXT_WALL,
|
164
|
-
text
|
165
|
+
text,
|
165
166
|
};
|
166
167
|
// Create and send display event with error handling
|
167
168
|
try {
|
@@ -203,7 +204,7 @@ class LayoutManager {
|
|
203
204
|
const layout = {
|
204
205
|
layoutType: types_1.LayoutType.DOUBLE_TEXT_WALL,
|
205
206
|
topText,
|
206
|
-
bottomText
|
207
|
+
bottomText,
|
207
208
|
};
|
208
209
|
this.sendMessage(this.createDisplayEvent(layout, options?.view, options?.durationMs));
|
209
210
|
}
|
@@ -232,45 +233,33 @@ class LayoutManager {
|
|
232
233
|
const layout = {
|
233
234
|
layoutType: types_1.LayoutType.REFERENCE_CARD,
|
234
235
|
title,
|
235
|
-
text
|
236
|
+
text,
|
236
237
|
};
|
237
238
|
this.sendMessage(this.createDisplayEvent(layout, options?.view, options?.durationMs));
|
238
239
|
}
|
239
240
|
/**
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
241
|
+
* 📇 Shows a bitmap
|
242
|
+
*
|
243
|
+
* Uses the proven animation system internally for proper L/R eye synchronization.
|
244
|
+
* This ensures single bitmap displays work consistently with the same
|
245
|
+
* hardware-optimized timing as animations.
|
246
|
+
*
|
247
|
+
* @param data - hex or base64 encoded bitmap data
|
248
|
+
* @param options - Optional parameters (view, duration)
|
249
|
+
*
|
250
|
+
* @example
|
251
|
+
* ```typescript
|
252
|
+
* layouts.showBitmapView(
|
253
|
+
* yourHexOrBase64EncodedBitmapDataString
|
254
|
+
* );
|
255
|
+
* ```
|
256
|
+
*/
|
256
257
|
showBitmapView(data, options) {
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
// Create short multi-frame animation for better L/R sync
|
263
|
-
// Repeat the same frame a few times to mimic continuous animation behavior
|
264
|
-
const frames = [data, data, data]; // Triple the frame for stability
|
265
|
-
const animation = this.showBitmapAnimation(frames, // Multi-frame array (same image repeated)
|
266
|
-
frameInterval, // Use proven 1650ms interval
|
267
|
-
false, // Don't repeat infinitely
|
268
|
-
{ view: options?.view });
|
269
|
-
// Auto-stop after duration
|
270
|
-
setTimeout(() => {
|
271
|
-
animation.stop();
|
272
|
-
}, duration);
|
273
|
-
return animation;
|
258
|
+
const layout = {
|
259
|
+
layoutType: types_1.LayoutType.BITMAP_VIEW,
|
260
|
+
data: data,
|
261
|
+
};
|
262
|
+
this.sendMessage(this.createDisplayEvent(layout, options?.view));
|
274
263
|
}
|
275
264
|
/**
|
276
265
|
* 📊 Shows a dashboard card with left and right text
|
@@ -293,7 +282,7 @@ class LayoutManager {
|
|
293
282
|
const layout = {
|
294
283
|
layoutType: types_1.LayoutType.DASHBOARD_CARD,
|
295
284
|
leftText,
|
296
|
-
rightText
|
285
|
+
rightText,
|
297
286
|
};
|
298
287
|
this.sendMessage(this.createDisplayEvent(layout, options?.view || types_1.ViewType.DASHBOARD, options?.durationMs));
|
299
288
|
}
|
@@ -315,7 +304,7 @@ class LayoutManager {
|
|
315
304
|
*/
|
316
305
|
clearView(options) {
|
317
306
|
const layout = {
|
318
|
-
layoutType: types_1.LayoutType.CLEAR_VIEW
|
307
|
+
layoutType: types_1.LayoutType.CLEAR_VIEW,
|
319
308
|
};
|
320
309
|
this.sendMessage(this.createDisplayEvent(layout, options?.view));
|
321
310
|
}
|
@@ -359,17 +348,17 @@ class LayoutManager {
|
|
359
348
|
layoutType: types_1.LayoutType.BITMAP_ANIMATION,
|
360
349
|
frames: bitmapDataArray,
|
361
350
|
interval: intervalMs,
|
362
|
-
repeat: repeat
|
351
|
+
repeat: repeat,
|
363
352
|
};
|
364
353
|
this.sendMessage(this.createDisplayEvent(layout, options?.view));
|
365
|
-
console.log(`🎬 Sent batched animation to iOS: ${bitmapDataArray.length} frames at ${intervalMs}ms${repeat ?
|
354
|
+
console.log(`🎬 Sent batched animation to iOS: ${bitmapDataArray.length} frames at ${intervalMs}ms${repeat ? " (repeating)" : ""}`);
|
366
355
|
// Return controller for compatibility
|
367
356
|
return {
|
368
357
|
stop: () => {
|
369
358
|
// Send stop command to iOS
|
370
359
|
this.clearView();
|
371
|
-
console.log(
|
372
|
-
}
|
360
|
+
console.log("🛑 Animation stop requested");
|
361
|
+
},
|
373
362
|
};
|
374
363
|
}
|
375
364
|
}
|
@@ -4,8 +4,8 @@
|
|
4
4
|
* Audio functionality for App Sessions.
|
5
5
|
* Handles audio playback on connected glasses.
|
6
6
|
*/
|
7
|
-
import { AudioPlayResponse } from
|
8
|
-
import { Logger } from
|
7
|
+
import { AudioPlayResponse } from "../../../types";
|
8
|
+
import { Logger } from "pino";
|
9
9
|
/**
|
10
10
|
* Options for audio playback
|
11
11
|
*/
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"audio.d.ts","sourceRoot":"","sources":["../../../../src/app/session/modules/audio.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAEL,iBAAiB,EAGlB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAE9B;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,cAAc,CAAC,EAAE;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAyB;IACrC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAC,CAAM;IACtB,OAAO,CAAC,MAAM,CAAS;IAEvB,uDAAuD;IACvD,OAAO,CAAC,oBAAoB,
|
1
|
+
{"version":3,"file":"audio.d.ts","sourceRoot":"","sources":["../../../../src/app/session/modules/audio.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAEL,iBAAiB,EAGlB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAE9B;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,cAAc,CAAC,EAAE;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAyB;IACrC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAC,CAAM;IACtB,OAAO,CAAC,MAAM,CAAS;IAEvB,uDAAuD;IACvD,OAAO,CAAC,oBAAoB,CAMxB;IAEJ;;;;;;;;OAQG;gBAED,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,EAC5B,OAAO,CAAC,EAAE,GAAG,EACb,MAAM,CAAC,EAAE,MAAM;IAajB;;;;;;;;;;;;;OAaG;IACG,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAqEpE;;;;;;;;OAQG;IACH,SAAS,IAAI,IAAI;IAqBjB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,KAAK,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,eAAe,CAAC;IA+C3B;;;;;;;;OAQG;IACH,uBAAuB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAkC1D;;;;OAIG;IACH,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO;IAO9C;;;OAGG;IACH,sBAAsB,IAAI,MAAM;IAIhC;;;OAGG;IACH,oBAAoB,IAAI,MAAM,EAAE;IAIhC;;;;OAIG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAW9C;;;OAGG;IACH,sBAAsB,IAAI,MAAM;IAyBhC;;;;OAIG;IACH,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAK3C;;;;OAIG;IACH,iBAAiB,IAAI;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE;CAI/C"}
|
@@ -71,7 +71,7 @@ class AudioManager {
|
|
71
71
|
try {
|
72
72
|
// Validate input
|
73
73
|
if (!options.audioUrl) {
|
74
|
-
reject(
|
74
|
+
reject("audioUrl must be provided");
|
75
75
|
return;
|
76
76
|
}
|
77
77
|
// Generate unique request ID
|
@@ -87,7 +87,7 @@ class AudioManager {
|
|
87
87
|
timestamp: new Date(),
|
88
88
|
audioUrl: options.audioUrl,
|
89
89
|
volume: options.volume ?? 1.0,
|
90
|
-
stopOtherAudio: options.stopOtherAudio ?? true
|
90
|
+
stopOtherAudio: options.stopOtherAudio ?? true,
|
91
91
|
};
|
92
92
|
// Send request to cloud
|
93
93
|
this.send(message);
|
@@ -97,7 +97,9 @@ class AudioManager {
|
|
97
97
|
// Use session's resource tracker for automatic cleanup
|
98
98
|
this.session.resources.setTimeout(() => {
|
99
99
|
if (this.pendingAudioRequests.has(requestId)) {
|
100
|
-
this.pendingAudioRequests
|
100
|
+
this.pendingAudioRequests
|
101
|
+
.get(requestId)
|
102
|
+
.reject("Audio play request timed out");
|
101
103
|
this.pendingAudioRequests.delete(requestId);
|
102
104
|
this.logger.warn({ requestId }, `🔊 Audio play request timed out`);
|
103
105
|
}
|
@@ -107,7 +109,9 @@ class AudioManager {
|
|
107
109
|
// Fallback to regular setTimeout if session not available
|
108
110
|
setTimeout(() => {
|
109
111
|
if (this.pendingAudioRequests.has(requestId)) {
|
110
|
-
this.pendingAudioRequests
|
112
|
+
this.pendingAudioRequests
|
113
|
+
.get(requestId)
|
114
|
+
.reject("Audio play request timed out");
|
111
115
|
this.pendingAudioRequests.delete(requestId);
|
112
116
|
this.logger.warn({ requestId }, `🔊 Audio play request timed out`);
|
113
117
|
}
|
@@ -116,7 +120,7 @@ class AudioManager {
|
|
116
120
|
}
|
117
121
|
catch (error) {
|
118
122
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
119
|
-
reject(
|
123
|
+
reject(`Failed to play audio: ${errorMessage}`);
|
120
124
|
}
|
121
125
|
});
|
122
126
|
}
|
@@ -136,7 +140,7 @@ class AudioManager {
|
|
136
140
|
type: types_1.AppToCloudMessageType.AUDIO_STOP_REQUEST,
|
137
141
|
packageName: this.packageName,
|
138
142
|
sessionId: this.sessionId,
|
139
|
-
timestamp: new Date()
|
143
|
+
timestamp: new Date(),
|
140
144
|
};
|
141
145
|
// Send request to cloud (one-way, no response expected)
|
142
146
|
this.send(message);
|
@@ -172,24 +176,24 @@ class AudioManager {
|
|
172
176
|
async speak(text, options = {}) {
|
173
177
|
// Validate input
|
174
178
|
if (!text) {
|
175
|
-
throw new Error(
|
179
|
+
throw new Error("text must be provided");
|
176
180
|
}
|
177
181
|
// Get the HTTPS server URL from the session
|
178
182
|
const baseUrl = this.session?.getHttpsServerUrl?.();
|
179
183
|
if (!baseUrl) {
|
180
|
-
throw new Error(
|
184
|
+
throw new Error("Cannot determine server URL for TTS endpoint");
|
181
185
|
}
|
182
186
|
// Build query parameters for the TTS endpoint
|
183
187
|
const queryParams = new URLSearchParams();
|
184
|
-
queryParams.append(
|
188
|
+
queryParams.append("text", text);
|
185
189
|
if (options.voice_id) {
|
186
|
-
queryParams.append(
|
190
|
+
queryParams.append("voice_id", options.voice_id);
|
187
191
|
}
|
188
192
|
if (options.model_id) {
|
189
|
-
queryParams.append(
|
193
|
+
queryParams.append("model_id", options.model_id);
|
190
194
|
}
|
191
195
|
if (options.voice_settings) {
|
192
|
-
queryParams.append(
|
196
|
+
queryParams.append("voice_settings", JSON.stringify(options.voice_settings));
|
193
197
|
}
|
194
198
|
// Construct the TTS URL
|
195
199
|
const ttsUrl = `${baseUrl}/api/tts?${queryParams.toString()}`;
|
@@ -197,7 +201,7 @@ class AudioManager {
|
|
197
201
|
// Use the existing playAudio method to play the TTS audio
|
198
202
|
return this.playAudio({
|
199
203
|
audioUrl: ttsUrl,
|
200
|
-
volume: options.volume
|
204
|
+
volume: options.volume,
|
201
205
|
});
|
202
206
|
}
|
203
207
|
// =====================================
|
@@ -219,14 +223,14 @@ class AudioManager {
|
|
219
223
|
pendingRequest.resolve({
|
220
224
|
success: response.success,
|
221
225
|
error: response.error,
|
222
|
-
duration: response.duration
|
226
|
+
duration: response.duration,
|
223
227
|
});
|
224
228
|
// Clean up
|
225
229
|
this.pendingAudioRequests.delete(response.requestId);
|
226
230
|
this.logger.info({
|
227
231
|
requestId: response.requestId,
|
228
232
|
success: response.success,
|
229
|
-
duration: response.duration
|
233
|
+
duration: response.duration,
|
230
234
|
}, `🔊 Audio play response received`);
|
231
235
|
}
|
232
236
|
else {
|
@@ -269,7 +273,7 @@ class AudioManager {
|
|
269
273
|
cancelAudioRequest(requestId) {
|
270
274
|
const pendingRequest = this.pendingAudioRequests.get(requestId);
|
271
275
|
if (pendingRequest) {
|
272
|
-
pendingRequest.reject(
|
276
|
+
pendingRequest.reject("Audio request cancelled");
|
273
277
|
this.pendingAudioRequests.delete(requestId);
|
274
278
|
this.logger.info({ requestId }, `🔊 Audio request cancelled`);
|
275
279
|
return true;
|
@@ -283,7 +287,7 @@ class AudioManager {
|
|
283
287
|
cancelAllAudioRequests() {
|
284
288
|
const count = this.pendingAudioRequests.size;
|
285
289
|
this.pendingAudioRequests.forEach((request, requestId) => {
|
286
|
-
request.reject(
|
290
|
+
request.reject("Audio request cancelled due to cleanup");
|
287
291
|
this.logger.debug({ requestId }, `🔊 Audio request cancelled during cleanup`);
|
288
292
|
});
|
289
293
|
this.pendingAudioRequests.clear();
|
@@ -4,10 +4,10 @@
|
|
4
4
|
* Unified camera functionality for App Sessions.
|
5
5
|
* Handles both photo requests and RTMP streaming from connected glasses.
|
6
6
|
*/
|
7
|
-
import { PhotoData, RtmpStreamStatus, ManagedStreamStatus } from
|
8
|
-
import { VideoConfig, AudioConfig, StreamConfig, StreamStatusHandler } from
|
9
|
-
import { Logger } from
|
10
|
-
import { ManagedStreamOptions, ManagedStreamResult } from
|
7
|
+
import { PhotoData, RtmpStreamStatus, ManagedStreamStatus } from "../../../types";
|
8
|
+
import { VideoConfig, AudioConfig, StreamConfig, StreamStatusHandler } from "../../../types/rtmp-stream";
|
9
|
+
import { Logger } from "pino";
|
10
|
+
import { ManagedStreamOptions, ManagedStreamResult } from "./camera-managed-extension";
|
11
11
|
/**
|
12
12
|
* Options for photo requests
|
13
13
|
*/
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../../../../src/app/session/modules/camera.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAEL,SAAS,EAIT,gBAAgB,EAEhB,mBAAmB,EAEpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,WAAW,EACX,WAAW,EACX,YAAY,EACZ,mBAAmB,EACpB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAC9B,OAAO,
|
1
|
+
{"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../../../../src/app/session/modules/camera.ts"],"names":[],"mappings":"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"}
|
@@ -90,7 +90,7 @@ class CameraModule {
|
|
90
90
|
sessionId: this.sessionId,
|
91
91
|
requestId,
|
92
92
|
timestamp: new Date(),
|
93
|
-
saveToGallery: options?.saveToGallery || false
|
93
|
+
saveToGallery: options?.saveToGallery || false,
|
94
94
|
};
|
95
95
|
// Send request to cloud
|
96
96
|
this.send(message);
|
@@ -101,7 +101,9 @@ class CameraModule {
|
|
101
101
|
// Use session's resource tracker for automatic cleanup
|
102
102
|
this.session.resources.setTimeout(() => {
|
103
103
|
if (this.pendingPhotoRequests.has(requestId)) {
|
104
|
-
this.pendingPhotoRequests
|
104
|
+
this.pendingPhotoRequests
|
105
|
+
.get(requestId)
|
106
|
+
.reject("Photo request timed out");
|
105
107
|
this.pendingPhotoRequests.delete(requestId);
|
106
108
|
this.logger.warn({ requestId }, `📸 Photo request timed out`);
|
107
109
|
}
|
@@ -111,7 +113,9 @@ class CameraModule {
|
|
111
113
|
// Fallback to regular setTimeout if session not available
|
112
114
|
setTimeout(() => {
|
113
115
|
if (this.pendingPhotoRequests.has(requestId)) {
|
114
|
-
this.pendingPhotoRequests
|
116
|
+
this.pendingPhotoRequests
|
117
|
+
.get(requestId)
|
118
|
+
.reject("Photo request timed out");
|
115
119
|
this.pendingPhotoRequests.delete(requestId);
|
116
120
|
this.logger.warn({ requestId }, `📸 Photo request timed out`);
|
117
121
|
}
|
@@ -120,7 +124,7 @@ class CameraModule {
|
|
120
124
|
}
|
121
125
|
catch (error) {
|
122
126
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
123
|
-
reject(
|
127
|
+
reject(`Failed to request photo: ${errorMessage}`);
|
124
128
|
}
|
125
129
|
});
|
126
130
|
}
|
@@ -181,7 +185,7 @@ class CameraModule {
|
|
181
185
|
cancelPhotoRequest(requestId) {
|
182
186
|
const pendingRequest = this.pendingPhotoRequests.get(requestId);
|
183
187
|
if (pendingRequest) {
|
184
|
-
pendingRequest.reject(
|
188
|
+
pendingRequest.reject("Photo request cancelled");
|
185
189
|
this.pendingPhotoRequests.delete(requestId);
|
186
190
|
this.logger.info({ requestId }, `📸 Photo request cancelled`);
|
187
191
|
return true;
|
@@ -196,7 +200,7 @@ class CameraModule {
|
|
196
200
|
cancelAllPhotoRequests() {
|
197
201
|
const count = this.pendingPhotoRequests.size;
|
198
202
|
for (const [requestId, { reject }] of this.pendingPhotoRequests) {
|
199
|
-
reject(
|
203
|
+
reject("Photo request cancelled - session cleanup");
|
200
204
|
this.logger.info({ requestId }, `📸 Photo request cancelled during cleanup`);
|
201
205
|
}
|
202
206
|
this.pendingPhotoRequests.clear();
|
@@ -223,14 +227,14 @@ class CameraModule {
|
|
223
227
|
async startStream(options) {
|
224
228
|
this.logger.info({ rtmpUrl: options.rtmpUrl }, `📹 RTMP stream request starting`);
|
225
229
|
if (!options.rtmpUrl) {
|
226
|
-
throw new Error(
|
230
|
+
throw new Error("rtmpUrl is required");
|
227
231
|
}
|
228
232
|
if (this.isStreaming) {
|
229
233
|
this.logger.error({
|
230
234
|
currentStreamUrl: this.currentStreamUrl,
|
231
|
-
requestedUrl: options.rtmpUrl
|
235
|
+
requestedUrl: options.rtmpUrl,
|
232
236
|
}, `📹 Already streaming error`);
|
233
|
-
throw new Error(
|
237
|
+
throw new Error("Already streaming. Stop the current stream before starting a new one.");
|
234
238
|
}
|
235
239
|
// Create stream request message
|
236
240
|
const message = {
|
@@ -241,7 +245,7 @@ class CameraModule {
|
|
241
245
|
video: options.video,
|
242
246
|
audio: options.audio,
|
243
247
|
stream: options.stream,
|
244
|
-
timestamp: new Date()
|
248
|
+
timestamp: new Date(),
|
245
249
|
};
|
246
250
|
// Save stream URL for reference
|
247
251
|
this.currentStreamUrl = options.rtmpUrl;
|
@@ -255,7 +259,7 @@ class CameraModule {
|
|
255
259
|
catch (error) {
|
256
260
|
this.logger.error({ error, rtmpUrl: options.rtmpUrl }, `📹 Failed to send RTMP stream request`);
|
257
261
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
258
|
-
return Promise.reject(
|
262
|
+
return Promise.reject(`Failed to request RTMP stream: ${errorMessage}`);
|
259
263
|
}
|
260
264
|
}
|
261
265
|
/**
|
@@ -271,7 +275,7 @@ class CameraModule {
|
|
271
275
|
async stopStream() {
|
272
276
|
this.logger.info({
|
273
277
|
isCurrentlyStreaming: this.isStreaming,
|
274
|
-
currentStreamUrl: this.currentStreamUrl
|
278
|
+
currentStreamUrl: this.currentStreamUrl,
|
275
279
|
}, `📹 RTMP stream stop request`);
|
276
280
|
if (!this.isStreaming) {
|
277
281
|
this.logger.info(`📹 Not streaming - no-op`);
|
@@ -284,7 +288,7 @@ class CameraModule {
|
|
284
288
|
packageName: this.packageName,
|
285
289
|
sessionId: this.sessionId,
|
286
290
|
streamId: this.currentStreamState?.streamId, // Include streamId if available
|
287
|
-
timestamp: new Date()
|
291
|
+
timestamp: new Date(),
|
288
292
|
};
|
289
293
|
// Send the request
|
290
294
|
try {
|
@@ -293,7 +297,7 @@ class CameraModule {
|
|
293
297
|
}
|
294
298
|
catch (error) {
|
295
299
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
296
|
-
return Promise.reject(
|
300
|
+
return Promise.reject(`Failed to stop RTMP stream: ${errorMessage}`);
|
297
301
|
}
|
298
302
|
}
|
299
303
|
/**
|
@@ -329,7 +333,7 @@ class CameraModule {
|
|
329
333
|
this.session.subscribe(streams_1.StreamType.RTMP_STREAM_STATUS);
|
330
334
|
}
|
331
335
|
else {
|
332
|
-
this.logger.error(
|
336
|
+
this.logger.error("Cannot subscribe to status updates: session reference not available");
|
333
337
|
}
|
334
338
|
}
|
335
339
|
/**
|
@@ -360,7 +364,7 @@ class CameraModule {
|
|
360
364
|
*/
|
361
365
|
onStreamStatus(handler) {
|
362
366
|
if (!this.session) {
|
363
|
-
this.logger.error(
|
367
|
+
this.logger.error("Cannot listen for status updates: session reference not available");
|
364
368
|
return () => { };
|
365
369
|
}
|
366
370
|
this.subscribeToStreamStatusUpdates();
|
@@ -376,7 +380,7 @@ class CameraModule {
|
|
376
380
|
this.logger.debug({
|
377
381
|
messageType: message?.type,
|
378
382
|
messageStatus: message?.status,
|
379
|
-
currentIsStreaming: this.isStreaming
|
383
|
+
currentIsStreaming: this.isStreaming,
|
380
384
|
}, `📹 Stream state update`);
|
381
385
|
// Verify this is a valid stream response
|
382
386
|
if (!(0, types_1.isRtmpStreamStatus)(message)) {
|
@@ -391,19 +395,21 @@ class CameraModule {
|
|
391
395
|
errorDetails: message.errorDetails,
|
392
396
|
appId: message.appId,
|
393
397
|
stats: message.stats,
|
394
|
-
timestamp: message.timestamp || new Date()
|
398
|
+
timestamp: message.timestamp || new Date(),
|
395
399
|
};
|
396
400
|
this.logger.info({
|
397
401
|
streamId: status.streamId,
|
398
402
|
oldStatus: this.currentStreamState?.status,
|
399
403
|
newStatus: status.status,
|
400
|
-
wasStreaming: this.isStreaming
|
404
|
+
wasStreaming: this.isStreaming,
|
401
405
|
}, `📹 Stream status processed`);
|
402
406
|
// Update local state based on status
|
403
|
-
if (status.status ===
|
407
|
+
if (status.status === "stopped" ||
|
408
|
+
status.status === "error" ||
|
409
|
+
status.status === "timeout") {
|
404
410
|
this.logger.info({
|
405
411
|
status: status.status,
|
406
|
-
wasStreaming: this.isStreaming
|
412
|
+
wasStreaming: this.isStreaming,
|
407
413
|
}, `📹 Stream stopped - updating local state`);
|
408
414
|
this.isStreaming = false;
|
409
415
|
this.currentStreamUrl = undefined;
|
@@ -500,7 +506,7 @@ class CameraModule {
|
|
500
506
|
// Stop streaming if active
|
501
507
|
if (this.isStreaming) {
|
502
508
|
this.stopStream().catch((error) => {
|
503
|
-
this.logger.error({ error },
|
509
|
+
this.logger.error({ error }, "Error stopping stream during cleanup");
|
504
510
|
});
|
505
511
|
}
|
506
512
|
// Clean up managed extension
|