@mentra/acs-meeting 3.3.0-dev.515 → 3.3.0-dev.516

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 CHANGED
@@ -36,7 +36,7 @@ differences are collected in [iOS](#ios).
36
36
  **The phone is a relay, not a capture device.** It receives what the glasses already
37
37
  encoded, decodes it, and re-publishes it into ACS. Live video is not drawn on the phone,
38
38
  and a live frame does not cross the JavaScript bridge. A photo shutter is the exception:
39
- the phone draws a "Taking a photo" card, then a still, and feeds those pixels through the
39
+ the phone draws a "Taking a photo..." card, then a still, and feeds those pixels through the
40
40
  same sender. See [Photo hold](#photo-hold).
41
41
 
42
42
  ```
@@ -542,7 +542,7 @@ publisher (`start_stream` on Direct link; Call's own stream on Cloudflare), keep
542
542
  meeting, the hotspot, and the ACS sender up, and replaces the tile with three pictures
543
543
  in order:
544
544
 
545
- 1. A full-frame card, "Taking a photo", from the button press until the still is ready.
545
+ 1. A full-frame card, "Taking a photo...", from the button press until the still is ready.
546
546
  2. The still, for a few seconds counted by the caller from `shownAt`.
547
547
  3. Live video again.
548
548
 
@@ -139,7 +139,11 @@ class AcsMeetingSession(
139
139
  private val muted = AtomicBoolean(false)
140
140
  private val frameSender = AcsFrameSender(stats, avSync)
141
141
  private val outgoingHold = OutgoingVideoHold(
142
- send = { planes -> frameSender.sendPlanes(planes) },
142
+ // The self-preview mirrors what Teams receives, so it shows the card and the still too.
143
+ send = { planes ->
144
+ DecodedFrameTap.offer(planes)
145
+ frameSender.sendPlanes(planes)
146
+ },
143
147
  // The negotiated size, not the profile ceiling: ACS renegotiates below the ceiling under load,
144
148
  // and sendPlanes drops any hold frame whose dimensions differ from the negotiated format.
145
149
  width = { frameSender.negotiatedSize()?.width ?: profile.width },
@@ -32,8 +32,8 @@ class OutgoingVideoHold(
32
32
  private val scheduler = Executors.newSingleThreadScheduledExecutor { runnable ->
33
33
  Thread(runnable, "acs-photo-hold").apply { isDaemon = true }
34
34
  }
35
- private var task: ScheduledFuture<*>? = null
36
- private var planes: HeldPlanes? = null
35
+ @Volatile private var task: ScheduledFuture<*>? = null
36
+ @Volatile private var planes: HeldPlanes? = null
37
37
 
38
38
  fun isActive(): Boolean = running.get()
39
39
 
@@ -45,13 +45,17 @@ class OutgoingVideoHold(
45
45
  * module thread cannot stall (and risk an ANR) waiting for ACS to accept the first frame.
46
46
  */
47
47
  fun start(kind: String, imageBytes: ByteArray?, onResult: (Boolean) -> Unit) {
48
- stop()
48
+ // Build the replacement before touching the current hold. Card -> still must not open a gap:
49
+ // while `running` is false the session forwards live glasses frames, and right after a photo
50
+ // those are the reopened camera's unexposed first frames.
49
51
  val w = width()
50
52
  val h = height()
51
53
  val frame = when (kind) {
52
54
  "image" -> imageBytes?.let { decode(it, w, h) } ?: card(w, h)
53
55
  else -> card(w, h)
54
56
  }
57
+ task?.cancel(false)
58
+ task = null
55
59
  planes = frame
56
60
  running.set(true)
57
61
  val settled = AtomicBoolean(false)
@@ -84,7 +88,7 @@ class OutgoingVideoHold(
84
88
  textAlign = Paint.Align.CENTER
85
89
  textSize = (h / 12f).coerceAtLeast(24f)
86
90
  }
87
- canvas.drawText("Taking a photo", w / 2f, h / 2f, paint)
91
+ canvas.drawText("Taking a photo...", w / 2f, h / 2f, paint)
88
92
  return HeldPlanes(bitmap)
89
93
  }
90
94
 
@@ -189,10 +193,26 @@ private class HeldPlanes(bitmap: Bitmap) {
189
193
  }
190
194
  }
191
195
 
192
- fun frame(): I420Planes {
193
- y.position(0)
194
- u.position(0)
195
- v.position(0)
196
- return I420Planes(y, width, u, chromaWidth, v, chromaWidth, width, height, System.nanoTime())
196
+ /**
197
+ * Fresh views per frame: the preview reads a frame on its own worker after the sender has
198
+ * moved on, so frames must not share buffer positions. The pixels never change while held, so
199
+ * retaining one past the callback is safe and needs no bookkeeping.
200
+ */
201
+ fun frame(): I420Planes = I420Planes(
202
+ y.duplicate().apply { position(0) },
203
+ width,
204
+ u.duplicate().apply { position(0) },
205
+ chromaWidth,
206
+ v.duplicate().apply { position(0) },
207
+ chromaWidth,
208
+ width,
209
+ height,
210
+ System.nanoTime(),
211
+ retain = NO_OP,
212
+ release = NO_OP,
213
+ )
214
+
215
+ private companion object {
216
+ val NO_OP: () -> Unit = {}
197
217
  }
198
218
  }
@@ -1574,7 +1574,7 @@ func takingPhotoCardPixelBuffer(width: Int, height: Int) -> CVPixelBuffer? {
1574
1574
  kCTFontAttributeName: font,
1575
1575
  kCTForegroundColorAttributeName: CGColor(gray: 1, alpha: 1),
1576
1576
  ] as CFDictionary
1577
- guard let attributed = CFAttributedStringCreate(nil, "Taking a photo" as CFString, attrs) else { return nil }
1577
+ guard let attributed = CFAttributedStringCreate(nil, "Taking a photo..." as CFString, attrs) else { return nil }
1578
1578
  let line = CTLineCreateWithAttributedString(attributed)
1579
1579
  var ascent: CGFloat = 0
1580
1580
  let lineWidth = CGFloat(CTLineGetTypographicBounds(line, &ascent, nil, nil))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/acs-meeting",
3
- "version": "3.3.0-dev.515",
3
+ "version": "3.3.0-dev.516",
4
4
  "description": "MentraOS native ACS Teams meeting module (WHEP decode → ACS raw media)",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@expo/config-plugins": "~55.0.10",
35
- "@mentra/glasses-media": "3.3.0-dev.515"
35
+ "@mentra/glasses-media": "3.3.0-dev.516"
36
36
  },
37
37
  "devDependencies": {
38
38
  "expo-module-scripts": "^55.0.2",