@mentra/acs-meeting 3.2.0-dev.222 → 3.2.0-dev.225
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/README.md +14 -3
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/mentra/acsmeeting/AcsMeetingModule.kt +10 -10
- package/android/src/main/java/com/mentra/acsmeeting/AcsMeetingSession.kt +37 -37
- package/android/src/main/java/com/mentra/acsmeeting/audio/UplinkSender.kt +4 -4
- package/android/src/main/java/com/mentra/acsmeeting/source/MeetingVideoSourceSpec.kt +3 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/AcsFrameSender.kt +12 -10
- package/android/src/main/java/com/mentra/acsmeeting/video/I420FormatSpec.kt +2 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/Nv12Packer.kt +2 -0
- package/android/src/test/java/com/mentra/acsmeeting/source/{AcsInvestigationTest.kt → MediaDiagnosticsTest.kt} +10 -10
- package/android/src/test/java/com/mentra/acsmeeting/source/MeetingVideoSourceSpecTest.kt +3 -0
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/VideoQualityTest.kt +2 -2
- package/android/src/test/java/com/mentra/acsmeeting/video/I420FormatSpecTest.kt +2 -0
- package/android/src/test/java/com/mentra/acsmeeting/video/VideoProfileTest.kt +2 -2
- package/build/AcsMeeting.types.d.ts +10 -1
- package/build/AcsMeeting.types.d.ts.map +1 -1
- package/build/AcsMeeting.types.js.map +1 -1
- package/build/AcsMeetingModule.d.ts +2 -2
- package/build/AcsMeetingModule.d.ts.map +1 -1
- package/build/AcsMeetingModule.js.map +1 -1
- package/ios/AcsMeeting.podspec +1 -1
- package/ios/AcsMeetingModule.swift +284 -148
- package/ios/PolicyKit/Package.swift +2 -1
- package/ios/PolicyKit/Sources/AcsAudioPolicy/CallbackOperation.swift +60 -0
- package/ios/PolicyKit/Sources/AcsAudioPolicy/CapturePolicy.swift +5 -0
- package/ios/PolicyKit/Tests/AcsAudioPolicyTests/AcsAudioPolicyTests.swift +1 -0
- package/ios/PolicyKit/Tests/AcsAudioPolicyTests/CallAgentRetirementTests.swift +113 -0
- package/package.json +3 -2
- package/src/AcsMeeting.types.ts +4 -1
- package/src/AcsMeetingModule.ts +2 -2
- package/android/src/androidTest/java/com/mentra/acsmeeting/network/SoftApFeasibilityGateTest.kt +0 -291
- package/android/src/main/java/com/mentra/acsmeeting/network/InternetHold.kt +0 -501
- package/android/src/main/java/com/mentra/acsmeeting/network/Ipv4Prefix.kt +0 -51
- package/android/src/main/java/com/mentra/acsmeeting/network/NetworkFacts.kt +0 -81
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkChangeDetector.kt +0 -355
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkError.kt +0 -109
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkObserver.kt +0 -160
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkReadiness.kt +0 -103
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkRequestSpec.kt +0 -53
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkState.kt +0 -127
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedSoftApNetwork.kt +0 -573
- package/android/src/main/java/com/mentra/acsmeeting/source/BitmapFont.kt +0 -99
- package/android/src/main/java/com/mentra/acsmeeting/source/CloudflareWhepSource.kt +0 -487
- package/android/src/main/java/com/mentra/acsmeeting/source/DecodedTrackRelay.kt +0 -147
- package/android/src/main/java/com/mentra/acsmeeting/source/FirstFrameGate.kt +0 -45
- package/android/src/main/java/com/mentra/acsmeeting/source/GlassesMediaSource.kt +0 -143
- package/android/src/main/java/com/mentra/acsmeeting/source/LocalWhipIngestSource.kt +0 -772
- package/android/src/main/java/com/mentra/acsmeeting/source/MediaListeners.kt +0 -57
- package/android/src/main/java/com/mentra/acsmeeting/source/SdpAdapter.kt +0 -17
- package/android/src/main/java/com/mentra/acsmeeting/source/SelectedIcePair.kt +0 -139
- package/android/src/main/java/com/mentra/acsmeeting/source/SoftApIceDiagnosis.kt +0 -144
- package/android/src/main/java/com/mentra/acsmeeting/source/SoftApIcePolicy.kt +0 -46
- package/android/src/main/java/com/mentra/acsmeeting/source/SoftApSdpGuard.kt +0 -160
- package/android/src/main/java/com/mentra/acsmeeting/source/SyntheticFrameFactory.kt +0 -205
- package/android/src/main/java/com/mentra/acsmeeting/source/SyntheticI420Source.kt +0 -214
- package/android/src/main/java/com/mentra/acsmeeting/source/TrackRegistry.kt +0 -33
- package/android/src/main/java/com/mentra/acsmeeting/source/VideoSourceArm.kt +0 -110
- package/android/src/main/java/com/mentra/acsmeeting/source/WhipIngestProtocol.kt +0 -207
- package/android/src/main/java/com/mentra/acsmeeting/source/WhipIngestServer.kt +0 -356
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/AvSyncProbe.kt +0 -155
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/ChromaProbe.kt +0 -78
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/PipelineStats.kt +0 -372
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/PipelineTicker.kt +0 -79
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/ProcessCpu.kt +0 -15
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/RingPercentile.kt +0 -48
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/VideoQuality.kt +0 -178
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/VideoRateVerdict.kt +0 -145
- package/android/src/main/java/com/mentra/acsmeeting/trace/SoftApTrace.kt +0 -143
- package/android/src/main/java/com/mentra/acsmeeting/video/FrameGeometry.kt +0 -22
- package/android/src/main/java/com/mentra/acsmeeting/video/I420Packer.kt +0 -72
- package/android/src/test/java/com/mentra/acsmeeting/network/InternetHoldLifecycleTest.kt +0 -102
- package/android/src/test/java/com/mentra/acsmeeting/network/InternetHoldTest.kt +0 -148
- package/android/src/test/java/com/mentra/acsmeeting/network/Ipv4PrefixTest.kt +0 -59
- package/android/src/test/java/com/mentra/acsmeeting/network/NetworkFactsTest.kt +0 -59
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedLocalNetworkPermissionTest.kt +0 -35
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkChangeDetectorTest.kt +0 -165
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkErrorTest.kt +0 -96
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkLifecycleTest.kt +0 -187
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkObserverTest.kt +0 -172
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkReadinessTest.kt +0 -141
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkRequestSpecTest.kt +0 -69
- package/android/src/test/java/com/mentra/acsmeeting/source/BitmapFontTest.kt +0 -22
- package/android/src/test/java/com/mentra/acsmeeting/source/DecodedTrackRelayTest.kt +0 -41
- package/android/src/test/java/com/mentra/acsmeeting/source/FirstFrameGateTest.kt +0 -60
- package/android/src/test/java/com/mentra/acsmeeting/source/GlassesMediaSourceTest.kt +0 -151
- package/android/src/test/java/com/mentra/acsmeeting/source/I420PlanesTest.kt +0 -67
- package/android/src/test/java/com/mentra/acsmeeting/source/SelectedIcePairTest.kt +0 -165
- package/android/src/test/java/com/mentra/acsmeeting/source/SoftApIceDiagnosisTest.kt +0 -148
- package/android/src/test/java/com/mentra/acsmeeting/source/SoftApIcePolicyTest.kt +0 -76
- package/android/src/test/java/com/mentra/acsmeeting/source/SoftApSdpGuardTest.kt +0 -275
- package/android/src/test/java/com/mentra/acsmeeting/source/SyntheticFrameFactoryTest.kt +0 -193
- package/android/src/test/java/com/mentra/acsmeeting/source/SyntheticI420SourceTest.kt +0 -176
- package/android/src/test/java/com/mentra/acsmeeting/source/TrackRegistryTest.kt +0 -45
- package/android/src/test/java/com/mentra/acsmeeting/source/WhipIngestProtocolTest.kt +0 -356
- package/android/src/test/java/com/mentra/acsmeeting/source/WhipIngestServerLoopbackTest.kt +0 -411
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/AvSyncProbeTest.kt +0 -83
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/ChromaProbeTest.kt +0 -61
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/PipelineStatsTest.kt +0 -322
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/ProcessCpuTest.kt +0 -18
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/RingPercentileTest.kt +0 -49
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/VideoRateVerdictTest.kt +0 -149
- package/android/src/test/java/com/mentra/acsmeeting/trace/SoftApTraceTest.kt +0 -97
- package/android/src/test/java/com/mentra/acsmeeting/video/FrameGeometryTest.kt +0 -33
- package/android/src/test/java/com/mentra/acsmeeting/video/I420PackerTest.kt +0 -126
- package/ios/PolicyKit/Sources/AcsAudioPolicy/GlassesMediaSource.swift +0 -68
- package/ios/WhepVideoSource.swift +0 -356
package/README.md
CHANGED
|
@@ -391,6 +391,15 @@ rollback; it takes effect on the next join.
|
|
|
391
391
|
`scripts/acs-ladder.ts` in the Mentra-Call repo parses these lines and prints pass/fail
|
|
392
392
|
over a trailing 10-second window, including the `recv`-vs-`dec` attribution.
|
|
393
393
|
|
|
394
|
+
## Shared media transport
|
|
395
|
+
|
|
396
|
+
The native receivers, decoded buffers, local WHIP server, and network helpers live in
|
|
397
|
+
[`glasses-media`](../glasses-media/README.md). ACS consumes that library on both platforms.
|
|
398
|
+
The iOS SoftAP path uses the existing persistent hotspot-join approach, receives WebRTC
|
|
399
|
+
locally, and feeds the ACS raw outgoing streams. `join` resolves with `ingestUrl` only
|
|
400
|
+
once the receiver is ready; `leaveAndAwait` waits for native cleanup. Physical routing,
|
|
401
|
+
screen-off streaming, and thermal qualification remain device checks.
|
|
402
|
+
|
|
394
403
|
## iOS host setup
|
|
395
404
|
|
|
396
405
|
Add `"@mentra/acs-meeting"` to the host's Expo `plugins` list, then run
|
|
@@ -406,7 +415,7 @@ The host's other pods retain their configured linkage.
|
|
|
406
415
|
## Tests
|
|
407
416
|
|
|
408
417
|
```bash
|
|
409
|
-
|
|
418
|
+
./scripts/check-android-compile.sh bluetooth-sdk :mentra-acs-meeting:testDebugUnitTest :mentra-glasses-media:testDebugUnitTest
|
|
410
419
|
```
|
|
411
420
|
|
|
412
421
|
These are plain JVM tests with no Robolectric. That constrains what can be tested: ACS SDK
|
|
@@ -426,8 +435,10 @@ counter race that caused the false conservation failures.
|
|
|
426
435
|
whether the glasses are actually producing 15 fps.
|
|
427
436
|
- **Outgoing glasses PCM reads as silence** (`P4 pcm meanAbs=0`) and needs its own
|
|
428
437
|
investigation.
|
|
429
|
-
- **iOS
|
|
430
|
-
|
|
438
|
+
- **iOS media qualification needs device coverage.** Screen-off operation and background
|
|
439
|
+
gallery transfers already work. Validate sustained local WebRTC reception and ACS
|
|
440
|
+
publishing on that existing background infrastructure. The Swift side does not yet
|
|
441
|
+
have the Android telemetry ladder.
|
|
431
442
|
iOS also has no `RemoteRoster` yet (`participants` is never emitted) and does not apply
|
|
432
443
|
`maxBitrateBps` to the outgoing stream.
|
|
433
444
|
|
package/android/build.gradle
CHANGED
|
@@ -66,6 +66,7 @@ afterEvaluate {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
dependencies {
|
|
69
|
+
implementation project(':mentra-glasses-media')
|
|
69
70
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
|
|
70
71
|
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
|
71
72
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
|
|
@@ -5,12 +5,12 @@ import android.content.Intent
|
|
|
5
5
|
import android.os.Build
|
|
6
6
|
import android.os.SystemClock
|
|
7
7
|
import android.provider.Settings
|
|
8
|
-
import com.mentra.
|
|
9
|
-
import com.mentra.
|
|
10
|
-
import com.mentra.
|
|
11
|
-
import com.mentra.
|
|
8
|
+
import com.mentra.glassesmedia.network.InternetHold
|
|
9
|
+
import com.mentra.glassesmedia.network.ScopedNetworkChangeDetector
|
|
10
|
+
import com.mentra.glassesmedia.network.ScopedNetworkError
|
|
11
|
+
import com.mentra.glassesmedia.network.ScopedSoftApNetwork
|
|
12
12
|
import com.mentra.acsmeeting.source.MeetingVideoSourceSpec
|
|
13
|
-
import com.mentra.
|
|
13
|
+
import com.mentra.glassesmedia.trace.SoftApTrace
|
|
14
14
|
import com.mentra.acsmeeting.video.VideoProfile
|
|
15
15
|
import expo.modules.kotlin.modules.Module
|
|
16
16
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
@@ -104,7 +104,7 @@ class AcsMeetingModule : Module() {
|
|
|
104
104
|
|
|
105
105
|
AsyncFunction("beginTrace") { traceId: String ->
|
|
106
106
|
require(traceId.matches(Regex("[A-Za-z0-9]*"))) { "invalid trace id" }
|
|
107
|
-
com.mentra.
|
|
107
|
+
com.mentra.glassesmedia.trace.SoftApTrace.begin(traceId)
|
|
108
108
|
Unit
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -133,7 +133,7 @@ class AcsMeetingModule : Module() {
|
|
|
133
133
|
override fun onAvailable(network: android.net.Network, localIpv4: String) = Unit
|
|
134
134
|
|
|
135
135
|
override fun onLost(error: ScopedNetworkError) {
|
|
136
|
-
com.mentra.
|
|
136
|
+
com.mentra.glassesmedia.trace.SoftApTrace.failure("scoped_network_lost_midcall", "code" to error.code)
|
|
137
137
|
sendEvent(
|
|
138
138
|
"onScopedNetworkLost",
|
|
139
139
|
mapOf("code" to error.code, "message" to (error.message ?: "The glasses hotspot went away")),
|
|
@@ -147,9 +147,9 @@ class AcsMeetingModule : Module() {
|
|
|
147
147
|
// then a retry reminted ACS after the user turned Wi-Fi on — and Android often made
|
|
148
148
|
// that new Wi-Fi the default route with no internet, so token mint hung on DNS.
|
|
149
149
|
promptToEnableWifi()
|
|
150
|
-
com.mentra.
|
|
150
|
+
com.mentra.glassesmedia.trace.SoftApTrace.stage("wifi_enable_wait", "timeoutMs" to ScopedSoftApNetwork.WIFI_ENABLE_WAIT_MS)
|
|
151
151
|
val enabled = scoped.awaitWifiEnabled()
|
|
152
|
-
com.mentra.
|
|
152
|
+
com.mentra.glassesmedia.trace.SoftApTrace.stage(
|
|
153
153
|
"wifi_enable_wait_done",
|
|
154
154
|
"enabled" to enabled,
|
|
155
155
|
)
|
|
@@ -161,7 +161,7 @@ class AcsMeetingModule : Module() {
|
|
|
161
161
|
// AP). Samsung then assoc-rejects the glasses SoftAP (status 1025) and fires
|
|
162
162
|
// onUnavailable. After that request dies the STA is idle — the same join from idle
|
|
163
163
|
// is what succeeded at 17:43:20 after a failed switch.
|
|
164
|
-
com.mentra.
|
|
164
|
+
com.mentra.glassesmedia.trace.SoftApTrace.stage(
|
|
165
165
|
"scoped_join_unavailable_retry",
|
|
166
166
|
"ssid" to ssid,
|
|
167
167
|
"settleMs" to ScopedSoftApNetwork.UNAVAILABLE_RETRY_SETTLE_MS,
|
|
@@ -61,26 +61,26 @@ import com.mentra.acsmeeting.audio.PcmBridge
|
|
|
61
61
|
import com.mentra.acsmeeting.audio.PhoneMicCapturer
|
|
62
62
|
import com.mentra.acsmeeting.audio.UplinkPacer
|
|
63
63
|
import com.mentra.acsmeeting.audio.UplinkSender
|
|
64
|
-
import com.mentra.
|
|
65
|
-
import com.mentra.
|
|
66
|
-
import com.mentra.
|
|
67
|
-
import com.mentra.
|
|
68
|
-
import com.mentra.
|
|
69
|
-
import com.mentra.
|
|
70
|
-
import com.mentra.
|
|
71
|
-
import com.mentra.
|
|
72
|
-
import com.mentra.
|
|
64
|
+
import com.mentra.glassesmedia.source.MediaDiagnostics
|
|
65
|
+
import com.mentra.glassesmedia.source.CloudflareWhepSource
|
|
66
|
+
import com.mentra.glassesmedia.source.DecoderMode
|
|
67
|
+
import com.mentra.glassesmedia.source.OutgoingRateArm
|
|
68
|
+
import com.mentra.glassesmedia.source.PixelFormatArm
|
|
69
|
+
import com.mentra.glassesmedia.source.GlassesMediaController
|
|
70
|
+
import com.mentra.glassesmedia.network.ScopedSoftApNetwork
|
|
71
|
+
import com.mentra.glassesmedia.source.GlassesMediaSourceFactory
|
|
72
|
+
import com.mentra.glassesmedia.source.LocalWhipIngestSource
|
|
73
73
|
import com.mentra.acsmeeting.source.MeetingVideoSourceSpec
|
|
74
|
-
import com.mentra.
|
|
75
|
-
import com.mentra.
|
|
76
|
-
import com.mentra.
|
|
77
|
-
import com.mentra.
|
|
78
|
-
import com.mentra.
|
|
79
|
-
import com.mentra.
|
|
80
|
-
import com.mentra.
|
|
81
|
-
import com.mentra.
|
|
82
|
-
import com.mentra.
|
|
83
|
-
import com.mentra.
|
|
74
|
+
import com.mentra.glassesmedia.source.SourceConfig
|
|
75
|
+
import com.mentra.glassesmedia.source.SourceKind
|
|
76
|
+
import com.mentra.glassesmedia.source.SourceState
|
|
77
|
+
import com.mentra.glassesmedia.source.SyntheticI420Source
|
|
78
|
+
import com.mentra.glassesmedia.source.TargetSize
|
|
79
|
+
import com.mentra.glassesmedia.source.VideoSourceArm
|
|
80
|
+
import com.mentra.glassesmedia.telemetry.AvSyncProbe
|
|
81
|
+
import com.mentra.glassesmedia.telemetry.PipelineStats
|
|
82
|
+
import com.mentra.glassesmedia.telemetry.PipelineTicker
|
|
83
|
+
import com.mentra.glassesmedia.trace.SoftApTrace
|
|
84
84
|
import com.mentra.acsmeeting.video.AcsFrameSender
|
|
85
85
|
import com.mentra.acsmeeting.video.VideoProfile
|
|
86
86
|
import java.util.concurrent.CountDownLatch
|
|
@@ -131,7 +131,7 @@ class AcsMeetingSession(
|
|
|
131
131
|
private val resolvedFactory = mediaSourceFactory ?: GlassesMediaSourceFactory { video, pcm, config ->
|
|
132
132
|
// The synthetic diagnostic arm overrides everything; otherwise the requested kind decides.
|
|
133
133
|
when {
|
|
134
|
-
|
|
134
|
+
MediaDiagnostics.videoArm == VideoSourceArm.SYNTHETIC ->
|
|
135
135
|
SyntheticI420Source(video, stats, frameSender::isReady)
|
|
136
136
|
|
|
137
137
|
config.kind == SourceKind.SOFTAP ->
|
|
@@ -207,7 +207,7 @@ class AcsMeetingSession(
|
|
|
207
207
|
@Volatile private var phase = "idle"
|
|
208
208
|
@Volatile private var lastError: String? = null
|
|
209
209
|
@Volatile private var audioSource = "glasses"
|
|
210
|
-
@Volatile private var configuredAudioDelayMs =
|
|
210
|
+
@Volatile private var configuredAudioDelayMs = MediaDiagnostics.acsAudioDelayMs
|
|
211
211
|
@Volatile private var lastSafety = AudioSafety.DEGRADED
|
|
212
212
|
// Health of the glasses WHEP feed, reported alongside the ACS phase so the host
|
|
213
213
|
// can tell "call is up, glasses video is dead" from a healthy call.
|
|
@@ -382,7 +382,7 @@ class AcsMeetingSession(
|
|
|
382
382
|
// moves it runs on this executor, so the value is stable for the rest of this join.
|
|
383
383
|
val generation = joinGeneration.get()
|
|
384
384
|
val requested = video
|
|
385
|
-
this.profile = when (
|
|
385
|
+
this.profile = when (MediaDiagnostics.outgoingRate) {
|
|
386
386
|
OutgoingRateArm.CLAMP_TO_SOFTWARE_CEILING -> requested.forSoftwareEncoder()
|
|
387
387
|
OutgoingRateArm.ADVERTISE_REQUESTED -> requested
|
|
388
388
|
}
|
|
@@ -396,7 +396,7 @@ class AcsMeetingSession(
|
|
|
396
396
|
} else {
|
|
397
397
|
Log.i(
|
|
398
398
|
TAG,
|
|
399
|
-
"ACS rate arm=${
|
|
399
|
+
"ACS rate arm=${MediaDiagnostics.outgoingRate} advertising " +
|
|
400
400
|
"${this.profile.width}x${this.profile.height}@${this.profile.fps} unclamped; " +
|
|
401
401
|
"P7 rate names what binds",
|
|
402
402
|
)
|
|
@@ -410,7 +410,7 @@ class AcsMeetingSession(
|
|
|
410
410
|
emit("connecting")
|
|
411
411
|
val bridge = PcmBridge(context.cacheDir, dumpWav)
|
|
412
412
|
pcmBridge = bridge
|
|
413
|
-
val delayMs = (audioDelayMs ?:
|
|
413
|
+
val delayMs = (audioDelayMs ?: MediaDiagnostics.acsAudioDelayMs)
|
|
414
414
|
.coerceIn(0, AudioUplinkChain.MAX_DELAY_MS)
|
|
415
415
|
configuredAudioDelayMs = delayMs
|
|
416
416
|
val headroom = if (videoSource.kind == SourceKind.SOFTAP && parsed != AudioSourceKind.PHONE) {
|
|
@@ -458,7 +458,7 @@ class AcsMeetingSession(
|
|
|
458
458
|
applyAudioPolicy("virtual-stream-state")
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
-
val synthetic =
|
|
461
|
+
val synthetic = MediaDiagnostics.videoArm == VideoSourceArm.SYNTHETIC
|
|
462
462
|
if (synthetic) muted.set(true)
|
|
463
463
|
val desired = desiredKind()
|
|
464
464
|
val plan = if (synthetic) {
|
|
@@ -489,7 +489,7 @@ class AcsMeetingSession(
|
|
|
489
489
|
Log.i(TAG, "raw incoming audio state=${incoming.state}")
|
|
490
490
|
}
|
|
491
491
|
incoming.addOnMixedAudioBufferReceivedListener { event: IncomingMixedAudioEvent ->
|
|
492
|
-
if (
|
|
492
|
+
if (MediaDiagnostics.videoArm == VideoSourceArm.SYNTHETIC) return@addOnMixedAudioBufferReceivedListener
|
|
493
493
|
try {
|
|
494
494
|
val data = event.audioBuffer?.buffer ?: return@addOnMixedAudioBufferReceivedListener
|
|
495
495
|
val bytes = ByteArray(data.remaining())
|
|
@@ -535,14 +535,14 @@ class AcsMeetingSession(
|
|
|
535
535
|
videoSource is MeetingVideoSourceSpec.SoftAp -> "softap"
|
|
536
536
|
else -> "whep"
|
|
537
537
|
}
|
|
538
|
-
stats.pathMode = if (
|
|
538
|
+
stats.pathMode = if (MediaDiagnostics.decoderMode == DecoderMode.BYTE_BUFFER) "bytebuf" else "texture"
|
|
539
539
|
stats.pathCopy = when {
|
|
540
|
-
|
|
541
|
-
|
|
540
|
+
MediaDiagnostics.pixelFormat == PixelFormatArm.NV12 -> "nv12"
|
|
541
|
+
MediaDiagnostics.zeroCopy -> "zerocopy"
|
|
542
542
|
else -> "planes"
|
|
543
543
|
}
|
|
544
|
-
stats.pix =
|
|
545
|
-
stats.zcOn = if (
|
|
544
|
+
stats.pix = MediaDiagnostics.pixelFormat.name.lowercase()
|
|
545
|
+
stats.zcOn = if (MediaDiagnostics.zeroCopy) 1 else 0
|
|
546
546
|
mediaRestartAttempts = 0
|
|
547
547
|
currentSourceKind = if (synthetic) SourceKind.DIRECT else videoSource.kind
|
|
548
548
|
media.setStateListener { state, reason ->
|
|
@@ -599,12 +599,12 @@ class AcsMeetingSession(
|
|
|
599
599
|
applyAudioPolicy("join")
|
|
600
600
|
Log.i(
|
|
601
601
|
TAG,
|
|
602
|
-
"ACS join started arm=${
|
|
602
|
+
"ACS join started arm=${MediaDiagnostics.videoArm.name.lowercase()} " +
|
|
603
603
|
"profile=${profile.width}x${profile.height}@${profile.fps} " +
|
|
604
604
|
"maxBitrate=${profile.maxBitrateBps} bitsPerFrame=${profile.bitsPerFrame()} " +
|
|
605
|
-
"syntheticFps=${
|
|
606
|
-
"decoderMode=${
|
|
607
|
-
"pixelFormat=${
|
|
605
|
+
"syntheticFps=${MediaDiagnostics.syntheticFps} entropy=${MediaDiagnostics.syntheticEntropy} " +
|
|
606
|
+
"decoderMode=${MediaDiagnostics.decoderMode} zeroCopy=${MediaDiagnostics.zeroCopy} " +
|
|
607
|
+
"pixelFormat=${MediaDiagnostics.pixelFormat} " +
|
|
608
608
|
"source=${this.audioSource} audio=${if (synthetic) "off" else "on"} " +
|
|
609
609
|
"armVirtual=${plan.armVirtual} transportMuted=${plan.transportMuted}",
|
|
610
610
|
)
|
|
@@ -748,7 +748,7 @@ class AcsMeetingSession(
|
|
|
748
748
|
}
|
|
749
749
|
|
|
750
750
|
fun setMuted(next: Boolean): Map<String, Any> {
|
|
751
|
-
if (
|
|
751
|
+
if (MediaDiagnostics.videoArm == VideoSourceArm.SYNTHETIC) {
|
|
752
752
|
muted.set(true)
|
|
753
753
|
return snapshot()
|
|
754
754
|
}
|
|
@@ -988,7 +988,7 @@ class AcsMeetingSession(
|
|
|
988
988
|
Log.i(
|
|
989
989
|
TAG,
|
|
990
990
|
"P8 audio-up config configuredDelayMs=$configuredAudioDelayMs " +
|
|
991
|
-
"audioTimestamps=${
|
|
991
|
+
"audioTimestamps=${MediaDiagnostics.acsAudioTimestamps}",
|
|
992
992
|
)
|
|
993
993
|
}
|
|
994
994
|
|
|
@@ -3,8 +3,8 @@ package com.mentra.acsmeeting.audio
|
|
|
3
3
|
import android.util.Log
|
|
4
4
|
import com.azure.android.communication.calling.RawAudioBuffer
|
|
5
5
|
import com.azure.android.communication.calling.RawOutgoingAudioStream
|
|
6
|
-
import com.mentra.
|
|
7
|
-
import com.mentra.
|
|
6
|
+
import com.mentra.glassesmedia.source.MediaDiagnostics
|
|
7
|
+
import com.mentra.glassesmedia.telemetry.RingPercentile
|
|
8
8
|
import com.mentra.acsmeeting.video.AcsTimestamp
|
|
9
9
|
import java.nio.ByteBuffer
|
|
10
10
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
@@ -36,13 +36,13 @@ fun interface UplinkTransport {
|
|
|
36
36
|
* Whether ACS actually *uses* these ticks to lip-sync raw audio at the receiver is an open question
|
|
37
37
|
* that only a receiver-side recording answers. Stamping them is the precondition for asking it, not
|
|
38
38
|
* the answer, and it is separately worth doing: a run of zero timestamps is what makes the Teams
|
|
39
|
-
* jitter buffer hold. [
|
|
39
|
+
* jitter buffer hold. [MediaDiagnostics.acsAudioTimestamps] turns it off so the question can be
|
|
40
40
|
* asked both ways on the same device.
|
|
41
41
|
*/
|
|
42
42
|
class AcsUplinkTransport(
|
|
43
43
|
private val stream: RawOutgoingAudioStream,
|
|
44
44
|
private val clock: () -> Long = System::nanoTime,
|
|
45
|
-
private val stamp: Boolean =
|
|
45
|
+
private val stamp: Boolean = MediaDiagnostics.acsAudioTimestamps,
|
|
46
46
|
) : UplinkTransport {
|
|
47
47
|
private var lastTicks = 0L
|
|
48
48
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package com.mentra.acsmeeting.video
|
|
2
2
|
|
|
3
|
+
import com.mentra.glassesmedia.video.I420Packer
|
|
4
|
+
|
|
3
5
|
import android.util.Log
|
|
4
6
|
import com.azure.android.communication.calling.RawOutgoingVideoStream
|
|
5
7
|
import com.azure.android.communication.calling.RawVideoFrameBuffer
|
|
@@ -10,13 +12,13 @@ import com.azure.android.communication.calling.VideoStreamResolution
|
|
|
10
12
|
import com.azure.android.communication.calling.VideoStreamState
|
|
11
13
|
import com.azure.android.communication.calling.VideoStreamStateChangedListener
|
|
12
14
|
import com.azure.android.communication.calling.VirtualOutgoingVideoStream
|
|
13
|
-
import com.mentra.
|
|
14
|
-
import com.mentra.
|
|
15
|
-
import com.mentra.
|
|
16
|
-
import com.mentra.
|
|
17
|
-
import com.mentra.
|
|
18
|
-
import com.mentra.
|
|
19
|
-
import com.mentra.
|
|
15
|
+
import com.mentra.glassesmedia.source.MediaDiagnostics
|
|
16
|
+
import com.mentra.glassesmedia.source.I420Planes
|
|
17
|
+
import com.mentra.glassesmedia.source.PixelFormatArm
|
|
18
|
+
import com.mentra.glassesmedia.source.TargetSize
|
|
19
|
+
import com.mentra.glassesmedia.telemetry.AvSyncProbe
|
|
20
|
+
import com.mentra.glassesmedia.telemetry.ChromaProbe
|
|
21
|
+
import com.mentra.glassesmedia.telemetry.PipelineStats
|
|
20
22
|
import java.nio.ByteBuffer
|
|
21
23
|
import java.util.concurrent.ConcurrentHashMap
|
|
22
24
|
import java.util.concurrent.ConcurrentLinkedQueue
|
|
@@ -297,12 +299,12 @@ class AcsFrameSender(
|
|
|
297
299
|
return try {
|
|
298
300
|
negotiated.pixelFormat == VideoStreamPixelFormat.NV12
|
|
299
301
|
} catch (_: Exception) {
|
|
300
|
-
|
|
302
|
+
MediaDiagnostics.pixelFormat == PixelFormatArm.NV12
|
|
301
303
|
}
|
|
302
304
|
}
|
|
303
305
|
|
|
304
306
|
private fun tryZeroCopy(planes: I420Planes): Boolean {
|
|
305
|
-
if (!
|
|
307
|
+
if (!MediaDiagnostics.zeroCopy) return false
|
|
306
308
|
if (planes.retain == null || planes.release == null) {
|
|
307
309
|
stats.onZcFell()
|
|
308
310
|
return false
|
|
@@ -407,7 +409,7 @@ class AcsFrameSender(
|
|
|
407
409
|
private const val ZC_GRACE_MS = 1_000L
|
|
408
410
|
|
|
409
411
|
fun outgoingFormat(profile: VideoProfile = VideoProfile.DEFAULT): VideoStreamFormat =
|
|
410
|
-
when (
|
|
412
|
+
when (MediaDiagnostics.pixelFormat) {
|
|
411
413
|
PixelFormatArm.NV12 -> nv12Format(profile)
|
|
412
414
|
PixelFormatArm.I420 -> i420Format(profile)
|
|
413
415
|
}
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
package com.mentra.
|
|
1
|
+
package com.mentra.glassesmedia.source
|
|
2
2
|
|
|
3
3
|
import com.mentra.acsmeeting.audio.AudioUplinkChain
|
|
4
4
|
import org.assertj.core.api.Assertions.assertThat
|
|
5
5
|
import org.junit.Test
|
|
6
6
|
|
|
7
|
-
class
|
|
7
|
+
class MediaDiagnosticsTest {
|
|
8
8
|
@Test
|
|
9
9
|
fun videoArmDefaultsToWhep() {
|
|
10
|
-
assertThat(
|
|
10
|
+
assertThat(MediaDiagnostics.videoArm).isEqualTo(VideoSourceArm.WHEP)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
@Test
|
|
14
14
|
fun decoderModeDefaultsToTexture() {
|
|
15
|
-
assertThat(
|
|
15
|
+
assertThat(MediaDiagnostics.decoderMode).isEqualTo(DecoderMode.TEXTURE)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
@Test
|
|
19
19
|
fun zeroCopyDefaultsToOff() {
|
|
20
|
-
assertThat(
|
|
20
|
+
assertThat(MediaDiagnostics.zeroCopy).isFalse()
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
@Test
|
|
24
24
|
fun pixelFormatDefaultsToI420() {
|
|
25
|
-
assertThat(
|
|
25
|
+
assertThat(MediaDiagnostics.pixelFormat).isEqualTo(PixelFormatArm.I420)
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -34,7 +34,7 @@ class AcsInvestigationTest {
|
|
|
34
34
|
*/
|
|
35
35
|
@Test
|
|
36
36
|
fun outgoingRateAdvertisesWhatWasRequested() {
|
|
37
|
-
assertThat(
|
|
37
|
+
assertThat(MediaDiagnostics.outgoingRate).isEqualTo(OutgoingRateArm.ADVERTISE_REQUESTED)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -46,12 +46,12 @@ class AcsInvestigationTest {
|
|
|
46
46
|
*/
|
|
47
47
|
@Test
|
|
48
48
|
fun audioDelayShipsAtZeroUntilItIsMeasured() {
|
|
49
|
-
assertThat(
|
|
49
|
+
assertThat(MediaDiagnostics.acsAudioDelayMs).isEqualTo(0)
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
@Test
|
|
53
53
|
fun theConfiguredDelayStaysInsideTheChainsCeiling() {
|
|
54
|
-
assertThat(
|
|
54
|
+
assertThat(MediaDiagnostics.acsAudioDelayMs).isBetween(0, AudioUplinkChain.MAX_DELAY_MS)
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
@@ -61,6 +61,6 @@ class AcsInvestigationTest {
|
|
|
61
61
|
*/
|
|
62
62
|
@Test
|
|
63
63
|
fun timestampsDefaultOnAndRemainSwitchable() {
|
|
64
|
-
assertThat(
|
|
64
|
+
assertThat(MediaDiagnostics.acsAudioTimestamps).isTrue()
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
package com.mentra.acsmeeting.source
|
|
2
2
|
|
|
3
|
+
import com.mentra.glassesmedia.source.SourceConfig
|
|
4
|
+
import com.mentra.glassesmedia.source.SourceKind
|
|
5
|
+
|
|
3
6
|
import org.assertj.core.api.Assertions.assertThat
|
|
4
7
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
|
5
8
|
import org.junit.Test
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
package com.mentra.
|
|
1
|
+
package com.mentra.glassesmedia.telemetry
|
|
2
2
|
|
|
3
|
-
import com.mentra.
|
|
3
|
+
import com.mentra.glassesmedia.telemetry.VideoQuality.Band
|
|
4
4
|
import com.mentra.acsmeeting.video.VideoProfile
|
|
5
5
|
import org.assertj.core.api.Assertions.assertThat
|
|
6
6
|
import org.assertj.core.api.Assertions.within
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
package com.mentra.acsmeeting.video
|
|
2
2
|
|
|
3
|
-
import com.mentra.
|
|
3
|
+
import com.mentra.glassesmedia.source.MediaDiagnostics
|
|
4
4
|
import org.assertj.core.api.Assertions.assertThat
|
|
5
5
|
import org.junit.Test
|
|
6
6
|
|
|
@@ -34,7 +34,7 @@ class VideoProfileTest {
|
|
|
34
34
|
*/
|
|
35
35
|
@Test
|
|
36
36
|
fun declaredRateMatchesTheSyntheticEmitRate() {
|
|
37
|
-
assertThat(VideoProfile.DEFAULT.fps).isEqualTo(
|
|
37
|
+
assertThat(VideoProfile.DEFAULT.fps).isEqualTo(MediaDiagnostics.syntheticFps)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -26,6 +26,8 @@ export type AcsMeetingState = {
|
|
|
26
26
|
activeStream?: AcsActiveStream;
|
|
27
27
|
audioSafety?: AcsAudioSafety;
|
|
28
28
|
mediaSource?: AcsMediaSourceState;
|
|
29
|
+
/** Local WHIP endpoint, available after a SoftAP join. */
|
|
30
|
+
ingestUrl?: string;
|
|
29
31
|
/** Remote roster (Android emits this; iOS does not yet). */
|
|
30
32
|
participants?: AcsMeetingParticipant[];
|
|
31
33
|
};
|
|
@@ -38,7 +40,14 @@ export type AcsOutgoingVideo = {
|
|
|
38
40
|
export type AcsMeetingJoinOptions = {
|
|
39
41
|
meetingUrl: string;
|
|
40
42
|
token: string;
|
|
41
|
-
whepUrl
|
|
43
|
+
whepUrl?: string;
|
|
44
|
+
videoSource?: {
|
|
45
|
+
type: "whep";
|
|
46
|
+
url: string;
|
|
47
|
+
} | {
|
|
48
|
+
type: "softap";
|
|
49
|
+
bindAddress: string;
|
|
50
|
+
};
|
|
42
51
|
displayName?: string;
|
|
43
52
|
/** "glasses" sends WHEP PCM. "phone" uses the ACS local mic (handset or BT). */
|
|
44
53
|
audioSource?: AcsAudioSource;
|
|
@@ -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,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,EAAE,MAAM,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,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 /** 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 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 /** 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"]}
|
|
@@ -10,7 +10,7 @@ declare class AcsMeetingNativeModule extends NativeModule<AcsMeetingModuleEvents
|
|
|
10
10
|
leave(): Promise<void>;
|
|
11
11
|
/**
|
|
12
12
|
* Leave, and resolve only once the hang-up, the agent disposal, and the network releases have
|
|
13
|
-
* finished. `leave()` only queues
|
|
13
|
+
* finished. Use this explicit barrier across platforms; Android `leave()` only queues cleanup.
|
|
14
14
|
*/
|
|
15
15
|
leaveAndAwait(options: {
|
|
16
16
|
timeoutMs: number;
|
|
@@ -22,7 +22,7 @@ declare class AcsMeetingNativeModule extends NativeModule<AcsMeetingModuleEvents
|
|
|
22
22
|
updateVideoSource(whepUrl: string): Promise<void>;
|
|
23
23
|
/** Force a WHEP rebuild on the current URL (phone changed networks). */
|
|
24
24
|
restartVideoSource(): Promise<void>;
|
|
25
|
-
/** SoftAP: join the glasses hotspot
|
|
25
|
+
/** SoftAP: join the glasses hotspot; resolves to the phone's IPv4 on it. */
|
|
26
26
|
joinScopedNetwork(ssid: string, passphrase: string): Promise<string>;
|
|
27
27
|
beginTrace(traceId: string): Promise<void>;
|
|
28
28
|
leaveScopedNetwork(): 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;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,
|
|
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 +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. `leave()` only queues
|
|
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"]}
|
package/ios/AcsMeeting.podspec
CHANGED
|
@@ -18,7 +18,7 @@ Pod::Spec.new do |s|
|
|
|
18
18
|
s.dependency 'AzureCommunicationCalling', '~> 2.15'
|
|
19
19
|
s.dependency 'AzureCommunicationCommon', '~> 1.1'
|
|
20
20
|
# Pin to the same WebRTC-SDK the Mentra App already links via LiveKit.
|
|
21
|
-
s.dependency '
|
|
21
|
+
s.dependency 'GlassesMedia'
|
|
22
22
|
s.frameworks = 'AVFoundation', 'AudioToolbox', 'CoreMedia', 'CoreVideo', 'UIKit'
|
|
23
23
|
s.pod_target_xcconfig = {
|
|
24
24
|
'DEFINES_MODULE' => 'YES',
|