From 242c8ec75190eed6efeb66a7dc43c1982f5349ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:49:38 +0200 Subject: [PATCH] feat(avlivebody): consume hands and face frames Route FrameTag.hands (=4) and FrameTag.face (=5) in USBSkeletonConsumer. Decode each payload and publish on the main queue, mirroring the existing .skeleton pattern. Add @Published hands/face properties so SwiftUI views can observe them directly. --- .../AVLiveBody/usb/USBSkeletonConsumer.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/avlivebody-mac/Sources/AVLiveBody/usb/USBSkeletonConsumer.swift b/avlivebody-mac/Sources/AVLiveBody/usb/USBSkeletonConsumer.swift index 601740a..7515831 100644 --- a/avlivebody-mac/Sources/AVLiveBody/usb/USBSkeletonConsumer.swift +++ b/avlivebody-mac/Sources/AVLiveBody/usb/USBSkeletonConsumer.swift @@ -11,6 +11,10 @@ final class USBSkeletonConsumer: ObservableObject { /// 91-joint skeleton payloads keyed by pid. @Published var skeletons: [Int: SkeletonPayload] = [:] @Published var connected = false + /// Latest Vision hand landmarks from the iPhone (tag=4). + @Published var hands: HandsPayload? + /// Latest Vision face landmarks from the iPhone (tag=5). + @Published var face: FacePayload? /// Called on the main queue for every decoded camera frame. var onVideoFrame: ((CVPixelBuffer) -> Void)? @@ -94,6 +98,18 @@ final class USBSkeletonConsumer: ObservableObject { guard let payload = VideoPayload(decoding: frame.payload) else { return } videoDecoder.decode(payload) + case .hands: + guard let payload = HandsPayload(decoding: frame.payload) + else { return } + DispatchQueue.main.async { [weak self] in + self?.hands = payload + } + case .face: + guard let payload = FacePayload(decoding: frame.payload) + else { return } + DispatchQueue.main.async { [weak self] in + self?.face = payload + } case .meta: break }