Files
ux/ios/Classes/Camera/CameraSession+iOS.swift
agra 14565ebd7a camera: macOS port via darwin/ split (no shared-file pragmas)
Reuse the AVFoundation Swift files between iOS and macOS without
sprinkling `#if canImport(UIKit)` through them. The split is:

  darwin/Camera/                 platform-shared (AVFoundation only)
    CameraPlugin                 channel + instance map
    CameraInstance               session + outputs + texture
    CameraSession                AVCaptureSession + runtime-error obs
    CaptureDevice                front/back discovery
    PhotoOutput                  AVCapturePhotoOutput
    PreviewSink                  CVPixelBuffer → FlutterTexture
    VideoRecorder                AVAssetWriter
    DeviceOrientation            wire-string enum

  ios/Classes/Camera/            iOS-only impls + extensions
    AudioSession                 AVAudioSession.upgradeForRecording
    DeviceOrientationBridge      UIDevice.orientation listener
    CameraSession+iOS            AVCaptureSessionWasInterrupted obs
                                 + InterruptionReason decode + the
                                 application-audio-session flags
                                 (all iOS-only on AVCaptureSession)
    CameraSettings               UIApplication.openSettingsURLString
    FlutterRegistrar+iOS         method-form of textures/messenger

  macos/Classes/Camera/          macOS no-op stubs (same surface)
    AudioSession                 no-op (no AVAudioSession on macOS)
    DeviceOrientationBridge      no-op (desktops don't rotate)
    CameraSession+macOS          no-op setupPlatform()
    CameraSettings               NSWorkspace → System Settings'
                                 Privacy_Camera pane
    FlutterRegistrar+macOS       property-form of textures/messenger

`CameraSession.init` now calls `setupPlatform()` which each platform
provides via an extension — keeps the iOS-only interruption observer
and the `automaticallyConfiguresApplicationAudioSession` /
`usesApplicationAudioSession` flags (both iOS-only on AVCaptureSession)
out of the shared file. Flash-mode in PhotoOutput uses
`if #available(macOS 11/13, *)` rather than `#if`, since those are
plain version gates not platform splits.

The shared files compile into the iOS pod from `ios/Classes/Camera-shared/`
and into the macOS pod from `macos/Classes/Camera-shared/`, each a
mirror populated by a `prepare_command` in the podspec:

    rm -rf Classes/Camera-shared && cp -R ../darwin/Camera Classes/Camera-shared

Symlinks and `../` source globs both fail — Pathname.glob bails on
symlinks, and CocoaPods silently drops paths that escape the pod
directory. The mirror destinations are .gitignore'd.

macOS UxPlugin now registers CameraPlugin alongside the others.
2026-05-13 18:53:46 +03:00

60 lines
2.3 KiB
Swift

import AVFoundation
import Foundation
/// iOS-only platform setup for [CameraSession]: enables the
/// "application owns the AVAudioSession" flags (so AVCaptureSession
/// doesn't yank our `.playAndRecord` category back), and subscribes
/// to `AVCaptureSessionWasInterrupted` / `InterruptionEnded`
/// notifications (which only fire on iOS `AVCaptureSession.InterruptionReason`
/// isn't even declared on macOS).
extension CameraSession {
func setupPlatform() {
// Telegram + camera_avfoundation both set these keeps
// AVFoundation from yanking our audio session category out
// from under the app. iOS-only properties on AVCaptureSession.
av.automaticallyConfiguresApplicationAudioSession = false
av.usesApplicationAudioSession = true
interruptedObserver = NotificationCenter.default.addObserver(
forName: .AVCaptureSessionWasInterrupted,
object: av,
queue: .main
) { [weak self] note in
let code = note.userInfo?[AVCaptureSessionInterruptionReasonKey]
as? Int ?? 0
self?.onInterrupted?(reasonName(for: code))
}
resumedObserver = NotificationCenter.default.addObserver(
forName: .AVCaptureSessionInterruptionEnded,
object: av,
queue: .main
) { [weak self] _ in
self?.onResumed?()
}
}
}
/// Decode the integer reason that comes with
/// `AVCaptureSessionWasInterrupted`. Used in the event payload sent
/// to Dart.
private func reasonName(for code: Int) -> String {
guard let reason = AVCaptureSession.InterruptionReason(rawValue: code) else {
return "unknown"
}
switch reason {
case .videoDeviceNotAvailableInBackground:
return "videoDeviceNotAvailableInBackground"
case .audioDeviceInUseByAnotherClient:
return "audioDeviceInUseByAnotherClient"
case .videoDeviceInUseByAnotherClient:
return "videoDeviceInUseByAnotherClient"
case .videoDeviceNotAvailableWithMultipleForegroundApps:
return "videoDeviceNotAvailableWithMultipleForegroundApps"
case .videoDeviceNotAvailableDueToSystemPressure:
return "videoDeviceNotAvailableDueToSystemPressure"
@unknown default:
return "unknown"
}
}