@mentra/acs-meeting 3.2.0-dev.120
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/NOTICE +20 -0
- package/README.md +412 -0
- package/THIRD_PARTY +7 -0
- package/android/build.gradle +64 -0
- package/android/src/main/AndroidManifest.xml +5 -0
- package/android/src/main/java/com/mentra/acsmeeting/AcsMeetingModule.kt +80 -0
- package/android/src/main/java/com/mentra/acsmeeting/AcsMeetingSession.kt +846 -0
- package/android/src/main/java/com/mentra/acsmeeting/RemoteRoster.kt +123 -0
- package/android/src/main/java/com/mentra/acsmeeting/audio/AcsAudioPolicy.kt +90 -0
- package/android/src/main/java/com/mentra/acsmeeting/audio/AudioPolicyApplier.kt +176 -0
- package/android/src/main/java/com/mentra/acsmeeting/audio/IncomingAudioPump.kt +112 -0
- package/android/src/main/java/com/mentra/acsmeeting/audio/IncomingRateProbe.kt +65 -0
- package/android/src/main/java/com/mentra/acsmeeting/audio/PcmBridge.kt +108 -0
- package/android/src/main/java/com/mentra/acsmeeting/audio/PcmResampler.kt +159 -0
- package/android/src/main/java/com/mentra/acsmeeting/audio/PhoneMicCapturer.kt +115 -0
- package/android/src/main/java/com/mentra/acsmeeting/audio/UplinkPacer.kt +302 -0
- package/android/src/main/java/com/mentra/acsmeeting/audio/UplinkSender.kt +230 -0
- package/android/src/main/java/com/mentra/acsmeeting/source/BitmapFont.kt +99 -0
- package/android/src/main/java/com/mentra/acsmeeting/source/CloudflareWhepSource.kt +578 -0
- package/android/src/main/java/com/mentra/acsmeeting/source/FirstFrameGate.kt +45 -0
- package/android/src/main/java/com/mentra/acsmeeting/source/GlassesMediaSource.kt +101 -0
- package/android/src/main/java/com/mentra/acsmeeting/source/MediaListeners.kt +57 -0
- package/android/src/main/java/com/mentra/acsmeeting/source/SyntheticFrameFactory.kt +205 -0
- package/android/src/main/java/com/mentra/acsmeeting/source/SyntheticI420Source.kt +214 -0
- package/android/src/main/java/com/mentra/acsmeeting/source/TrackRegistry.kt +31 -0
- package/android/src/main/java/com/mentra/acsmeeting/source/VideoSourceArm.kt +41 -0
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/ChromaProbe.kt +78 -0
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/PipelineStats.kt +281 -0
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/PipelineTicker.kt +51 -0
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/ProcessCpu.kt +15 -0
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/RingPercentile.kt +33 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/AcsFrameSender.kt +475 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/AcsTimestamp.kt +26 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/FrameGeometry.kt +22 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/I420FormatSpec.kt +90 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/I420Packer.kt +72 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/Nv12FormatSpec.kt +36 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/Nv12Packer.kt +51 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/SendGate.kt +27 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/VideoProfile.kt +57 -0
- package/android/src/test/java/com/mentra/acsmeeting/RemoteRosterTest.kt +40 -0
- package/android/src/test/java/com/mentra/acsmeeting/audio/AcsAudioPolicyTest.kt +204 -0
- package/android/src/test/java/com/mentra/acsmeeting/audio/AudioPolicyApplierTest.kt +223 -0
- package/android/src/test/java/com/mentra/acsmeeting/audio/CapturePolicyTest.kt +12 -0
- package/android/src/test/java/com/mentra/acsmeeting/audio/IncomingAudioPumpTest.kt +97 -0
- package/android/src/test/java/com/mentra/acsmeeting/audio/IncomingRateProbeTest.kt +78 -0
- package/android/src/test/java/com/mentra/acsmeeting/audio/PcmResamplerTest.kt +125 -0
- package/android/src/test/java/com/mentra/acsmeeting/audio/UplinkPacerTest.kt +201 -0
- package/android/src/test/java/com/mentra/acsmeeting/audio/UplinkSenderTest.kt +166 -0
- package/android/src/test/java/com/mentra/acsmeeting/source/AcsInvestigationTest.kt +26 -0
- package/android/src/test/java/com/mentra/acsmeeting/source/BitmapFontTest.kt +22 -0
- package/android/src/test/java/com/mentra/acsmeeting/source/FirstFrameGateTest.kt +60 -0
- package/android/src/test/java/com/mentra/acsmeeting/source/GlassesMediaSourceTest.kt +133 -0
- package/android/src/test/java/com/mentra/acsmeeting/source/I420PlanesTest.kt +67 -0
- package/android/src/test/java/com/mentra/acsmeeting/source/SyntheticFrameFactoryTest.kt +193 -0
- package/android/src/test/java/com/mentra/acsmeeting/source/SyntheticI420SourceTest.kt +176 -0
- package/android/src/test/java/com/mentra/acsmeeting/source/TrackRegistryTest.kt +36 -0
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/ChromaProbeTest.kt +61 -0
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/PipelineStatsTest.kt +256 -0
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/ProcessCpuTest.kt +18 -0
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/RingPercentileTest.kt +49 -0
- package/android/src/test/java/com/mentra/acsmeeting/video/AcsTimestampTest.kt +43 -0
- package/android/src/test/java/com/mentra/acsmeeting/video/FrameGeometryTest.kt +33 -0
- package/android/src/test/java/com/mentra/acsmeeting/video/I420FormatSpecTest.kt +77 -0
- package/android/src/test/java/com/mentra/acsmeeting/video/I420PackerTest.kt +126 -0
- package/android/src/test/java/com/mentra/acsmeeting/video/Nv12FormatSpecTest.kt +36 -0
- package/android/src/test/java/com/mentra/acsmeeting/video/Nv12PackerTest.kt +58 -0
- package/android/src/test/java/com/mentra/acsmeeting/video/SendGateTest.kt +79 -0
- package/android/src/test/java/com/mentra/acsmeeting/video/VideoProfileTest.kt +68 -0
- package/build/AcsMeeting.types.d.ts +59 -0
- package/build/AcsMeeting.types.d.ts.map +1 -0
- package/build/AcsMeeting.types.js +2 -0
- package/build/AcsMeeting.types.js.map +1 -0
- package/build/AcsMeetingModule.d.ts +15 -0
- package/build/AcsMeetingModule.d.ts.map +1 -0
- package/build/AcsMeetingModule.js +3 -0
- package/build/AcsMeetingModule.js.map +1 -0
- package/build/AcsMeetingModule.web.d.ts +16 -0
- package/build/AcsMeetingModule.web.d.ts.map +1 -0
- package/build/AcsMeetingModule.web.js +31 -0
- package/build/AcsMeetingModule.web.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +3 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/AcsFrameSender.swift +47 -0
- package/ios/AcsMeeting.podspec +27 -0
- package/ios/AcsMeetingModule.swift +558 -0
- package/ios/PcmBridge.swift +117 -0
- package/ios/PhoneMicCapturer.swift +64 -0
- package/ios/PolicyKit/Package.swift +17 -0
- package/ios/PolicyKit/Sources/AcsAudioPolicy/AcsAudioPolicy.swift +102 -0
- package/ios/PolicyKit/Sources/AcsAudioPolicy/AudioPolicyApplier.swift +162 -0
- package/ios/PolicyKit/Sources/AcsAudioPolicy/GlassesMediaSource.swift +68 -0
- package/ios/PolicyKit/Tests/AcsAudioPolicyTests/AcsAudioPolicyTests.swift +273 -0
- package/ios/WhepVideoSource.swift +356 -0
- package/package.json +56 -0
- package/src/AcsMeeting.types.ts +66 -0
- package/src/AcsMeetingModule.ts +16 -0
- package/src/AcsMeetingModule.web.ts +33 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package com.mentra.acsmeeting.video
|
|
2
|
+
|
|
3
|
+
import org.assertj.core.api.Assertions.assertThat
|
|
4
|
+
import org.junit.Test
|
|
5
|
+
|
|
6
|
+
class Nv12FormatSpecTest {
|
|
7
|
+
@Test
|
|
8
|
+
fun hdOffersTightBiplanarStrides() {
|
|
9
|
+
val spec = Nv12FormatSpec.of()
|
|
10
|
+
assertThat(spec.width).isEqualTo(1280)
|
|
11
|
+
assertThat(spec.height).isEqualTo(720)
|
|
12
|
+
assertThat(spec.strideY).isEqualTo(1280)
|
|
13
|
+
assertThat(spec.strideUv).isEqualTo(1280)
|
|
14
|
+
assertThat(spec.namedResolution()).isEqualTo(AcsNamedResolution.P720)
|
|
15
|
+
assertThat(spec.packedSize()).isEqualTo(Nv12Packer.packedSize(1280, 720))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@Test
|
|
19
|
+
fun oddWidthRoundsUvStrideUp() {
|
|
20
|
+
val spec = Nv12FormatSpec.of(width = 3, height = 3, fps = 15f)
|
|
21
|
+
assertThat(spec.strideY).isEqualTo(3)
|
|
22
|
+
assertThat(spec.strideUv).isEqualTo(4)
|
|
23
|
+
assertThat(spec.packedSize()).isEqualTo(3 * 3 + 4 * 2)
|
|
24
|
+
assertThat(spec.withinAcsBounds()).isFalse()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Test
|
|
28
|
+
fun advertisedSizeMatchesPacker() {
|
|
29
|
+
for ((w, h) in listOf(1280 to 720, 960 to 540, 640 to 360, 3 to 3, 641 to 361)) {
|
|
30
|
+
val spec = Nv12FormatSpec.of(width = w, height = h)
|
|
31
|
+
assertThat(spec.packedSize())
|
|
32
|
+
.describedAs("%dx%d", w, h)
|
|
33
|
+
.isEqualTo(Nv12Packer.packedSize(w, h))
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
package com.mentra.acsmeeting.video
|
|
2
|
+
|
|
3
|
+
import org.assertj.core.api.Assertions.assertThat
|
|
4
|
+
import org.junit.Test
|
|
5
|
+
import java.nio.ByteBuffer
|
|
6
|
+
|
|
7
|
+
class Nv12PackerTest {
|
|
8
|
+
@Test
|
|
9
|
+
fun strideUvIsTwiceChromaWidth() {
|
|
10
|
+
assertThat(Nv12Packer.strideUv(1280)).isEqualTo(1280)
|
|
11
|
+
assertThat(Nv12Packer.strideUv(3)).isEqualTo(4)
|
|
12
|
+
assertThat(Nv12Packer.uvSize(1280, 720)).isEqualTo(1280 * 360)
|
|
13
|
+
assertThat(Nv12Packer.packedSize(1280, 720)).isEqualTo(1280 * 720 * 3 / 2)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Test
|
|
17
|
+
fun interleaveWritesUvPairsFromPaddedPlanes() {
|
|
18
|
+
val width = 4
|
|
19
|
+
val height = 4
|
|
20
|
+
val chromaW = 2
|
|
21
|
+
val chromaH = 2
|
|
22
|
+
val u = ByteBuffer.allocate(4 * 2)
|
|
23
|
+
val v = ByteBuffer.allocate(4 * 2)
|
|
24
|
+
// visible U: 10 11 / 12 13 V: 20 21 / 22 23, stride 4
|
|
25
|
+
u.put(0, 10); u.put(1, 11); u.put(4, 12); u.put(5, 13)
|
|
26
|
+
v.put(0, 20); v.put(1, 21); v.put(4, 22); v.put(5, 23)
|
|
27
|
+
val dest = ByteBuffer.allocate(Nv12Packer.uvSize(width, height))
|
|
28
|
+
Nv12Packer.interleaveUv(u, 4, v, 4, chromaW, chromaH, dest)
|
|
29
|
+
dest.flip()
|
|
30
|
+
val out = ByteArray(dest.remaining())
|
|
31
|
+
dest.get(out)
|
|
32
|
+
assertThat(out).containsExactly(10, 20, 11, 21, 12, 22, 13, 23)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test
|
|
36
|
+
fun copyYThenInterleaveMatchesPackedNv12() {
|
|
37
|
+
val width = 4
|
|
38
|
+
val height = 2
|
|
39
|
+
val y = ByteBuffer.allocate(width * height)
|
|
40
|
+
repeat(width * height) { y.put(it, (40 + it).toByte()) }
|
|
41
|
+
val u = ByteBuffer.allocate(2)
|
|
42
|
+
val v = ByteBuffer.allocate(2)
|
|
43
|
+
u.put(0, 80); u.put(1, 81)
|
|
44
|
+
v.put(0, 90); v.put(1, 91)
|
|
45
|
+
val destY = ByteBuffer.allocate(width * height)
|
|
46
|
+
val destUv = ByteBuffer.allocate(Nv12Packer.uvSize(width, height))
|
|
47
|
+
Nv12Packer.copyY(y, width, width, height, destY)
|
|
48
|
+
Nv12Packer.interleaveUv(u, 2, v, 2, 2, 1, destUv)
|
|
49
|
+
destY.flip()
|
|
50
|
+
destUv.flip()
|
|
51
|
+
val yOut = ByteArray(destY.remaining())
|
|
52
|
+
val uvOut = ByteArray(destUv.remaining())
|
|
53
|
+
destY.get(yOut)
|
|
54
|
+
destUv.get(uvOut)
|
|
55
|
+
assertThat(yOut).containsExactly(40, 41, 42, 43, 44, 45, 46, 47)
|
|
56
|
+
assertThat(uvOut).containsExactly(80, 90, 81, 91)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
package com.mentra.acsmeeting.video
|
|
2
|
+
|
|
3
|
+
import org.assertj.core.api.Assertions.assertThat
|
|
4
|
+
import org.junit.Test
|
|
5
|
+
|
|
6
|
+
class SendGateTest {
|
|
7
|
+
@Test
|
|
8
|
+
fun burstyTenFpsAllSubmitWhenGateIsFree() {
|
|
9
|
+
val intervals = burstyTenFps()
|
|
10
|
+
assertThat(oldPacerDrops(intervals, minIntervalMs = 67)).isGreaterThan((intervals.size * 0.3).toInt())
|
|
11
|
+
val gate = SendGate()
|
|
12
|
+
assertThat(submitAll(intervals, gate)).isEqualTo(intervals.size)
|
|
13
|
+
assertThat(gate.busyCount()).isEqualTo(0)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Test
|
|
17
|
+
fun decoderBatchingAtFifteenFpsAverageDropsNone() {
|
|
18
|
+
val intervals = longArrayOf(20, 110, 25, 111, 20, 110, 25, 111)
|
|
19
|
+
assertThat(oldPacerDrops(intervals, minIntervalMs = 33)).isGreaterThan(0)
|
|
20
|
+
val gate = SendGate()
|
|
21
|
+
assertThat(submitAll(intervals, gate)).isEqualTo(intervals.size)
|
|
22
|
+
assertThat(gate.busyCount()).isEqualTo(0)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Test
|
|
26
|
+
fun threeFramesOneMsApartAllSubmitWhenFree() {
|
|
27
|
+
val gate = SendGate()
|
|
28
|
+
assertThat(submitAll(longArrayOf(1, 1, 1), gate)).isEqualTo(3)
|
|
29
|
+
assertThat(gate.busyCount()).isEqualTo(0)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Test
|
|
33
|
+
fun heldGateIncrementsBusyAndDropsContendedFrames() {
|
|
34
|
+
val gate = SendGate()
|
|
35
|
+
assertThat(gate.tryAcquire()).isTrue()
|
|
36
|
+
assertThat(gate.tryAcquire()).isFalse()
|
|
37
|
+
assertThat(gate.tryAcquire()).isFalse()
|
|
38
|
+
assertThat(gate.busyCount()).isEqualTo(2)
|
|
39
|
+
gate.release()
|
|
40
|
+
assertThat(gate.tryAcquire()).isTrue()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private fun submitAll(intervalsMs: LongArray, gate: SendGate): Int {
|
|
44
|
+
var submitted = 0
|
|
45
|
+
for (ignored in intervalsMs) {
|
|
46
|
+
if (gate.tryAcquire()) {
|
|
47
|
+
submitted += 1
|
|
48
|
+
gate.release()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return submitted
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Observed WHEP pattern: unique ~10 fps delivered as 40 ms pairs then a 160 ms gap. */
|
|
55
|
+
private fun burstyTenFps(): LongArray {
|
|
56
|
+
val out = ArrayList<Long>(20)
|
|
57
|
+
repeat(10) {
|
|
58
|
+
out.add(40)
|
|
59
|
+
out.add(160)
|
|
60
|
+
}
|
|
61
|
+
return out.toLongArray()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Old 1x / 2x min-interval gate, kept only so a reintroduced pacer fails these cases. */
|
|
65
|
+
private fun oldPacerDrops(intervalsMs: LongArray, minIntervalMs: Long): Int {
|
|
66
|
+
var last = Long.MIN_VALUE
|
|
67
|
+
var t = 0L
|
|
68
|
+
var drops = 0
|
|
69
|
+
for (dt in intervalsMs) {
|
|
70
|
+
t += dt
|
|
71
|
+
if (last != Long.MIN_VALUE && t - last < minIntervalMs) {
|
|
72
|
+
drops += 1
|
|
73
|
+
} else {
|
|
74
|
+
last = t
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return drops
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
package com.mentra.acsmeeting.video
|
|
2
|
+
|
|
3
|
+
import com.mentra.acsmeeting.source.AcsInvestigation
|
|
4
|
+
import org.assertj.core.api.Assertions.assertThat
|
|
5
|
+
import org.junit.Test
|
|
6
|
+
|
|
7
|
+
class VideoProfileTest {
|
|
8
|
+
@Test
|
|
9
|
+
fun defaultRemainsHd720p15() {
|
|
10
|
+
assertThat(VideoProfile.DEFAULT).isEqualTo(VideoProfile.HD)
|
|
11
|
+
assertThat(VideoProfile.DEFAULT.width).isEqualTo(1280)
|
|
12
|
+
assertThat(VideoProfile.DEFAULT.height).isEqualTo(720)
|
|
13
|
+
assertThat(VideoProfile.DEFAULT.fps).isEqualTo(15)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Test
|
|
17
|
+
fun defaultProfileIsWithinAcsFormatBounds() {
|
|
18
|
+
val spec = VideoProfile.DEFAULT.spec()
|
|
19
|
+
assertThat(spec.withinAcsBounds()).isTrue()
|
|
20
|
+
assertThat(spec.fps).isEqualTo(VideoProfile.DEFAULT.fps.toFloat())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Test
|
|
24
|
+
fun everyProfileIsWithinAcsFormatBounds() {
|
|
25
|
+
for (profile in listOf(VideoProfile.HD, VideoProfile.SD, VideoProfile.P540, VideoProfile.P540_15)) {
|
|
26
|
+
assertThat(profile.spec().withinAcsBounds()).isTrue()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The declared rate must equal the rate we actually emit. Advertising a rate
|
|
32
|
+
* we do not sustain makes the ACS rate controller reserve budget for frames
|
|
33
|
+
* that never arrive, which costs wire fps.
|
|
34
|
+
*/
|
|
35
|
+
@Test
|
|
36
|
+
fun declaredRateMatchesTheSyntheticEmitRate() {
|
|
37
|
+
assertThat(VideoProfile.DEFAULT.fps).isEqualTo(AcsInvestigation.syntheticFps)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Regression for the 1.4 fps collapse: 720p noise needed ~1.4 Mbit per frame
|
|
42
|
+
* against a ~1.9 Mbps budget. Keep enough headroom per frame that ordinary
|
|
43
|
+
* content never puts the rate controller in that position.
|
|
44
|
+
*/
|
|
45
|
+
@Test
|
|
46
|
+
fun eachProfileLeavesUsableBitsPerFrame() {
|
|
47
|
+
for (profile in listOf(VideoProfile.HD, VideoProfile.SD, VideoProfile.P540_15)) {
|
|
48
|
+
assertThat(profile.bitsPerFrame()).isGreaterThan(20_000)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@Test
|
|
53
|
+
fun parseAcceptsDocumentedVirtualCameraSizesAndRejectsPortraitAnd480p() {
|
|
54
|
+
assertThat(VideoProfile.parse(1280, 720, 15, 2_500_000)).isEqualTo(VideoProfile.HD)
|
|
55
|
+
assertThat(VideoProfile.parse(960, 540, 30, 1_500_000)).isEqualTo(VideoProfile.P540)
|
|
56
|
+
assertThat(VideoProfile.parse(960, 540, 15, 1_500_000)).isEqualTo(VideoProfile.P540_15)
|
|
57
|
+
assertThat(VideoProfile.parse(540, 960, 30, 1_500_000)).isNull()
|
|
58
|
+
assertThat(VideoProfile.parse(854, 480, 15, 1_500_000)).isNull()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The fallback only earns its place if it is a real cut in encoder work. */
|
|
62
|
+
@Test
|
|
63
|
+
fun sdIsAQuarterOfTheHdPixelCount() {
|
|
64
|
+
val hd = VideoProfile.HD.width * VideoProfile.HD.height
|
|
65
|
+
val sd = VideoProfile.SD.width * VideoProfile.SD.height
|
|
66
|
+
assertThat(sd).isEqualTo(hd / 4)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type MeetingPhase = "idle" | "connecting" | "lobby" | "connected" | "disconnected" | "error";
|
|
2
|
+
export type AcsAudioSource = "glasses" | "phone";
|
|
3
|
+
export type AcsActiveStream = "none" | "virtual" | "local";
|
|
4
|
+
export type AcsAudioSafety = "safe" | "degraded" | "unsafe";
|
|
5
|
+
/**
|
|
6
|
+
* Health of the glasses WHEP subscription feeding the call. `failed` = ICE dropped or
|
|
7
|
+
* the WHEP endpoint went away while the ACS call may still be `connected`. Native
|
|
8
|
+
* rebuilds it on its own with backoff; the host can force one via `restartVideoSource`.
|
|
9
|
+
*/
|
|
10
|
+
export type AcsMediaSourceState = "idle" | "connecting" | "live" | "failed";
|
|
11
|
+
export type AcsMeetingParticipantState = "idle" | "connecting" | "connected" | "lobby" | "hold" | "disconnected";
|
|
12
|
+
export type AcsMeetingParticipant = {
|
|
13
|
+
id: string;
|
|
14
|
+
displayName: string | null;
|
|
15
|
+
state: AcsMeetingParticipantState;
|
|
16
|
+
isMuted: boolean;
|
|
17
|
+
isSpeaking: boolean;
|
|
18
|
+
};
|
|
19
|
+
export type AcsMeetingState = {
|
|
20
|
+
state: MeetingPhase;
|
|
21
|
+
muted: boolean;
|
|
22
|
+
error?: string;
|
|
23
|
+
meetingUrl?: string;
|
|
24
|
+
provider?: "acs-teams";
|
|
25
|
+
audioSource?: AcsAudioSource;
|
|
26
|
+
activeStream?: AcsActiveStream;
|
|
27
|
+
audioSafety?: AcsAudioSafety;
|
|
28
|
+
mediaSource?: AcsMediaSourceState;
|
|
29
|
+
/** Remote roster (Android emits this; iOS does not yet). */
|
|
30
|
+
participants?: AcsMeetingParticipant[];
|
|
31
|
+
};
|
|
32
|
+
export type AcsOutgoingVideo = {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
fps: number;
|
|
36
|
+
maxBitrateBps: number;
|
|
37
|
+
};
|
|
38
|
+
export type AcsMeetingJoinOptions = {
|
|
39
|
+
meetingUrl: string;
|
|
40
|
+
token: string;
|
|
41
|
+
whepUrl: string;
|
|
42
|
+
displayName?: string;
|
|
43
|
+
/** "glasses" sends WHEP PCM. "phone" uses the ACS local mic (handset or BT). */
|
|
44
|
+
audioSource?: AcsAudioSource;
|
|
45
|
+
/** Dump WHEP PCM to a WAV in cache for P4 verification. */
|
|
46
|
+
dumpPcmWav?: boolean;
|
|
47
|
+
/** When omitted, native keeps 1280×720@15 / 2.5 Mbps. */
|
|
48
|
+
video?: AcsOutgoingVideo;
|
|
49
|
+
};
|
|
50
|
+
export type AcsIncomingPcmEvent = {
|
|
51
|
+
base64: string;
|
|
52
|
+
sampleRate: number;
|
|
53
|
+
channels: number;
|
|
54
|
+
};
|
|
55
|
+
export type AcsMeetingModuleEvents = {
|
|
56
|
+
onState: (state: AcsMeetingState) => void;
|
|
57
|
+
onIncomingPcm: (pcm: AcsIncomingPcmEvent) => void;
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=AcsMeeting.types.d.ts.map
|
|
@@ -0,0 +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;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,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"}
|
|
@@ -0,0 +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 /** 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"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NativeModule } from "expo";
|
|
2
|
+
import type { AcsMeetingJoinOptions, AcsMeetingModuleEvents, AcsMeetingState } from "./AcsMeeting.types";
|
|
3
|
+
declare class AcsMeetingNativeModule extends NativeModule<AcsMeetingModuleEvents> {
|
|
4
|
+
join(options: AcsMeetingJoinOptions): Promise<AcsMeetingState>;
|
|
5
|
+
leave(): Promise<void>;
|
|
6
|
+
setMuted(muted: boolean): Promise<AcsMeetingState>;
|
|
7
|
+
setAudioSource(source: "glasses" | "phone"): Promise<AcsMeetingState>;
|
|
8
|
+
updateVideoSource(whepUrl: string): Promise<void>;
|
|
9
|
+
/** Force a WHEP rebuild on the current URL (phone changed networks). */
|
|
10
|
+
restartVideoSource(): Promise<void>;
|
|
11
|
+
getState(): Promise<AcsMeetingState>;
|
|
12
|
+
}
|
|
13
|
+
declare const _default: AcsMeetingNativeModule;
|
|
14
|
+
export default _default;
|
|
15
|
+
//# sourceMappingURL=AcsMeetingModule.d.ts.map
|
|
@@ -0,0 +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,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IACtB,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,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC;CACrC;;AAED,wBAA8E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AcsMeetingModule.js","sourceRoot":"","sources":["../src/AcsMeetingModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,mBAAmB,EAAC,MAAM,MAAM,CAAA;AAetD,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 leave(): Promise<void>\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 getState(): Promise<AcsMeetingState>\n}\n\nexport default requireNativeModule<AcsMeetingNativeModule>(\"MentraAcsMeeting\")\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AcsMeetingJoinOptions, AcsMeetingState } from "./AcsMeeting.types";
|
|
2
|
+
declare const _default: {
|
|
3
|
+
join(_options: AcsMeetingJoinOptions): Promise<AcsMeetingState>;
|
|
4
|
+
leave(): Promise<void>;
|
|
5
|
+
setMuted(_muted: boolean): Promise<AcsMeetingState>;
|
|
6
|
+
setAudioSource(_source: "glasses" | "phone"): Promise<AcsMeetingState>;
|
|
7
|
+
updateVideoSource(_whepUrl: string): Promise<void>;
|
|
8
|
+
restartVideoSource(): Promise<void>;
|
|
9
|
+
getState(): Promise<AcsMeetingState>;
|
|
10
|
+
addListener(): {
|
|
11
|
+
remove(): void;
|
|
12
|
+
};
|
|
13
|
+
removeListeners(): void;
|
|
14
|
+
};
|
|
15
|
+
export default _default;
|
|
16
|
+
//# sourceMappingURL=AcsMeetingModule.web.d.ts.map
|
|
@@ -0,0 +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;aAGtD,OAAO,CAAC,IAAI,CAAC;qBAGL,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;gBAGvB,OAAO,CAAC,eAAe,CAAC;;;;;;AAnB5C,wBA0BC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function unavailable() {
|
|
2
|
+
throw new Error("ACS meeting is not available on web");
|
|
3
|
+
}
|
|
4
|
+
export default {
|
|
5
|
+
async join(_options) {
|
|
6
|
+
unavailable();
|
|
7
|
+
},
|
|
8
|
+
async leave() {
|
|
9
|
+
unavailable();
|
|
10
|
+
},
|
|
11
|
+
async setMuted(_muted) {
|
|
12
|
+
unavailable();
|
|
13
|
+
},
|
|
14
|
+
async setAudioSource(_source) {
|
|
15
|
+
unavailable();
|
|
16
|
+
},
|
|
17
|
+
async updateVideoSource(_whepUrl) {
|
|
18
|
+
unavailable();
|
|
19
|
+
},
|
|
20
|
+
async restartVideoSource() {
|
|
21
|
+
unavailable();
|
|
22
|
+
},
|
|
23
|
+
async getState() {
|
|
24
|
+
return { state: "idle", muted: false };
|
|
25
|
+
},
|
|
26
|
+
addListener() {
|
|
27
|
+
return { remove() { } };
|
|
28
|
+
},
|
|
29
|
+
removeListeners() { },
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=AcsMeetingModule.web.js.map
|
|
@@ -0,0 +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,KAAK;QACT,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,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 leave(): Promise<void> {\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 getState(): Promise<AcsMeetingState> {\n return {state: \"idle\", muted: false}\n },\n addListener() {\n return {remove() {}}\n },\n removeListeners() {},\n}\n"]}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAA;AAC1C,cAAc,oBAAoB,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAA;AAC1C,cAAc,oBAAoB,CAAA","sourcesContent":["export {default} from \"./AcsMeetingModule\"\nexport * from \"./AcsMeeting.types\"\n"]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import AzureCommunicationCalling
|
|
2
|
+
import CoreVideo
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
/// Fresh Swift sender on ACS iOS RawOutgoingVideoStream + CVPixelBuffer.
|
|
6
|
+
/// No RealWear code (they have no iOS equivalent).
|
|
7
|
+
final class AcsFrameSender {
|
|
8
|
+
private var stream: RawOutgoingVideoStream?
|
|
9
|
+
private var running = false
|
|
10
|
+
private var lastSent: CFTimeInterval = 0
|
|
11
|
+
|
|
12
|
+
func attach(_ stream: VirtualOutgoingVideoStream) {
|
|
13
|
+
detach()
|
|
14
|
+
self.stream = stream
|
|
15
|
+
stream.delegate = self
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func send(_ pixelBuffer: CVPixelBuffer) {
|
|
19
|
+
guard running, let stream else { return }
|
|
20
|
+
let fps = stream.format?.framesPerSecond ?? 15
|
|
21
|
+
let now = CFAbsoluteTimeGetCurrent()
|
|
22
|
+
if lastSent > 0, now - lastSent < 1.0 / max(fps, 1) { return }
|
|
23
|
+
lastSent = now
|
|
24
|
+
do {
|
|
25
|
+
let frame = RawVideoFrameBuffer(pixelBuffer, streamFormat: stream.format)
|
|
26
|
+
try stream.send(frame)
|
|
27
|
+
} catch {
|
|
28
|
+
NSLog("ACS-SPIKE send frame failed: \(error)")
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func detach() {
|
|
33
|
+
running = false
|
|
34
|
+
// Drop the delegate on the previous stream so a late STOPPED from a prior
|
|
35
|
+
// call cannot freeze outgoing video for the current meeting.
|
|
36
|
+
stream?.delegate = nil
|
|
37
|
+
stream = nil
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
extension AcsFrameSender: RawOutgoingVideoStreamDelegate {
|
|
42
|
+
func rawOutgoingVideoStream(_ rawOutgoingVideoStream: RawOutgoingVideoStream, didChangeState args: VideoStreamStateChangedEventArgs) {
|
|
43
|
+
guard rawOutgoingVideoStream === stream else { return }
|
|
44
|
+
running = rawOutgoingVideoStream.state == .started
|
|
45
|
+
NSLog("ACS-SPIKE iOS raw video state=\(rawOutgoingVideoStream.state)")
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'AcsMeeting'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.description = package['description']
|
|
10
|
+
s.license = package['license']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.homepage = package['homepage']
|
|
13
|
+
s.platforms = { :ios => '15.1' }
|
|
14
|
+
s.swift_version = '5.9'
|
|
15
|
+
s.source = { git: 'https://github.com/Mentra-Community/MentraOS.git' }
|
|
16
|
+
s.static_framework = true
|
|
17
|
+
s.dependency 'ExpoModulesCore'
|
|
18
|
+
s.dependency 'AzureCommunicationCalling', '~> 2.15'
|
|
19
|
+
s.dependency 'AzureCommunicationCommon', '~> 1.1'
|
|
20
|
+
# Pin to the same WebRTC-SDK the Mentra App already links via LiveKit.
|
|
21
|
+
s.dependency 'WebRTC-SDK', '137.7151.09'
|
|
22
|
+
s.frameworks = 'AVFoundation', 'AudioToolbox', 'CoreMedia', 'CoreVideo', 'UIKit'
|
|
23
|
+
s.pod_target_xcconfig = {
|
|
24
|
+
'DEFINES_MODULE' => 'YES',
|
|
25
|
+
}
|
|
26
|
+
s.source_files = '*.{h,m,mm,swift}', 'PolicyKit/Sources/AcsAudioPolicy/*.swift'
|
|
27
|
+
end
|