Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -628,13 +628,19 @@ - (void)getUserVideo:(NSDictionary*)constraints
[videoDevice unlockForConfiguration];
}

__weak FlutterWebRTCPlugin* weakSelf = self;
[self.videoCapturer startCaptureWithDevice:videoDevice
format:selectedFormat
fps:selectedFps
completionHandler:^(NSError* error) {
if (error) {
NSLog(@"Start capture error: %@", [error localizedDescription]);
return;
}

// This is a freshly created capture session, so it
// starts without multitasking camera access.
[weakSelf applyMultitaskingCameraAccessToCaptureSession];
}];

NSString* trackUUID = [[NSUUID UUID] UUIDString];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,8 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
BOOL enable = [argsMap[@"enable"] boolValue];

[self enableMultitaskingCameraAccess:enable result:result];
} else if ([@"isIOSMultitaskingCameraAccessSupported" isEqualToString:call.method]) {
[self multitaskingCameraAccessSupported:result];
} else if ([@"mediaStreamTrackHasTorch" isEqualToString:call.method]) {
NSDictionary* argsMap = call.arguments;
NSString* trackId = argsMap[@"trackId"];
Expand Down Expand Up @@ -2416,8 +2418,61 @@ - (void)mediaStreamTrackSetVideoEffects:(nonnull NSString*)trackId
}
}

/// Applies the remembered multitasking camera access request to the capture
/// session in use.
///
/// A capture session is created per `getUserMedia` call and starts without
/// multitasking camera access, so the request is applied again every time one
/// is created.
- (void)applyMultitaskingCameraAccessToCaptureSession {
#if !TARGET_OS_OSX
if (!self.multitaskingCameraAccessRequested) {
return;
}

if (@available(iOS 16.0, *)) {
AVCaptureSession* session = self.videoCapturer.captureSession;
if (session == nil || !session.isMultitaskingCameraAccessSupported ||
session.multitaskingCameraAccessEnabled) {
return;
}

@try {
[session beginConfiguration];
[session setMultitaskingCameraAccessEnabled:YES];
[session commitConfiguration];
} @catch (NSException* exception) {
NSLog(@"applyMultitaskingCameraAccessToCaptureSession: Exception occurred: %@ - %@",
exception.name, exception.reason);
}
}
#endif
}

/// Reports whether the capture session in use supports camera access while
/// multitasking, or nil when there is no capture session to ask.
- (void)multitaskingCameraAccessSupported:(FlutterResult)result {
#if TARGET_OS_OSX
result(@NO);
#else
if (@available(iOS 16.0, *)) {
AVCaptureSession* session = self.videoCapturer.captureSession;
if (session == nil) {
result(nil);
return;
}

result(@(session.isMultitaskingCameraAccessSupported));
} else {
result(@NO);
}
#endif
}

- (void)enableMultitaskingCameraAccess:(BOOL)enable result:(FlutterResult)result {
@try {
self.multitaskingCameraAccessRequested = enable;

AVCaptureSession* session = self.videoCapturer.captureSession;
if (session == nil) {
NSLog(@"enableMultitaskingCameraAccess: Capture session is nil.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ typedef void (^CapturerStopHandler)(CompletionHandler _Nonnull handler);
@property(nonatomic, strong) NSMutableDictionary<NSString*, NSNumber*>* _Nonnull pausedTrackVolumes;
@property(nonatomic) BOOL isAudioPlayoutPaused;

/// Whether camera access while multitasking was asked for. Remembered so that
/// a capture session created afterwards is started with it applied.
@property(nonatomic) BOOL multitaskingCameraAccessRequested;

- (void)mediaStreamTrackSetVideoEffects:(nonnull NSString*)trackId
names:(nonnull NSArray<NSString*>*)names;
- (RTCMediaStream* _Nullable)streamForId:(NSString* _Nonnull)streamId
Expand All @@ -108,6 +112,7 @@ typedef void (^CapturerStopHandler)(CompletionHandler _Nonnull handler);
- (RTCMediaStreamTrack* _Nullable)remoteTrackForId:(NSString* _Nonnull)trackId;

- (BOOL)hasLocalAudioTrack;
- (void)applyMultitaskingCameraAccessToCaptureSession;
- (void)ensureAudioSession;
- (void)deactiveRtcAudioSession;

Expand Down
8 changes: 8 additions & 0 deletions lib/src/helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,14 @@ class Helper {
static Future<bool> enableIOSMultitaskingCameraAccess(bool enable) =>
IosAudioManagement.enableMultitaskingCameraAccess(enable);

/// Whether the capture session in use supports camera access while
/// multitasking (iOS only).
///
/// Returns `null` when there is no capture session to ask, which is the case
/// until the camera is started.
static Future<bool?> isIOSMultitaskingCameraAccessSupported() =>
IosAudioManagement.isMultitaskingCameraAccessSupported();

/// Trigger the iOS audio route selection UI (iOS only).
static Future<void> triggeriOSAudioRouteSelectionUI() =>
IosAudioManagement.triggerAudioRouteSelectionUI();
Expand Down
16 changes: 16 additions & 0 deletions lib/src/native/ios/audio_management.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,20 @@ class IosAudioManagement {
'enableMultitaskingCameraAccess is only supported for iOS');
}
}

/// Whether the capture session in use supports camera access while
/// multitasking (iOS only).
///
/// Returns `null` when there is no capture session to ask, which is the case
/// until the camera is started.
static Future<bool?> isMultitaskingCameraAccessSupported() async {
if (WebRTC.platformIsIOS) {
return await WebRTC.invokeMethod(
'isIOSMultitaskingCameraAccessSupported',
);
} else {
throw Exception(
'isMultitaskingCameraAccessSupported is only supported for iOS');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,8 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
BOOL enable = [argsMap[@"enable"] boolValue];

[self enableMultitaskingCameraAccess:enable result:result];
} else if ([@"isIOSMultitaskingCameraAccessSupported" isEqualToString:call.method]) {
[self multitaskingCameraAccessSupported:result];
} else if ([@"mediaStreamTrackHasTorch" isEqualToString:call.method]) {
NSDictionary* argsMap = call.arguments;
NSString* trackId = argsMap[@"trackId"];
Expand Down Expand Up @@ -2151,6 +2153,10 @@ - (void)enableMultitaskingCameraAccess:(BOOL)enable result:(FlutterResult)result
result(@NO);
}

- (void)multitaskingCameraAccessSupported:(FlutterResult)result {
result(@NO);
}

- (void)mediaStreamGetTracks:(NSString*)streamId result:(FlutterResult)result {
RTCMediaStream* stream = [self streamForId:streamId peerConnectionId:@""];
if (stream) {
Expand Down
Loading