Skip to content

Commit 99101f4

Browse files
committed
Migrate startImageStream and setUpCaptureSessionForAudioIfNeeded methods to Swift
1 parent 568c139 commit 99101f4

File tree

4 files changed

+95
-98
lines changed

4 files changed

+95
-98
lines changed

packages/camera/camera_avfoundation/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 0.9.20+7
22

3-
* Migrates `startImageStream` method to Swift.
3+
* Migrates `startImageStream` and `setUpCaptureSessionForAudioIfNeeded` methods to Swift.
4+
* Removes Objective-C implementation of `reportErrorMessage` method.
45

56
## 0.9.20+6
67

packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,92 @@ final class DefaultCamera: FLTCam, Camera {
9292
return (captureVideoInput, captureVideoOutput, connection)
9393
}
9494

95+
func setUpCaptureSessionForAudioIfNeeded() {
96+
// Don't setup audio twice or we will lose the audio.
97+
guard !mediaSettings.enableAudio || !isAudioSetup else { return }
98+
99+
let audioDevice = audioCaptureDeviceFactory()
100+
do {
101+
// Create a device input with the device and add it to the session.
102+
// Setup the audio input.
103+
let audioInput = try captureDeviceInputFactory.deviceInput(with: audioDevice)
104+
105+
// Setup the audio output.
106+
let audioOutput = AVCaptureAudioDataOutput()
107+
108+
let block = {
109+
// Set up options implicit to AVAudioSessionCategoryPlayback to avoid conflicts with other
110+
// plugins like video_player.
111+
DefaultCamera.upgradeAudioSessionCategory(
112+
requestedCategory: .playAndRecord,
113+
options: [.defaultToSpeaker, .allowBluetoothA2DP, .allowAirPlay]
114+
)
115+
}
116+
117+
if !Thread.isMainThread {
118+
DispatchQueue.main.sync(execute: block)
119+
} else {
120+
block()
121+
}
122+
123+
if audioCaptureSession.canAddInput(audioInput) {
124+
audioCaptureSession.addInput(audioInput)
125+
126+
if audioCaptureSession.canAddOutput(audioOutput) {
127+
audioCaptureSession.addOutput(audioOutput)
128+
audioOutput.setSampleBufferDelegate(self, queue: captureSessionQueue)
129+
isAudioSetup = true
130+
} else {
131+
reportErrorMessage("Unable to add Audio input/output to session capture")
132+
isAudioSetup = false
133+
}
134+
}
135+
} catch let error as NSError {
136+
reportErrorMessage(error.description)
137+
}
138+
}
139+
140+
// This function, although slightly modified, is also in video_player_avfoundation (in ObjC).
141+
// Both need to do the same thing and run on the same thread (for example main thread).
142+
// Configure application wide audio session manually to prevent overwriting flag
143+
// MixWithOthers by capture session.
144+
// Only change category if it is considered an upgrade which means it can only enable
145+
// ability to play in silent mode or ability to record audio but never disables it,
146+
// that could affect other plugins which depend on this global state. Only change
147+
// category or options if there is change to prevent unnecessary lags and silence.
148+
private static func upgradeAudioSessionCategory(
149+
requestedCategory: AVAudioSession.Category,
150+
options: AVAudioSession.CategoryOptions
151+
) {
152+
let playCategories: Set<AVAudioSession.Category> = [.playback, .playAndRecord]
153+
let recordCategories: Set<AVAudioSession.Category> = [.record, .playAndRecord]
154+
let requiredCategories: Set<AVAudioSession.Category> = [
155+
requestedCategory, AVAudioSession.sharedInstance().category,
156+
]
157+
158+
let requiresPlay = !requiredCategories.isDisjoint(with: playCategories)
159+
let requiresRecord = !requiredCategories.isDisjoint(with: recordCategories)
160+
161+
var finalCategory = requestedCategory
162+
if requiresPlay && requiresRecord {
163+
finalCategory = .playAndRecord
164+
} else if requiresPlay {
165+
finalCategory = .playback
166+
} else if requiresRecord {
167+
finalCategory = .record
168+
}
169+
170+
let finalOptions = AVAudioSession.sharedInstance().categoryOptions.union(options)
171+
172+
if finalCategory == AVAudioSession.sharedInstance().category
173+
&& finalOptions == AVAudioSession.sharedInstance().categoryOptions
174+
{
175+
return
176+
}
177+
178+
try? AVAudioSession.sharedInstance().setCategory(finalCategory, options: finalOptions)
179+
}
180+
95181
func reportInitializationState() {
96182
// Get all the state on the current thread, not the main thread.
97183
let state = FCPPlatformCameraState.make(
@@ -1027,6 +1113,9 @@ final class DefaultCamera: FLTCam, Camera {
10271113
}
10281114
}
10291115

1116+
/// Reports the given error message to the Dart side of the plugin.
1117+
///
1118+
/// Can be called from any thread.
10301119
private func reportErrorMessage(_ errorMessage: String) {
10311120
FLTEnsureToRunOnMainQueue { [weak self] in
10321121
self?.dartAPI?.reportError(errorMessage) { _ in

packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/FLTCam.m

Lines changed: 1 addition & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,11 @@ @interface FLTCam () <AVCaptureVideoDataOutputSampleBufferDelegate,
2828
@property(strong, nonatomic)
2929
NSObject<FLTAssetWriterInputPixelBufferAdaptor> *assetWriterPixelBufferAdaptor;
3030
@property(strong, nonatomic) AVCaptureVideoDataOutput *videoOutput;
31-
@property(assign, nonatomic) BOOL isAudioSetup;
3231

3332
/// A wrapper for CMVideoFormatDescriptionGetDimensions.
3433
/// Allows for alternate implementations in tests.
3534
@property(nonatomic, copy) VideoDimensionsForFormat videoDimensionsForFormat;
36-
/// A wrapper for AVCaptureDevice creation to allow for dependency injection in tests.
37-
@property(nonatomic, copy) AudioCaptureDeviceFactory audioCaptureDeviceFactory;
38-
/// Reports the given error message to the Dart side of the plugin.
39-
///
40-
/// Can be called from any thread.
41-
- (void)reportErrorMessage:(NSString *)errorMessage;
35+
4236
@end
4337

4438
@implementation FLTCam
@@ -308,92 +302,4 @@ - (BOOL)setCaptureSessionPreset:(FCPPlatformResolutionPreset)resolutionPreset
308302
return bestFormat;
309303
}
310304

311-
// This function, although slightly modified, is also in video_player_avfoundation.
312-
// Both need to do the same thing and run on the same thread (for example main thread).
313-
// Configure application wide audio session manually to prevent overwriting flag
314-
// MixWithOthers by capture session.
315-
// Only change category if it is considered an upgrade which means it can only enable
316-
// ability to play in silent mode or ability to record audio but never disables it,
317-
// that could affect other plugins which depend on this global state. Only change
318-
// category or options if there is change to prevent unnecessary lags and silence.
319-
static void upgradeAudioSessionCategory(AVAudioSessionCategory requestedCategory,
320-
AVAudioSessionCategoryOptions options) {
321-
NSSet *playCategories = [NSSet
322-
setWithObjects:AVAudioSessionCategoryPlayback, AVAudioSessionCategoryPlayAndRecord, nil];
323-
NSSet *recordCategories =
324-
[NSSet setWithObjects:AVAudioSessionCategoryRecord, AVAudioSessionCategoryPlayAndRecord, nil];
325-
NSSet *requiredCategories =
326-
[NSSet setWithObjects:requestedCategory, AVAudioSession.sharedInstance.category, nil];
327-
BOOL requiresPlay = [requiredCategories intersectsSet:playCategories];
328-
BOOL requiresRecord = [requiredCategories intersectsSet:recordCategories];
329-
if (requiresPlay && requiresRecord) {
330-
requestedCategory = AVAudioSessionCategoryPlayAndRecord;
331-
} else if (requiresPlay) {
332-
requestedCategory = AVAudioSessionCategoryPlayback;
333-
} else if (requiresRecord) {
334-
requestedCategory = AVAudioSessionCategoryRecord;
335-
}
336-
options = AVAudioSession.sharedInstance.categoryOptions | options;
337-
if ([requestedCategory isEqualToString:AVAudioSession.sharedInstance.category] &&
338-
options == AVAudioSession.sharedInstance.categoryOptions) {
339-
return;
340-
}
341-
[AVAudioSession.sharedInstance setCategory:requestedCategory withOptions:options error:nil];
342-
}
343-
344-
- (void)setUpCaptureSessionForAudioIfNeeded {
345-
// Don't setup audio twice or we will lose the audio.
346-
if (!_mediaSettings.enableAudio || _isAudioSetup) {
347-
return;
348-
}
349-
350-
NSError *error = nil;
351-
// Create a device input with the device and add it to the session.
352-
// Setup the audio input.
353-
NSObject<FLTCaptureDevice> *audioDevice = self.audioCaptureDeviceFactory();
354-
NSObject<FLTCaptureInput> *audioInput =
355-
[_captureDeviceInputFactory deviceInputWithDevice:audioDevice error:&error];
356-
if (error) {
357-
[self reportErrorMessage:error.description];
358-
}
359-
// Setup the audio output.
360-
_audioOutput = [[AVCaptureAudioDataOutput alloc] init];
361-
362-
dispatch_block_t block = ^{
363-
// Set up options implicit to AVAudioSessionCategoryPlayback to avoid conflicts with other
364-
// plugins like video_player.
365-
upgradeAudioSessionCategory(AVAudioSessionCategoryPlayAndRecord,
366-
AVAudioSessionCategoryOptionDefaultToSpeaker |
367-
AVAudioSessionCategoryOptionAllowBluetoothA2DP |
368-
AVAudioSessionCategoryOptionAllowAirPlay);
369-
};
370-
if (!NSThread.isMainThread) {
371-
dispatch_sync(dispatch_get_main_queue(), block);
372-
} else {
373-
block();
374-
}
375-
376-
if ([_audioCaptureSession canAddInput:audioInput]) {
377-
[_audioCaptureSession addInput:audioInput];
378-
379-
if ([_audioCaptureSession canAddOutput:_audioOutput]) {
380-
[_audioCaptureSession addOutput:_audioOutput];
381-
_isAudioSetup = YES;
382-
} else {
383-
[self reportErrorMessage:@"Unable to add Audio input/output to session capture"];
384-
_isAudioSetup = NO;
385-
}
386-
}
387-
}
388-
389-
- (void)reportErrorMessage:(NSString *)errorMessage {
390-
__weak typeof(self) weakSelf = self;
391-
FLTEnsureToRunOnMainQueue(^{
392-
[weakSelf.dartAPI reportError:errorMessage
393-
completion:^(FlutterError *error){
394-
// Ignore any errors, as this is just an event broadcast.
395-
}];
396-
});
397-
}
398-
399305
@end

packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/FLTCam.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,14 @@ NS_ASSUME_NONNULL_BEGIN
6565
@property(readonly, nonatomic) FCPPlatformMediaSettings *mediaSettings;
6666
@property(nonatomic, copy) InputPixelBufferAdaptorFactory inputPixelBufferAdaptorFactory;
6767
@property(strong, nonatomic) AVCaptureAudioDataOutput *audioOutput;
68+
@property(assign, nonatomic) BOOL isAudioSetup;
69+
/// A wrapper for AVCaptureDevice creation to allow for dependency injection in tests.
70+
@property(nonatomic, copy) AudioCaptureDeviceFactory audioCaptureDeviceFactory;
6871

6972
/// Initializes an `FLTCam` instance with the given configuration.
7073
/// @param error report to the caller if any error happened creating the camera.
7174
- (instancetype)initWithConfiguration:(FLTCamConfiguration *)configuration error:(NSError **)error;
7275

73-
- (void)setUpCaptureSessionForAudioIfNeeded;
74-
7576
// Methods exposed for the Swift DefaultCamera subclass
7677
- (void)updateOrientation;
7778

0 commit comments

Comments
 (0)