sample-app-guide

作者: facebook

使用相機串流與拍照功能建構完整的 DAT 應用程式

npx skills add https://github.com/facebook/meta-wearables-dat-ios --skill sample-app-guide

Sample App Guide (iOS)

Build an iOS DAT app with camera streaming and photo capture.

This walkthrough covers app setup, registration, streaming, and capture. Pair it with the CameraAccess sample.

Project setup

  1. Create a new Xcode project (SwiftUI App)
  2. Add the SDK via SPM: https://github.com/facebook/meta-wearables-dat-ios
  3. Add MWDATCore, MWDATCamera, and MWDATMockDevice to your target
  4. Configure Info.plist (see Getting Started)

App architecture

A typical DAT app has these components:

MyDATApp/
├── MyDATApp.swift              # App entry point, SDK init
├── ViewModels/
│   ├── WearablesViewModel.swift    # Registration, device management
│   └── StreamViewModel.swift # Streaming, photo capture
└── Views/
    ├── MainAppView.swift           # Navigation
    ├── RegistrationView.swift      # Registration UI
    └── StreamView.swift            # Video preview, capture button

SDK initialization

import MWDATCore

@main
struct MyDATApp: App {
    init() {
        do {
            try Wearables.configure()
        } catch {
            assertionFailure("Wearables SDK configuration failed: \(error)")
        }
    }

    var body: some Scene {
        WindowGroup {
            MainAppView()
                .onOpenURL { url in
                    guard
                        let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
                        components.queryItems?.contains(where: { $0.name == "metaWearablesAction" }) == true
                    else {
                        return
                    }
                    Task {
                        _ = try? await Wearables.shared.handleUrl(url)
                    }
                }
        }
    }
}

Wearables ViewModel

devicesStream() yields [DeviceIdentifier] (a [String]), not device objects. Resolve each identifier with wearables.deviceForIdentifier(_:) when you need nameOrId(), deviceType(), linkState, or compatibility().

import MWDATCore

@MainActor
class WearablesViewModel: ObservableObject {
    @Published var registrationState: RegistrationState
    @Published var devices: [DeviceIdentifier] = []

    private let wearables = Wearables.shared

    init() {
        registrationState = Wearables.shared.registrationState
    }

    func observeState() {
        Task {
            for await state in wearables.registrationStateStream() {
                self.registrationState = state
            }
        }
        Task {
            for await deviceIds in wearables.devicesStream() {
                self.devices = deviceIds
            }
        }
    }

    func register() async {
        try? await wearables.startRegistration()
    }

    func unregister() async {
        try? await wearables.startUnregistration()
    }
}

Stream ViewModel

import MWDATCamera
import MWDATCore

@MainActor
class StreamViewModel: ObservableObject {
    @Published var currentFrame: UIImage?
    @Published var streamState: String = "Stopped"
    @Published var capturedPhoto: Data?

    private let wearables = Wearables.shared
    private var deviceSession: DeviceSession?
    private var camera: Camera?
    private var stream: Stream?

    func startStream() async {
        let config = StreamConfiguration(
            videoCodec: .raw,
            resolution: .medium,
            frameRate: 24
        )
        let selector = AutoDeviceSelector(wearables: wearables)

        do {
            let deviceSession = try wearables.createSession(deviceSelector: selector)
            try deviceSession.start()
            // Wait for the device session to reach the started state
            for await state in deviceSession.stateStream() {
                if state == .started { break }
            }
            guard let camera = try deviceSession.addCamera(config: config) else { return }
            self.deviceSession = deviceSession
            self.camera = camera
            self.stream = camera.stream
        } catch {
            return
        }

        guard let stream else { return }

        _ = stream.statePublisher.listen { [weak self] state in
            Task { @MainActor in
                self?.streamState = "\(state)"
            }
        }

        _ = stream.videoFramePublisher.listen { [weak self] frame in
            guard let image = frame.makeUIImage() else { return }
            Task { @MainActor in
                self?.currentFrame = image
            }
        }

        _ = stream.photoDataPublisher.listen { [weak self] photoData in
            Task { @MainActor in
                self?.capturedPhoto = photoData.data
            }
        }

        stream.start()
    }

    func stopStream() {
        // Stopping the camera cascades to its stream child
        camera?.stop()
        deviceSession?.stop()
        stream = nil
        camera = nil
        deviceSession = nil
    }

    func capturePhoto() {
        stream?.capturePhoto(format: .jpeg)
    }
}

Behaviors the CameraAccess sample also covers

  • Two explicit steps. "Start Session" creates and starts the DeviceSession; "Preview" adds the Camera and starts the stream. Stopping the preview leaves the session connected so the user can start it again without re-creating the session.
  • Bind the UI to the SDK's own states. Mirror DeviceSessionState and StreamState instead of maintaining a parallel state machine, and show a busy indicator while starting, waitingForDevice, or stopping.
  • Background handling. On UIApplication.didEnterBackgroundNotification, end the session. Suppress the teardown errors that the SDK emits during that intentional stop so the user does not see a false failure on return.
  • Update prompts. Watch device.compatibility() with device.addCompatibilityListener; on .deviceUpdateRequired, offer Wearables.shared.openFirmwareUpdate(). When session start reports DeviceSessionError.datAppOnTheGlassesUpdateRequired, offer Wearables.shared.openDATGlassesAppUpdate().
  • Single error surface. Present error.localizedDescription from session.errorPublisher and stream.errorPublisher in one alert rather than an app-maintained error map.

Testing with MockDeviceKit

Add mock device support to develop without glasses:

import MWDATMockDevice

func setupMockDevice() async {
    let mockDeviceKit = MockDeviceKit.shared
    mockDeviceKit.enable()

    guard let device = try? mockDeviceKit.pairGlasses(model: .rayBanMeta) else { return }
    device.powerOn()
    device.unfold()
    device.don()

    if let videoURL = Bundle.main.url(forResource: "test_video", withExtension: "mp4") {
        let camera = device.services.camera
        camera.setCameraFeed(fileURL: videoURL)
    }
}

func tearDownMockDevice() {
    MockDeviceKit.shared.disable()
}

Allowed dependencies

Your DAT app should only depend on:

  • MWDATCore — always required
  • MWDATCamera — for camera streaming
  • MWDATMockDevice — for testing (can be test-only dependency)
  • MWDATDisplay — for Display experiences

Links

來自 facebook 的更多技能

gc-safe-coding
facebook
完整解釋與理由請參閱 doc/GCSafeCoding.md。
app-review-prep
facebook
準備將 Meta 應用程式提交至 App Review——檢查目前狀態、未完成的要求、已授予的權限及提交紀錄。請在提交應用程式前使用……
api-health
facebook
監控 Meta 應用程式的 API 健康狀態 — 檢查速率限制、呼叫量與 API 棄用情況。用於診斷節流問題、規劃容量,或為 API 版本…做好準備。
debug-webhooks
facebook
排解 Meta 應用程式的 webhook 問題——檢查作用中的訂閱、找出設定錯誤,並傳送測試負載以驗證傳遞。當……時使用。
api-integration
facebook
引導開發者從零開始設定 Meta API 整合 — 找出正確的 API、取得設定指南、驗證需求、…
webhook-setup
facebook
端到端設定 Meta 應用程式的 Webhook — 探索可用主題、訂閱欄位,並透過測試負載驗證。在為… 設定 Webhook 時使用。
test-ui
facebook
使用 iwsdk CLI 針對 poke 範例測試 UI 系統(PanelUI、ScreenSpace)。
flags
facebook
檢查並比較 React 發佈通道中的功能旗標狀態。檢視所有通道(www、www-modern、canary、next、experimental、rn 變體)的旗標,或使用 --diff 比較特定通道。輸出格式包括預設表格檢視、CSV 匯出及清理狀態分組。旗標狀態以符號表示:啟用(✅)、停用(❌)、變體測試(🧪)、僅分析(📊)。常見陷阱:__VARIANT__ 旗標在 www 上會以兩種狀態進行測試;使用 --diff 可找出有意義的差異。