camera: macOS device discovery now includes external + Continuity
Discovery was hard-coded to `.builtInWideAngleCamera` only — that catches the iOS front/back cameras and the macOS FaceTime HD on macOS 14+, but missed USB webcams (`.external` on 14+, `.externalUnknown` before) and iPhone-as-webcam Continuity Camera (`.continuityCamera` on 14+). On Macs whose built-in camera doesn't expose itself as `.builtInWideAngleCamera`, the result was "no camera detected". Device types are now platform-conditional: iOS keeps the wide-angle filter as-is; macOS adds the externals + Continuity (gated via `if #available(macOS 14.0, *)` for the post-14 forms vs the deprecated `.externalUnknown` for older macOS). The `#if os(macOS)` guard is unavoidable — `.external` and friends literally aren't declared as enum cases on iOS.
This commit is contained in:
@@ -8,18 +8,20 @@ import AVFoundation
|
|||||||
/// `TripleCamera` / `DualCamera` is for multi-cam zoom UX we don't
|
/// `TripleCamera` / `DualCamera` is for multi-cam zoom UX we don't
|
||||||
/// build today; if we ever need it, this is where it goes.
|
/// build today; if we ever need it, this is where it goes.
|
||||||
enum CaptureDevice {
|
enum CaptureDevice {
|
||||||
/// Enumerate the front + back wide-angle cameras the system
|
/// Enumerate the cameras the system exposes. On iOS that's the
|
||||||
/// exposes. Order: back first, front second. Stable for the
|
/// front + back wide-angle cameras (in that order, back first).
|
||||||
/// lifetime of the process — iOS doesn't hot-swap cameras.
|
/// On macOS it's the built-in FaceTime HD plus any external /
|
||||||
|
/// Continuity cameras the OS reports.
|
||||||
static func discover() -> [DiscoveredCamera] {
|
static func discover() -> [DiscoveredCamera] {
|
||||||
let session = AVCaptureDevice.DiscoverySession(
|
let session = AVCaptureDevice.DiscoverySession(
|
||||||
deviceTypes: [.builtInWideAngleCamera],
|
deviceTypes: deviceTypes,
|
||||||
mediaType: .video,
|
mediaType: .video,
|
||||||
position: .unspecified
|
position: .unspecified
|
||||||
)
|
)
|
||||||
// Sort so back devices come first; the chat composer opens
|
// Sort so back devices come first; the chat composer opens
|
||||||
// the back camera by default elsewhere, so this matches the
|
// the back camera by default elsewhere, so this matches the
|
||||||
// common "first available" pick.
|
// common "first available" pick. On macOS positions are all
|
||||||
|
// `.unspecified`, so the sort is a no-op there.
|
||||||
return session.devices
|
return session.devices
|
||||||
.sorted { positionRank($0.position) < positionRank($1.position) }
|
.sorted { positionRank($0.position) < positionRank($1.position) }
|
||||||
.map { device in
|
.map { device in
|
||||||
@@ -29,12 +31,34 @@ enum CaptureDevice {
|
|||||||
// iOS doesn't expose sensor orientation directly;
|
// iOS doesn't expose sensor orientation directly;
|
||||||
// 90° matches what `camera_avfoundation` reports
|
// 90° matches what `camera_avfoundation` reports
|
||||||
// and what banlu's `normalizeCameraCapture` math
|
// and what banlu's `normalizeCameraCapture` math
|
||||||
// assumes for iOS sensors.
|
// assumes for iOS sensors. macOS desktop cameras
|
||||||
|
// are already landscape — 0 is the right answer
|
||||||
|
// there but the value is unused on macOS (no
|
||||||
|
// recording rotation, no preview rotation).
|
||||||
sensorOrientation: 90
|
sensorOrientation: 90
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Per-platform set of device types the discovery session asks
|
||||||
|
/// for. iOS only has the built-in cameras; macOS additionally
|
||||||
|
/// surfaces externals + Continuity Camera (iPhone-as-webcam).
|
||||||
|
/// `.external` / `.externalUnknown` / `.continuityCamera` are
|
||||||
|
/// macOS-only enum cases — referencing them on iOS would not
|
||||||
|
/// compile, hence the `#if os(macOS)` block.
|
||||||
|
private static var deviceTypes: [AVCaptureDevice.DeviceType] {
|
||||||
|
var types: [AVCaptureDevice.DeviceType] = [.builtInWideAngleCamera]
|
||||||
|
#if os(macOS)
|
||||||
|
if #available(macOS 14.0, *) {
|
||||||
|
types.append(.external)
|
||||||
|
types.append(.continuityCamera)
|
||||||
|
} else {
|
||||||
|
types.append(.externalUnknown)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return types
|
||||||
|
}
|
||||||
|
|
||||||
/// Apply our default per-device config: continuous autofocus,
|
/// Apply our default per-device config: continuous autofocus,
|
||||||
/// continuous auto-exposure, torch off. Idempotent; safe to call
|
/// continuous auto-exposure, torch off. Idempotent; safe to call
|
||||||
/// repeatedly. The block is wrapped in
|
/// repeatedly. The block is wrapped in
|
||||||
|
|||||||
Reference in New Issue
Block a user