@mentra/engine 3.2.0-dev.116 → 3.2.0-dev.121
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/engine.d.ts.map +1 -1
- package/build/engine.js +7 -2
- package/build/engine.js.map +1 -1
- package/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/runtime/streamConfig.d.ts +7 -0
- package/build/runtime/streamConfig.d.ts.map +1 -1
- package/build/runtime/streamConfig.js +7 -0
- package/build/runtime/streamConfig.js.map +1 -1
- package/build/services/AcsMeetingService.d.ts +154 -0
- package/build/services/AcsMeetingService.d.ts.map +1 -0
- package/build/services/AcsMeetingService.js +477 -0
- package/build/services/AcsMeetingService.js.map +1 -0
- package/build/services/AppRegistry.d.ts +9 -0
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +22 -0
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/DeviceEventRouter.d.ts.map +1 -1
- package/build/services/DeviceEventRouter.js +12 -3
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/GlassesSettingsSync.d.ts.map +1 -1
- package/build/services/GlassesSettingsSync.js +11 -1
- package/build/services/GlassesSettingsSync.js.map +1 -1
- package/build/services/GlassesStatusProjection.d.ts +12 -0
- package/build/services/GlassesStatusProjection.d.ts.map +1 -1
- package/build/services/GlassesStatusProjection.js +23 -0
- package/build/services/GlassesStatusProjection.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +12 -0
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +184 -2
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MentraJSRouter.d.ts.map +1 -1
- package/build/services/MentraJSRouter.js +8 -1
- package/build/services/MentraJSRouter.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts +3 -1
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +66 -50
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/PhoneCameraFovCoordinator.d.ts +18 -0
- package/build/services/PhoneCameraFovCoordinator.d.ts.map +1 -1
- package/build/services/PhoneCameraFovCoordinator.js +36 -0
- package/build/services/PhoneCameraFovCoordinator.js.map +1 -1
- package/build/services/PhoneStreamCoordinator.d.ts +67 -1
- package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
- package/build/services/PhoneStreamCoordinator.js +204 -15
- package/build/services/PhoneStreamCoordinator.js.map +1 -1
- package/build/services/StreamLifecycleController.d.ts +23 -5
- package/build/services/StreamLifecycleController.d.ts.map +1 -1
- package/build/services/StreamLifecycleController.js +31 -10
- package/build/services/StreamLifecycleController.js.map +1 -1
- package/build/services/acsAudioSource.d.ts +24 -0
- package/build/services/acsAudioSource.d.ts.map +1 -0
- package/build/services/acsAudioSource.js +37 -0
- package/build/services/acsAudioSource.js.map +1 -0
- package/build/utils/devMiniappLaunch.d.ts.map +1 -1
- package/build/utils/devMiniappLaunch.js +4 -0
- package/build/utils/devMiniappLaunch.js.map +1 -1
- package/build/utils/devMiniappSnapshot.d.ts +46 -0
- package/build/utils/devMiniappSnapshot.d.ts.map +1 -0
- package/build/utils/devMiniappSnapshot.js +94 -0
- package/build/utils/devMiniappSnapshot.js.map +1 -0
- package/package.json +7 -6
- package/src/engine.ts +10 -2
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/index.ts +4 -0
- package/src/runtime/streamConfig.ts +13 -0
- package/src/services/AcsMeetingService.ts +570 -0
- package/src/services/AppRegistry.ts +23 -0
- package/src/services/DeviceEventRouter.ts +12 -3
- package/src/services/GlassesSettingsSync.ts +13 -3
- package/src/services/GlassesStatusProjection.ts +28 -0
- package/src/services/LocalMiniappRuntime.ts +214 -2
- package/src/services/MentraJSRouter.ts +7 -1
- package/src/services/MiniappLauncher.ts +73 -50
- package/src/services/PhoneCameraFovCoordinator.ts +34 -0
- package/src/services/PhoneStreamCoordinator.ts +232 -19
- package/src/services/StreamLifecycleController.ts +42 -12
- package/src/services/acsAudioSource.ts +53 -0
- package/src/utils/devMiniappLaunch.ts +4 -0
- package/src/utils/devMiniappSnapshot.ts +119 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GlassesSettingsSync.js","sourceRoot":"","sources":["../../src/services/GlassesSettingsSync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Glasses settings sync — engine-owned. Keeps the connected glasses in sync with\n * the phone's device settings (`BLUETOOTH_SETTING_KEYS`) over the bluetooth-sdk, so\n * `engine.glasses.settings.set()` (and any other settings-store write) reaches the\n * device for ANY host — not just the first-party Mentra app, where this used to live\n * in MantleManager.\n *\n * Two triggers:\n * 1. on change — push the keys that changed.\n * 2. on (re)connect — push the FULL set, so a freshly-connected device gets the\n * phone's current settings (not just future changes).\n *\n * Started by `engine.start()`. Idempotent.\n */\nimport {shallow} from \"zustand/shallow\"\n\nimport BluetoothSdk from \"@mentra/bluetooth-sdk/internal\"\nimport {useSettingsStore, PAIRING_IDENTITY_KEYS} from \"../stores/settings\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {createDebouncedPatchFlusher} from \"../utils/debouncedPatch\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport micStateCoordinator from \"./MicStateCoordinator\"\n\n/**\n * Change-pushes are debounced (300ms) and merged so a burst of setSetting\n * calls becomes ONE BLE write (wire v2 keeps BLE JSON small and infrequent).\n * The full on-connect/seed pushes stay direct — callers await their ordering.\n */\nconst flushBluetoothSettingsPatch = createDebouncedPatchFlusher<Record<string, unknown>>((patch) => {\n // Settings can change while the glasses are disconnected — a native\n // rejection must not surface as an unhandled promise rejection.\n // Apply mic overrides at flush time, not enqueue time: raw PCM can stop\n // during the debounce window and a captured VAD=false would then be stale.\n const runtimePatch = micStateCoordinator.applyRuntimeOverrides(patch)\n void Promise.resolve(BluetoothSdk.updateBluetoothSettings(runtimePatch)).catch((error) => {\n console.warn(\"GlassesSettingsSync: updateBluetoothSettings failed:\", error)\n })\n}, 300)\n\n/**\n * The changed-keys diff for the change-push, MINUS the pairing-identity keys.\n * Identity is native-authoritative (promoted on pairing success, cleared on\n * forget, echoed down via save_setting) and reaches native only through the\n * explicit full seeds. Pushing identity from the change subscription would\n * close a feedback loop: native echo → store setSetting → change-push →\n * native apply → echo…, which self-sustains the moment two different values\n * are in flight (e.g. a boot demotion crossing a native promotion) and flaps\n * the pairing UI.\n */\nexport function diffBluetoothSettingsForPush(\n settings: Record<string, unknown>,\n previous: Record<string, unknown>,\n): Record<string, unknown> {\n const changed: Record<string, unknown> = {}\n for (const key in settings) {\n if (PAIRING_IDENTITY_KEYS.includes(key)) continue\n if (settings[key] !== previous[key]) changed[key] = settings[key]\n }\n return changed\n}\n\n/** A copy of the settings record without the pairing-identity keys. */\nexport function stripPairingIdentity(settings: Record<string, unknown>): Record<string, unknown> {\n const stripped: Record<string, unknown> = {}\n for (const key in settings) {\n if (PAIRING_IDENTITY_KEYS.includes(key)) continue\n stripped[key] = settings[key]\n }\n return stripped\n}\n\nlet unsubChange: (() => void) | null = null\nlet unsubConnect: (() => void) | null = null\n\n/**\n * Push the FULL current device-settings set — identity included — to native.\n * This is the explicit identity SEED, for the flows where native genuinely\n * needs the phone's persisted identity BEFORE it can act: the device-store\n * hydration at start, the pre-connect seed (connectDefault targets the seeded\n * identity), the post-demotion re-push, and the abandon re-seed.\n */\nexport async function pushAllBluetoothSettings(): Promise<void> {\n // Returns the native write promise so callers can await the seed before the\n // connect handshake replays settings to the glasses (otherwise the handshake\n // can race ahead and replay stale native settings).\n const settings = useSettingsStore.getState().getBluetoothSettings()\n await BluetoothSdk.updateBluetoothSettings(micStateCoordinator.applyRuntimeOverrides(settings))\n}\n\n/**\n * Push the device-settings set MINUS the pairing identity. The on-connect\n * replay uses this: while connected, NATIVE owns the identity (it promotes at\n * device-ready and echoes down), and the JS snapshot can be mid-relay stale —\n * on Mentra Live \"connected\" lands together with device-ready, and a full\n * replay raced the promotion echoes and overwrote the just-promoted identity\n * with pre-promotion empties (wiping the pairing it had just made).\n */\nexport async function pushDeviceSettingsOnConnect(): Promise<void> {\n const settings = stripPairingIdentity(useSettingsStore.getState().getBluetoothSettings())\n await BluetoothSdk.updateBluetoothSettings(micStateCoordinator.applyRuntimeOverrides(settings))\n}\n\nexport function startGlassesSettingsSync(): void {\n if (unsubChange || unsubConnect) return\n\n // 1. Push only the keys that changed (matching the prior MantleManager sync).\n // Pairing identity is excluded — see diffBluetoothSettingsForPush.\n unsubChange = useSettingsStore.subscribe(\n (state) => state.getBluetoothSettings(),\n (settings: Record<string, unknown>, previous: Record<string, unknown>) => {\n const changed = diffBluetoothSettingsForPush(settings, previous)\n if (Object.keys(changed).length > 0) {\n flushBluetoothSettingsPatch(changed)\n }\n },\n {equalityFn: shallow},\n )\n\n // 2. Push the device settings whenever the glasses transition to connected —\n // identity EXCLUDED: native owns the identity it just promoted, and a full\n // replay here raced the promotion echoes (see pushDeviceSettingsOnConnect).\n let wasConnected = isGlassesConnected(useGlassesStore.getState().connection)\n unsubConnect = useGlassesStore.subscribe(() => {\n const connected = isGlassesConnected(useGlassesStore.getState().connection)\n if (connected && !wasConnected) {\n // Background sync: log-and-continue if the device drops right after connect.\n void pushDeviceSettingsOnConnect().catch((error) => {\n console.warn(\"GlassesSettingsSync: on-connect settings push failed:\", error)\n })\n }\n wasConnected = connected\n })\n}\n\nexport function stopGlassesSettingsSync(): void {\n unsubChange?.()\n unsubConnect?.()\n unsubChange = null\n unsubConnect = null\n}\n"]}
|
|
1
|
+
{"version":3,"file":"GlassesSettingsSync.js","sourceRoot":"","sources":["../../src/services/GlassesSettingsSync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAA;AAEvC,OAAO,YAAY,MAAM,gCAAgC,CAAA;AACzD,OAAO,EAAC,gBAAgB,EAAE,qBAAqB,EAAC,MAAM,oBAAoB,CAAA;AAC1E,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,2BAA2B,EAAC,MAAM,yBAAyB,CAAA;AACnE,OAAO,EAAC,kBAAkB,EAAC,MAAM,oBAAoB,CAAA;AACrD,OAAO,mBAAmB,MAAM,uBAAuB,CAAA;AACvD,OAAO,EAAC,yBAAyB,EAAC,MAAM,6BAA6B,CAAA;AAErE;;;;GAIG;AACH,MAAM,2BAA2B,GAAG,2BAA2B,CAA0B,CAAC,KAAK,EAAE,EAAE;IACjG,oEAAoE;IACpE,gEAAgE;IAChE,wEAAwE;IACxE,2EAA2E;IAC3E,MAAM,YAAY,GAAG,mBAAmB,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAA;IACrE,KAAK,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,uBAAuB,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACvF,OAAO,CAAC,IAAI,CAAC,sDAAsD,EAAE,KAAK,CAAC,CAAA;IAC7E,CAAC,CAAC,CAAA;AACJ,CAAC,EAAE,GAAG,CAAC,CAAA;AAEP;;;;;;;;;GASG;AACH,MAAM,UAAU,4BAA4B,CAC1C,QAAiC,EACjC,QAAiC;IAEjC,MAAM,OAAO,GAA4B,EAAE,CAAA;IAC3C,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,qBAAqB,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,SAAQ;QACjD,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,GAAG,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAA;IACnE,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,oBAAoB,CAAC,QAAiC;IACpE,MAAM,QAAQ,GAA4B,EAAE,CAAA;IAC5C,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,qBAAqB,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,SAAQ;QACjD,QAAQ,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAA;IAC/B,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,IAAI,WAAW,GAAwB,IAAI,CAAA;AAC3C,IAAI,YAAY,GAAwB,IAAI,CAAA;AAE5C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC5C,4EAA4E;IAC5E,6EAA6E;IAC7E,oDAAoD;IACpD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,EAAE,CAAC,oBAAoB,EAAE,CAAA;IACnE,MAAM,YAAY,CAAC,uBAAuB,CAAC,mBAAmB,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAA;AACjG,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B;IAC/C,MAAM,QAAQ,GAAG,oBAAoB,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,oBAAoB,EAAE,CAAC,CAAA;IACzF,MAAM,YAAY,CAAC,uBAAuB,CAAC,mBAAmB,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAA;AACjG,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,IAAI,WAAW,IAAI,YAAY;QAAE,OAAM;IAEvC,8EAA8E;IAC9E,mEAAmE;IACnE,WAAW,GAAG,gBAAgB,CAAC,SAAS,CACtC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,oBAAoB,EAAE,EACvC,CAAC,QAAiC,EAAE,QAAiC,EAAE,EAAE;QACvE,MAAM,OAAO,GAAG,4BAA4B,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QAChE,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpC,2BAA2B,CAAC,OAAO,CAAC,CAAA;QACtC,CAAC;IACH,CAAC,EACD,EAAC,UAAU,EAAE,OAAO,EAAC,CACtB,CAAA;IAED,6EAA6E;IAC7E,2EAA2E;IAC3E,4EAA4E;IAC5E,IAAI,YAAY,GAAG,kBAAkB,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,CAAA;IAC5E,YAAY,GAAG,eAAe,CAAC,SAAS,CAAC,GAAG,EAAE;QAC5C,MAAM,SAAS,GAAG,kBAAkB,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,CAAA;QAC3E,IAAI,SAAS,IAAI,CAAC,YAAY,EAAE,CAAC;YAC/B,6EAA6E;YAC7E,KAAK,2BAA2B,EAAE;iBAC/B,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBACf,OAAO,CAAC,IAAI,CAAC,uDAAuD,EAAE,KAAK,CAAC,CAAA;YAC9E,CAAC,CAAC;iBACD,OAAO,CAAC,GAAG,EAAE;gBACZ,qEAAqE;gBACrE,oEAAoE;gBACpE,kDAAkD;gBAClD,KAAK,yBAAyB,CAAC,wBAAwB,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;oBACxE,OAAO,CAAC,IAAI,CAAC,kDAAkD,EAAE,KAAK,CAAC,CAAA;gBACzE,CAAC,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;QACN,CAAC;QACD,YAAY,GAAG,SAAS,CAAA;IAC1B,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,uBAAuB;IACrC,WAAW,EAAE,EAAE,CAAA;IACf,YAAY,EAAE,EAAE,CAAA;IAChB,WAAW,GAAG,IAAI,CAAA;IAClB,YAAY,GAAG,IAAI,CAAA;AACrB,CAAC","sourcesContent":["/**\n * Glasses settings sync — engine-owned. Keeps the connected glasses in sync with\n * the phone's device settings (`BLUETOOTH_SETTING_KEYS`) over the bluetooth-sdk, so\n * `engine.glasses.settings.set()` (and any other settings-store write) reaches the\n * device for ANY host — not just the first-party Mentra app, where this used to live\n * in MantleManager.\n *\n * Two triggers:\n * 1. on change — push the keys that changed.\n * 2. on (re)connect — push the FULL set, so a freshly-connected device gets the\n * phone's current settings (not just future changes).\n *\n * Started by `engine.start()`. Idempotent.\n */\nimport {shallow} from \"zustand/shallow\"\n\nimport BluetoothSdk from \"@mentra/bluetooth-sdk/internal\"\nimport {useSettingsStore, PAIRING_IDENTITY_KEYS} from \"../stores/settings\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {createDebouncedPatchFlusher} from \"../utils/debouncedPatch\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport micStateCoordinator from \"./MicStateCoordinator\"\nimport {phoneCameraFovCoordinator} from \"./PhoneCameraFovCoordinator\"\n\n/**\n * Change-pushes are debounced (300ms) and merged so a burst of setSetting\n * calls becomes ONE BLE write (wire v2 keeps BLE JSON small and infrequent).\n * The full on-connect/seed pushes stay direct — callers await their ordering.\n */\nconst flushBluetoothSettingsPatch = createDebouncedPatchFlusher<Record<string, unknown>>((patch) => {\n // Settings can change while the glasses are disconnected — a native\n // rejection must not surface as an unhandled promise rejection.\n // Apply mic overrides at flush time, not enqueue time: raw PCM can stop\n // during the debounce window and a captured VAD=false would then be stale.\n const runtimePatch = micStateCoordinator.applyRuntimeOverrides(patch)\n void Promise.resolve(BluetoothSdk.updateBluetoothSettings(runtimePatch)).catch((error) => {\n console.warn(\"GlassesSettingsSync: updateBluetoothSettings failed:\", error)\n })\n}, 300)\n\n/**\n * The changed-keys diff for the change-push, MINUS the pairing-identity keys.\n * Identity is native-authoritative (promoted on pairing success, cleared on\n * forget, echoed down via save_setting) and reaches native only through the\n * explicit full seeds. Pushing identity from the change subscription would\n * close a feedback loop: native echo → store setSetting → change-push →\n * native apply → echo…, which self-sustains the moment two different values\n * are in flight (e.g. a boot demotion crossing a native promotion) and flaps\n * the pairing UI.\n */\nexport function diffBluetoothSettingsForPush(\n settings: Record<string, unknown>,\n previous: Record<string, unknown>,\n): Record<string, unknown> {\n const changed: Record<string, unknown> = {}\n for (const key in settings) {\n if (PAIRING_IDENTITY_KEYS.includes(key)) continue\n if (settings[key] !== previous[key]) changed[key] = settings[key]\n }\n return changed\n}\n\n/** A copy of the settings record without the pairing-identity keys. */\nexport function stripPairingIdentity(settings: Record<string, unknown>): Record<string, unknown> {\n const stripped: Record<string, unknown> = {}\n for (const key in settings) {\n if (PAIRING_IDENTITY_KEYS.includes(key)) continue\n stripped[key] = settings[key]\n }\n return stripped\n}\n\nlet unsubChange: (() => void) | null = null\nlet unsubConnect: (() => void) | null = null\n\n/**\n * Push the FULL current device-settings set — identity included — to native.\n * This is the explicit identity SEED, for the flows where native genuinely\n * needs the phone's persisted identity BEFORE it can act: the device-store\n * hydration at start, the pre-connect seed (connectDefault targets the seeded\n * identity), the post-demotion re-push, and the abandon re-seed.\n */\nexport async function pushAllBluetoothSettings(): Promise<void> {\n // Returns the native write promise so callers can await the seed before the\n // connect handshake replays settings to the glasses (otherwise the handshake\n // can race ahead and replay stale native settings).\n const settings = useSettingsStore.getState().getBluetoothSettings()\n await BluetoothSdk.updateBluetoothSettings(micStateCoordinator.applyRuntimeOverrides(settings))\n}\n\n/**\n * Push the device-settings set MINUS the pairing identity. The on-connect\n * replay uses this: while connected, NATIVE owns the identity (it promotes at\n * device-ready and echoes down), and the JS snapshot can be mid-relay stale —\n * on Mentra Live \"connected\" lands together with device-ready, and a full\n * replay raced the promotion echoes and overwrote the just-promoted identity\n * with pre-promotion empties (wiping the pairing it had just made).\n */\nexport async function pushDeviceSettingsOnConnect(): Promise<void> {\n const settings = stripPairingIdentity(useSettingsStore.getState().getBluetoothSettings())\n await BluetoothSdk.updateBluetoothSettings(micStateCoordinator.applyRuntimeOverrides(settings))\n}\n\nexport function startGlassesSettingsSync(): void {\n if (unsubChange || unsubConnect) return\n\n // 1. Push only the keys that changed (matching the prior MantleManager sync).\n // Pairing identity is excluded — see diffBluetoothSettingsForPush.\n unsubChange = useSettingsStore.subscribe(\n (state) => state.getBluetoothSettings(),\n (settings: Record<string, unknown>, previous: Record<string, unknown>) => {\n const changed = diffBluetoothSettingsForPush(settings, previous)\n if (Object.keys(changed).length > 0) {\n flushBluetoothSettingsPatch(changed)\n }\n },\n {equalityFn: shallow},\n )\n\n // 2. Push the device settings whenever the glasses transition to connected —\n // identity EXCLUDED: native owns the identity it just promoted, and a full\n // replay here raced the promotion echoes (see pushDeviceSettingsOnConnect).\n let wasConnected = isGlassesConnected(useGlassesStore.getState().connection)\n unsubConnect = useGlassesStore.subscribe(() => {\n const connected = isGlassesConnected(useGlassesStore.getState().connection)\n if (connected && !wasConnected) {\n // Background sync: log-and-continue if the device drops right after connect.\n void pushDeviceSettingsOnConnect()\n .catch((error) => {\n console.warn(\"GlassesSettingsSync: on-connect settings push failed:\", error)\n })\n .finally(() => {\n // That push replays the persistent camera_fov, which stomps whatever\n // override a miniapp currently owns. Re-assert after it either way:\n // a failed push may still have written some keys.\n void phoneCameraFovCoordinator.reapplyEffectiveOverride().catch((error) => {\n console.warn(\"GlassesSettingsSync: camera FOV re-apply failed:\", error)\n })\n })\n }\n wasConnected = connected\n })\n}\n\nexport function stopGlassesSettingsSync(): void {\n unsubChange?.()\n unsubConnect?.()\n unsubChange = null\n unsubConnect = null\n}\n"]}
|
|
@@ -12,6 +12,18 @@
|
|
|
12
12
|
* Started by `engine.start()`. Idempotent.
|
|
13
13
|
*/
|
|
14
14
|
import { type PublicGlassesStatus } from "@mentra/bluetooth-sdk";
|
|
15
|
+
/** Miniapp `session.glasses.onConnection` payload. */
|
|
16
|
+
export type MiniappConnectionData = {
|
|
17
|
+
connected: boolean;
|
|
18
|
+
modelName?: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Native `glasses_status` is a store delta (`connection.state`, battery, wifi…).
|
|
22
|
+
* Miniapps subscribe to a boolean `connected` field. Forwarding the raw status
|
|
23
|
+
* makes `Boolean(data.connected)` false on every heartbeat while the Mentra App
|
|
24
|
+
* still shows the glasses linked.
|
|
25
|
+
*/
|
|
26
|
+
export declare function toMiniappConnectionData(status: unknown): MiniappConnectionData | null;
|
|
15
27
|
export declare function startGlassesStatusProjection(forwarder?: (status: Partial<PublicGlassesStatus>) => void): Promise<void>;
|
|
16
28
|
export declare function stopGlassesStatusProjection(): void;
|
|
17
29
|
//# sourceMappingURL=GlassesStatusProjection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GlassesStatusProjection.d.ts","sourceRoot":"","sources":["../../src/services/GlassesStatusProjection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAqB,EAAC,KAAK,mBAAmB,EAAC,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"GlassesStatusProjection.d.ts","sourceRoot":"","sources":["../../src/services/GlassesStatusProjection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAqB,EAAC,KAAK,mBAAmB,EAAC,MAAM,uBAAuB,CAAA;AAK5E,sDAAsD;AACtD,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,OAAO,GAAG,qBAAqB,GAAG,IAAI,CAarF;AAOD,wBAAgB,4BAA4B,CAC1C,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,mBAAmB,CAAC,KAAK,IAAI,GACzD,OAAO,CAAC,IAAI,CAAC,CAiDf;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAMlD"}
|
|
@@ -14,6 +14,29 @@
|
|
|
14
14
|
import BluetoothSdk from "@mentra/bluetooth-sdk";
|
|
15
15
|
import { useCoreStore } from "../stores/core";
|
|
16
16
|
import { useGlassesStore } from "../stores/glasses";
|
|
17
|
+
import { isGlassesConnected } from "./GlassesReadiness";
|
|
18
|
+
/**
|
|
19
|
+
* Native `glasses_status` is a store delta (`connection.state`, battery, wifi…).
|
|
20
|
+
* Miniapps subscribe to a boolean `connected` field. Forwarding the raw status
|
|
21
|
+
* makes `Boolean(data.connected)` false on every heartbeat while the Mentra App
|
|
22
|
+
* still shows the glasses linked.
|
|
23
|
+
*/
|
|
24
|
+
export function toMiniappConnectionData(status) {
|
|
25
|
+
if (!status || typeof status !== "object")
|
|
26
|
+
return null;
|
|
27
|
+
const rec = status;
|
|
28
|
+
if (typeof rec.connected === "boolean") {
|
|
29
|
+
const modelName = rec.modelName || rec.deviceModel;
|
|
30
|
+
return { connected: rec.connected, ...(modelName ? { modelName } : {}) };
|
|
31
|
+
}
|
|
32
|
+
if (!rec.connection)
|
|
33
|
+
return null;
|
|
34
|
+
const modelName = rec.deviceModel;
|
|
35
|
+
return {
|
|
36
|
+
connected: isGlassesConnected(rec.connection),
|
|
37
|
+
...(modelName ? { modelName } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
17
40
|
let unsubs = [];
|
|
18
41
|
let projectionRunId = 0;
|
|
19
42
|
let glassesStatusForwarder = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GlassesStatusProjection.js","sourceRoot":"","sources":["../../src/services/GlassesStatusProjection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,YAAwC,MAAM,uBAAuB,CAAA;AAC5E,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"GlassesStatusProjection.js","sourceRoot":"","sources":["../../src/services/GlassesStatusProjection.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Glasses status projection — engine-owned. Subscribes to the native bluetooth-sdk\n * status events and projects them into the engine device stores, so engine's own\n * services (which react to those stores) work for ANY host — not just the first-party\n * Mentra app, where this used to live in MantleManager.\n *\n * This is the INBOUND mirror of GlassesSettingsSync: that pushes the settings store\n * OUT to the device; this pulls device status IN to the stores. It's the feed that\n * the rest of the engine runtime depends on (MicStateCoordinator, the settings-sync\n * on-connect trigger, the facade read-models all read these stores).\n *\n * Started by `engine.start()`. Idempotent.\n */\nimport BluetoothSdk, {type PublicGlassesStatus} from \"@mentra/bluetooth-sdk\"\nimport {useCoreStore} from \"../stores/core\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\n\n/** Miniapp `session.glasses.onConnection` payload. */\nexport type MiniappConnectionData = {\n connected: boolean\n modelName?: string\n}\n\n/**\n * Native `glasses_status` is a store delta (`connection.state`, battery, wifi…).\n * Miniapps subscribe to a boolean `connected` field. Forwarding the raw status\n * makes `Boolean(data.connected)` false on every heartbeat while the Mentra App\n * still shows the glasses linked.\n */\nexport function toMiniappConnectionData(status: unknown): MiniappConnectionData | null {\n if (!status || typeof status !== \"object\") return null\n const rec = status as Partial<PublicGlassesStatus> & {connected?: boolean; modelName?: string}\n if (typeof rec.connected === \"boolean\") {\n const modelName = rec.modelName || rec.deviceModel\n return {connected: rec.connected, ...(modelName ? {modelName} : {})}\n }\n if (!rec.connection) return null\n const modelName = rec.deviceModel\n return {\n connected: isGlassesConnected(rec.connection),\n ...(modelName ? {modelName} : {}),\n }\n}\n\nlet unsubs: Array<() => void> = []\nlet projectionRunId = 0\nlet glassesStatusForwarder: ((status: Partial<PublicGlassesStatus>) => void) | null = null\nlet hydrationPromise: Promise<void> | null = null\n\nexport function startGlassesStatusProjection(\n forwarder?: (status: Partial<PublicGlassesStatus>) => void,\n): Promise<void> {\n if (forwarder) glassesStatusForwarder = forwarder\n if (unsubs.length) return hydrationPromise ?? Promise.resolve()\n\n const runId = ++projectionRunId\n let bluetoothEventSeen = false\n let glassesEventSeen = false\n\n const bluetoothHydration = BluetoothSdk.getBluetoothStatus()\n .then((status) => {\n if (runId !== projectionRunId || bluetoothEventSeen) return\n useCoreStore.getState().setCoreInfo(status)\n })\n .catch((error) => {\n console.warn(\"GlassesStatusProjection: getBluetoothStatus failed\", error)\n })\n\n const glassesHydration = BluetoothSdk.getGlassesStatus()\n .then((status) => {\n if (runId !== projectionRunId || glassesEventSeen) return\n useGlassesStore.getState().setGlassesInfo(status)\n })\n .catch((error) => {\n console.warn(\"GlassesStatusProjection: getGlassesStatus failed\", error)\n })\n\n // Bluetooth-adapter status -> core store.\n unsubs.push(\n BluetoothSdk.subscribeBluetoothStatus((changed) => {\n bluetoothEventSeen = true\n useCoreStore.getState().setCoreInfo(changed)\n }),\n )\n\n // Glasses status -> glasses store (+ optional full-runtime forwarding to local\n // miniapps; clear any stale OTA-available flag on disconnect).\n unsubs.push(\n BluetoothSdk.subscribeGlassesStatus((changed) => {\n glassesEventSeen = true\n useGlassesStore.getState().setGlassesInfo(changed)\n glassesStatusForwarder?.(changed)\n if (changed.connection?.state === \"disconnected\") {\n useGlassesStore.getState().setOtaUpdateAvailable(null)\n }\n }),\n )\n\n hydrationPromise = Promise.allSettled([bluetoothHydration, glassesHydration]).then(() => undefined)\n return hydrationPromise\n}\n\nexport function stopGlassesStatusProjection(): void {\n projectionRunId++\n unsubs.forEach((unsub) => unsub())\n unsubs = []\n glassesStatusForwarder = null\n hydrationPromise = null\n}\n"]}
|
|
@@ -345,6 +345,12 @@ declare class LocalMiniappRuntime {
|
|
|
345
345
|
private handleOpenUrl;
|
|
346
346
|
private handleCopyClipboard;
|
|
347
347
|
private handleDownload;
|
|
348
|
+
/**
|
|
349
|
+
* Manifest permission gate shared by the camera-and-mic RPCs (streams, meetings).
|
|
350
|
+
* Sends PERMISSION_NOT_DECLARED and returns false when the miniapp did not declare
|
|
351
|
+
* `permission`; the caller must return without doing the work.
|
|
352
|
+
*/
|
|
353
|
+
private requireManifestPermission;
|
|
348
354
|
private handlePhoto;
|
|
349
355
|
private handleCameraWarmUp;
|
|
350
356
|
private handleVideoRecordingStart;
|
|
@@ -359,6 +365,12 @@ declare class LocalMiniappRuntime {
|
|
|
359
365
|
private handleStreamStop;
|
|
360
366
|
private handleManagedStreamStart;
|
|
361
367
|
private handleManagedStreamStop;
|
|
368
|
+
private ensureMeetingStateBridge;
|
|
369
|
+
private handleMeetingJoin;
|
|
370
|
+
private handleMeetingLeave;
|
|
371
|
+
private handleMeetingSetMuted;
|
|
372
|
+
private handleMeetingUpdateVideoSource;
|
|
373
|
+
private handleMeetingGetState;
|
|
362
374
|
/**
|
|
363
375
|
* session.glasses.requestWifiSetup — open the phone's glasses Wi-Fi setup
|
|
364
376
|
* flow. The host owns the actual UI via the `engine.configure({ui})` seam;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalMiniappRuntime.d.ts","sourceRoot":"","sources":["../../src/services/LocalMiniappRuntime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"LocalMiniappRuntime.d.ts","sourceRoot":"","sources":["../../src/services/LocalMiniappRuntime.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,6 +22,7 @@ import { BgTimer } from "../utils/timers";
|
|
|
22
22
|
import devServerBridge from "./DevServerBridge";
|
|
23
23
|
import { islandNotifications } from "./NotificationsEmitter";
|
|
24
24
|
import { isGlassesConnected } from "./GlassesReadiness";
|
|
25
|
+
import { toMiniappConnectionData } from "./GlassesStatusProjection";
|
|
25
26
|
import audioPlaybackService from "./AudioPlaybackService";
|
|
26
27
|
import { phoneLocationService } from "./PhoneLocationService";
|
|
27
28
|
import { useGlassesStore } from "../stores/glasses";
|
|
@@ -45,7 +46,7 @@ import { miniappLauncher } from "./MiniappLauncher";
|
|
|
45
46
|
import { ISLAND_SETTINGS_KEYS, } from "../runtime/config";
|
|
46
47
|
import { getAnalytics, getUiSeams } from "../runtime/bootstrap";
|
|
47
48
|
import { invokeScanQrSeam } from "../runtime/scanQrSeam";
|
|
48
|
-
import { normalizeStreamAudioConfig, normalizeStreamVideoConfig } from "../runtime/streamConfig";
|
|
49
|
+
import { normalizeStreamAudioConfig, normalizeStreamVideoConfig, resolveCaptureAudio } from "../runtime/streamConfig";
|
|
49
50
|
import { toLanguageHint } from "@mentra/cloud-protocol/languages";
|
|
50
51
|
import { buildMiniappManifestSnapshot } from "../utils/miniappDiagnostics";
|
|
51
52
|
import ttsModelManager from "./TTSModelManager";
|
|
@@ -56,6 +57,7 @@ import { resolveForegroundLocationPermission } from "./ForegroundLocationPermiss
|
|
|
56
57
|
import { advanceMiniappPingLiveness } from "./MiniappLiveness";
|
|
57
58
|
import { listPhoneCalendarEvents, PhoneCalendarError } from "./PhoneCalendarService";
|
|
58
59
|
import { LocalMiniappStorage } from "./LocalMiniappStorage";
|
|
60
|
+
import acsMeetingService, { parseAcsOutgoingVideo, resolveAcsAudioSource } from "./AcsMeetingService";
|
|
59
61
|
/**
|
|
60
62
|
* Strictness ordering for `location_stream` rate values. Higher index =
|
|
61
63
|
* higher accuracy and higher power cost. The aggregate tier applied to
|
|
@@ -594,6 +596,20 @@ class LocalMiniappRuntime {
|
|
|
594
596
|
this.replaceStreamSubscribers(packageName, existing.subscriptions, []);
|
|
595
597
|
this.recomputeMicRequirements();
|
|
596
598
|
this.updateCloudSubscriptions();
|
|
599
|
+
// Managed streams and ACS meetings deliberately survive a respawn: the new
|
|
600
|
+
// incarnation re-adopts them on restore (Mentra Call does exactly this) so a
|
|
601
|
+
// WebView reload does not drop the wearer out of a live call. An unmanaged
|
|
602
|
+
// stream cannot be re-adopted — startUnmanaged rejects while any stream is
|
|
603
|
+
// active, even for the same owner — so release it or the respawned miniapp
|
|
604
|
+
// is stuck behind STREAM_ALREADY_ACTIVE until the process restarts.
|
|
605
|
+
const streamSnapshot = phoneStreamCoordinator.getDiagnosticSnapshot();
|
|
606
|
+
if (streamSnapshot.active === true &&
|
|
607
|
+
streamSnapshot.kind === "unmanaged" &&
|
|
608
|
+
streamSnapshot.ownerPackageName === packageName) {
|
|
609
|
+
void phoneStreamCoordinator.stop(packageName).catch((error) => {
|
|
610
|
+
console.warn(`${LOG_TAG}: failed to stop unmanaged stream while replacing ${packageName}`, error);
|
|
611
|
+
});
|
|
612
|
+
}
|
|
597
613
|
}
|
|
598
614
|
this.connectedApps.set(packageName, {
|
|
599
615
|
subscriptions: new Set(),
|
|
@@ -780,6 +796,9 @@ class LocalMiniappRuntime {
|
|
|
780
796
|
void phoneVideoCoordinator.stopForApp(packageName).catch((error) => {
|
|
781
797
|
console.warn(`${LOG_TAG}: failed to stop video recording for ${packageName} on unregister`, error);
|
|
782
798
|
});
|
|
799
|
+
void acsMeetingService.leaveIfOwner(packageName).catch((error) => {
|
|
800
|
+
console.warn(`${LOG_TAG}: failed to leave ACS meeting for ${packageName} on unregister`, error);
|
|
801
|
+
});
|
|
783
802
|
// Detach the per-app nav event forwarder but leave the native nav session
|
|
784
803
|
// running. The user may have just closed the mini-app UI and will reopen
|
|
785
804
|
// it; stopping the session here would kill an active trip mid-route.
|
|
@@ -1089,6 +1108,21 @@ class LocalMiniappRuntime {
|
|
|
1089
1108
|
case MiniappRequestType.MANAGED_STREAM_STOP:
|
|
1090
1109
|
void this.handleManagedStreamStop(packageName, payload, requestId);
|
|
1091
1110
|
break;
|
|
1111
|
+
case MiniappRequestType.MEETING_JOIN:
|
|
1112
|
+
void this.handleMeetingJoin(packageName, payload, requestId);
|
|
1113
|
+
break;
|
|
1114
|
+
case MiniappRequestType.MEETING_LEAVE:
|
|
1115
|
+
void this.handleMeetingLeave(packageName, requestId);
|
|
1116
|
+
break;
|
|
1117
|
+
case MiniappRequestType.MEETING_SET_MUTED:
|
|
1118
|
+
void this.handleMeetingSetMuted(packageName, payload, requestId);
|
|
1119
|
+
break;
|
|
1120
|
+
case MiniappRequestType.MEETING_UPDATE_VIDEO_SOURCE:
|
|
1121
|
+
void this.handleMeetingUpdateVideoSource(packageName, payload, requestId);
|
|
1122
|
+
break;
|
|
1123
|
+
case MiniappRequestType.MEETING_GET_STATE:
|
|
1124
|
+
void this.handleMeetingGetState(packageName, requestId);
|
|
1125
|
+
break;
|
|
1092
1126
|
case REQUEST_WIFI_SETUP_TYPE:
|
|
1093
1127
|
void this.handleRequestWifiSetup(packageName, payload, requestId);
|
|
1094
1128
|
break;
|
|
@@ -1172,6 +1206,7 @@ class LocalMiniappRuntime {
|
|
|
1172
1206
|
packageName,
|
|
1173
1207
|
capabilities,
|
|
1174
1208
|
permissions: declaredPermissions,
|
|
1209
|
+
hostFeatures: { captureAudio: true },
|
|
1175
1210
|
...(initialAuth ? { auth: initialAuth } : {}),
|
|
1176
1211
|
}, requestId);
|
|
1177
1212
|
if (!initialAuth) {
|
|
@@ -1534,7 +1569,10 @@ class LocalMiniappRuntime {
|
|
|
1534
1569
|
this.sendToMiniapp(packageName, {
|
|
1535
1570
|
type: MiniappResponseType.EVENT,
|
|
1536
1571
|
streamType: "glasses_connection",
|
|
1537
|
-
data:
|
|
1572
|
+
data: {
|
|
1573
|
+
connected: glassesState.connected,
|
|
1574
|
+
modelName: glassesState.deviceModel || undefined,
|
|
1575
|
+
},
|
|
1538
1576
|
});
|
|
1539
1577
|
}
|
|
1540
1578
|
else if (stream === "glasses_wifi") {
|
|
@@ -1554,6 +1592,7 @@ class LocalMiniappRuntime {
|
|
|
1554
1592
|
streamType: "glasses_wifi",
|
|
1555
1593
|
data: {
|
|
1556
1594
|
connected,
|
|
1595
|
+
linkConnected: glassesState.connected === true,
|
|
1557
1596
|
ssid: connected ? wifi?.ssid : undefined,
|
|
1558
1597
|
localIp: connected ? wifi?.localIp : undefined,
|
|
1559
1598
|
timestamp: Date.now(),
|
|
@@ -2792,6 +2831,24 @@ class LocalMiniappRuntime {
|
|
|
2792
2831
|
// ===========================================================================
|
|
2793
2832
|
// Photo + streaming handlers (cloud-coordinated)
|
|
2794
2833
|
// ===========================================================================
|
|
2834
|
+
/**
|
|
2835
|
+
* Manifest permission gate shared by the camera-and-mic RPCs (streams, meetings).
|
|
2836
|
+
* Sends PERMISSION_NOT_DECLARED and returns false when the miniapp did not declare
|
|
2837
|
+
* `permission`; the caller must return without doing the work.
|
|
2838
|
+
*/
|
|
2839
|
+
requireManifestPermission(packageName, requestId, permission, purpose, operation) {
|
|
2840
|
+
const app = this.connectedApps.get(packageName);
|
|
2841
|
+
if (app?.installedManifest?.permissions?.some((p) => p.type === permission))
|
|
2842
|
+
return true;
|
|
2843
|
+
logPermissionNotDeclared(packageName, permission, purpose, `{"type": "${permission}"}`);
|
|
2844
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
2845
|
+
code: MiniappErrorCode.PERMISSION_NOT_DECLARED,
|
|
2846
|
+
message: `${permission} permission not declared in miniapp.json. Add {"type": "${permission}"} to the "permissions" array.`,
|
|
2847
|
+
permission,
|
|
2848
|
+
operation,
|
|
2849
|
+
});
|
|
2850
|
+
return false;
|
|
2851
|
+
}
|
|
2795
2852
|
async handlePhoto(packageName, payload, requestId) {
|
|
2796
2853
|
// Manifest CAMERA permission gate.
|
|
2797
2854
|
const app = this.connectedApps.get(packageName);
|
|
@@ -2931,6 +2988,10 @@ class LocalMiniappRuntime {
|
|
|
2931
2988
|
});
|
|
2932
2989
|
return;
|
|
2933
2990
|
}
|
|
2991
|
+
// A stream is the glasses camera (and mic) leaving the device; gate it like a photo.
|
|
2992
|
+
if (!this.requireManifestPermission(packageName, requestId, "CAMERA", "to stream the camera", MiniappRequestType.STREAM_START)) {
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
2934
2995
|
try {
|
|
2935
2996
|
const result = await streaming.startUnmanaged(packageName, {
|
|
2936
2997
|
streamUrl: payload.streamUrl,
|
|
@@ -2938,6 +2999,7 @@ class LocalMiniappRuntime {
|
|
|
2938
2999
|
audio: normalizeStreamAudioConfig(payload.audio),
|
|
2939
3000
|
sound: payload.sound,
|
|
2940
3001
|
authToken: typeof payload.authToken === "string" ? payload.authToken : undefined,
|
|
3002
|
+
captureAudio: resolveCaptureAudio(payload.captureAudio, resolveAcsAudioSource().source),
|
|
2941
3003
|
});
|
|
2942
3004
|
this.sendResult(packageName, requestId, true, result);
|
|
2943
3005
|
}
|
|
@@ -2979,6 +3041,9 @@ class LocalMiniappRuntime {
|
|
|
2979
3041
|
});
|
|
2980
3042
|
return;
|
|
2981
3043
|
}
|
|
3044
|
+
if (!this.requireManifestPermission(packageName, requestId, "CAMERA", "to stream the camera", MiniappRequestType.MANAGED_STREAM_START)) {
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
2982
3047
|
try {
|
|
2983
3048
|
const result = await streaming.startManaged(packageName, {
|
|
2984
3049
|
restreamDestinations: payload.restreamDestinations,
|
|
@@ -2986,6 +3051,7 @@ class LocalMiniappRuntime {
|
|
|
2986
3051
|
audio: normalizeStreamAudioConfig(payload.audio),
|
|
2987
3052
|
sound: payload.sound,
|
|
2988
3053
|
ingest: payload.ingest,
|
|
3054
|
+
captureAudio: resolveCaptureAudio(payload.captureAudio, resolveAcsAudioSource().source),
|
|
2989
3055
|
});
|
|
2990
3056
|
this.sendResult(packageName, requestId, true, result);
|
|
2991
3057
|
}
|
|
@@ -3001,6 +3067,119 @@ class LocalMiniappRuntime {
|
|
|
3001
3067
|
async handleManagedStreamStop(packageName, payload, requestId) {
|
|
3002
3068
|
return this.handleStreamStop(packageName, payload, requestId);
|
|
3003
3069
|
}
|
|
3070
|
+
ensureMeetingStateBridge() {
|
|
3071
|
+
acsMeetingService.setStateHandler((owner, state) => {
|
|
3072
|
+
this.sendToMiniapp(owner, {
|
|
3073
|
+
type: MiniappResponseType.MEETING_STATE,
|
|
3074
|
+
...state,
|
|
3075
|
+
});
|
|
3076
|
+
});
|
|
3077
|
+
}
|
|
3078
|
+
async handleMeetingJoin(packageName, payload, requestId) {
|
|
3079
|
+
this.ensureMeetingStateBridge();
|
|
3080
|
+
// A meeting puts the glasses camera and mic in front of remote strangers; it
|
|
3081
|
+
// needs both declared, same as photo/stream and mic capture do individually.
|
|
3082
|
+
if (!this.requireManifestPermission(packageName, requestId, "CAMERA", "to join a meeting", MiniappRequestType.MEETING_JOIN) ||
|
|
3083
|
+
!this.requireManifestPermission(packageName, requestId, "MICROPHONE", "to join a meeting", MiniappRequestType.MEETING_JOIN)) {
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
const meetingUrl = typeof payload.meetingUrl === "string" ? payload.meetingUrl : "";
|
|
3087
|
+
const token = typeof payload.token === "string" ? payload.token : "";
|
|
3088
|
+
const videoSource = payload.videoSource;
|
|
3089
|
+
const whepUrl = videoSource?.type === "whep" ? videoSource.url ?? "" : "";
|
|
3090
|
+
const displayName = typeof payload.displayName === "string" ? payload.displayName : undefined;
|
|
3091
|
+
if (!meetingUrl || !token || !whepUrl) {
|
|
3092
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
3093
|
+
code: MiniappErrorCode.INVALID_ARGUMENT,
|
|
3094
|
+
message: "meetingUrl, token, and a WHEP videoSource are required",
|
|
3095
|
+
});
|
|
3096
|
+
return;
|
|
3097
|
+
}
|
|
3098
|
+
try {
|
|
3099
|
+
let video;
|
|
3100
|
+
try {
|
|
3101
|
+
video = parseAcsOutgoingVideo(payload.video);
|
|
3102
|
+
}
|
|
3103
|
+
catch (error) {
|
|
3104
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
3105
|
+
code: MiniappErrorCode.INVALID_ARGUMENT,
|
|
3106
|
+
message: error instanceof Error ? error.message : "Invalid meeting video",
|
|
3107
|
+
});
|
|
3108
|
+
return;
|
|
3109
|
+
}
|
|
3110
|
+
const state = await acsMeetingService.join(packageName, {
|
|
3111
|
+
meetingUrl,
|
|
3112
|
+
token,
|
|
3113
|
+
whepUrl,
|
|
3114
|
+
displayName,
|
|
3115
|
+
...(video ? { video } : {}),
|
|
3116
|
+
});
|
|
3117
|
+
this.sendResult(packageName, requestId, true, state);
|
|
3118
|
+
}
|
|
3119
|
+
catch (err) {
|
|
3120
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
3121
|
+
code: MiniappErrorCode.INTERNAL,
|
|
3122
|
+
message: err instanceof Error ? err.message : "ACS meeting join failed",
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
async handleMeetingLeave(packageName, requestId) {
|
|
3127
|
+
try {
|
|
3128
|
+
await acsMeetingService.leave(packageName);
|
|
3129
|
+
this.sendResult(packageName, requestId, true);
|
|
3130
|
+
}
|
|
3131
|
+
catch (err) {
|
|
3132
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
3133
|
+
code: MiniappErrorCode.INTERNAL,
|
|
3134
|
+
message: err instanceof Error ? err.message : "ACS meeting leave failed",
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
async handleMeetingSetMuted(packageName, payload, requestId) {
|
|
3139
|
+
try {
|
|
3140
|
+
const state = await acsMeetingService.setMuted(packageName, Boolean(payload.muted));
|
|
3141
|
+
this.sendResult(packageName, requestId, true, state);
|
|
3142
|
+
}
|
|
3143
|
+
catch (err) {
|
|
3144
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
3145
|
+
code: MiniappErrorCode.INTERNAL,
|
|
3146
|
+
message: err instanceof Error ? err.message : "ACS mute failed",
|
|
3147
|
+
});
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
async handleMeetingUpdateVideoSource(packageName, payload, requestId) {
|
|
3151
|
+
const videoSource = payload.videoSource;
|
|
3152
|
+
const whepUrl = videoSource?.type === "whep" ? videoSource.url ?? "" : "";
|
|
3153
|
+
if (!whepUrl) {
|
|
3154
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
3155
|
+
code: MiniappErrorCode.INVALID_ARGUMENT,
|
|
3156
|
+
message: "videoSource must be a WHEP URL",
|
|
3157
|
+
});
|
|
3158
|
+
return;
|
|
3159
|
+
}
|
|
3160
|
+
try {
|
|
3161
|
+
await acsMeetingService.updateVideoSource(packageName, whepUrl);
|
|
3162
|
+
this.sendResult(packageName, requestId, true);
|
|
3163
|
+
}
|
|
3164
|
+
catch (err) {
|
|
3165
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
3166
|
+
code: MiniappErrorCode.INTERNAL,
|
|
3167
|
+
message: err instanceof Error ? err.message : "ACS video source update failed",
|
|
3168
|
+
});
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
async handleMeetingGetState(packageName, requestId) {
|
|
3172
|
+
try {
|
|
3173
|
+
const state = await acsMeetingService.readState(packageName);
|
|
3174
|
+
this.sendResult(packageName, requestId, true, state);
|
|
3175
|
+
}
|
|
3176
|
+
catch (err) {
|
|
3177
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
3178
|
+
code: MiniappErrorCode.INTERNAL,
|
|
3179
|
+
message: err instanceof Error ? err.message : "ACS getState failed",
|
|
3180
|
+
});
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3004
3183
|
/**
|
|
3005
3184
|
* session.glasses.requestWifiSetup — open the phone's glasses Wi-Fi setup
|
|
3006
3185
|
* flow. The host owns the actual UI via the `engine.configure({ui})` seam;
|
|
@@ -3390,6 +3569,9 @@ class LocalMiniappRuntime {
|
|
|
3390
3569
|
// The bare `touch_event` stream above still catches `onTouch(handler)`.
|
|
3391
3570
|
let perGestureStream = null;
|
|
3392
3571
|
let outboundData = data;
|
|
3572
|
+
if (normalizedStream === MiniappStreamType.GLASSES_CONNECTION) {
|
|
3573
|
+
outboundData = toMiniappConnectionData(data) ?? data;
|
|
3574
|
+
}
|
|
3393
3575
|
if (normalizedStream === MiniappStreamType.TOUCH_EVENT) {
|
|
3394
3576
|
// The Bluetooth SDK delivers the gesture under `gestureName` (single_tap /
|
|
3395
3577
|
// double_tap / triple_tap / long_press / swipe_up / swipe_down). Surface it
|