feat(av-live-body): wire USB consumer to renderer

ContentView owns and starts a USBSkeletonConsumer, threaded
through BodyView into Skeleton3DRenderer.attach. The renderer
subscribes its $bodies into lastArkit, so the iPhone's USB
skeleton drives the on-screen 91-joint markers.
This commit is contained in:
L'électron rare
2026-05-18 17:51:50 +02:00
parent 8099b79ec8
commit 73f8f75771
3 changed files with 19 additions and 3 deletions
@@ -67,17 +67,20 @@ struct ContentView: View {
@StateObject private var poseListener = PoseOSCListener()
@StateObject private var arkitListener = ArkitOSCListener()
@StateObject private var dataFeeds = DataFeedsOSCListener()
@StateObject private var usbConsumer = USBSkeletonConsumer()
var body: some View {
ZStack(alignment: .topLeading) {
BodyView(renderer: renderer, settings: settings,
poseListener: poseListener,
arkitListener: arkitListener)
arkitListener: arkitListener,
usbConsumer: usbConsumer)
.onAppear {
renderer.startOSCServer()
poseListener.start()
arkitListener.start()
dataFeeds.start()
usbConsumer.start()
}
.onReceive(NotificationCenter.default.publisher(
for: .toggleSettings)) { _ in
@@ -13,6 +13,7 @@ struct BodyView: NSViewRepresentable {
@ObservedObject var settings: RenderSettings
@ObservedObject var poseListener: PoseOSCListener
@ObservedObject var arkitListener: ArkitOSCListener
@ObservedObject var usbConsumer: USBSkeletonConsumer
func makeNSView(context: Context) -> NSView {
let container = NSView(frame: .zero)
@@ -109,7 +110,8 @@ struct BodyView: NSViewRepresentable {
arView.scene.addAnchor(skel3dAnchor)
let skel3d = Skeleton3DRenderer()
skel3d.attach(to: skel3dAnchor, listener: poseListener,
arkitListener: arkitListener)
arkitListener: arkitListener,
usbConsumer: usbConsumer)
context.coordinator.skel3dAnchor = skel3dAnchor
context.coordinator.skel3d = skel3d
context.coordinator.keyLight = key
@@ -87,6 +87,7 @@ final class Skeleton3DRenderer: ObservableObject {
private var faceSub: AnyCancellable?
private var handSub: AnyCancellable?
private var arkitSub: AnyCancellable?
private var usbSub: AnyCancellable?
private var lastArkit: [Int: ArkitOSCListener.ArkitBodyFrame] = [:]
private var lastUpdateAt: TimeInterval = 0
/// Optional per-pid offset to align the skeleton with another
@@ -112,7 +113,8 @@ final class Skeleton3DRenderer: ObservableObject {
/// Attach to a scene by giving it an AnchorEntity that owns all
/// skeleton entities, and start observing the listener.
func attach(to anchor: Entity, listener: PoseOSCListener,
arkitListener: ArkitOSCListener? = nil) {
arkitListener: ArkitOSCListener? = nil,
usbConsumer: USBSkeletonConsumer? = nil) {
rootAnchor = anchor
poseSub = listener.$body3d
.receive(on: DispatchQueue.main)
@@ -136,6 +138,13 @@ final class Skeleton3DRenderer: ObservableObject {
Task { @MainActor in self?.lastArkit = frames }
}
}
if let usb = usbConsumer {
usbSub = usb.$bodies
.receive(on: DispatchQueue.main)
.sink { [weak self] frames in
Task { @MainActor in self?.lastArkit = frames }
}
}
}
func detach() {
@@ -147,6 +156,8 @@ final class Skeleton3DRenderer: ObservableObject {
handSub = nil
arkitSub?.cancel()
arkitSub = nil
usbSub?.cancel()
usbSub = nil
for (_, p) in persons { p.root.removeFromParent() }
persons.removeAll()
lastSeenAt.removeAll()