@mentra/acs-meeting 3.2.1-dev.311 → 3.3.0-dev.316
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/android/src/main/java/com/mentra/acsmeeting/AcsMeetingModule.kt +3 -1
- package/android/src/main/java/com/mentra/acsmeeting/AcsMeetingSession.kt +41 -19
- package/android/src/main/java/com/mentra/acsmeeting/LobbyAdmission.kt +2 -2
- package/android/src/main/java/com/mentra/acsmeeting/RemoteRoster.kt +3 -3
- package/build/AcsMeeting.types.d.ts +1 -0
- package/build/AcsMeeting.types.d.ts.map +1 -1
- package/build/AcsMeeting.types.js.map +1 -1
- package/build/AcsMeetingModule.d.ts +2 -0
- package/build/AcsMeetingModule.d.ts.map +1 -1
- package/build/AcsMeetingModule.js.map +1 -1
- package/build/AcsMeetingModule.web.d.ts +1 -0
- package/build/AcsMeetingModule.web.d.ts.map +1 -1
- package/build/AcsMeetingModule.web.js.map +1 -1
- package/ios/AcsMeetingModule.swift +98 -29
- package/package.json +2 -2
- package/src/AcsMeeting.types.ts +1 -0
- package/src/AcsMeetingModule.ts +6 -1
- package/src/AcsMeetingModule.web.ts +5 -1
|
@@ -86,6 +86,7 @@ class AcsMeetingModule : Module() {
|
|
|
86
86
|
|
|
87
87
|
override fun definition() = ModuleDefinition {
|
|
88
88
|
Name("MentraAcsMeeting")
|
|
89
|
+
Function("supportsTeamsIdentity") { true }
|
|
89
90
|
// `onScopedNetworkLost` fires only for a hotspot that went away while we still wanted it: the
|
|
90
91
|
// scoped state machine drops the framework's `onLost` for a network we released ourselves, so a
|
|
91
92
|
// normal Leave or End cannot manufacture a mid-call network error.
|
|
@@ -268,7 +269,7 @@ class AcsMeetingModule : Module() {
|
|
|
268
269
|
// follows, because ACS re-binds its signalling when the network moves and keeps whatever
|
|
269
270
|
// route it got. Teardown's `leaveAndAwait` is what drops it, and only once a validated
|
|
270
271
|
// default internet exists — not `leaveScopedNetwork`, which used to unpin onto SoftAP.
|
|
271
|
-
meeting.prepareAgent(token, displayName)
|
|
272
|
+
meeting.prepareAgent(token, displayName, options["identityMode"] as? String ?: "guest")
|
|
272
273
|
meeting.snapshot()
|
|
273
274
|
}
|
|
274
275
|
}
|
|
@@ -326,6 +327,7 @@ class AcsMeetingModule : Module() {
|
|
|
326
327
|
video,
|
|
327
328
|
audioDelayMs,
|
|
328
329
|
origin,
|
|
330
|
+
identityMode = options["identityMode"] as? String ?: "guest",
|
|
329
331
|
bindIngestUnpinned = { bind -> withIngestUnpinned(bind) },
|
|
330
332
|
)
|
|
331
333
|
// Prefer the join snapshot: getState() can race a leave from a respawned miniapp
|
|
@@ -12,7 +12,11 @@ import com.azure.android.communication.calling.AudioStreamState
|
|
|
12
12
|
import com.azure.android.communication.calling.AudioStreamType
|
|
13
13
|
import com.azure.android.communication.calling.CapabilitiesCallFeature
|
|
14
14
|
import com.azure.android.communication.calling.CapabilitiesChangedListener
|
|
15
|
-
import com.azure.android.communication.calling.
|
|
15
|
+
import com.azure.android.communication.calling.CommonCall
|
|
16
|
+
import com.azure.android.communication.calling.CommonCallAgent
|
|
17
|
+
import com.azure.android.communication.calling.TeamsCallAgent
|
|
18
|
+
import com.azure.android.communication.calling.TeamsCallAgentOptions
|
|
19
|
+
import com.azure.android.communication.calling.JoinTeamsCallOptions
|
|
16
20
|
import com.azure.android.communication.calling.CallAgent
|
|
17
21
|
import com.azure.android.communication.calling.CallAgentOptions
|
|
18
22
|
import com.azure.android.communication.calling.CallClient
|
|
@@ -200,8 +204,8 @@ class AcsMeetingSession(
|
|
|
200
204
|
private var noNetworkListener: DiagnosticFlagChangedListener? = null
|
|
201
205
|
private var relaysListener: DiagnosticFlagChangedListener? = null
|
|
202
206
|
private var callClient: CallClient? = null
|
|
203
|
-
private var callAgent:
|
|
204
|
-
private var call:
|
|
207
|
+
private var callAgent: CommonCallAgent? = null
|
|
208
|
+
private var call: CommonCall? = null
|
|
205
209
|
private var audioOut: RawOutgoingAudioStream? = null
|
|
206
210
|
private var localOut: LocalOutgoingAudioStream? = null
|
|
207
211
|
private var audioIn: RawIncomingAudioStream? = null
|
|
@@ -265,13 +269,15 @@ class AcsMeetingSession(
|
|
|
265
269
|
* `createCallAgent` Future we stopped waiting on. ACS still finishes signing in; this is the only
|
|
266
270
|
* handle that can dispose that leftover agent before the next join.
|
|
267
271
|
*/
|
|
268
|
-
private var abandonedAgent: Future<
|
|
272
|
+
private var abandonedAgent: Future<out CommonCallAgent>? = null
|
|
269
273
|
/**
|
|
270
274
|
* Agent signed in before the glasses hotspot came up. SoftAP DNS cannot resolve ACS hosts, so
|
|
271
275
|
* `createCallAgent` after the scoped join stalls until the hotspot is torn down. Join reuses this
|
|
272
276
|
* instead of signing in again on the broken resolver.
|
|
273
277
|
*/
|
|
274
278
|
@Volatile private var agentPrepared = false
|
|
279
|
+
private var preparedToken: String? = null
|
|
280
|
+
private var preparedIdentityMode: String? = null
|
|
275
281
|
private val controller = SessionAudioController()
|
|
276
282
|
private val applier = AudioPolicyApplier(controller, scheduler) { Log.i(TAG, it) }
|
|
277
283
|
|
|
@@ -314,7 +320,7 @@ class AcsMeetingSession(
|
|
|
314
320
|
* hotspot too. Here nothing is torn down while we wait, and a cold sign-in on a slow AP measured
|
|
315
321
|
* 35 s on device — under the old 20 s cap that agent arrived just in time to be thrown away.
|
|
316
322
|
*/
|
|
317
|
-
fun prepareAgent(token: String, displayName: String
|
|
323
|
+
fun prepareAgent(token: String, displayName: String?, identityMode: String = "guest") {
|
|
318
324
|
val done = CountDownLatch(1)
|
|
319
325
|
val error = AtomicReference<Exception?>(null)
|
|
320
326
|
phase = "connecting"
|
|
@@ -332,8 +338,10 @@ class AcsMeetingSession(
|
|
|
332
338
|
callClient = CallClient()
|
|
333
339
|
val agentOptions = CallAgentOptions()
|
|
334
340
|
agentOptions.displayName = displayName ?: "Mentra Call"
|
|
335
|
-
callAgent = obtainCallAgent(callClient!!, credential, agentOptions, generation, PREPARE_AGENT_WAIT_MS)
|
|
341
|
+
callAgent = obtainCallAgent(callClient!!, credential, agentOptions, generation, identityMode, PREPARE_AGENT_WAIT_MS)
|
|
336
342
|
agentPrepared = true
|
|
343
|
+
preparedToken = token
|
|
344
|
+
preparedIdentityMode = identityMode
|
|
337
345
|
Log.i(TAG, "ACS call agent prepared before SoftAP")
|
|
338
346
|
SoftApTrace.stage(
|
|
339
347
|
"session_prepare_agent_end",
|
|
@@ -379,6 +387,7 @@ class AcsMeetingSession(
|
|
|
379
387
|
audioDelayMs: Int? = null,
|
|
380
388
|
/** `created` (Start) or `joined` (Join); see [callOrigin]. Diagnostic only. */
|
|
381
389
|
origin: String = "unknown",
|
|
390
|
+
identityMode: String = "guest",
|
|
382
391
|
/**
|
|
383
392
|
* Runs the WHIP listener bind, and exists so the caller can lift a process-wide network pin
|
|
384
393
|
* across exactly that call. Takes the block rather than being a pair of before/after hooks so
|
|
@@ -426,7 +435,7 @@ class AcsMeetingSession(
|
|
|
426
435
|
// the host and miniapp flash out of "joining" on every join.
|
|
427
436
|
// A SoftAP join that already signed in on cellular must keep that agent:
|
|
428
437
|
// recreating it on the glasses hotspot is the ACS_AGENT_TIMEOUT we just hit.
|
|
429
|
-
val reuseAgent = agentPrepared && callAgent != null
|
|
438
|
+
val reuseAgent = agentPrepared && callAgent != null && preparedToken == token && preparedIdentityMode == identityMode
|
|
430
439
|
leaveLocked(emitIdle = false, keepAgent = reuseAgent)
|
|
431
440
|
// After the teardown, because that teardown bumps the generation itself. Everything that
|
|
432
441
|
// moves it runs on this executor, so the value is stable for the rest of this join.
|
|
@@ -483,7 +492,7 @@ class AcsMeetingSession(
|
|
|
483
492
|
callClient = CallClient()
|
|
484
493
|
val agentOptions = CallAgentOptions()
|
|
485
494
|
agentOptions.displayName = displayName ?: "Mentra Call"
|
|
486
|
-
callAgent = obtainCallAgent(callClient!!, credential, agentOptions, generation)
|
|
495
|
+
callAgent = obtainCallAgent(callClient!!, credential, agentOptions, generation, identityMode)
|
|
487
496
|
}
|
|
488
497
|
|
|
489
498
|
val videoOptions = RawOutgoingVideoStreamOptions()
|
|
@@ -641,7 +650,12 @@ class AcsMeetingSession(
|
|
|
641
650
|
}
|
|
642
651
|
|
|
643
652
|
val locator = TeamsMeetingLinkLocator(teamsUrl)
|
|
644
|
-
val joined =
|
|
653
|
+
val joined = when (val agent = callAgent!!) {
|
|
654
|
+
is TeamsCallAgent -> agent.join(context, locator, JoinTeamsCallOptions()
|
|
655
|
+
.setOutgoingVideoOptions(ov).setOutgoingAudioOptions(oa).setIncomingAudioOptions(ia))
|
|
656
|
+
is CallAgent -> agent.join(context, locator, joinOptions)
|
|
657
|
+
else -> throw IllegalStateException("Unsupported meeting agent")
|
|
658
|
+
}
|
|
645
659
|
call = joined
|
|
646
660
|
roster.attach(joined)
|
|
647
661
|
joined.addOnStateChangedListener { pushCallState(joined.state) }
|
|
@@ -1224,7 +1238,7 @@ class AcsMeetingSession(
|
|
|
1224
1238
|
""
|
|
1225
1239
|
}
|
|
1226
1240
|
|
|
1227
|
-
private fun attachMediaStats(joined:
|
|
1241
|
+
private fun attachMediaStats(joined: CommonCall) {
|
|
1228
1242
|
detachMediaStats()
|
|
1229
1243
|
mediaStatsReports.set(0)
|
|
1230
1244
|
lastWireSizeKey = null
|
|
@@ -1443,7 +1457,7 @@ class AcsMeetingSession(
|
|
|
1443
1457
|
* "sender's video is frozen" guidance points at these diagnostics instead:
|
|
1444
1458
|
* they are the only send-side network signal the SDK exposes.
|
|
1445
1459
|
*/
|
|
1446
|
-
private fun attachDiagnostics(joined:
|
|
1460
|
+
private fun attachDiagnostics(joined: CommonCall) {
|
|
1447
1461
|
detachDiagnostics()
|
|
1448
1462
|
try {
|
|
1449
1463
|
val feature = joined.feature(Features.LOCAL_USER_DIAGNOSTICS) as LocalUserDiagnosticsCallFeature
|
|
@@ -1638,7 +1652,7 @@ class AcsMeetingSession(
|
|
|
1638
1652
|
* The capability that matters is `HANG_UP_FOR_EVERYONE`. It can flip mid-call — a presenter role
|
|
1639
1653
|
* granted or removed — so the listener stays attached rather than reading once at connect.
|
|
1640
1654
|
*/
|
|
1641
|
-
private fun attachCapabilities(joined:
|
|
1655
|
+
private fun attachCapabilities(joined: CommonCall) {
|
|
1642
1656
|
detachCapabilities()
|
|
1643
1657
|
try {
|
|
1644
1658
|
val feature = joined.feature(Features.CAPABILITIES)
|
|
@@ -1750,16 +1764,17 @@ class AcsMeetingSession(
|
|
|
1750
1764
|
credential: CommunicationTokenCredential,
|
|
1751
1765
|
options: CallAgentOptions,
|
|
1752
1766
|
generation: Int,
|
|
1767
|
+
identityMode: String,
|
|
1753
1768
|
waitMs: Long = CALL_AGENT_WAIT_MS,
|
|
1754
|
-
):
|
|
1769
|
+
): CommonCallAgent {
|
|
1755
1770
|
disposeAbandonedAgent(waitMs = ABANDONED_AGENT_REJOIN_WAIT_MS)
|
|
1756
1771
|
return try {
|
|
1757
|
-
awaitCallAgent(client, credential, options, generation, waitMs)
|
|
1772
|
+
awaitCallAgent(client, credential, options, generation, identityMode, waitMs)
|
|
1758
1773
|
} catch (error: Exception) {
|
|
1759
1774
|
if (!AbandonedCallAgent.isExistingAgentError(error)) throw error
|
|
1760
1775
|
Log.w(TAG, "createCallAgent hit leftover identity; disposing abandoned agent and retrying")
|
|
1761
1776
|
disposeAbandonedAgent(waitMs = ABANDONED_AGENT_REJOIN_WAIT_MS)
|
|
1762
|
-
awaitCallAgent(client, credential, options, generation, waitMs)
|
|
1777
|
+
awaitCallAgent(client, credential, options, generation, identityMode, waitMs)
|
|
1763
1778
|
}
|
|
1764
1779
|
}
|
|
1765
1780
|
|
|
@@ -1768,11 +1783,16 @@ class AcsMeetingSession(
|
|
|
1768
1783
|
credential: CommunicationTokenCredential,
|
|
1769
1784
|
options: CallAgentOptions,
|
|
1770
1785
|
generation: Int,
|
|
1786
|
+
identityMode: String,
|
|
1771
1787
|
waitMs: Long,
|
|
1772
|
-
):
|
|
1788
|
+
): CommonCallAgent {
|
|
1773
1789
|
val startedAt = SystemClock.elapsedRealtime()
|
|
1774
1790
|
SoftApTrace.stage("session_call_agent_wait", "waitMs" to waitMs, "generation" to generation)
|
|
1775
|
-
val pending =
|
|
1791
|
+
val pending: Future<out CommonCallAgent> = when (identityMode) {
|
|
1792
|
+
"guest" -> client.createCallAgent(context, credential, options)
|
|
1793
|
+
"teams-user" -> client.createTeamsCallAgent(context, credential, TeamsCallAgentOptions())
|
|
1794
|
+
else -> throw IllegalArgumentException("Unsupported meeting identity mode")
|
|
1795
|
+
}
|
|
1776
1796
|
val agent = try {
|
|
1777
1797
|
pending.get(waitMs, TimeUnit.MILLISECONDS)
|
|
1778
1798
|
} catch (timeout: TimeoutException) {
|
|
@@ -1815,7 +1835,7 @@ class AcsMeetingSession(
|
|
|
1815
1835
|
* Gives up after a bounded number of sweeps: by then the process has either got the agent or the
|
|
1816
1836
|
* future is never completing, and an endless timer is its own leak.
|
|
1817
1837
|
*/
|
|
1818
|
-
private fun sweepLateAgent(pending: Future<
|
|
1838
|
+
private fun sweepLateAgent(pending: Future<out CommonCallAgent>, sweep: Int) {
|
|
1819
1839
|
if (sweep >= LATE_AGENT_SWEEPS) {
|
|
1820
1840
|
Log.w(TAG, "abandoned call agent never completed; stopping sweep")
|
|
1821
1841
|
// Giving up on the sweep is giving up on disposing that agent. It is a bounded leak by
|
|
@@ -1992,6 +2012,8 @@ class AcsMeetingSession(
|
|
|
1992
2012
|
callClient = null
|
|
1993
2013
|
call = null
|
|
1994
2014
|
callAgent = null
|
|
2015
|
+
preparedToken = null
|
|
2016
|
+
preparedIdentityMode = null
|
|
1995
2017
|
agentPrepared = false
|
|
1996
2018
|
}
|
|
1997
2019
|
media.stop()
|
|
@@ -2151,7 +2173,7 @@ class AcsMeetingSession(
|
|
|
2151
2173
|
null -> null
|
|
2152
2174
|
}
|
|
2153
2175
|
|
|
2154
|
-
private fun describeEndReason(call:
|
|
2176
|
+
private fun describeEndReason(call: CommonCall?): Map<String, Any?> {
|
|
2155
2177
|
if (call == null) return mapOf("hasCall" to false)
|
|
2156
2178
|
return try {
|
|
2157
2179
|
val getter = call.javaClass.methods.firstOrNull {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
package com.mentra.acsmeeting
|
|
2
2
|
|
|
3
|
-
import com.azure.android.communication.calling.
|
|
3
|
+
import com.azure.android.communication.calling.CommonCall
|
|
4
4
|
import com.azure.android.communication.calling.CallState
|
|
5
5
|
import com.azure.android.communication.calling.ParticipantState
|
|
6
6
|
|
|
7
7
|
/** Admit one current lobby guest without blocking the session executor or changing meeting policy. */
|
|
8
8
|
internal fun admitLobbyParticipant(
|
|
9
|
-
active:
|
|
9
|
+
active: CommonCall,
|
|
10
10
|
participantId: String,
|
|
11
11
|
allowed: Boolean,
|
|
12
12
|
isCurrent: () -> Boolean,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
package com.mentra.acsmeeting
|
|
2
2
|
|
|
3
3
|
import android.util.Log
|
|
4
|
-
import com.azure.android.communication.calling.
|
|
4
|
+
import com.azure.android.communication.calling.CommonCall
|
|
5
5
|
import com.azure.android.communication.calling.ParticipantState
|
|
6
6
|
import com.azure.android.communication.calling.ParticipantsUpdatedListener
|
|
7
7
|
import com.azure.android.communication.calling.PropertyChangedListener
|
|
@@ -21,11 +21,11 @@ class RemoteRoster(
|
|
|
21
21
|
val listener: PropertyChangedListener,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
-
private var call:
|
|
24
|
+
private var call: CommonCall? = null
|
|
25
25
|
private var updatedListener: ParticipantsUpdatedListener? = null
|
|
26
26
|
private val tracked = ConcurrentHashMap<String, Tracked>()
|
|
27
27
|
|
|
28
|
-
fun attach(joined:
|
|
28
|
+
fun attach(joined: CommonCall) {
|
|
29
29
|
detach()
|
|
30
30
|
call = joined
|
|
31
31
|
val listener = ParticipantsUpdatedListener { event ->
|
|
@@ -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,wFAAwF;IACxF,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qFAAqF;IACrF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mFAAmF;IACnF,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,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mFAAmF;IACnF,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,YAAY,CAAC,EAAE,OAAO,GAAG,YAAY,CAAA;IACrC,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 /** 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 or `rebindSoftApIngest`. */\n ingestUrl?: string\n /** Remote roster, including participant state, mute state and speaking updates. */\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 or `rebindSoftApIngest`. */\n ingestUrl?: string\n /** Remote roster, including participant state, mute state and speaking updates. */\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 identityMode?: \"guest\" | \"teams-user\"\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,11 +1,13 @@
|
|
|
1
1
|
import { NativeModule } from "expo";
|
|
2
2
|
import type { AcsMeetingJoinOptions, AcsMeetingModuleEvents, AcsMeetingState } from "./AcsMeeting.types";
|
|
3
3
|
declare class AcsMeetingNativeModule extends NativeModule<AcsMeetingModuleEvents> {
|
|
4
|
+
supportsTeamsIdentity(): boolean;
|
|
4
5
|
join(options: AcsMeetingJoinOptions): Promise<AcsMeetingState>;
|
|
5
6
|
/** Sign in to ACS before SoftAP so Teams is not resolved through glasses DNS. */
|
|
6
7
|
prepareAgent(options: {
|
|
7
8
|
token: string;
|
|
8
9
|
displayName?: string;
|
|
10
|
+
identityMode?: "guest" | "teams-user";
|
|
9
11
|
}): Promise<AcsMeetingState>;
|
|
10
12
|
leave(): Promise<void>;
|
|
11
13
|
admitParticipant?(participantId: string): Promise<void>;
|
|
@@ -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;
|
|
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,qBAAqB,IAAI,OAAO;IAChC,IAAI,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IAC9D,iFAAiF;IACjF,YAAY,CAAC,OAAO,EAAE;QACpB,KAAK,EAAE,MAAM,CAAA;QACb,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,YAAY,CAAC,EAAE,OAAO,GAAG,YAAY,CAAA;KACtC,GAAG,OAAO,CAAC,eAAe,CAAC;IAC5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IACtB,gBAAgB,CAAC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACvD;;;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;;;;OAIG;IACH,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;IACrC,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;
|
|
1
|
+
{"version":3,"file":"AcsMeetingModule.js","sourceRoot":"","sources":["../src/AcsMeetingModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,mBAAmB,EAAC,MAAM,MAAM,CAAA;AA4CtD,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 supportsTeamsIdentity(): boolean\n join(options: AcsMeetingJoinOptions): Promise<AcsMeetingState>\n /** Sign in to ACS before SoftAP so Teams is not resolved through glasses DNS. */\n prepareAgent(options: {\n token: string\n displayName?: string\n identityMode?: \"guest\" | \"teams-user\"\n }): Promise<AcsMeetingState>\n leave(): Promise<void>\n admitParticipant?(participantId: string): 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 /**\n * SoftAP: destroy the current ingest listener generation and bind a new one.\n * Resolves with the new URL the glasses must publish to. Rejects rather than\n * reuse a stale listener.\n */\n rebindSoftApIngest(): Promise<string>\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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AcsMeetingModule.web.d.ts","sourceRoot":"","sources":["../src/AcsMeetingModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAE,eAAe,EAAC,MAAM,oBAAoB,CAAA;;mBAOvD,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;2BAGxC;
|
|
1
|
+
{"version":3,"file":"AcsMeetingModule.web.d.ts","sourceRoot":"","sources":["../src/AcsMeetingModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAE,eAAe,EAAC,MAAM,oBAAoB,CAAA;;mBAOvD,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;2BAGxC;QAC3B,KAAK,EAAE,MAAM,CAAA;QACb,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,YAAY,CAAC,EAAE,OAAO,GAAG,YAAY,CAAA;KACtC,GAAG,OAAO,CAAC,eAAe,CAAC;aAGb,OAAO,CAAC,IAAI,CAAC;4BAGE;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAC,CAAC;qBAG1D,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC;4BAG3B,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC;gCAG1C,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;0BAG5B,OAAO,CAAC,IAAI,CAAC;0BAGb,OAAO,CAAC,MAAM,CAAC;6BAGZ,MAAM,eAAe,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;0BAGhD,OAAO,CAAC,IAAI,CAAC;0BACb,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,CAAC;gBAGvD,OAAO,CAAC,eAAe,CAAC;;;;;;AAvC5C,wBA8CC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AcsMeetingModule.web.js","sourceRoot":"","sources":["../src/AcsMeetingModule.web.ts"],"names":[],"mappings":"AAEA,SAAS,WAAW;IAClB,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;AACxD,CAAC;AAED,eAAe;IACb,KAAK,CAAC,IAAI,CAAC,QAA+B;QACxC,WAAW,EAAE,CAAA;IACf,CAAC;IACD,KAAK,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"AcsMeetingModule.web.js","sourceRoot":"","sources":["../src/AcsMeetingModule.web.ts"],"names":[],"mappings":"AAEA,SAAS,WAAW;IAClB,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;AACxD,CAAC;AAED,eAAe;IACb,KAAK,CAAC,IAAI,CAAC,QAA+B;QACxC,WAAW,EAAE,CAAA;IACf,CAAC;IACD,KAAK,CAAC,YAAY,CAAC,QAIlB;QACC,WAAW,EAAE,CAAA;IACf,CAAC;IACD,KAAK,CAAC,KAAK;QACT,WAAW,EAAE,CAAA;IACf,CAAC;IACD,KAAK,CAAC,aAAa,CAAC,QAA6B;QAC/C,WAAW,EAAE,CAAA;IACf,CAAC;IACD,KAAK,CAAC,QAAQ,CAAC,MAAe;QAC5B,WAAW,EAAE,CAAA;IACf,CAAC;IACD,KAAK,CAAC,cAAc,CAAC,OAA4B;QAC/C,WAAW,EAAE,CAAA;IACf,CAAC;IACD,KAAK,CAAC,iBAAiB,CAAC,QAAgB;QACtC,WAAW,EAAE,CAAA;IACf,CAAC;IACD,KAAK,CAAC,kBAAkB;QACtB,WAAW,EAAE,CAAA;IACf,CAAC;IACD,KAAK,CAAC,kBAAkB;QACtB,WAAW,EAAE,CAAA;IACf,CAAC;IACD,KAAK,CAAC,iBAAiB,CAAC,KAAa,EAAE,WAAmB;QACxD,WAAW,EAAE,CAAA;IACf,CAAC;IACD,KAAK,CAAC,kBAAkB,KAAmB,CAAC;IAC5C,KAAK,CAAC,kBAAkB;QACtB,OAAO,EAAC,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,0BAA0B,EAAC,CAAA;IAC/D,CAAC;IACD,KAAK,CAAC,QAAQ;QACZ,OAAO,EAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAC,CAAA;IACtC,CAAC;IACD,WAAW;QACT,OAAO,EAAC,MAAM,KAAI,CAAC,EAAC,CAAA;IACtB,CAAC;IACD,eAAe,KAAI,CAAC;CACrB,CAAA","sourcesContent":["import type {AcsMeetingJoinOptions, AcsMeetingState} from \"./AcsMeeting.types\"\n\nfunction unavailable(): never {\n throw new Error(\"ACS meeting is not available on web\")\n}\n\nexport default {\n async join(_options: AcsMeetingJoinOptions): Promise<AcsMeetingState> {\n unavailable()\n },\n async prepareAgent(_options: {\n token: string\n displayName?: string\n identityMode?: \"guest\" | \"teams-user\"\n }): Promise<AcsMeetingState> {\n unavailable()\n },\n async leave(): Promise<void> {\n unavailable()\n },\n async leaveAndAwait(_options: {timeoutMs: number}): Promise<{completed: boolean}> {\n unavailable()\n },\n async setMuted(_muted: boolean): Promise<AcsMeetingState> {\n unavailable()\n },\n async setAudioSource(_source: \"glasses\" | \"phone\"): Promise<AcsMeetingState> {\n unavailable()\n },\n async updateVideoSource(_whepUrl: string): Promise<void> {\n unavailable()\n },\n async restartVideoSource(): Promise<void> {\n unavailable()\n },\n async rebindSoftApIngest(): Promise<string> {\n unavailable()\n },\n async joinScopedNetwork(_ssid: string, _passphrase: string): Promise<string> {\n unavailable()\n },\n async leaveScopedNetwork(): Promise<void> {},\n async probeScopedGateway(): Promise<{reachable: boolean; detail: string}> {\n return {reachable: false, detail: \"no scoped network on web\"}\n },\n async getState(): Promise<AcsMeetingState> {\n return {state: \"idle\", muted: false}\n },\n addListener() {\n return {remove() {}}\n },\n removeListeners() {},\n}\n"]}
|
|
@@ -38,11 +38,12 @@ public class AcsMeetingModule: Module {
|
|
|
38
38
|
|
|
39
39
|
public func definition() -> ModuleDefinition {
|
|
40
40
|
Name("MentraAcsMeeting")
|
|
41
|
+
Function("supportsTeamsIdentity") { true }
|
|
41
42
|
Events("onState", "onIncomingPcm", "onScopedNetworkLost", "onScopedNetworkProgress")
|
|
42
43
|
|
|
43
44
|
AsyncFunction("prepareAgent") { (options: [String: Any], promise: Promise) in
|
|
44
45
|
let token = try requireString(options, "token")
|
|
45
|
-
self.meetingSession().prepareAgent(token: token, displayName: options["displayName"] as? String) { result in
|
|
46
|
+
self.meetingSession().prepareAgent(token: token, displayName: options["displayName"] as? String, identityMode: options["identityMode"] as? String ?? "guest") { result in
|
|
46
47
|
switch result {
|
|
47
48
|
case let .success(state): promise.resolve(state)
|
|
48
49
|
case let .failure(error): promise.reject(error)
|
|
@@ -59,7 +60,7 @@ public class AcsMeetingModule: Module {
|
|
|
59
60
|
}
|
|
60
61
|
let video = try parseAcsOutgoingVideo(options["video"])
|
|
61
62
|
self.meetingSession().join(
|
|
62
|
-
token: token, meetingUrl: meetingUrl, sourceConfig: source,
|
|
63
|
+
token: token, identityMode: options["identityMode"] as? String ?? "guest", meetingUrl: meetingUrl, sourceConfig: source,
|
|
63
64
|
displayName: options["displayName"] as? String,
|
|
64
65
|
dumpWav: options["dumpPcmWav"] as? Bool ?? false,
|
|
65
66
|
audioSource: options["audioSource"] as? String ?? "glasses", video: video
|
|
@@ -239,12 +240,13 @@ final class AcsMeetingSession {
|
|
|
239
240
|
private var lastError: String?
|
|
240
241
|
private var callEndReason: (code: Int, subcode: Int)?
|
|
241
242
|
private var callClient: CallClient?
|
|
242
|
-
private var callAgent:
|
|
243
|
-
private var call:
|
|
243
|
+
private var callAgent: CommonCallAgent?
|
|
244
|
+
private var call: CommonCall?
|
|
244
245
|
private var media: DecodedGlassesMediaSource?
|
|
245
246
|
private var sourceConfig = SourceConfig(url: "")
|
|
246
|
-
private var preparedAgent:
|
|
247
|
+
private var preparedAgent: CommonCallAgent?
|
|
247
248
|
private var preparedClient: CallClient?
|
|
249
|
+
private var preparedIdentityMode: String?
|
|
248
250
|
private var preparedToken: String?
|
|
249
251
|
private var pendingJoin: ((Result<[String: Any], Error>) -> Void)?
|
|
250
252
|
private var pendingPrepare: ((Result<[String: Any], Error>) -> Void)?
|
|
@@ -281,6 +283,11 @@ final class AcsMeetingSession {
|
|
|
281
283
|
onMuteChange: { [weak self] call in self?.handleCallMuteChange(call) },
|
|
282
284
|
onRosterChange: { [weak self] call in self?.refreshRoster(call) }
|
|
283
285
|
)
|
|
286
|
+
private lazy var teamsCallDelegateProxy = AcsTeamsCallDelegateProxy(
|
|
287
|
+
onStateChange: { [weak self] call in self?.handleCallStateChange(call) },
|
|
288
|
+
onMuteChange: { [weak self] call in self?.handleCallMuteChange(call) },
|
|
289
|
+
onRosterChange: { [weak self] call in self?.refreshRoster(call) }
|
|
290
|
+
)
|
|
284
291
|
private var remoteParticipants: [ObjectIdentifier: RemoteParticipant] = [:]
|
|
285
292
|
private lazy var participantDelegateProxy = AcsParticipantDelegateProxy { [weak self] participant in
|
|
286
293
|
self?.queue.async {
|
|
@@ -342,7 +349,7 @@ final class AcsMeetingSession {
|
|
|
342
349
|
return result
|
|
343
350
|
}
|
|
344
351
|
|
|
345
|
-
func prepareAgent(token: String, displayName: String?, completion: @escaping (Result<[String: Any], Error>) -> Void) {
|
|
352
|
+
func prepareAgent(token: String, displayName: String?, identityMode: String = "guest", completion: @escaping (Result<[String: Any], Error>) -> Void) {
|
|
346
353
|
queue.async {
|
|
347
354
|
guard self.cleanup.wait(timeout: .now()) == .success, self.pendingPrepare == nil, self.pendingJoin == nil, self.call == nil, self.media == nil else {
|
|
348
355
|
completion(.failure(AcsMeetingError("Previous call cleanup is still pending"))); return
|
|
@@ -356,7 +363,7 @@ final class AcsMeetingSession {
|
|
|
356
363
|
let credential = try CommunicationTokenCredential(token: token)
|
|
357
364
|
let options = CallAgentOptions()
|
|
358
365
|
options.displayName = displayName ?? "Mentra Call"
|
|
359
|
-
client
|
|
366
|
+
createMeetingAgent(client: client, credential: credential, guestOptions: options, identityMode: identityMode) { agent, error in
|
|
360
367
|
self.queue.async {
|
|
361
368
|
guard self.joinGeneration == generation, self.pendingPrepare != nil else { agent?.dispose(); return }
|
|
362
369
|
let reply = self.pendingPrepare
|
|
@@ -364,6 +371,7 @@ final class AcsMeetingSession {
|
|
|
364
371
|
if let agent, error == nil {
|
|
365
372
|
self.preparedAgent = agent
|
|
366
373
|
self.preparedToken = token
|
|
374
|
+
self.preparedIdentityMode = identityMode
|
|
367
375
|
reply?(.success(self.snapshotLocked()))
|
|
368
376
|
} else {
|
|
369
377
|
agent?.dispose()
|
|
@@ -384,7 +392,7 @@ final class AcsMeetingSession {
|
|
|
384
392
|
}
|
|
385
393
|
}
|
|
386
394
|
|
|
387
|
-
func join(token: String, meetingUrl: String, sourceConfig: SourceConfig, displayName: String?, dumpWav: Bool,
|
|
395
|
+
func join(token: String, identityMode: String = "guest", meetingUrl: String, sourceConfig: SourceConfig, displayName: String?, dumpWav: Bool,
|
|
388
396
|
audioSource: String = "glasses", video: AcsOutgoingVideo = .hd,
|
|
389
397
|
completion: @escaping (Result<[String: Any], Error>) -> Void)
|
|
390
398
|
{
|
|
@@ -392,7 +400,7 @@ final class AcsMeetingSession {
|
|
|
392
400
|
guard self.cleanup.wait(timeout: .now()) == .success, self.pendingPrepare == nil, self.pendingJoin == nil, self.call == nil, self.media == nil else {
|
|
393
401
|
completion(.failure(AcsMeetingError("Previous call cleanup is still pending"))); return
|
|
394
402
|
}
|
|
395
|
-
let prepared = self.preparedToken == token ? self.preparedAgent : nil
|
|
403
|
+
let prepared = self.preparedToken == token && self.preparedIdentityMode == identityMode ? self.preparedAgent : nil
|
|
396
404
|
let preparedClient = self.preparedClient
|
|
397
405
|
if prepared != nil { self.preparedAgent = nil; self.preparedClient = nil; self.preparedToken = nil }
|
|
398
406
|
self.leaveLocked(emitIdle: false)
|
|
@@ -404,7 +412,7 @@ final class AcsMeetingSession {
|
|
|
404
412
|
self.lastError = nil
|
|
405
413
|
self.callEndReason = nil
|
|
406
414
|
self.emit("connecting")
|
|
407
|
-
let useAgent: (
|
|
415
|
+
let useAgent: (CommonCallAgent) -> Void = { agent in
|
|
408
416
|
do { try self.joinWithAgentLocked(agent, generation: generation, meetingUrl: meetingUrl,
|
|
409
417
|
sourceConfig: sourceConfig, dumpWav: dumpWav, video: video) } catch { self.failJoinLocked(error, generation: generation) }
|
|
410
418
|
}
|
|
@@ -421,7 +429,7 @@ final class AcsMeetingSession {
|
|
|
421
429
|
options.displayName = displayName ?? "Mentra Call"
|
|
422
430
|
let agentStartedAt = ProcessInfo.processInfo.systemUptime
|
|
423
431
|
NSLog("ACS-SPIKE iOS agent creation started generation=\(generation)")
|
|
424
|
-
client
|
|
432
|
+
createMeetingAgent(client: client, credential: credential, guestOptions: options, identityMode: identityMode) { agent, error in
|
|
425
433
|
self.queue.async {
|
|
426
434
|
let elapsedMs = Int((ProcessInfo.processInfo.systemUptime - agentStartedAt) * 1000)
|
|
427
435
|
NSLog("ACS-SPIKE iOS agent creation completed generation=\(generation) elapsedMs=\(elapsedMs) success=\(agent != nil && error == nil)")
|
|
@@ -444,7 +452,7 @@ final class AcsMeetingSession {
|
|
|
444
452
|
}
|
|
445
453
|
|
|
446
454
|
private func joinWithAgentLocked(
|
|
447
|
-
_ agent:
|
|
455
|
+
_ agent: CommonCallAgent,
|
|
448
456
|
generation: UInt64,
|
|
449
457
|
meetingUrl: String,
|
|
450
458
|
sourceConfig: SourceConfig,
|
|
@@ -523,14 +531,14 @@ final class AcsMeetingSession {
|
|
|
523
531
|
let locator = TeamsMeetingLinkLocator(meetingLink: meetingUrl)
|
|
524
532
|
// Cancellation reserves cleanup and retains the agent until the late join result
|
|
525
533
|
// can be hung up. The retirement deadline still handles an SDK callback that is lost.
|
|
526
|
-
let joinRetirement = CallJoinRetirement<
|
|
534
|
+
let joinRetirement = CallJoinRetirement<CommonCall>(group: cleanup, queue: queue, dispose: { agent.dispose() }) { call, finished in
|
|
527
535
|
call.hangUp(options: nil) { error in
|
|
528
536
|
if let error { NSLog("ACS-SPIKE cancelled join hangUp failed: \(error)") }
|
|
529
537
|
finished()
|
|
530
538
|
}
|
|
531
539
|
}
|
|
532
540
|
cancelPendingCallJoin = { joinRetirement.cancel() }
|
|
533
|
-
agent
|
|
541
|
+
joinMeeting(agent: agent, locator: locator, guestOptions: joinOptions) { call, error in
|
|
534
542
|
self.queue.async {
|
|
535
543
|
guard joinRetirement.receive(call) else { return }
|
|
536
544
|
guard self.callAgent === agent, self.joinGeneration == generation else {
|
|
@@ -559,7 +567,7 @@ final class AcsMeetingSession {
|
|
|
559
567
|
}
|
|
560
568
|
|
|
561
569
|
private func finishJoinLocked(
|
|
562
|
-
_ call:
|
|
570
|
+
_ call: CommonCall,
|
|
563
571
|
generation: UInt64,
|
|
564
572
|
sourceConfig: SourceConfig,
|
|
565
573
|
dumpWav: Bool,
|
|
@@ -571,7 +579,8 @@ final class AcsMeetingSession {
|
|
|
571
579
|
return
|
|
572
580
|
}
|
|
573
581
|
self.call = call
|
|
574
|
-
call
|
|
582
|
+
(call as? Call)?.delegate = callDelegateProxy
|
|
583
|
+
(call as? TeamsCall)?.delegate = teamsCallDelegateProxy
|
|
575
584
|
refreshRosterLocked(call)
|
|
576
585
|
attachCapabilities(call)
|
|
577
586
|
|
|
@@ -845,7 +854,7 @@ final class AcsMeetingSession {
|
|
|
845
854
|
promoted to presenter mid-call), and an End button that never turns on is the same bug as one
|
|
846
855
|
that lies about what it does.
|
|
847
856
|
*/
|
|
848
|
-
private func attachCapabilities(_ call:
|
|
857
|
+
private func attachCapabilities(_ call: CommonCall) {
|
|
849
858
|
let feature = call.feature(Features.capabilities)
|
|
850
859
|
capabilitiesFeature = feature
|
|
851
860
|
feature.delegate = capabilitiesDelegateProxy
|
|
@@ -976,7 +985,8 @@ final class AcsMeetingSession {
|
|
|
976
985
|
let leavingAgent = callAgent
|
|
977
986
|
let cancelJoin = cancelPendingCallJoin
|
|
978
987
|
cancelPendingCallJoin = nil
|
|
979
|
-
leavingCall?.delegate = nil
|
|
988
|
+
(leavingCall as? Call)?.delegate = nil
|
|
989
|
+
(leavingCall as? TeamsCall)?.delegate = nil
|
|
980
990
|
if let leavingCall {
|
|
981
991
|
// CallAgent.dispose releases all local SDK resources. The retirement deadline
|
|
982
992
|
// also disposes the agent if hangUp loses its callback, before opening the barrier.
|
|
@@ -1017,7 +1027,7 @@ final class AcsMeetingSession {
|
|
|
1017
1027
|
}
|
|
1018
1028
|
}
|
|
1019
1029
|
|
|
1020
|
-
fileprivate func currentCall() ->
|
|
1030
|
+
fileprivate func currentCall() -> CommonCall? {
|
|
1021
1031
|
call
|
|
1022
1032
|
}
|
|
1023
1033
|
|
|
@@ -1029,14 +1039,14 @@ final class AcsMeetingSession {
|
|
|
1029
1039
|
phoneMic.setEnabled(enabled)
|
|
1030
1040
|
}
|
|
1031
1041
|
|
|
1032
|
-
private func handleCallStateChange(_ changedCall:
|
|
1042
|
+
private func handleCallStateChange(_ changedCall: CommonCall) {
|
|
1033
1043
|
queue.async {
|
|
1034
1044
|
guard self.call === changedCall, self.lastError == nil else { return }
|
|
1035
1045
|
self.refreshCallStateLocked(changedCall)
|
|
1036
1046
|
}
|
|
1037
1047
|
}
|
|
1038
1048
|
|
|
1039
|
-
private func refreshCallStateLocked(_ changedCall:
|
|
1049
|
+
private func refreshCallStateLocked(_ changedCall: CommonCall) {
|
|
1040
1050
|
switch changedCall.state {
|
|
1041
1051
|
case .connecting: emit("connecting")
|
|
1042
1052
|
case .inLobby: emit("lobby")
|
|
@@ -1071,14 +1081,14 @@ final class AcsMeetingSession {
|
|
|
1071
1081
|
}
|
|
1072
1082
|
}
|
|
1073
1083
|
|
|
1074
|
-
private func handleCallMuteChange(_ changedCall:
|
|
1084
|
+
private func handleCallMuteChange(_ changedCall: CommonCall) {
|
|
1075
1085
|
queue.async {
|
|
1076
1086
|
guard self.call === changedCall, self.lastError == nil else { return }
|
|
1077
1087
|
self.applyAudioPolicyOnQueue("outgoing-audio-state")
|
|
1078
1088
|
}
|
|
1079
1089
|
}
|
|
1080
1090
|
|
|
1081
|
-
private func refreshRoster(_ changedCall:
|
|
1091
|
+
private func refreshRoster(_ changedCall: CommonCall) {
|
|
1082
1092
|
queue.async {
|
|
1083
1093
|
guard self.call === changedCall else { return }
|
|
1084
1094
|
self.refreshRosterLocked(changedCall)
|
|
@@ -1086,7 +1096,7 @@ final class AcsMeetingSession {
|
|
|
1086
1096
|
}
|
|
1087
1097
|
}
|
|
1088
1098
|
|
|
1089
|
-
private func refreshRosterLocked(_ changedCall:
|
|
1099
|
+
private func refreshRosterLocked(_ changedCall: CommonCall) {
|
|
1090
1100
|
dispatchPrecondition(condition: .onQueue(queue))
|
|
1091
1101
|
let current = Dictionary(uniqueKeysWithValues: changedCall.remoteParticipants.map {
|
|
1092
1102
|
(ObjectIdentifier($0), $0)
|
|
@@ -1209,12 +1219,12 @@ final class SessionAudioController: AudioStreamController {
|
|
|
1209
1219
|
}
|
|
1210
1220
|
|
|
1211
1221
|
private final class AcsCallDelegateProxy: NSObject, CallDelegate {
|
|
1212
|
-
private let onStateChange: (
|
|
1213
|
-
private let onMuteChange: (
|
|
1214
|
-
private let onRosterChange: (
|
|
1222
|
+
private let onStateChange: (CommonCall) -> Void
|
|
1223
|
+
private let onMuteChange: (CommonCall) -> Void
|
|
1224
|
+
private let onRosterChange: (CommonCall) -> Void
|
|
1215
1225
|
|
|
1216
|
-
init(onStateChange: @escaping (
|
|
1217
|
-
onRosterChange: @escaping (
|
|
1226
|
+
init(onStateChange: @escaping (CommonCall) -> Void, onMuteChange: @escaping (CommonCall) -> Void,
|
|
1227
|
+
onRosterChange: @escaping (CommonCall) -> Void)
|
|
1218
1228
|
{
|
|
1219
1229
|
self.onStateChange = onStateChange
|
|
1220
1230
|
self.onMuteChange = onMuteChange
|
|
@@ -1234,6 +1244,32 @@ private final class AcsCallDelegateProxy: NSObject, CallDelegate {
|
|
|
1234
1244
|
}
|
|
1235
1245
|
}
|
|
1236
1246
|
|
|
1247
|
+
private final class AcsTeamsCallDelegateProxy: NSObject, TeamsCallDelegate {
|
|
1248
|
+
private let onStateChange: (CommonCall) -> Void
|
|
1249
|
+
private let onMuteChange: (CommonCall) -> Void
|
|
1250
|
+
private let onRosterChange: (CommonCall) -> Void
|
|
1251
|
+
|
|
1252
|
+
init(onStateChange: @escaping (CommonCall) -> Void, onMuteChange: @escaping (CommonCall) -> Void,
|
|
1253
|
+
onRosterChange: @escaping (CommonCall) -> Void)
|
|
1254
|
+
{
|
|
1255
|
+
self.onStateChange = onStateChange
|
|
1256
|
+
self.onMuteChange = onMuteChange
|
|
1257
|
+
self.onRosterChange = onRosterChange
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
func teamsCall(_ call: TeamsCall, didChangeState _: PropertyChangedEventArgs) {
|
|
1261
|
+
onStateChange(call)
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
func teamsCall(_ call: TeamsCall, didUpdateOutgoingAudioState _: PropertyChangedEventArgs) {
|
|
1265
|
+
onMuteChange(call)
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
func teamsCall(_ call: TeamsCall, didUpdateRemoteParticipant _: ParticipantsUpdatedEventArgs) {
|
|
1269
|
+
onRosterChange(call)
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1237
1273
|
private final class AcsParticipantDelegateProxy: NSObject, RemoteParticipantDelegate {
|
|
1238
1274
|
private let onChanged: (RemoteParticipant) -> Void
|
|
1239
1275
|
|
|
@@ -1337,3 +1373,36 @@ private func parseAcsOutgoingVideo(_ raw: Any?) throws -> AcsOutgoingVideo {
|
|
|
1337
1373
|
}
|
|
1338
1374
|
return AcsOutgoingVideo(width: width, height: height, fps: fps, maxBitrateBps: bitrate)
|
|
1339
1375
|
}
|
|
1376
|
+
|
|
1377
|
+
/// Both identity modes use the same raw media and lifecycle code. Only agent creation and
|
|
1378
|
+
/// join options differ; the authenticated Teams display name is supplied by Microsoft.
|
|
1379
|
+
private func createMeetingAgent(client: CallClient, credential: CommunicationTokenCredential,
|
|
1380
|
+
guestOptions: CallAgentOptions, identityMode: String,
|
|
1381
|
+
completion: @escaping (CommonCallAgent?, Error?) -> Void)
|
|
1382
|
+
{
|
|
1383
|
+
switch identityMode {
|
|
1384
|
+
case "guest":
|
|
1385
|
+
client.createCallAgent(userCredential: credential, options: guestOptions) { completion($0, $1) }
|
|
1386
|
+
case "teams-user":
|
|
1387
|
+
client.createTeamsCallAgent(userCredential: credential, options: TeamsCallAgentOptions()) { completion($0, $1) }
|
|
1388
|
+
default:
|
|
1389
|
+
completion(nil, AcsMeetingError("Unsupported meeting identity mode"))
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
private func joinMeeting(agent: CommonCallAgent, locator: TeamsMeetingLinkLocator,
|
|
1394
|
+
guestOptions: JoinCallOptions,
|
|
1395
|
+
completion: @escaping (CommonCall?, Error?) -> Void)
|
|
1396
|
+
{
|
|
1397
|
+
if let teams = agent as? TeamsCallAgent {
|
|
1398
|
+
let options = JoinTeamsCallOptions()
|
|
1399
|
+
options.outgoingVideoOptions = guestOptions.outgoingVideoOptions
|
|
1400
|
+
options.outgoingAudioOptions = guestOptions.outgoingAudioOptions
|
|
1401
|
+
options.incomingAudioOptions = guestOptions.incomingAudioOptions
|
|
1402
|
+
teams.join(with: locator, joinTeamsCallOptions: options) { completion($0, $1) }
|
|
1403
|
+
} else if let guest = agent as? CallAgent {
|
|
1404
|
+
guest.join(with: locator, joinCallOptions: guestOptions) { completion($0, $1) }
|
|
1405
|
+
} else {
|
|
1406
|
+
completion(nil, AcsMeetingError("Unsupported meeting agent"))
|
|
1407
|
+
}
|
|
1408
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mentra/acs-meeting",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0-dev.316",
|
|
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.
|
|
35
|
+
"@mentra/glasses-media": "3.3.0-dev.316"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"expo-module-scripts": "^55.0.2",
|
package/src/AcsMeeting.types.ts
CHANGED
|
@@ -51,6 +51,7 @@ export type AcsOutgoingVideo = {
|
|
|
51
51
|
export type AcsMeetingJoinOptions = {
|
|
52
52
|
meetingUrl: string
|
|
53
53
|
token: string
|
|
54
|
+
identityMode?: "guest" | "teams-user"
|
|
54
55
|
whepUrl?: string
|
|
55
56
|
videoSource?: {type: "whep"; url: string} | {type: "softap"; bindAddress: string}
|
|
56
57
|
displayName?: string
|
package/src/AcsMeetingModule.ts
CHANGED
|
@@ -3,9 +3,14 @@ import {NativeModule, requireNativeModule} from "expo"
|
|
|
3
3
|
import type {AcsMeetingJoinOptions, AcsMeetingModuleEvents, AcsMeetingState} from "./AcsMeeting.types"
|
|
4
4
|
|
|
5
5
|
declare class AcsMeetingNativeModule extends NativeModule<AcsMeetingModuleEvents> {
|
|
6
|
+
supportsTeamsIdentity(): boolean
|
|
6
7
|
join(options: AcsMeetingJoinOptions): Promise<AcsMeetingState>
|
|
7
8
|
/** Sign in to ACS before SoftAP so Teams is not resolved through glasses DNS. */
|
|
8
|
-
prepareAgent(options: {
|
|
9
|
+
prepareAgent(options: {
|
|
10
|
+
token: string
|
|
11
|
+
displayName?: string
|
|
12
|
+
identityMode?: "guest" | "teams-user"
|
|
13
|
+
}): Promise<AcsMeetingState>
|
|
9
14
|
leave(): Promise<void>
|
|
10
15
|
admitParticipant?(participantId: string): Promise<void>
|
|
11
16
|
/**
|
|
@@ -8,7 +8,11 @@ export default {
|
|
|
8
8
|
async join(_options: AcsMeetingJoinOptions): Promise<AcsMeetingState> {
|
|
9
9
|
unavailable()
|
|
10
10
|
},
|
|
11
|
-
async prepareAgent(_options: {
|
|
11
|
+
async prepareAgent(_options: {
|
|
12
|
+
token: string
|
|
13
|
+
displayName?: string
|
|
14
|
+
identityMode?: "guest" | "teams-user"
|
|
15
|
+
}): Promise<AcsMeetingState> {
|
|
12
16
|
unavailable()
|
|
13
17
|
},
|
|
14
18
|
async leave(): Promise<void> {
|