Manually setup AVAudioSession (allow background music playback)

This commit is contained in:
Marc Rousavy 2021-02-23 10:27:31 +01:00
parent bcdf30fa9d
commit 49e5dd67dc
3 changed files with 31 additions and 13 deletions

View File

@ -133,17 +133,25 @@ enum FormatError {
} }
} }
enum SessionError: String { enum SessionError {
case cameraNotReady = "camera-not-ready" case cameraNotReady
case audioSessionSetupFailed(reason: String)
var code: String { var code: String {
return rawValue switch self {
case .cameraNotReady:
return "camera-not-ready"
case .audioSessionSetupFailed:
return "audio-session-setup-failed"
}
} }
var message: String { var message: String {
switch self { switch self {
case .cameraNotReady: case .cameraNotReady:
return "The Camera is not ready yet! Wait for the onInitialized() callback!" return "The Camera is not ready yet! Wait for the onInitialized() callback!"
case let .audioSessionSetupFailed(reason):
return "The audio session failed to setup! \(reason)"
} }
} }
} }

View File

@ -111,12 +111,12 @@ final class CameraView: UIView {
name: .AVCaptureSessionRuntimeError, name: .AVCaptureSessionRuntimeError,
object: captureSession) object: captureSession)
} }
override func removeFromSuperview() { override func removeFromSuperview() {
captureSession.stopRunning() captureSession.stopRunning()
super.removeFromSuperview() super.removeFromSuperview()
} }
@objc @objc
func sessionRuntimeError(notification: Notification) { func sessionRuntimeError(notification: Notification) {
guard let error = notification.userInfo?[AVCaptureSessionErrorKey] as? AVError else { guard let error = notification.userInfo?[AVCaptureSessionErrorKey] as? AVError else {
@ -136,19 +136,19 @@ final class CameraView: UIView {
required init?(coder _: NSCoder) { required init?(coder _: NSCoder) {
fatalError("init(coder:) is not implemented.") fatalError("init(coder:) is not implemented.")
} }
// pragma MARK: Props updating // pragma MARK: Props updating
override final func didSetProps(_ changedProps: [String]!) { override final func didSetProps(_ changedProps: [String]!) {
let shouldReconfigure = changedProps.contains { propsThatRequireReconfiguration.contains($0) } let shouldReconfigure = changedProps.contains { propsThatRequireReconfiguration.contains($0) }
let shouldReconfigureFormat = shouldReconfigure || changedProps.contains("format") let shouldReconfigureFormat = shouldReconfigure || changedProps.contains("format")
let shouldReconfigureDevice = shouldReconfigureFormat || changedProps.contains { propsThatRequireDeviceReconfiguration.contains($0) } let shouldReconfigureDevice = shouldReconfigureFormat || changedProps.contains { propsThatRequireDeviceReconfiguration.contains($0) }
let willReconfigure = shouldReconfigure || shouldReconfigureFormat || shouldReconfigureDevice let willReconfigure = shouldReconfigure || shouldReconfigureFormat || shouldReconfigureDevice
let shouldCheckActive = willReconfigure || changedProps.contains("isActive") || captureSession.isRunning != isActive let shouldCheckActive = willReconfigure || changedProps.contains("isActive") || captureSession.isRunning != isActive
let shouldUpdateTorch = willReconfigure || changedProps.contains("torch") || shouldCheckActive let shouldUpdateTorch = willReconfigure || changedProps.contains("torch") || shouldCheckActive
let shouldUpdateZoom = willReconfigure || changedProps.contains("zoom") || shouldCheckActive let shouldUpdateZoom = willReconfigure || changedProps.contains("zoom") || shouldCheckActive
if shouldReconfigure || shouldCheckActive || shouldUpdateTorch || shouldUpdateZoom || shouldReconfigureFormat || shouldReconfigureDevice { if shouldReconfigure || shouldCheckActive || shouldUpdateTorch || shouldUpdateZoom || shouldReconfigureFormat || shouldReconfigureDevice {
queue.async { queue.async {
if shouldReconfigure { if shouldReconfigure {
@ -160,14 +160,14 @@ final class CameraView: UIView {
if shouldReconfigureDevice { if shouldReconfigureDevice {
self.configureDevice() self.configureDevice()
} }
if shouldUpdateZoom { if shouldUpdateZoom {
let zoomPercent = CGFloat(max(min(self.zoom.doubleValue, 1.0), 0.0)) let zoomPercent = CGFloat(max(min(self.zoom.doubleValue, 1.0), 0.0))
let zoomScaled = (zoomPercent * (self.maxAvailableZoom - self.minAvailableZoom)) + self.minAvailableZoom let zoomScaled = (zoomPercent * (self.maxAvailableZoom - self.minAvailableZoom)) + self.minAvailableZoom
self.zoom(factor: zoomScaled, animated: false) self.zoom(factor: zoomScaled, animated: false)
self.pinchScaleOffset = zoomScaled self.pinchScaleOffset = zoomScaled
} }
if shouldCheckActive && self.captureSession.isRunning != self.isActive { if shouldCheckActive && self.captureSession.isRunning != self.isActive {
if self.isActive { if self.isActive {
self.captureSession.startRunning() self.captureSession.startRunning()
@ -175,7 +175,7 @@ final class CameraView: UIView {
self.captureSession.stopRunning() self.captureSession.stopRunning()
} }
} }
// This is a wack workaround, but if I immediately set torch mode after `startRunning()`, the session isn't quite ready yet and will ignore torch. // This is a wack workaround, but if I immediately set torch mode after `startRunning()`, the session isn't quite ready yet and will ignore torch.
self.queue.asyncAfter(deadline: .now() + 0.1) { self.queue.asyncAfter(deadline: .now() + 0.1) {
if shouldUpdateTorch { if shouldUpdateTorch {
@ -228,6 +228,16 @@ final class CameraView: UIView {
} }
// INPUTS // INPUTS
// Audio Setup
do {
captureSession.automaticallyConfiguresApplicationAudioSession = false
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, options: [.mixWithOthers, .allowBluetooth, .defaultToSpeaker])
try AVAudioSession.sharedInstance().setActive(true)
} catch let error as NSError {
// not critical, so don't return
invokeOnError(.session(.audioSessionSetupFailed(reason: error.description)))
}
// Video Input // Video Input
do { do {
if let videoDeviceInput = self.videoDeviceInput { if let videoDeviceInput = self.videoDeviceInput {

View File

@ -20,7 +20,7 @@ export type FormatError =
| 'format/invalid-low-light-boost' | 'format/invalid-low-light-boost'
| 'format/invalid-format' | 'format/invalid-format'
| 'format/invalid-preset'; | 'format/invalid-preset';
export type SessionError = 'session/camera-not-ready'; export type SessionError = 'session/camera-not-ready' | 'session/audio-session-setup-failed';
export type CaptureError = export type CaptureError =
| 'capture/invalid-photo-format' | 'capture/invalid-photo-format'
| 'capture/encoder-error' | 'capture/encoder-error'