Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CaptureExample/CaptureExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -309,13 +309,14 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.luni.CaptureExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
Expand All @@ -342,13 +343,14 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.luni.CaptureExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
Expand Down
18 changes: 7 additions & 11 deletions CaptureExample/CaptureExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct ContentView: View {
@State private var path = NavigationPath()
@State private var isPaused: Bool = false

@StateObject private var camera: Camera = .default
@StateObject private var camera: Camera = .userPreferredCamera
@State private var tab: Tab = .photo
@Environment(\.takePicture) var takePicture

Expand All @@ -38,12 +38,6 @@ struct ContentView: View {
// Environment values override:
// - recordingAudioSettings
// - recordingVideoSettings
.environment(\.recordingVideoSettings, VideoSettings(
codec: .h264,
width: 200,
height: 200,
scalingMode: .resizeAspectFill
))
.overlay(alignment: .topTrailing) {
cameraDevicePicker
}
Expand All @@ -55,10 +49,12 @@ struct ContentView: View {
.sheet(item: $capturedImage) { image in
#if os(iOS)
Image(uiImage: image)
.resizable()
.scaledToFit()
.ignoresSafeArea()
#elseif os(macOS)
Image(nsImage: image)
.resizable()
.scaledToFit()
.ignoresSafeArea()
#endif
Expand Down Expand Up @@ -87,10 +83,10 @@ struct ContentView: View {
}

@ViewBuilder var cameraDevicePicker: some View {
Picker(selection: $camera.deviceId) {
Picker(selection: $camera.captureDevice) {
ForEach(camera.devices, id: \.uniqueID) { device in
Text(device.localizedName)
.tag(device.uniqueID)
.tag(device)
}
} label: { EmptyView() }
}
Expand Down Expand Up @@ -130,13 +126,13 @@ struct ContentView: View {
.preferredColorScheme(.dark)
}

extension URL: Identifiable {
extension URL: @retroactive Identifiable {
public var id: String {
absoluteString
}
}

extension PlatformImage: Identifiable {
extension PlatformImage: @retroactive Identifiable {
public var id: ObjectIdentifier {
ObjectIdentifier(self)
}
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.9
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by Quentin Fasquel on 17/12/2023.
//

import AVFoundation
@preconcurrency import AVFoundation
import OSLog

extension AVCaptureDeviceInput {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by Quentin Fasquel on 16/12/2023.
//

import AVFoundation
@preconcurrency import AVFoundation

extension AVCapturePhotoOutput {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//

#if canImport(UIKit)
import AVFoundation
@preconcurrency import AVFoundation
import UIKit.UIDevice

extension AVCaptureVideoOrientation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@
//

#if os(macOS)
import AVFoundation
@preconcurrency import AVFoundation
import AppKit

extension NSImage {
public convenience init?(photo: AVCapturePhoto) {
if let cgImage = photo.cgImageRepresentation() {
let imageSize = NSSize(width: cgImage.width, height: cgImage.height)
self.init(cgImage: cgImage, size: imageSize)
return
}

// Get the pixel buffer from the AVCapturePhoto
guard let pixelBuffer = photo.pixelBuffer else {
return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by Quentin Fasquel on 07/12/2023.
//

@preconcurrency import AVFoundation
#if canImport(UIKit)
import UIKit.UIImage

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//

#if canImport(UIKit)
import AVFoundation
@preconcurrency import AVFoundation
import UIKit

extension UIImage.Orientation {
Expand Down
44 changes: 13 additions & 31 deletions Sources/Capture/Internal/AVCaptureVideoFileOutput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,16 @@
// Created by Quentin Fasquel on 17/12/2023.
//

import AVFoundation

protocol CaptureRecording: NSObject {
func stopRecording()
}

extension AVCaptureMovieFileOutput: CaptureRecording {
}
@preconcurrency import AVFoundation

///
/// A replacement for `AVCaptureMovieFileOutput`
///
final class AVCaptureVideoFileOutput: NSObject, CaptureRecording {
final class AVCaptureVideoFileOutput: NSObject, @unchecked Sendable {

private let outputQueue = DispatchQueue(label: "\(bundleIdentifier).CaptureVideoFileOutput")
fileprivate let audioDataOutput = AVCaptureAudioDataOutput()
fileprivate let videoDataOutput = AVCaptureVideoDataOutput()
let audioDataOutput = AVCaptureAudioDataOutput()
let videoDataOutput = AVCaptureVideoDataOutput()

private var assetWriter: AVAssetWriter?
private var audioWriterInput: AVAssetWriterInput?
Expand All @@ -37,7 +30,9 @@ final class AVCaptureVideoFileOutput: NSObject, CaptureRecording {
isRecording && !isStoppingRecording
}

override init() {
init(audioSettings: AudioSettings = .default, videoSettings: VideoSettings = .default) {
self.audioSettings = audioSettings
self.videoSettings = videoSettings
super.init()
audioDataOutput.setSampleBufferDelegate(self, queue: outputQueue)
videoDataOutput.setSampleBufferDelegate(self, queue: outputQueue)
Expand All @@ -49,23 +44,10 @@ final class AVCaptureVideoFileOutput: NSObject, CaptureRecording {

// MARK: - Recording

public private(set) var audioSettings = AudioSettings(
formatID: kAudioFormatMPEG4AAC,
sampleRate: 44100,
numberOfChannels: 2,
audioFileType: kAudioFileMPEG4Type,
// encoderAudioQuality: .high,
encoderBitRate: .bitRate(128000)
)

public private(set) var videoSettings = VideoSettings(
codec: .h264,
width: 0,
height: 0,
scalingMode: .resizeAspectFill
)

public func configureOutput(audioSettings: AudioSettings? = nil, videoSettings: VideoSettings) {
private(set) var audioSettings: AudioSettings
private(set) var videoSettings: VideoSettings

func configureOutput(audioSettings: AudioSettings? = nil, videoSettings: VideoSettings) {
if let audioSettings {
self.audioSettings = audioSettings
}
Expand All @@ -77,7 +59,7 @@ final class AVCaptureVideoFileOutput: NSObject, CaptureRecording {
return
}

let outputFileType = fileType(for: videoSettings.codec) ?? .mov
let outputFileType = Capture.fileType(for: videoSettings.codec) ?? .mov
assetWriter = try? AVAssetWriter(outputURL: outputURL, fileType: outputFileType)
delegate = recordingDelegate

Expand Down Expand Up @@ -115,7 +97,7 @@ final class AVCaptureVideoFileOutput: NSObject, CaptureRecording {
}

isRecording = true

DispatchQueue.main.async { [self] in
delegate?.videoFileOutput(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by Quentin Fasquel on 17/12/2023.
//

import Foundation
@preconcurrency import AVFoundation

protocol AVCaptureVideoFileOutputRecordingDelegate: AnyObject {

Expand Down
6 changes: 5 additions & 1 deletion Sources/Capture/Internal/AVFileType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
// Created by Quentin Fasquel on 02/01/2024.
//

import AVFoundation
@preconcurrency import AVFoundation

extension AVFileType {
var utType: UTType { UTType(rawValue)! }
}

func fileType(for videoCodec: AVVideoCodecType) -> AVFileType? {
switch videoCodec {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,35 @@
// Created by Quentin Fasquel on 17/12/2023.
//

@preconcurrency import AVFoundation
import Foundation

extension Camera {
func takePicture(outputSize: CGSize) async -> PlatformImage? {

func takePicture() async -> PlatformImage? {
do {
let capturePhoto = try await takePicture()
let image = PlatformImage(photo: capturePhoto)
#if os(iOS)
return image?.fixOrientation().scaleToFill(in: outputSize)
#elseif os(macOS)
return image?.scaleToFill(in: outputSize)
#endif
let capturePhoto = try await takePicture() as AVCapturePhoto
return PlatformImage(photo: capturePhoto)
} catch {
return nil
}
}

func takePicture(outputSize: CGSize) async -> PlatformImage? {
guard let image = await takePicture() else {
return nil
}

#if os(iOS)
return image.fixOrientation().scaleToFill(in: outputSize)
#elseif os(macOS)
return image.scaleToFill(in: outputSize)
#endif
}
}

extension Camera {

func stopRecording() async -> URL? {
do {
return try await stopRecording() as URL
Expand Down
6 changes: 3 additions & 3 deletions Sources/Capture/Internal/CameraConfigurationWarning.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

enum CameraConfigurationWarning {
enum CaptureConfigurationWarning {
case audioDeviceNotFound
case cameraDeviceNotSet
case cannotAddAudioInput
Expand All @@ -18,9 +18,9 @@ enum CameraConfigurationWarning {
case cannotSetSessionPreset
}

extension Camera {
extension CaptureService {

func log(_ warning: CameraConfigurationWarning) {
nonisolated func log(_ warning: CaptureConfigurationWarning) {
switch warning {
case .audioDeviceNotFound:
logger.warning("Audio device not found")
Expand Down
Loading