@mentra/acs-meeting 3.2.0-dev.226 → 3.2.0-dev.232

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.
@@ -26,6 +26,11 @@ export type AcsMeetingState = {
26
26
  activeStream?: AcsActiveStream;
27
27
  audioSafety?: AcsAudioSafety;
28
28
  mediaSource?: AcsMediaSourceState;
29
+ /** Native receiver diagnostic, including the reason a local WHIP offer was rejected. */
30
+ mediaSourceReason?: string;
31
+ /** ACS disconnect diagnostics, emitted when the final disconnected state arrives. */
32
+ endReason_code?: number;
33
+ endReason_subcode?: number;
29
34
  /** Local WHIP endpoint, available after a SoftAP join. */
30
35
  ingestUrl?: string;
31
36
  /** Remote roster (Android emits this; iOS does not yet). */
@@ -1 +1 @@
1
- {"version":3,"file":"AcsMeeting.types.d.ts","sourceRoot":"","sources":["../src/AcsMeeting.types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,YAAY,GAAG,OAAO,GAAG,WAAW,GAAG,cAAc,GAAG,OAAO,CAAA;AAEnG,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,OAAO,CAAA;AAChD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAA;AAC1D,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAA;AAC3D;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAA;AAE3E,MAAM,MAAM,0BAA0B,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,cAAc,CAAA;AAEhH,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,0BAA0B,CAAA;IACjC,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,YAAY,CAAA;IACnB,KAAK,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,WAAW,CAAA;IACtB,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,YAAY,CAAC,EAAE,eAAe,CAAA;IAC9B,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,WAAW,CAAC,EAAE,mBAAmB,CAAA;IACjC,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,aAAa,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,GAAG;QAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAAA;IACjF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,yDAAyD;IACzD,KAAK,CAAC,EAAE,gBAAgB,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAA;IACzC,aAAa,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,IAAI,CAAA;CAClD,CAAA"}
1
+ {"version":3,"file":"AcsMeeting.types.d.ts","sourceRoot":"","sources":["../src/AcsMeeting.types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,YAAY,GAAG,OAAO,GAAG,WAAW,GAAG,cAAc,GAAG,OAAO,CAAA;AAEnG,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,OAAO,CAAA;AAChD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAA;AAC1D,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAA;AAC3D;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAA;AAE3E,MAAM,MAAM,0BAA0B,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,cAAc,CAAA;AAEhH,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,0BAA0B,CAAA;IACjC,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,YAAY,CAAA;IACnB,KAAK,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,WAAW,CAAA;IACtB,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,YAAY,CAAC,EAAE,eAAe,CAAA;IAC9B,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,WAAW,CAAC,EAAE,mBAAmB,CAAA;IACjC,wFAAwF;IACxF,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qFAAqF;IACrF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,aAAa,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,GAAG;QAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAAA;IACjF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,yDAAyD;IACzD,KAAK,CAAC,EAAE,gBAAgB,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAA;IACzC,aAAa,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,IAAI,CAAA;CAClD,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"AcsMeeting.types.js","sourceRoot":"","sources":["../src/AcsMeeting.types.ts"],"names":[],"mappings":"","sourcesContent":["export type MeetingPhase = \"idle\" | \"connecting\" | \"lobby\" | \"connected\" | \"disconnected\" | \"error\"\n\nexport type AcsAudioSource = \"glasses\" | \"phone\"\nexport type AcsActiveStream = \"none\" | \"virtual\" | \"local\"\nexport type AcsAudioSafety = \"safe\" | \"degraded\" | \"unsafe\"\n/**\n * Health of the glasses WHEP subscription feeding the call. `failed` = ICE dropped or\n * the WHEP endpoint went away while the ACS call may still be `connected`. Native\n * rebuilds it on its own with backoff; the host can force one via `restartVideoSource`.\n */\nexport type AcsMediaSourceState = \"idle\" | \"connecting\" | \"live\" | \"failed\"\n\nexport type AcsMeetingParticipantState = \"idle\" | \"connecting\" | \"connected\" | \"lobby\" | \"hold\" | \"disconnected\"\n\nexport type AcsMeetingParticipant = {\n id: string\n displayName: string | null\n state: AcsMeetingParticipantState\n isMuted: boolean\n isSpeaking: boolean\n}\n\nexport type AcsMeetingState = {\n state: MeetingPhase\n muted: boolean\n error?: string\n meetingUrl?: string\n provider?: \"acs-teams\"\n audioSource?: AcsAudioSource\n activeStream?: AcsActiveStream\n audioSafety?: AcsAudioSafety\n mediaSource?: AcsMediaSourceState\n /** Local WHIP endpoint, available after a SoftAP join. */\n ingestUrl?: string\n /** Remote roster (Android emits this; iOS does not yet). */\n participants?: AcsMeetingParticipant[]\n}\n\nexport type AcsOutgoingVideo = {\n width: number\n height: number\n fps: number\n maxBitrateBps: number\n}\n\nexport type AcsMeetingJoinOptions = {\n meetingUrl: string\n token: string\n whepUrl?: string\n videoSource?: {type: \"whep\"; url: string} | {type: \"softap\"; bindAddress: string}\n displayName?: string\n /** \"glasses\" sends WHEP PCM. \"phone\" uses the ACS local mic (handset or BT). */\n audioSource?: AcsAudioSource\n /**\n * Hold outgoing PCM this many ms before ACS. SoftAP+LC3 sets this in the host\n * because BLE audio reaches the phone before SoftAP video.\n */\n audioDelayMs?: number\n /** Dump WHEP PCM to a WAV in cache for P4 verification. */\n dumpPcmWav?: boolean\n /** When omitted, native keeps 1280×720@15 / 2.5 Mbps. */\n video?: AcsOutgoingVideo\n}\n\nexport type AcsIncomingPcmEvent = {\n base64: string\n sampleRate: number\n channels: number\n}\n\nexport type AcsMeetingModuleEvents = {\n onState: (state: AcsMeetingState) => void\n onIncomingPcm: (pcm: AcsIncomingPcmEvent) => void\n}\n"]}
1
+ {"version":3,"file":"AcsMeeting.types.js","sourceRoot":"","sources":["../src/AcsMeeting.types.ts"],"names":[],"mappings":"","sourcesContent":["export type MeetingPhase = \"idle\" | \"connecting\" | \"lobby\" | \"connected\" | \"disconnected\" | \"error\"\n\nexport type AcsAudioSource = \"glasses\" | \"phone\"\nexport type AcsActiveStream = \"none\" | \"virtual\" | \"local\"\nexport type AcsAudioSafety = \"safe\" | \"degraded\" | \"unsafe\"\n/**\n * Health of the glasses WHEP subscription feeding the call. `failed` = ICE dropped or\n * the WHEP endpoint went away while the ACS call may still be `connected`. Native\n * rebuilds it on its own with backoff; the host can force one via `restartVideoSource`.\n */\nexport type AcsMediaSourceState = \"idle\" | \"connecting\" | \"live\" | \"failed\"\n\nexport type AcsMeetingParticipantState = \"idle\" | \"connecting\" | \"connected\" | \"lobby\" | \"hold\" | \"disconnected\"\n\nexport type AcsMeetingParticipant = {\n id: string\n displayName: string | null\n state: AcsMeetingParticipantState\n isMuted: boolean\n isSpeaking: boolean\n}\n\nexport type AcsMeetingState = {\n state: MeetingPhase\n muted: boolean\n error?: string\n meetingUrl?: string\n provider?: \"acs-teams\"\n audioSource?: AcsAudioSource\n activeStream?: AcsActiveStream\n audioSafety?: AcsAudioSafety\n mediaSource?: AcsMediaSourceState\n /** Native receiver diagnostic, including the reason a local WHIP offer was rejected. */\n mediaSourceReason?: string\n /** ACS disconnect diagnostics, emitted when the final disconnected state arrives. */\n endReason_code?: number\n endReason_subcode?: number\n /** Local WHIP endpoint, available after a SoftAP join. */\n ingestUrl?: string\n /** Remote roster (Android emits this; iOS does not yet). */\n participants?: AcsMeetingParticipant[]\n}\n\nexport type AcsOutgoingVideo = {\n width: number\n height: number\n fps: number\n maxBitrateBps: number\n}\n\nexport type AcsMeetingJoinOptions = {\n meetingUrl: string\n token: string\n whepUrl?: string\n videoSource?: {type: \"whep\"; url: string} | {type: \"softap\"; bindAddress: string}\n displayName?: string\n /** \"glasses\" sends WHEP PCM. \"phone\" uses the ACS local mic (handset or BT). */\n audioSource?: AcsAudioSource\n /**\n * Hold outgoing PCM this many ms before ACS. SoftAP+LC3 sets this in the host\n * because BLE audio reaches the phone before SoftAP video.\n */\n audioDelayMs?: number\n /** Dump WHEP PCM to a WAV in cache for P4 verification. */\n dumpPcmWav?: boolean\n /** When omitted, native keeps 1280×720@15 / 2.5 Mbps. */\n video?: AcsOutgoingVideo\n}\n\nexport type AcsIncomingPcmEvent = {\n base64: string\n sampleRate: number\n channels: number\n}\n\nexport type AcsMeetingModuleEvents = {\n onState: (state: AcsMeetingState) => void\n onIncomingPcm: (pcm: AcsIncomingPcmEvent) => void\n}\n"]}
@@ -24,8 +24,16 @@ declare class AcsMeetingNativeModule extends NativeModule<AcsMeetingModuleEvents
24
24
  restartVideoSource(): Promise<void>;
25
25
  /** SoftAP: join the glasses hotspot; resolves to the phone's IPv4 on it. */
26
26
  joinScopedNetwork(ssid: string, passphrase: string): Promise<string>;
27
+ /** iOS: verify DHCP against the gateway advertised by the glasses before resolving. */
28
+ joinScopedNetworkWithGateway?(ssid: string, passphrase: string, gateway: string): Promise<string>;
27
29
  beginTrace(traceId: string): Promise<void>;
28
30
  leaveScopedNetwork(): Promise<void>;
31
+ cancelScopedNetworkJoin?(): Promise<void>;
32
+ awaitDefaultNetworkAfterHotspot?(): Promise<{
33
+ usable: boolean;
34
+ detail: string;
35
+ transport: string;
36
+ }>;
29
37
  /** SoftAP: TCP-probe the hotspot gateway over the scoped network. */
30
38
  probeScopedGateway(): Promise<{
31
39
  reachable: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"AcsMeetingModule.d.ts","sourceRoot":"","sources":["../src/AcsMeetingModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAsB,MAAM,MAAM,CAAA;AAEtD,OAAO,KAAK,EAAC,qBAAqB,EAAE,sBAAsB,EAAE,eAAe,EAAC,MAAM,oBAAoB,CAAA;AAEtG,OAAO,OAAO,sBAAuB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;IAC/E,IAAI,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IAC9D,iFAAiF;IACjF,YAAY,CAAC,OAAO,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,eAAe,CAAC;IACtF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IACtB;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAC,CAAC;IAC1E,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC;IAClD,cAAc,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC;IACrE,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACjD,wEAAwE;IACxE,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IACnC,4EAA4E;IAC5E,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACpE,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1C,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IACnC,qEAAqE;IACrE,kBAAkB,IAAI,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,CAAC;IACnE,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC;CACrC;;AAED,wBAA8E"}
1
+ {"version":3,"file":"AcsMeetingModule.d.ts","sourceRoot":"","sources":["../src/AcsMeetingModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAsB,MAAM,MAAM,CAAA;AAEtD,OAAO,KAAK,EAAC,qBAAqB,EAAE,sBAAsB,EAAE,eAAe,EAAC,MAAM,oBAAoB,CAAA;AAEtG,OAAO,OAAO,sBAAuB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;IAC/E,IAAI,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IAC9D,iFAAiF;IACjF,YAAY,CAAC,OAAO,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,eAAe,CAAC;IACtF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IACtB;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAC,CAAC;IAC1E,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC;IAClD,cAAc,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC;IACrE,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACjD,wEAAwE;IACxE,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IACnC,4EAA4E;IAC5E,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACpE,uFAAuF;IACvF,4BAA4B,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACjG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1C,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IACnC,uBAAuB,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;IACzC,+BAA+B,CAAC,IAAI,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,CAAC;IACjG,qEAAqE;IACrE,kBAAkB,IAAI,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,CAAC;IACnE,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC;CACrC;;AAED,wBAA8E"}
@@ -1 +1 @@
1
- {"version":3,"file":"AcsMeetingModule.js","sourceRoot":"","sources":["../src/AcsMeetingModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,mBAAmB,EAAC,MAAM,MAAM,CAAA;AA4BtD,eAAe,mBAAmB,CAAyB,kBAAkB,CAAC,CAAA","sourcesContent":["import {NativeModule, requireNativeModule} from \"expo\"\n\nimport type {AcsMeetingJoinOptions, AcsMeetingModuleEvents, AcsMeetingState} from \"./AcsMeeting.types\"\n\ndeclare class AcsMeetingNativeModule extends NativeModule<AcsMeetingModuleEvents> {\n join(options: AcsMeetingJoinOptions): Promise<AcsMeetingState>\n /** Sign in to ACS before SoftAP so Teams is not resolved through glasses DNS. */\n prepareAgent(options: {token: string; displayName?: string}): Promise<AcsMeetingState>\n leave(): Promise<void>\n /**\n * Leave, and resolve only once the hang-up, the agent disposal, and the network releases have\n * finished. Use this explicit barrier across platforms; Android `leave()` only queues cleanup.\n */\n leaveAndAwait(options: {timeoutMs: number}): Promise<{completed: boolean}>\n setMuted(muted: boolean): Promise<AcsMeetingState>\n setAudioSource(source: \"glasses\" | \"phone\"): Promise<AcsMeetingState>\n updateVideoSource(whepUrl: string): Promise<void>\n /** Force a WHEP rebuild on the current URL (phone changed networks). */\n restartVideoSource(): Promise<void>\n /** SoftAP: join the glasses hotspot; resolves to the phone's IPv4 on it. */\n joinScopedNetwork(ssid: string, passphrase: string): Promise<string>\n beginTrace(traceId: string): Promise<void>\n leaveScopedNetwork(): Promise<void>\n /** SoftAP: TCP-probe the hotspot gateway over the scoped network. */\n probeScopedGateway(): Promise<{reachable: boolean; detail: string}>\n getState(): Promise<AcsMeetingState>\n}\n\nexport default requireNativeModule<AcsMeetingNativeModule>(\"MentraAcsMeeting\")\n"]}
1
+ {"version":3,"file":"AcsMeetingModule.js","sourceRoot":"","sources":["../src/AcsMeetingModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,mBAAmB,EAAC,MAAM,MAAM,CAAA;AAgCtD,eAAe,mBAAmB,CAAyB,kBAAkB,CAAC,CAAA","sourcesContent":["import {NativeModule, requireNativeModule} from \"expo\"\n\nimport type {AcsMeetingJoinOptions, AcsMeetingModuleEvents, AcsMeetingState} from \"./AcsMeeting.types\"\n\ndeclare class AcsMeetingNativeModule extends NativeModule<AcsMeetingModuleEvents> {\n join(options: AcsMeetingJoinOptions): Promise<AcsMeetingState>\n /** Sign in to ACS before SoftAP so Teams is not resolved through glasses DNS. */\n prepareAgent(options: {token: string; displayName?: string}): Promise<AcsMeetingState>\n leave(): Promise<void>\n /**\n * Leave, and resolve only once the hang-up, the agent disposal, and the network releases have\n * finished. Use this explicit barrier across platforms; Android `leave()` only queues cleanup.\n */\n leaveAndAwait(options: {timeoutMs: number}): Promise<{completed: boolean}>\n setMuted(muted: boolean): Promise<AcsMeetingState>\n setAudioSource(source: \"glasses\" | \"phone\"): Promise<AcsMeetingState>\n updateVideoSource(whepUrl: string): Promise<void>\n /** Force a WHEP rebuild on the current URL (phone changed networks). */\n restartVideoSource(): Promise<void>\n /** SoftAP: join the glasses hotspot; resolves to the phone's IPv4 on it. */\n joinScopedNetwork(ssid: string, passphrase: string): Promise<string>\n /** iOS: verify DHCP against the gateway advertised by the glasses before resolving. */\n joinScopedNetworkWithGateway?(ssid: string, passphrase: string, gateway: string): Promise<string>\n beginTrace(traceId: string): Promise<void>\n leaveScopedNetwork(): Promise<void>\n cancelScopedNetworkJoin?(): Promise<void>\n awaitDefaultNetworkAfterHotspot?(): Promise<{usable: boolean; detail: string; transport: string}>\n /** SoftAP: TCP-probe the hotspot gateway over the scoped network. */\n probeScopedGateway(): Promise<{reachable: boolean; detail: string}>\n getState(): Promise<AcsMeetingState>\n}\n\nexport default requireNativeModule<AcsMeetingNativeModule>(\"MentraAcsMeeting\")\n"]}
@@ -21,6 +21,18 @@ public class AcsMeetingModule: Module {
21
21
  return session
22
22
  }
23
23
 
24
+ private func joinHotspot(ssid: String, passphrase: String, gateway: String?, promise: Promise) {
25
+ hotspot.onLost = { [weak self] reason in
26
+ self?.sendEvent("onScopedNetworkLost", ["code": "SOFTAP_LOST", "message": reason])
27
+ }
28
+ hotspot.join(ssid: ssid, passphrase: passphrase, gateway: gateway) { result in
29
+ switch result {
30
+ case let .success(address): promise.resolve(address)
31
+ case let .failure(error): promise.reject(error)
32
+ }
33
+ }
34
+ }
35
+
24
36
  public func definition() -> ModuleDefinition {
25
37
  Name("MentraAcsMeeting")
26
38
  Events("onState", "onIncomingPcm", "onScopedNetworkLost")
@@ -57,25 +69,30 @@ public class AcsMeetingModule: Module {
57
69
  }
58
70
 
59
71
  AsyncFunction("joinScopedNetwork") { (ssid: String, passphrase: String, promise: Promise) in
60
- self.hotspot.onLost = { [weak self] reason in
61
- self?.sendEvent("onScopedNetworkLost", ["code": "SOFTAP_LOST", "message": reason])
62
- }
63
- self.hotspot.join(ssid: ssid, passphrase: passphrase) { result in
64
- switch result {
65
- case let .success(address): promise.resolve(address)
66
- case let .failure(error): promise.reject(error)
67
- }
68
- }
72
+ self.joinHotspot(ssid: ssid, passphrase: passphrase, gateway: nil, promise: promise)
73
+ }
74
+
75
+ AsyncFunction("joinScopedNetworkWithGateway") { (ssid: String, passphrase: String, gateway: String, promise: Promise) in
76
+ self.joinHotspot(ssid: ssid, passphrase: passphrase, gateway: gateway, promise: promise)
69
77
  }
70
78
 
71
79
  AsyncFunction("leaveScopedNetwork") { (promise: Promise) in
72
80
  self.hotspot.leave { promise.resolve(nil) }
73
81
  }
74
82
 
83
+ AsyncFunction("cancelScopedNetworkJoin") { (promise: Promise) in
84
+ self.hotspot.leave { promise.resolve(nil) }
85
+ }
86
+
87
+ AsyncFunction("awaitDefaultNetworkAfterHotspot") { (promise: Promise) in
88
+ self.hotspot.awaitInternet(requireCellular: false) { usable, detail in
89
+ promise.resolve(["usable": usable, "detail": detail, "transport": usable ? detail : "unknown", "validated": usable, "present": usable])
90
+ }
91
+ }
92
+
75
93
  AsyncFunction("beginTrace") { (traceId: String) in
76
94
  NSLog("SOFTAP_TRACE trace=\(traceId) stage=ios_begin")
77
95
  }
78
-
79
96
  AsyncFunction("probeScopedGateway") { (promise: Promise) in
80
97
  self.hotspot.probeGateway { reachable, detail in
81
98
  promise.resolve(["reachable": reachable, "detail": detail])
@@ -188,6 +205,7 @@ final class AcsMeetingSession {
188
205
  private var muted = false
189
206
  private var meetingUrl: String?
190
207
  private var lastError: String?
208
+ private var callEndReason: (code: Int, subcode: Int)?
191
209
  private var callClient: CallClient?
192
210
  private var callAgent: CallAgent?
193
211
  private var call: Call?
@@ -213,6 +231,7 @@ final class AcsMeetingSession {
213
231
  // Health of the glasses WHEP feed, reported alongside the ACS phase so the host
214
232
  // can tell "call is up, glasses video is dead" from a healthy call.
215
233
  private var mediaSource: SourceState = .idle
234
+ private var mediaSourceReason: String?
216
235
  private var mediaRestartAttempts = 0
217
236
  private var mediaRestartTask: DispatchWorkItem?
218
237
  private static let mediaRestartBaseMs = 1000
@@ -254,8 +273,13 @@ final class AcsMeetingSession {
254
273
  }
255
274
  result["capabilities"] = ["hangUpForEveryone": hangUp]
256
275
  if let ingestUrl = media?.ingestUrl { result["ingestUrl"] = ingestUrl }
276
+ if let mediaSourceReason { result["mediaSourceReason"] = mediaSourceReason }
257
277
  if let meetingUrl { result["meetingUrl"] = meetingUrl }
258
278
  if let lastError { result["error"] = lastError }
279
+ if let callEndReason {
280
+ result["endReason_code"] = callEndReason.code
281
+ result["endReason_subcode"] = callEndReason.subcode
282
+ }
259
283
  return result
260
284
  }
261
285
 
@@ -319,6 +343,7 @@ final class AcsMeetingSession {
319
343
  self.audioSource = AcsAudioPolicy.parseSource(audioSource) == .phone ? "phone" : "glasses"
320
344
  self.meetingUrl = meetingUrl
321
345
  self.lastError = nil
346
+ self.callEndReason = nil
322
347
  self.emit("connecting")
323
348
  let useAgent: (CallAgent) -> Void = { agent in
324
349
  do { try self.joinWithAgentLocked(agent, generation: generation, meetingUrl: meetingUrl,
@@ -508,6 +533,9 @@ final class AcsMeetingSession {
508
533
  guard self.joinGeneration == generation, self.media === source else { return }
509
534
  switch result {
510
535
  case .success:
536
+ // The SDK may have changed state before its delegate was attached.
537
+ // Reading it here also makes the join result reflect admission already granted.
538
+ self.refreshCallStateLocked(call)
511
539
  let reply = self.pendingJoin
512
540
  self.pendingJoin = nil
513
541
  reply?(.success(self.snapshot()))
@@ -559,6 +587,7 @@ final class AcsMeetingSession {
559
587
  private func onMediaSourceState(_ state: SourceState, reason: String) {
560
588
  let previous = mediaSource
561
589
  mediaSource = state
590
+ mediaSourceReason = reason
562
591
  if state == .live { mediaRestartAttempts = 0 }
563
592
  if state == .failed { scheduleMediaRestart(reason: reason) }
564
593
  // start() emits idle then connecting back to back; one snapshot per real change.
@@ -787,6 +816,7 @@ final class AcsMeetingSession {
787
816
  media?.onFrame = nil
788
817
  media?.onPcm = nil
789
818
  mediaSource = .idle
819
+ mediaSourceReason = nil
790
820
  if let local = media as? LocalWhipIngestSource {
791
821
  cleanup.enter()
792
822
  local.stop { self.cleanup.leave() }
@@ -832,6 +862,7 @@ final class AcsMeetingSession {
832
862
  // carries it and the call delegate keeps ignoring late disconnected callbacks.
833
863
  if emitIdle {
834
864
  lastError = nil
865
+ callEndReason = nil
835
866
  emit("idle")
836
867
  }
837
868
  }
@@ -851,15 +882,33 @@ final class AcsMeetingSession {
851
882
  private func handleCallStateChange(_ changedCall: Call) {
852
883
  queue.async {
853
884
  guard self.call === changedCall, self.lastError == nil else { return }
854
- switch changedCall.state {
855
- case .connecting: self.emit("connecting")
856
- case .inLobby: self.emit("lobby")
857
- case .connected:
858
- self.emit("connected")
859
- self.applyAudioPolicyOnQueue("call-connected")
860
- case .disconnecting, .disconnected: self.emit("disconnected")
861
- default: break
885
+ self.refreshCallStateLocked(changedCall)
886
+ }
887
+ }
888
+
889
+ private func refreshCallStateLocked(_ changedCall: Call) {
890
+ switch changedCall.state {
891
+ case .connecting: emit("connecting")
892
+ case .inLobby: emit("lobby")
893
+ case .connected:
894
+ emit("connected")
895
+ applyAudioPolicyOnQueue("call-connected")
896
+ case .disconnected:
897
+ let reason = changedCall.callEndReason
898
+ callEndReason = (Int(reason.code), Int(reason.subcode))
899
+ if phase == "connecting" || phase == "lobby" {
900
+ let error = AcsMeetingError("The Teams call ended before admission (ACS \(reason.code)/\(reason.subcode))")
901
+ if pendingJoin != nil {
902
+ failJoinLocked(error, generation: joinGeneration)
903
+ return
904
+ }
905
+ lastError = error.localizedDescription
862
906
  }
907
+ emit("disconnected")
908
+ // The final reason is only available at disconnected. Reporting disconnecting as
909
+ // terminal makes the host dispose the call before that diagnostic can arrive.
910
+ case .disconnecting: break
911
+ default: break
863
912
  }
864
913
  }
865
914
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/acs-meeting",
3
- "version": "3.2.0-dev.226",
3
+ "version": "3.2.0-dev.232",
4
4
  "description": "MentraOS native ACS Teams meeting module (WHEP decode → ACS raw media)",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@expo/config-plugins": "~55.0.10",
35
- "@mentra/glasses-media": "3.2.0-dev.226"
35
+ "@mentra/glasses-media": "3.2.0-dev.232"
36
36
  },
37
37
  "devDependencies": {
38
38
  "expo-module-scripts": "^55.0.2",
@@ -30,6 +30,11 @@ export type AcsMeetingState = {
30
30
  activeStream?: AcsActiveStream
31
31
  audioSafety?: AcsAudioSafety
32
32
  mediaSource?: AcsMediaSourceState
33
+ /** Native receiver diagnostic, including the reason a local WHIP offer was rejected. */
34
+ mediaSourceReason?: string
35
+ /** ACS disconnect diagnostics, emitted when the final disconnected state arrives. */
36
+ endReason_code?: number
37
+ endReason_subcode?: number
33
38
  /** Local WHIP endpoint, available after a SoftAP join. */
34
39
  ingestUrl?: string
35
40
  /** Remote roster (Android emits this; iOS does not yet). */
@@ -19,8 +19,12 @@ declare class AcsMeetingNativeModule extends NativeModule<AcsMeetingModuleEvents
19
19
  restartVideoSource(): Promise<void>
20
20
  /** SoftAP: join the glasses hotspot; resolves to the phone's IPv4 on it. */
21
21
  joinScopedNetwork(ssid: string, passphrase: string): Promise<string>
22
+ /** iOS: verify DHCP against the gateway advertised by the glasses before resolving. */
23
+ joinScopedNetworkWithGateway?(ssid: string, passphrase: string, gateway: string): Promise<string>
22
24
  beginTrace(traceId: string): Promise<void>
23
25
  leaveScopedNetwork(): Promise<void>
26
+ cancelScopedNetworkJoin?(): Promise<void>
27
+ awaitDefaultNetworkAfterHotspot?(): Promise<{usable: boolean; detail: string; transport: string}>
24
28
  /** SoftAP: TCP-probe the hotspot gateway over the scoped network. */
25
29
  probeScopedGateway(): Promise<{reachable: boolean; detail: string}>
26
30
  getState(): Promise<AcsMeetingState>