Skip to content

Commit

Permalink
Atomic action execution
Browse files Browse the repository at this point in the history
  • Loading branch information
ipavlidakis committed Jan 31, 2025
1 parent b392998 commit 2b2d1f0
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ extension AVAudioSessionRouteDescription {

let outputNames = outputs.map(\.portName).joined(separator: ",")
let outputTypes = outputs.map(\.portType.rawValue).joined(separator: ",")
return "AudioSessionRoute isExternal:\(isExternal) input:[name:\(inputNames) types:\(inputTypes)] output:[name:\(outputNames) types:\(outputTypes)]."
return "AudioSessionRoute(isExternal:\(isExternal) input:[name:\(inputNames) types:\(inputTypes)] output:[name:\(outputNames) types:\(outputTypes)])"
}

/// A set of port types that represent external audio outputs, such as
Expand Down
166 changes: 146 additions & 20 deletions Sources/StreamVideo/Utils/AudioSession/StreamAudioSessionAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,35 +176,35 @@ final class StreamAudioSessionAdapter: NSObject, RTCAudioSessionDelegate, @unche
? AVAudioSession.PortOverride.speaker
: .none

let overrideInputAudioPort: AVAudioSession.Port? = audioSession.category == AVAudioSession.Category.playAndRecord.rawValue && requiredCategory == .playback && !audioSession.currentRoute.isExternal
? .builtInMic
: nil

self.perform(tag: .overrideInputAudioPort(overrideInputAudioPort))

if requiredCategory.rawValue != audioSession.category, requiredCategory.supports(mode: requiredMode) {
try audioSession.setCategory(
requiredCategory,
mode: requiredMode,
with: requiredOptions
self.perform(
tag: .setCategory(
requiredCategory,
requiredMode,
requiredOptions
)
)

if audioSession.category == AVAudioSession.Category.playAndRecord.rawValue, requiredCategory == .playback {
try audioSession.audioSource.overrideInputAudioPort(.builtInMic)
}
try? audioSession.setActive(true)
self.perform(tag: .setActive(true))
} else {
if requiredMode != audioSession.audioSource.mode, requiredCategory.supports(mode: requiredMode) {
try audioSession.setMode(requiredMode.rawValue)
self.perform(tag: .setMode(requiredMode))
}

if requiredOptions != audioSession.audioSource.categoryOptions {
try audioSession.setCategory(
requiredCategory,
mode: requiredMode,
with: categoryOptions
)
self.perform(tag: .setCategoryOptions(requiredOptions))
}
}

try audioSession.overrideOutputAudioPort(overrideOutputAudioPort)
self.perform(tag: .overrideOutputAudioPort(overrideOutputAudioPort))

log.debug(
"AudioSession updated category:\(requiredCategory.rawValue) mode:\(requiredMode.rawValue) options:\(requiredOptions) overrideOutputAudioPort:\(overrideOutputAudioPort == .speaker ? ".speaker" : ".none")",
"AudioSession updated category:\(requiredCategory.rawValue) mode:\(requiredMode.rawValue) options:\(requiredOptions) overrideOutputAudioPort:\(overrideOutputAudioPort == .speaker ? ".speaker" : ".none") overrideInputAudioPort:\(overrideInputAudioPort == .builtInMic ? ".builtInMic" : "none")",
subsystems: .audioSession,
functionName: function,
fileName: file,
Expand Down Expand Up @@ -267,6 +267,79 @@ final class StreamAudioSessionAdapter: NSObject, RTCAudioSessionDelegate, @unche
line: line
) { try $0.setActive(isActive) }
}

private enum Tag: CustomStringConvertible {
case overrideOutputAudioPort(AVAudioSession.PortOverride)
case overrideInputAudioPort(AVAudioSession.Port?)
case setCategory(AVAudioSession.Category, AVAudioSession.Mode, AVAudioSession.CategoryOptions)
case setActive(Bool)
case setMode(AVAudioSession.Mode)
case setCategoryOptions(AVAudioSession.CategoryOptions)

var description: String {
switch self {
case .overrideOutputAudioPort(let portOverride):
return ".overrideOutputAudioPort(\(portOverride))"
case .overrideInputAudioPort(let port):
return ".overrideInputAudioPort(\(port.map { $0.description } ?? "nil"))"
case .setCategory(let category, let mode, let categoryOptions):
return ".setCategory(category:\(category), mode:\(mode), options:\(categoryOptions))"
case .setActive(let value):
return ".setActive(\(value))"
case .setMode(let mode):
return ".setMode(\(mode))"
case .setCategoryOptions(let categoryOptions):
return ".setCategoryOptions(\(categoryOptions))"
}
}

func perform(on audioSession: AudioSessionProtocol) throws {
switch self {
case .overrideOutputAudioPort(let portOverride):
return try audioSession.overrideOutputAudioPort(portOverride)
case .overrideInputAudioPort(let port):
return try audioSession.audioSource.overrideInputAudioPort(port)
case .setCategory(let category, let mode, let categoryOptions):
return try audioSession.setCategory(
category,
mode: mode,
with: categoryOptions
)
case .setActive(let value):
return try audioSession.setActive(value)
case .setMode(let mode):
return try audioSession.setMode(mode.rawValue)
case .setCategoryOptions(let categoryOptions):
return try audioSession.setCategory(
.init(rawValue: audioSession.category),
mode: audioSession.audioSource.mode,
with: categoryOptions
)
}
}
}

private func perform(
tag: Tag,
file: StaticString = #file,
function: StaticString = #function,
line: UInt = #line
) {
do {
log.debug("Will perform action with tag:\(tag)", subsystems: .audioSession)
try tag.perform(on: audioSession)
log.debug("Successfully performed action with tag:\(tag)", subsystems: .audioSession)
} catch {
log.error(
"Unable to perform action with tag:\(tag)",
subsystems: .audioSession,
error: error,
functionName: function,
fileName: file,
lineNumber: line
)
}
}
}

extension AudioSessionProtocol {
Expand Down Expand Up @@ -311,9 +384,17 @@ extension AVAudioSession.Category {
let supportedOptions: AVAudioSession.CategoryOptions
switch self {
case .playback:
supportedOptions = [.mixWithOthers, .duckOthers, .interruptSpokenAudioAndMixWithOthers]
supportedOptions = [
.mixWithOthers,
.duckOthers,
.interruptSpokenAudioAndMixWithOthers
]
case .record:
supportedOptions = [.mixWithOthers, .duckOthers, .allowBluetooth]
supportedOptions = [
.mixWithOthers,
.duckOthers,
.allowBluetooth
]
case .playAndRecord:
supportedOptions = [
.mixWithOthers,
Expand All @@ -334,7 +415,12 @@ extension AVAudioSession.Category {

extension AVAudioSession {

func overrideInputAudioPort(_ portType: AVAudioSession.Port) throws {
func overrideInputAudioPort(_ portType: AVAudioSession.Port?) throws {
guard let portType else {
try setPreferredInput(nil)
return
}

guard
let port = availableInputs?.first(where: { $0.portType == portType })
else {
Expand All @@ -344,3 +430,43 @@ extension AVAudioSession {
log.debug("AudioSession override inputAudioPort:\(portType).", subsystems: .audioSession)
}
}

extension AVAudioSession.PortOverride: CustomStringConvertible {
public var description: String {
switch self {
case .none:
return ".none"
case .speaker:
return ".speaker"
@unknown default:
return "unknown"
}
}
}

extension AVAudioSession.Port: CustomStringConvertible {
public var description: String {
switch self {
case .builtInMic:
return ".builtInMic"
case .builtInReceiver:
return ".builtInReceiver"
case .builtInSpeaker:
return ".builtInSpeaker"
default:
return "\(self)"
}
}
}

extension AVAudioSession.Category: CustomStringConvertible {
public var description: String {
rawValue
}
}

extension AVAudioSession.Mode: CustomStringConvertible {
public var description: String {
rawValue
}
}

0 comments on commit 2b2d1f0

Please sign in to comment.