Skip to content

Commit

Permalink
Merge pull request #1656 from shogo4405/feature/delegate-error
Browse files Browse the repository at this point in the history
Migration CaptureSessionDelegate to 2.0.x.
  • Loading branch information
shogo4405 authored Jan 2, 2025
2 parents 23eb0dd + fc99fee commit 8290464
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 23 deletions.
52 changes: 29 additions & 23 deletions HaishinKit/Sources/Mixer/CaptureSession.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
import AVFoundation

protocol CaptureSessionDelegate: AnyObject {
@available(tvOS 17.0, *)
func captureSession(_ session: CaptureSession, sessionRuntimeError session: AVCaptureSession, error: AVError)
#if os(iOS) || os(tvOS) || os(visionOS)
@available(tvOS 17.0, *)
func captureSession(_ session: CaptureSession, sessionWasInterrupted session: AVCaptureSession, reason: AVCaptureSession.InterruptionReason?)
@available(tvOS 17.0, *)
func captureSession(_ session: CaptureSession, sessionInterruptionEnded session: AVCaptureSession)
#endif
}

final class CaptureSession {
#if os(iOS) || os(tvOS)
static var isMultiCamSupported: Bool {
Expand All @@ -35,11 +24,11 @@ final class CaptureSession {
}
}
}

@available(tvOS 17.0, *)
var isMultitaskingCameraAccessEnabled: Bool {
return session.isMultitaskingCameraAccessEnabled
}

#elseif os(macOS)
let isMultiCamSessionEnabled = true
let isMultitaskingCameraAccessEnabled = true
Expand All @@ -48,9 +37,20 @@ final class CaptureSession {
let isMultitaskingCameraAccessEnabled = false
#endif

weak var delegate: (any CaptureSessionDelegate)?
private(set) var isRunning = false

var isInturreped: AsyncStream<Bool> {
AsyncStream { continuation in
isInturrepedContinutation = continuation
}
}

var runtimeError: AsyncStream<AVError> {
AsyncStream { continutation in
runtimeErrorContinutation = continutation
}
}

#if os(tvOS)
private var _session: Any?
/// The capture session instance.
Expand Down Expand Up @@ -108,6 +108,18 @@ final class CaptureSession {
#endif
}

private var isInturrepedContinutation: AsyncStream<Bool>.Continuation? {
didSet {
oldValue?.finish()
}
}

private var runtimeErrorContinutation: AsyncStream<AVError>.Continuation? {
didSet {
oldValue?.finish()
}
}

deinit {
guard #available(tvOS 17.0, *) else {
return
Expand Down Expand Up @@ -225,6 +237,7 @@ final class CaptureSession {
NotificationCenter.default.removeObserver(self, name: .AVCaptureSessionInterruptionEnded, object: session)
#endif
NotificationCenter.default.removeObserver(self, name: .AVCaptureSessionRuntimeError, object: session)
runtimeErrorContinutation = nil
}

@available(tvOS 17.0, *)
Expand All @@ -235,8 +248,7 @@ final class CaptureSession {
let errorValue = notification.userInfo?[AVCaptureSessionErrorKey] as? NSError else {
return
}
let error = AVError(_nsError: errorValue)
delegate?.captureSession(self, sessionRuntimeError: session, error: error)
runtimeErrorContinutation?.yield(AVError(_nsError: errorValue))
}

#if os(iOS) || os(tvOS) || os(visionOS)
Expand All @@ -246,19 +258,13 @@ final class CaptureSession {
guard let session = notification.object as? AVCaptureSession else {
return
}
guard let userInfoValue = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as AnyObject?,
let reasonIntegerValue = userInfoValue.integerValue,
let reason = AVCaptureSession.InterruptionReason(rawValue: reasonIntegerValue) else {
delegate?.captureSession(self, sessionWasInterrupted: session, reason: nil)
return
}
delegate?.captureSession(self, sessionWasInterrupted: session, reason: reason)
isInturrepedContinutation?.yield(true)
}

@available(tvOS 17.0, *)
@objc
private func sessionInterruptionEnded(_ notification: Notification) {
delegate?.captureSession(self, sessionInterruptionEnded: session)
isInturrepedContinutation?.yield(false)
}
#endif
}
Expand Down
47 changes: 47 additions & 0 deletions HaishinKit/Sources/Mixer/MediaMixer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ public final actor MediaMixer {
session.isRunning
}

/// The interrupts events is occured or not.
public var isInterputted: AsyncStream<Bool> {
session.isInturreped
}

#if os(iOS) || os(macOS)
/// The video orientation for stream.
public var videoOrientation: AVCaptureVideoOrientation {
Expand Down Expand Up @@ -383,6 +388,41 @@ public final actor MediaMixer {
}
}
#endif

@available(tvOS 17.0, *)
private func sessionRuntimeErrorOccured(_ error: AVError) async {
switch error.code {
#if os(iOS) || os(tvOS) || os(visionOS)
case .mediaServicesWereReset:
session.startRunningIfNeeded()
#endif
#if os(iOS) || os(tvOS) || os(macOS)
case .unsupportedDeviceActiveFormat:
guard let device = error.device, let format = device.videoFormat(
width: session.sessionPreset.width ?? Int32.max,
height: session.sessionPreset.height ?? Int32.max,
frameRate: videoIO.frameRate,
isMultiCamSupported: session.isMultiCamSessionEnabled
), device.activeFormat != format else {
return
}
do {
try device.lockForConfiguration()
device.activeFormat = format
if format.isFrameRateSupported(videoIO.frameRate) {
device.activeVideoMinFrameDuration = CMTime(value: 100, timescale: CMTimeScale(100 * videoIO.frameRate))
device.activeVideoMaxFrameDuration = CMTime(value: 100, timescale: CMTimeScale(100 * videoIO.frameRate))
}
device.unlockForConfiguration()
session.startRunningIfNeeded()
} catch {
logger.warn(error)
}
#endif
default:
break
}
}
}

extension MediaMixer: AsyncRunner {
Expand Down Expand Up @@ -421,6 +461,13 @@ extension MediaMixer: AsyncRunner {
}
}
}
if #available(tvOS 17.0, *) {
Task {
for await runtimeError in session.runtimeError {
await sessionRuntimeErrorOccured(runtimeError)
}
}
}
setVideoRenderingMode(videoMixerSettings.mode)
if useManualCapture {
session.startRunning()
Expand Down

0 comments on commit 8290464

Please sign in to comment.