Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix AVCaptureSession race condition. #1239

Merged
merged 1 commit into from
Jul 21, 2023
Merged
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
21 changes: 19 additions & 2 deletions Sources/Extension/AVCaptureSession+Extension.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import AVFoundation
import Foundation

// swiftlint:disable unused_setter_value
#if targetEnvironment(macCatalyst)
extension AVCaptureSession {
var isMultitaskingCameraAccessSupported: Bool {
get {
false
}
// swiftlint:disable unused_setter_value
set {
logger.warn("isMultitaskingCameraAccessSupported is unavailabled in Mac Catalyst.")
}
Expand All @@ -17,10 +17,27 @@ extension AVCaptureSession {
get {
false
}
// swiftlint:disable unused_setter_value
set {
logger.warn("isMultitaskingCameraAccessEnabled is unavailabled in Mac Catalyst.")
}
}
}
#else
extension AVCaptureSession {
@available(iOS, obsoleted: 16.0)
var isMultitaskingCameraAccessEnabled: Bool {
get {
false
}
set {
logger.warn("isMultitaskingCameraAccessEnabled is unavailabled in under iOS 16.")
}
}

@available(iOS, obsoleted: 16.0)
var isMultitaskingCameraAccessSupported: Bool {
false
}
}
#endif
// swiftlint:enable unused_setter_value
68 changes: 26 additions & 42 deletions Sources/Media/IOMixer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ protocol IOMixerDelegate: AnyObject {
func mixer(_ mixer: IOMixer, sessionWasInterrupted session: AVCaptureSession, reason: AVCaptureSession.InterruptionReason)
func mixer(_ mixer: IOMixer, sessionInterruptionEnded session: AVCaptureSession, reason: AVCaptureSession.InterruptionReason)
#endif
func mixerSessionWillResume(_ mixer: IOMixer)
}

/// An object that mixies audio and video for streaming.
Expand Down Expand Up @@ -77,6 +76,26 @@ public class IOMixer {
}
}

var inBackgroundMode = false {
didSet {
guard inBackgroundMode != oldValue else {
return
}
if inBackgroundMode {
if !session.isMultitaskingCameraAccessEnabled {
videoIO.multiCamCapture.detachSession(session)
videoIO.capture.detachSession(session)
}
} else {
startCaptureSessionIfNeeded()
if !session.isMultitaskingCameraAccessEnabled {
videoIO.capture.attachSession(session)
videoIO.multiCamCapture.attachSession(session)
}
}
}
}

private var readyState: ReadyState = .standby
private(set) lazy var audioEngine: AVAudioEngine? = {
return IOMixer.audioEngineHolder.retain()
Expand Down Expand Up @@ -218,7 +237,7 @@ public class IOMixer {
if session.canSetSessionPreset(sessionPreset) {
session.sessionPreset = sessionPreset
}
if #available(iOS 16.0, *), isMultitaskingCameraAccessEnabled, session.isMultitaskingCameraAccessSupported {
if isMultitaskingCameraAccessEnabled && session.isMultitaskingCameraAccessSupported {
session.isMultitaskingCameraAccessEnabled = true
}
return session
Expand Down Expand Up @@ -303,10 +322,6 @@ extension IOMixer: Running {
guard !isRunning.value else {
return
}
#if os(iOS)
NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
#endif
addSessionObservers(session)
session.startRunning()
isRunning.mutate { $0 = session.isRunning }
Expand All @@ -318,14 +333,13 @@ extension IOMixer: Running {
}
removeSessionObservers(session)
session.stopRunning()
#if os(iOS)
NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
#endif
isRunning.mutate { $0 = session.isRunning }
}

func startCaptureSession() {
func startCaptureSessionIfNeeded() {
guard isRunning.value && !session.isRunning else {
return
}
session.startRunning()
isRunning.mutate { $0 = session.isRunning }
}
Expand Down Expand Up @@ -387,7 +401,7 @@ extension IOMixer: Running {
}
#if os(iOS)
case .mediaServicesWereReset:
resumeCaptureSessionIfNeeded()
startCaptureSessionIfNeeded()
#endif
default:
break
Expand Down Expand Up @@ -416,37 +430,7 @@ extension IOMixer: Running {
}
delegate?.mixer(self, sessionInterruptionEnded: session, reason: reason)
}

@objc
private func didEnterBackground(_ notification: Notification) {
if #available(iOS 16, *) {
guard !session.isMultitaskingCameraAccessEnabled else {
return
}
}
videoIO.multiCamCapture.detachSession(session)
videoIO.capture.detachSession(session)
}

@objc
private func willEnterForeground(_ notification: Notification) {
resumeCaptureSessionIfNeeded()
if #available(iOS 16, *) {
guard !session.isMultitaskingCameraAccessEnabled else {
return
}
}
videoIO.capture.attachSession(session)
videoIO.multiCamCapture.attachSession(session)
}
#endif

private func resumeCaptureSessionIfNeeded() {
guard isRunning.value && !session.isRunning else {
return
}
delegate?.mixerSessionWillResume(self)
}
}
#else
extension IOMixer: Running {
Expand Down
30 changes: 24 additions & 6 deletions Sources/Net/NetStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ open class NetStream: NSObject {
}
}

/// Creates a NetStream object.
override public init() {
super.init()
#if os(iOS)
NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
#endif
}

#if os(iOS) || os(macOS)
/// Attaches the primary camera object.
/// - Warning: This method can't use appendSampleBuffer at the same time.
Expand Down Expand Up @@ -284,6 +293,21 @@ open class NetStream: NSObject {
public func stopRecording() {
mixer.recorder.stopRunning()
}

#if os(iOS)
@objc
private func didEnterBackground(_ notification: Notification) {
// Require main thread. Otherwise the microphone cannot be used in the background.
mixer.inBackgroundMode = true
}

@objc
private func willEnterForeground(_ notification: Notification) {
lockQueue.async {
self.mixer.inBackgroundMode = false
}
}
#endif
}

extension NetStream: IOMixerDelegate {
Expand All @@ -296,12 +320,6 @@ extension NetStream: IOMixerDelegate {
delegate?.stream(self, didOutput: audio, presentationTimeStamp: presentationTimeStamp)
}

func mixerSessionWillResume(_ mixer: IOMixer) {
lockQueue.async {
mixer.startCaptureSession()
}
}

#if os(iOS)
func mixer(_ mixer: IOMixer, sessionWasInterrupted session: AVCaptureSession, reason: AVCaptureSession.InterruptionReason) {
delegate?.stream(self, sessionWasInterrupted: session, reason: reason)
Expand Down