@limrun/ui 0.9.0-rc.11 → 0.9.0-rc.13
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/dist/components/remote-control.d.ts +28 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +726 -713
- package/package.json +1 -1
- package/src/components/remote-control.tsx +65 -1
package/package.json
CHANGED
|
@@ -140,6 +140,35 @@ interface RemoteControlProps {
|
|
|
140
140
|
* @default 2000
|
|
141
141
|
*/
|
|
142
142
|
axMaxBackoffMs?: number;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Optional outbound camera input.
|
|
146
|
+
*
|
|
147
|
+
* When provided, every video track on this `MediaStream` is forwarded
|
|
148
|
+
* up the same `RTCPeerConnection` that already carries the device
|
|
149
|
+
* screen downstream. On iOS instances, the limulator-side injector
|
|
150
|
+
* splices those frames into apps that use AVFoundation, so the user's
|
|
151
|
+
* browser camera becomes the iOS camera (`AVCaptureDevice.default(for:
|
|
152
|
+
* .video)`).
|
|
153
|
+
*
|
|
154
|
+
* Today this is only honored by iOS instances launched with the
|
|
155
|
+
* camera runtime; Android instances ignore it.
|
|
156
|
+
*
|
|
157
|
+
* Lifecycle expectations:
|
|
158
|
+
* - Pass `null`/`undefined` to skip outbound camera (default).
|
|
159
|
+
* - Pass a `MediaStream` obtained from
|
|
160
|
+
* `navigator.mediaDevices.getUserMedia({ video: true })` (or any
|
|
161
|
+
* other source — screen capture, virtual camera, etc.).
|
|
162
|
+
* - Changing the `MediaStream.id` triggers a connection restart so
|
|
163
|
+
* the new track is included in the SDP offer. The reference itself
|
|
164
|
+
* is allowed to be unstable across renders — we key the reconnect
|
|
165
|
+
* on `id`, not object identity, so memoization isn't required.
|
|
166
|
+
* - Stopping the tracks (e.g. `track.stop()`) on the parent side is
|
|
167
|
+
* sufficient to cut the outbound feed without restarting; the peer
|
|
168
|
+
* connection just stops getting frames on that track. To fully
|
|
169
|
+
* detach the track from the SDP, swap the prop to `null`.
|
|
170
|
+
*/
|
|
171
|
+
cameraStream?: MediaStream | null;
|
|
143
172
|
}
|
|
144
173
|
|
|
145
174
|
interface ScreenshotData {
|
|
@@ -338,6 +367,7 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
338
367
|
onAxStatusChange,
|
|
339
368
|
axPollIntervalMs,
|
|
340
369
|
axMaxBackoffMs,
|
|
370
|
+
cameraStream,
|
|
341
371
|
}: RemoteControlProps,
|
|
342
372
|
ref,
|
|
343
373
|
) => {
|
|
@@ -363,6 +393,13 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
363
393
|
// Mirrored to a ref so stale closures in event handlers see the latest value.
|
|
364
394
|
const autoReconnectRef = useRef(autoReconnect);
|
|
365
395
|
autoReconnectRef.current = autoReconnect;
|
|
396
|
+
// Outbound camera input. Held in a ref so the deferred WebRTC setup
|
|
397
|
+
// path picks up the latest stream even if React re-renders between
|
|
398
|
+
// the connection effect firing and `startAttempt` reaching the
|
|
399
|
+
// `addTrack` step. Reconnects are keyed on `MediaStream.id`, not
|
|
400
|
+
// reference identity (see the connection effect's deps).
|
|
401
|
+
const cameraStreamRef = useRef<MediaStream | null>(cameraStream ?? null);
|
|
402
|
+
cameraStreamRef.current = cameraStream ?? null;
|
|
366
403
|
const firstFrameShownRef = useRef(false);
|
|
367
404
|
const pendingScreenshotResolversRef = useRef<
|
|
368
405
|
Map<string, (value: ScreenshotData | PromiseLike<ScreenshotData>) => void>
|
|
@@ -1644,6 +1681,27 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
1644
1681
|
peerConnection.addTransceiver('audio', { direction: 'recvonly' });
|
|
1645
1682
|
const videoTransceiver = peerConnection.addTransceiver('video', { direction: 'recvonly' });
|
|
1646
1683
|
|
|
1684
|
+
// Outbound camera input — splice the parent-provided MediaStream's
|
|
1685
|
+
// video tracks into the same PC. `addTrack` creates an implicit
|
|
1686
|
+
// sendonly transceiver and lands the track in the SDP offer; the
|
|
1687
|
+
// limulator side picks it up via `peerConnection(_:didAdd:)` and
|
|
1688
|
+
// forwards every frame into the camera-injector IOSurface ring.
|
|
1689
|
+
// We pass the original MediaStream so the answerer can recover
|
|
1690
|
+
// the parent stream id if it cares; today nothing does, but it
|
|
1691
|
+
// costs nothing to be correct.
|
|
1692
|
+
const outboundCamera = cameraStreamRef.current;
|
|
1693
|
+
if (outboundCamera) {
|
|
1694
|
+
for (const track of outboundCamera.getVideoTracks()) {
|
|
1695
|
+
peerConnection.addTrack(track, outboundCamera);
|
|
1696
|
+
}
|
|
1697
|
+
debugLog(
|
|
1698
|
+
'Attached outbound camera tracks:',
|
|
1699
|
+
outboundCamera.getVideoTracks().length,
|
|
1700
|
+
'streamId:',
|
|
1701
|
+
outboundCamera.id,
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1647
1705
|
// As hardware encoder, we use H265 for iOS and VP9 for Android.
|
|
1648
1706
|
// We make sure these two are the first ones in the list.
|
|
1649
1707
|
// If not, the fallback is H264 which is also hardware accelerated, although not as good,
|
|
@@ -2080,7 +2138,13 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
2080
2138
|
stop();
|
|
2081
2139
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
2082
2140
|
};
|
|
2083
|
-
|
|
2141
|
+
// We key reconnects on `MediaStream.id` rather than the prop's
|
|
2142
|
+
// object identity so callers don't have to memoize the stream —
|
|
2143
|
+
// turning the camera on/off swaps the underlying MediaStream and
|
|
2144
|
+
// its id, but rerenders that happen to re-create the parent's
|
|
2145
|
+
// wrapping object don't trigger churn.
|
|
2146
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2147
|
+
}, [url, token, propSessionId, cameraStream?.id]);
|
|
2084
2148
|
|
|
2085
2149
|
// Recompute the inspect-overlay geometry (container-local pixel rect of
|
|
2086
2150
|
// the actually-rendered video content) from the current mapping context.
|