@mentra/glasses-media 3.2.0-dev.227 → 3.2.0-dev.233

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.
@@ -53,7 +53,11 @@ public enum LocalMediaPolicy {
53
53
  candidates += 1
54
54
  return true
55
55
  }
56
- guard candidates > 0 else { throw LocalMediaError("No host ICE candidate on the glasses hotspot") }
56
+ guard candidates > 0 else {
57
+ throw LocalMediaError(answer
58
+ ? "Phone answer has no host ICE candidate on the glasses hotspot"
59
+ : "Glasses offer has no host ICE candidate on the glasses hotspot")
60
+ }
57
61
  return lines.joined(separator: "\r\n") + "\r\n"
58
62
  }
59
63
  }
@@ -7,6 +7,7 @@ import NetworkExtension
7
7
  public final class GlassesHotspotNetwork {
8
8
  private let queue = DispatchQueue(label: "com.mentra.glassesmedia.hotspot")
9
9
  private var ssid: String?
10
+ private var lastHotspotSSID: String?
10
11
  private var localAddress: String?
11
12
  private var gatewayAddress: String?
12
13
  private var generation = 0
@@ -27,6 +28,7 @@ public final class GlassesHotspotNetwork {
27
28
  self.generation += 1
28
29
  let gen = self.generation
29
30
  self.ssid = ssid
31
+ self.lastHotspotSSID = ssid
30
32
  self.gatewayAddress = gateway
31
33
  self.cancelled = false
32
34
  self.applying = true
@@ -104,7 +106,7 @@ public final class GlassesHotspotNetwork {
104
106
  }
105
107
  }
106
108
 
107
- public func awaitInternet(completion: @escaping (Bool, String) -> Void) {
109
+ public func awaitInternet(requireCellular: Bool = true, completion: @escaping (Bool, String) -> Void) {
108
110
  queue.async {
109
111
  let monitor = NWPathMonitor()
110
112
  var finished = false
@@ -116,9 +118,21 @@ public final class GlassesHotspotNetwork {
116
118
  }
117
119
  monitor.pathUpdateHandler = { path in
118
120
  if path.status == .satisfied, path.usesInterfaceType(.cellular) { finish(true, "cellular") }
121
+ // Once the hotspot is released, a return to the user's Wi-Fi is also valid.
122
+ // Do not mistake the departing glasses AP's local-only path for restored internet.
123
+ if !requireCellular, path.status == .satisfied, path.usesInterfaceType(.wifi) {
124
+ NEHotspotNetwork.fetchCurrent { network in
125
+ self.queue.async {
126
+ guard let network, !network.ssid.isEmpty, network.ssid != self.lastHotspotSSID else { return }
127
+ finish(true, "wifi")
128
+ }
129
+ }
130
+ }
119
131
  }
120
132
  monitor.start(queue: self.queue)
121
- self.queue.asyncAfter(deadline: .now() + 15) { finish(false, "Cellular internet did not become the default route") }
133
+ self.queue.asyncAfter(deadline: .now() + 15) {
134
+ finish(false, requireCellular ? "Cellular internet did not become the default route" : "Internet did not return after leaving the glasses hotspot")
135
+ }
122
136
  }
123
137
  }
124
138
 
@@ -136,7 +150,9 @@ public final class GlassesHotspotNetwork {
136
150
  } else if remaining > 0 {
137
151
  self.queue.asyncAfter(deadline: .now() + 0.5) { self.waitForAddress(ssid: ssid, generation: gen, remaining: remaining - 1) }
138
152
  } else {
139
- self.finishJoin(.failure(LocalMediaError("Glasses hotspot has no verified Wi-Fi address")))
153
+ let association = network == nil ? "unavailable" : (network?.ssid == ssid ? "matched" : "different")
154
+ let address = Self.wifiAddress() ?? "none"
155
+ self.finishJoin(.failure(LocalMediaError("Glasses hotspot has no verified Wi-Fi address (SSID=\(association), Wi-Fi IPv4=\(address))")))
140
156
  self.finishLeave()
141
157
  }
142
158
  }
@@ -12,7 +12,8 @@ Pod::Spec.new do |s|
12
12
  s.swift_version = '5.9'
13
13
  s.static_framework = true
14
14
  s.dependency 'ExpoModulesCore'
15
- s.dependency 'WebRTC-SDK', '137.7151.09'
15
+ # Keep in sync with the iOS-only @livekit/react-native-webrtc podspec patch.
16
+ s.dependency 'WebRTC-SDK', '144.7559.15'
16
17
  s.frameworks = 'AVFoundation', 'CoreMedia', 'CoreVideo', 'Network', 'NetworkExtension'
17
18
  s.source_files = '*.{swift,h,m,mm}', 'CoreKit/Sources/GlassesMediaCore/*.swift'
18
19
  s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
@@ -0,0 +1,23 @@
1
+ import WebRTC
2
+
3
+ /// Both legs need real interface enumeration: local Wi-Fi can be absent from the default
4
+ /// internet NWPath while cellular carries the uplink. Each factory's adapter mask and the
5
+ /// receiver's SDP policy still restrict candidates to the appropriate leg.
6
+ enum GlassesPeerFactory {
7
+ /// M144 honors this field trial for factories with custom audio devices; M137 installed
8
+ /// NWPathMonitor unconditionally (including when disableNetworkMonitor was set).
9
+ /// The SDK setting is process-wide. Set it once and never toggle it around factory creation:
10
+ /// receiver, relay, and React Native WebRTC factories may initialize concurrently.
11
+ private static let configure: Void = {
12
+ RTCPeerConnectionFactory.configureFieldTrials("WebRTC-Network-UseNWPathMonitor/Disabled/")
13
+ }()
14
+
15
+ static func make(audioDevice: RTCAudioDevice) -> RTCPeerConnectionFactory {
16
+ _ = configure
17
+ return RTCPeerConnectionFactory(
18
+ encoderFactory: RTCDefaultVideoEncoderFactory(),
19
+ decoderFactory: RTCDefaultVideoDecoderFactory(),
20
+ audioDevice: audioDevice
21
+ )
22
+ }
23
+ }
@@ -67,7 +67,7 @@ public final class LocalWhipIngestSource: NSObject, DecodedGlassesMediaSource {
67
67
  let gen = self.generation
68
68
  self.transition(.connecting, "listening")
69
69
  RTCInitializeSSL()
70
- let factory = RTCPeerConnectionFactory(encoderFactory: RTCDefaultVideoEncoderFactory(), decoderFactory: RTCDefaultVideoDecoderFactory(), audioDevice: ReceiveOnlyAudioDevice())
70
+ let factory = GlassesPeerFactory.make(audioDevice: ReceiveOnlyAudioDevice())
71
71
  let options = RTCPeerConnectionFactoryOptions()
72
72
  options.ignoreCellularNetworkAdapter = true
73
73
  options.ignoreVPNNetworkAdapter = true
@@ -39,7 +39,7 @@ final class PhoneWhipPublisher: NSObject {
39
39
  queue.async {
40
40
  guard !self.stopped else { return }
41
41
  RTCInitializeSSL()
42
- let factory = RTCPeerConnectionFactory(encoderFactory: RTCDefaultVideoEncoderFactory(), decoderFactory: RTCDefaultVideoDecoderFactory(), audioDevice: self.audioDevice)
42
+ let factory = GlassesPeerFactory.make(audioDevice: self.audioDevice)
43
43
  let options = RTCPeerConnectionFactoryOptions()
44
44
  options.ignoreWiFiNetworkAdapter = true
45
45
  options.ignoreVPNNetworkAdapter = true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/glasses-media",
3
- "version": "3.2.0-dev.227",
3
+ "version": "3.2.0-dev.233",
4
4
  "description": "Shared native glasses media receivers and local network transport",
5
5
  "license": "MIT",
6
6
  "author": "Mentra Community",