@mentra/glasses-media 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.
Files changed (56) hide show
  1. package/README.md +35 -0
  2. package/android/build.gradle +84 -0
  3. package/android/src/main/AndroidManifest.xml +6 -0
  4. package/android/src/main/java/com/mentra/glassesmedia/network/InternetHold.kt +501 -0
  5. package/android/src/main/java/com/mentra/glassesmedia/network/Ipv4Prefix.kt +51 -0
  6. package/android/src/main/java/com/mentra/glassesmedia/network/NetworkFacts.kt +81 -0
  7. package/android/src/main/java/com/mentra/glassesmedia/network/ScopedNetworkChangeDetector.kt +355 -0
  8. package/android/src/main/java/com/mentra/glassesmedia/network/ScopedNetworkError.kt +109 -0
  9. package/android/src/main/java/com/mentra/glassesmedia/network/ScopedNetworkObserver.kt +160 -0
  10. package/android/src/main/java/com/mentra/glassesmedia/network/ScopedNetworkReadiness.kt +103 -0
  11. package/android/src/main/java/com/mentra/glassesmedia/network/ScopedNetworkRequestSpec.kt +53 -0
  12. package/android/src/main/java/com/mentra/glassesmedia/network/ScopedNetworkState.kt +127 -0
  13. package/android/src/main/java/com/mentra/glassesmedia/network/ScopedSoftApNetwork.kt +573 -0
  14. package/android/src/main/java/com/mentra/glassesmedia/source/BitmapFont.kt +99 -0
  15. package/android/src/main/java/com/mentra/glassesmedia/source/CloudflareWhepSource.kt +487 -0
  16. package/android/src/main/java/com/mentra/glassesmedia/source/DecodedTrackRelay.kt +147 -0
  17. package/android/src/main/java/com/mentra/glassesmedia/source/FirstFrameGate.kt +45 -0
  18. package/android/src/main/java/com/mentra/glassesmedia/source/GlassesMediaSource.kt +143 -0
  19. package/android/src/main/java/com/mentra/glassesmedia/source/LocalWhipIngestSource.kt +772 -0
  20. package/android/src/main/java/com/mentra/glassesmedia/source/MediaListeners.kt +57 -0
  21. package/android/src/main/java/com/mentra/glassesmedia/source/SdpAdapter.kt +17 -0
  22. package/android/src/main/java/com/mentra/glassesmedia/source/SelectedIcePair.kt +139 -0
  23. package/android/src/main/java/com/mentra/glassesmedia/source/SoftApIceDiagnosis.kt +144 -0
  24. package/android/src/main/java/com/mentra/glassesmedia/source/SoftApIcePolicy.kt +46 -0
  25. package/android/src/main/java/com/mentra/glassesmedia/source/SoftApSdpGuard.kt +160 -0
  26. package/android/src/main/java/com/mentra/glassesmedia/source/SyntheticFrameFactory.kt +205 -0
  27. package/android/src/main/java/com/mentra/glassesmedia/source/SyntheticI420Source.kt +214 -0
  28. package/android/src/main/java/com/mentra/glassesmedia/source/TrackRegistry.kt +33 -0
  29. package/android/src/main/java/com/mentra/glassesmedia/source/VideoSourceArm.kt +110 -0
  30. package/android/src/main/java/com/mentra/glassesmedia/source/WhipIngestProtocol.kt +207 -0
  31. package/android/src/main/java/com/mentra/glassesmedia/source/WhipIngestServer.kt +356 -0
  32. package/android/src/main/java/com/mentra/glassesmedia/telemetry/AvSyncProbe.kt +155 -0
  33. package/android/src/main/java/com/mentra/glassesmedia/telemetry/ChromaProbe.kt +78 -0
  34. package/android/src/main/java/com/mentra/glassesmedia/telemetry/PipelineStats.kt +372 -0
  35. package/android/src/main/java/com/mentra/glassesmedia/telemetry/PipelineTicker.kt +79 -0
  36. package/android/src/main/java/com/mentra/glassesmedia/telemetry/ProcessCpu.kt +15 -0
  37. package/android/src/main/java/com/mentra/glassesmedia/telemetry/RingPercentile.kt +48 -0
  38. package/android/src/main/java/com/mentra/glassesmedia/telemetry/VideoQuality.kt +178 -0
  39. package/android/src/main/java/com/mentra/glassesmedia/telemetry/VideoRateVerdict.kt +145 -0
  40. package/android/src/main/java/com/mentra/glassesmedia/trace/SoftApTrace.kt +143 -0
  41. package/android/src/main/java/com/mentra/glassesmedia/video/FrameGeometry.kt +22 -0
  42. package/android/src/main/java/com/mentra/glassesmedia/video/I420Packer.kt +72 -0
  43. package/expo-module.config.json +12 -0
  44. package/index.js +1 -0
  45. package/ios/CoreKit/Sources/GlassesMediaCore/GlassesMediaSource.swift +104 -0
  46. package/ios/CoreKit/Sources/GlassesMediaCore/LocalMediaPolicy.swift +51 -0
  47. package/ios/CoreKit/Sources/GlassesMediaCore/WhipIngestServer.swift +145 -0
  48. package/ios/CoreKit/Sources/GlassesMediaCore/WhipRequest.swift +59 -0
  49. package/ios/DecodedGlassesMediaSource.swift +9 -0
  50. package/ios/DecodedPcm.swift +33 -0
  51. package/ios/GlassesHotspotNetwork.swift +185 -0
  52. package/ios/GlassesMedia.podspec +18 -0
  53. package/ios/LocalWhipIngestSource.swift +325 -0
  54. package/ios/ReceiveOnlyAudioDevice.swift +106 -0
  55. package/ios/WhepVideoSource.swift +332 -0
  56. package/package.json +27 -0
@@ -0,0 +1,143 @@
1
+ package com.mentra.glassesmedia.source
2
+
3
+ enum class SourceKind {
4
+ /** Subscribe to a Cloudflare WHEP endpoint. The phone is the offerer. */
5
+ WHEP,
6
+ DIRECT,
7
+
8
+ /**
9
+ * Serve a WHIP endpoint on the glasses hotspot and let the glasses publish into it. The phone is
10
+ * the answerer, and no traffic leaves the local link. See [LocalWhipIngestSource].
11
+ */
12
+ SOFTAP,
13
+ }
14
+
15
+ enum class SourceState { IDLE, CONNECTING, LIVE, FAILED }
16
+
17
+ data class SourceConfig(
18
+ /**
19
+ * WHEP: the endpoint to subscribe to. SOFTAP: unused, because the URL is not known until the
20
+ * listener has bound a port — the source reports it back through
21
+ * [GlassesMediaSource.ingestUrl] instead.
22
+ */
23
+ val url: String,
24
+ val kind: SourceKind = SourceKind.WHEP,
25
+ /** SOFTAP only: the phone's own IPv4 address on the hotspot, to bind the listener to. */
26
+ val bindAddress: String? = null,
27
+ )
28
+
29
+ /** ACS-negotiated output size. One object so a frame cannot observe a torn width/height. */
30
+ data class TargetSize(val width: Int, val height: Int)
31
+
32
+ /**
33
+ * Whether a subscriber already on [currentUrl] can serve [next] as-is instead of
34
+ * being rebuilt.
35
+ *
36
+ * Reuse is the whole point: a rebuild mints a new peer and restarts the wait for
37
+ * the first frame, which is the outage we are trying to shorten. `CONNECTING`
38
+ * counts as reusable, and since `LIVE` means "a frame reached the sink" that now
39
+ * spans the answer → first frame window too. It cannot strand a caller, because
40
+ * every `CONNECTING` is bounded: the offer post fails, or the answer's
41
+ * first-frame deadline expires into `FAILED` and this returns false.
42
+ *
43
+ * SoftAP never reuses: the listener, port and peer belong to one publish attempt,
44
+ * and the ingest URL is an output of binding, not an input that could stay valid.
45
+ *
46
+ * Owned by an object rather than a file-level function so Kotlin incremental
47
+ * compile cannot see two [GlassesMediaSourceKt] facades and report a phantom
48
+ * overload of the same signature.
49
+ */
50
+ object SourceReusePolicy {
51
+ fun canReuse(currentUrl: String?, state: SourceState, next: SourceConfig): Boolean =
52
+ currentUrl == next.url && next.kind == SourceKind.WHEP && state != SourceState.FAILED
53
+ }
54
+
55
+ /**
56
+ * Observes source health transitions. `FAILED` is the one that matters: ICE
57
+ * dropped, the WHEP endpoint went away, or an answered subscription never
58
+ * delivered a frame — and nothing downstream will notice on its own, because
59
+ * ACS keeps the call up with a frozen last frame.
60
+ */
61
+ fun interface SourceStateListener {
62
+ fun onSourceState(state: SourceState, reason: String?)
63
+ }
64
+
65
+ /**
66
+ * Transports whatever the glasses already captured. Capture policy lives
67
+ * outside this interface so a Cloudflare hop can be replaced without touching ACS.
68
+ */
69
+ interface GlassesMediaSource {
70
+ fun start(config: SourceConfig)
71
+ fun restart(config: SourceConfig)
72
+ /** Rebuild the transport for the current config even if it looks healthy. */
73
+ fun forceRestart() {}
74
+ fun stop()
75
+ val state: SourceState
76
+
77
+ /**
78
+ * SOFTAP only: the WHIP URL the glasses must POST their offer to, known only after [start] has
79
+ * bound a listener. Null for every other kind, where the URL is an input rather than an output.
80
+ */
81
+ val ingestUrl: String? get() = null
82
+
83
+ fun setPcmDeliveryEnabled(enabled: Boolean)
84
+ fun setTargetSize(size: TargetSize?) {}
85
+ fun setStateListener(listener: SourceStateListener?) {}
86
+ }
87
+
88
+ /**
89
+ * Builds the transport for one attempt. Takes [SourceConfig] because the kind decides the class:
90
+ * a Cloudflare subscription and a SoftAP listener are different objects, not one object with a
91
+ * mode flag, and the session must not have to know which.
92
+ */
93
+ fun interface GlassesMediaSourceFactory {
94
+ fun create(video: VideoFrameListener, pcm: PcmListener, config: SourceConfig): GlassesMediaSource
95
+ }
96
+
97
+ class GlassesMediaController(
98
+ private val factory: GlassesMediaSourceFactory,
99
+ ) {
100
+ private var source: GlassesMediaSource? = null
101
+ private var stateListener: SourceStateListener? = null
102
+
103
+ val state: SourceState
104
+ get() = source?.state ?: SourceState.IDLE
105
+
106
+ /** SOFTAP only: the URL the glasses must publish to, once a listener has bound. */
107
+ val ingestUrl: String?
108
+ get() = source?.ingestUrl
109
+
110
+ fun attach(video: VideoFrameListener, pcm: PcmListener, config: SourceConfig) {
111
+ source?.stop()
112
+ source = factory.create(video, pcm, config).also {
113
+ it.setStateListener(stateListener)
114
+ it.start(config)
115
+ }
116
+ }
117
+
118
+ fun restart(config: SourceConfig) {
119
+ source?.restart(config)
120
+ }
121
+
122
+ fun forceRestart() {
123
+ source?.forceRestart()
124
+ }
125
+
126
+ fun setStateListener(listener: SourceStateListener?) {
127
+ stateListener = listener
128
+ source?.setStateListener(listener)
129
+ }
130
+
131
+ fun stop() {
132
+ source?.stop()
133
+ source = null
134
+ }
135
+
136
+ fun setPcmDeliveryEnabled(enabled: Boolean) {
137
+ source?.setPcmDeliveryEnabled(enabled)
138
+ }
139
+
140
+ fun setTargetSize(size: TargetSize?) {
141
+ source?.setTargetSize(size)
142
+ }
143
+ }