Skip to content

Commit

Permalink
Customization of iOS audio categories from flutter
Browse files Browse the repository at this point in the history
  • Loading branch information
chanonly123 committed Sep 5, 2024
1 parent 809a5e4 commit ae59cd0
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 11 deletions.
2 changes: 1 addition & 1 deletion record/example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import UIKit
import Flutter

@UIApplicationMain
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
Expand Down
43 changes: 39 additions & 4 deletions record_darwin/darwin/Classes/RecordConfig.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import AVFoundation

public enum AudioEncoder: String {
case aacLc = "aacLc"
case aacEld = "aacEld"
Expand All @@ -19,6 +21,7 @@ public class RecordConfig {
let autoGain: Bool
let echoCancel: Bool
let noiseSuppress: Bool
let iosConfig: IosConfig?

init(encoder: String,
bitRate: Int,
Expand All @@ -27,7 +30,8 @@ public class RecordConfig {
device: Device? = nil,
autoGain: Bool = false,
echoCancel: Bool = false,
noiseSuppress: Bool = false
noiseSuppress: Bool = false,
iosConfig: IosConfig? = nil
) {
self.encoder = encoder
self.bitRate = bitRate
Expand All @@ -37,27 +41,58 @@ public class RecordConfig {
self.autoGain = autoGain
self.echoCancel = echoCancel
self.noiseSuppress = noiseSuppress
self.iosConfig = iosConfig
}
}

public class Device {
let id: String
let label: String

init(id: String, label: String) {
self.id = id
self.label = label
}

init(map: [String: Any]) {
self.id = map["id"] as! String
self.label = map["label"] as! String
}

func toMap() -> [String: Any] {
return [
"id": id,
"label": label
]
}
}

struct IosConfig {
let audioCategories: [AVAudioSession.CategoryOptions]

init(map: [String: Any]) {
let comps = map["audioCategories"] as? String
let options: [AVAudioSession.CategoryOptions]? = comps?.split(separator: ",").compactMap {
switch $0 {
case "mixWithOthers":
.mixWithOthers
case "duckOthers":
.duckOthers
case "allowBluetooth":
.allowBluetooth
case "defaultToSpeaker":
.defaultToSpeaker
case "interruptSpokenAudioAndMixWithOthers":
.interruptSpokenAudioAndMixWithOthers
case "allowBluetoothA2DP":
.allowBluetoothA2DP
case "allowAirPlay":
.allowAirPlay
case "overrideMutedMicrophoneInterruption":
if #available(iOS 14.5, *) { .overrideMutedMicrophoneInterruption } else { nil }
default: nil
}
}
self.audioCategories = options ?? []
}
}
10 changes: 8 additions & 2 deletions record_darwin/darwin/Classes/SwiftRecordPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,12 @@ public class SwiftRecordPlugin: NSObject, FlutterPlugin {
if let deviceMap = args["device"] as? [String : Any] {
device = Device(map: deviceMap)
}


var iosConfig: IosConfig? = nil
if let iosConfigMap = args["iosConfig"] as? [String : Any] {
iosConfig = IosConfig(map: iosConfigMap)
}

let config = RecordConfig(
encoder: encoder,
bitRate: args["bitRate"] as? Int ?? 128000,
Expand All @@ -193,7 +198,8 @@ public class SwiftRecordPlugin: NSObject, FlutterPlugin {
device: device,
autoGain: args["autoGain"] as? Bool ?? false,
echoCancel: args["echoCancel"] as? Bool ?? false,
noiseSuppress: args["noiseSuppress"] as? Bool ?? false
noiseSuppress: args["noiseSuppress"] as? Bool ?? false,
iosConfig: iosConfig
)

return config
Expand Down
7 changes: 3 additions & 4 deletions record_darwin/ios/Classes/RecorderIOS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func listInputs() throws -> [Device] {
func listInputDevices() throws -> [AVAudioSessionPortDescription]? {
let audioSession = AVAudioSession.sharedInstance()
let options: AVAudioSession.CategoryOptions = [.defaultToSpeaker, .allowBluetooth]

do {
try audioSession.setCategory(.playAndRecord, options: options)
} catch {
Expand Down Expand Up @@ -46,10 +46,9 @@ private func setInput(_ config: RecordConfig) throws {
extension AudioRecordingDelegate {
func initAVAudioSession(config: RecordConfig) throws {
let audioSession = AVAudioSession.sharedInstance()
let options: AVAudioSession.CategoryOptions = [.defaultToSpeaker, .allowBluetooth, .allowBluetoothA2DP]


do {
try audioSession.setCategory(.playAndRecord, options: options)
try audioSession.setCategory(.playAndRecord, options: AVAudioSession.CategoryOptions(config.iosConfig?.audioCategories ?? []))
} catch {
throw RecorderError.error(message: "Failed to start recording", details: "setCategory: \(error.localizedDescription)")
}
Expand Down
16 changes: 16 additions & 0 deletions record_platform_interface/lib/src/types/ios_audio_categories.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/// Constants that specify optional audio behaviors.
/// https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions
enum IosAudioCategories {
mixWithOthers,
duckOthers,
allowBluetooth,
defaultToSpeaker,
/// available from iOS 9.0
interruptSpokenAudioAndMixWithOthers,
/// available from iOS 10.0
allowBluetoothA2DP,
/// available from iOS 10.0
allowAirPlay,
/// available from iOS 14.5
overrideMutedMicrophoneInterruption
}
23 changes: 23 additions & 0 deletions record_platform_interface/lib/src/types/record_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class RecordConfig {
/// Android specific configuration.
final AndroidRecordConfig androidConfig;

/// iOS specific audioCategories
final IosRecordConfig iosConfig;

const RecordConfig({
this.encoder = AudioEncoder.aacLc,
this.bitRate = 128000,
Expand All @@ -61,6 +64,7 @@ class RecordConfig {
this.echoCancel = false,
this.noiseSuppress = false,
this.androidConfig = const AndroidRecordConfig(),
this.iosConfig = const IosRecordConfig(),
});

Map<String, dynamic> toMap() {
Expand All @@ -74,6 +78,7 @@ class RecordConfig {
'echoCancel': echoCancel,
'noiseSuppress': noiseSuppress,
'androidConfig': androidConfig.toMap(),
'iosConfig': iosConfig.toMap(),
};
}
}
Expand Down Expand Up @@ -108,3 +113,21 @@ class AndroidRecordConfig {
};
}
}

///
class IosRecordConfig {

/// Constants that specify optional audio behaviors.
/// https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions
final List<IosAudioCategories> audioCategories;

const IosRecordConfig({
this.audioCategories = const [IosAudioCategories.defaultToSpeaker, IosAudioCategories.allowBluetooth, IosAudioCategories.allowBluetoothA2DP]
});
Map<String, dynamic> toMap() {
return {
"audioCategories": audioCategories.map((e) => e.name).join(',')
};
}

}
1 change: 1 addition & 0 deletions record_platform_interface/lib/src/types/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export 'package:record_platform_interface/src/types/audio_encoder.dart';
export 'package:record_platform_interface/src/types/input_device.dart';
export 'package:record_platform_interface/src/types/record_config.dart';
export 'package:record_platform_interface/src/types/record_state.dart';
export 'package:record_platform_interface/src/types/ios_audio_categories.dart';

0 comments on commit ae59cd0

Please sign in to comment.