Skip to content

Architecture

OpenZCine is a shared Swift business/protocol core with native platform shells. Policy lives in Swift; sockets, permissions, lifecycle, rendering, storage and UI live in the shells.

Layer Path Role
Shared core Sources/OpenZCineCore/ PTP and PTP-IP, camera state, Nikon property codes, discovery, pairing, layout and monitor policy. Foundation only, portable.
Core tests Tests/OpenZCineCoreTests/ Swift Testing suite for the portable core.
iOS app ios/Runner/ SwiftUI shell, Bonjour discovery, Network.framework and ImageCaptureCore transports, live-view rendering.
Watch companion ios/OpenZCineWatch/ watchOS monitor and record remote. The phone stays the camera link.
Android facade Sources/OpenZCineAndroidFacade/ The app’s own JNI exports; links OpenCine OpenCineNikonAndroid (Swift-owned Android sessions, PTP-IP and USB transaction serialization, JNI boundary).
Android app Apps/Android/app/ Jetpack Compose shell, lifecycle, rendering, storage and platform adapters.
Android session OpenCine Android/nikon Typed Kotlin interface between the app and the camera session, and its implementation.
Wear OS Apps/Android/wear/, Apps/Android/wear-relay/ Wear UI and the phone-mediated relay.
Bug relay services/bug-relay/ Cloudflare Worker behind the earlier anonymous Report a Problem path; both apps now report through Sentry.
Prototype reference/flutter-prototype/ Archived Flutter reference. Not built in CI.

Sources/OpenZCineCore must never import SwiftUI, UIKit, AppKit, Android or Compose. The interop boundary between core and shell stays as narrow as possible.

Wi-Fi (PTP-IP) and USB-C share one session layer behind the portable CameraTransport protocol. It is transaction-level: one call runs a whole PTP transaction (command, optional data, response), and a separate call reads the next camera event. That level is set by iOS, which exposes USB PTP only through ImageCaptureCore, one full transaction per call.

Transport iOS Android
Wi-Fi PTPIPTransport (OpenCineNikon): two TCP sockets, the Init Command / Init Event handshake, PTP-IP framing Socket adapter in the app; framing and session in the Swift facade
USB-C USBCameraTransport (OpenCineNikon): ImageCaptureCore discovery and authorization, PTP USB containers USB Host adapter hands raw bulk and interrupt bytes to the Swift facade

Separately, each saved camera keeps the path the operator declared at setup (camera access point, router, phone hotspot, USB-C). Behavior routes through that declared path instead of being inferred from an SSID or subnet, so a router connect can never raise a camera Wi-Fi join prompt. Design record: docs/transport-architecture.md.

The camera remembers a client by the GUID it sends in the Init Command request. A random GUID on each launch would force re-pairing every time, so the app keeps a stable identity per install and reuses it on every connect.

The live monitor is one view tree for landscape and portrait on iOS, and one Compose tree on Android. Both lay out from the shared OpenCine Field Monitor geometry (FieldMonitorLayout on iOS, MonitorLayoutPolicy.fieldMonitor on Android), as Pocket and Insta do. Rotation is a geometry change under stable view identity, not a tree swap.

Operator-visible behavior should match across iOS and Android. The full seam table and decision records: docs/ARCHITECTURE.md. Screen-level flows: docs/flows/.

Wire format is in Protocol.