Skip to content

Commit 787f41c

Browse files
committed
never overwrite but only upgrade audio session category
1 parent 7022a44 commit 787f41c

File tree

8 files changed

+159
-12
lines changed

8 files changed

+159
-12
lines changed

packages/camera/camera_avfoundation/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.9.17+1
2+
3+
* Fixes overwriting flag MixWithOthers set by video_player.
4+
15
## 0.9.17
26

37
* Adds Swift Package Manager compatibility.

packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,27 @@ - (void)testDidOutputSampleBufferSampleTimesMustBeNumericAfterPauseResume {
200200
CFRelease(audioSample);
201201
}
202202

203+
- (void)testStartVideoRecordingWithCompletionShouldNotDisableMixWithOthers {
204+
FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL));
205+
206+
id writerMock = OCMClassMock([AVAssetWriter class]);
207+
OCMStub([writerMock alloc]).andReturn(writerMock);
208+
OCMStub([writerMock initWithURL:OCMOCK_ANY fileType:OCMOCK_ANY error:[OCMArg setTo:nil]])
209+
.andReturn(writerMock);
210+
211+
[AVAudioSession.sharedInstance setCategory:AVAudioSessionCategoryPlayback
212+
withOptions:AVAudioSessionCategoryOptionMixWithOthers
213+
error:nil];
214+
215+
[cam
216+
startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) {
217+
}
218+
messengerForStreaming:nil];
219+
XCTAssert(
220+
AVAudioSession.sharedInstance.categoryOptions & AVAudioSessionCategoryOptionMixWithOthers,
221+
@"Flag MixWithOthers was removed.");
222+
XCTAssert(AVAudioSession.sharedInstance.category == AVAudioSessionCategoryPlayAndRecord,
223+
@"Category should be PlayAndRecord.");
224+
}
225+
203226
@end

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

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ - (instancetype)initWithMediaSettings:(FCPPlatformMediaSettings *)mediaSettings
183183
_videoFormat = kCVPixelFormatType_32BGRA;
184184
_inProgressSavePhotoDelegates = [NSMutableDictionary dictionary];
185185
_fileFormat = FCPPlatformImageFileFormatJpeg;
186+
_videoCaptureSession.automaticallyConfiguresApplicationAudioSession = NO;
187+
_audioCaptureSession.automaticallyConfiguresApplicationAudioSession = NO;
186188

187189
// To limit memory consumption, limit the number of frames pending processing.
188190
// After some testing, 4 was determined to be the best maximum value.
@@ -673,7 +675,8 @@ - (void)captureOutput:(AVCaptureOutput *)output
673675
[_videoWriter startWriting];
674676
[_videoWriter startSessionAtSourceTime:currentSampleTime];
675677
// fix sample times not being numeric when pause/resume happens before first sample buffer
676-
// arrives https://github.com/flutter/flutter/issues/132014
678+
// arrives
679+
// https://github.com/flutter/flutter/issues/132014
677680
_lastVideoSampleTime = currentSampleTime;
678681
_lastAudioSampleTime = currentSampleTime;
679682
}
@@ -1216,9 +1219,7 @@ - (BOOL)setupWriterForPath:(NSString *)path {
12161219
return NO;
12171220
}
12181221

1219-
if (_mediaSettings.enableAudio && !_isAudioSetup) {
1220-
[self setUpCaptureSessionForAudio];
1221-
}
1222+
[self setUpCaptureSessionForAudio];
12221223

12231224
_videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL
12241225
fileType:AVFileTypeMPEG4
@@ -1298,9 +1299,47 @@ - (BOOL)setupWriterForPath:(NSString *)path {
12981299
return YES;
12991300
}
13001301

1302+
// configure application wide audio session manually to prevent overwriting
1303+
// flag MixWithOthers by capture session, only change category if it is considered
1304+
// as upgrade which means it can only enable ability to play in silent mode or
1305+
// ability to record audio but never disables it, that could affect other plugins
1306+
// which depend on this global state, only change category or options if there is
1307+
// change to prevent unnecessary route changes which can cause lags
1308+
// https://github.com/flutter/flutter/issues/131553
1309+
static void upgradeAudioSessionCategory(AVAudioSessionCategory category,
1310+
AVAudioSessionCategoryOptions options,
1311+
AVAudioSessionCategoryOptions clearOptions) {
1312+
if (!NSThread.isMainThread) {
1313+
dispatch_sync(dispatch_get_main_queue(), ^{
1314+
upgradeAudioSessionCategory(category, options, clearOptions);
1315+
});
1316+
return;
1317+
}
1318+
NSSet *playCategories = [NSSet
1319+
setWithObjects:AVAudioSessionCategoryPlayback, AVAudioSessionCategoryPlayAndRecord, nil];
1320+
NSSet *recordCategories =
1321+
[NSSet setWithObjects:AVAudioSessionCategoryRecord, AVAudioSessionCategoryPlayAndRecord, nil];
1322+
NSSet *categories = [NSSet setWithObjects:category, AVAudioSession.sharedInstance.category, nil];
1323+
BOOL needPlay = [categories intersectsSet:playCategories];
1324+
BOOL needRecord = [categories intersectsSet:recordCategories];
1325+
if (needPlay && needRecord) {
1326+
category = AVAudioSessionCategoryPlayAndRecord;
1327+
} else if (needPlay) {
1328+
category = AVAudioSessionCategoryPlayback;
1329+
} else if (needRecord) {
1330+
category = AVAudioSessionCategoryRecord;
1331+
}
1332+
options = (AVAudioSession.sharedInstance.categoryOptions & ~clearOptions) | options;
1333+
if ([category isEqualToString:AVAudioSession.sharedInstance.category] &&
1334+
options == AVAudioSession.sharedInstance.categoryOptions) {
1335+
return;
1336+
}
1337+
[AVAudioSession.sharedInstance setCategory:category withOptions:options error:nil];
1338+
}
1339+
13011340
- (void)setUpCaptureSessionForAudio {
13021341
// Don't setup audio twice or we will lose the audio.
1303-
if (_isAudioSetup) {
1342+
if (!_mediaSettings.enableAudio || _isAudioSetup) {
13041343
return;
13051344
}
13061345

@@ -1316,6 +1355,9 @@ - (void)setUpCaptureSessionForAudio {
13161355
// Setup the audio output.
13171356
_audioOutput = [[AVCaptureAudioDataOutput alloc] init];
13181357

1358+
upgradeAudioSessionCategory(AVAudioSessionCategoryPlayAndRecord,
1359+
AVAudioSessionCategoryOptionDefaultToSpeaker, 0);
1360+
13191361
if ([_audioCaptureSession canAddInput:audioInput]) {
13201362
[_audioCaptureSession addInput:audioInput];
13211363

packages/camera/camera_avfoundation/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: camera_avfoundation
22
description: iOS implementation of the camera plugin.
33
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
5-
version: 0.9.17
5+
version: 0.9.17+1
66

77
environment:
88
sdk: ^3.2.3

packages/video_player/video_player_avfoundation/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.6.2
2+
3+
* Fixes audio recorded only with first recording.
4+
15
## 2.6.1
26

37
* Adds files to make include directory permanent.

packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,40 @@ - (void)testPublishesInRegistration {
791791
}
792792

793793
#if TARGET_OS_IOS
794+
- (void)testVideoPlayerShouldNotOverwritePlayAndRecordNorDefaultToSpeaker {
795+
NSObject<FlutterPluginRegistrar> *registrar = [GetPluginRegistry()
796+
registrarForPlugin:@"testVideoPlayerShouldNotOverwritePlayAndRecordNorDefaultToSpeaker"];
797+
FVPVideoPlayerPlugin *videoPlayerPlugin =
798+
[[FVPVideoPlayerPlugin alloc] initWithRegistrar:registrar];
799+
FlutterError *error;
800+
801+
[AVAudioSession.sharedInstance setCategory:AVAudioSessionCategoryPlayAndRecord
802+
withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker
803+
error:nil];
804+
805+
[videoPlayerPlugin initialize:&error];
806+
[videoPlayerPlugin setMixWithOthers:true error:&error];
807+
XCTAssert(AVAudioSession.sharedInstance.category == AVAudioSessionCategoryPlayAndRecord,
808+
@"Category should be PlayAndRecord.");
809+
XCTAssert(
810+
AVAudioSession.sharedInstance.categoryOptions & AVAudioSessionCategoryOptionDefaultToSpeaker,
811+
@"Flag DefaultToSpeaker was removed.");
812+
XCTAssert(
813+
AVAudioSession.sharedInstance.categoryOptions & AVAudioSessionCategoryOptionMixWithOthers,
814+
@"Flag MixWithOthers should be set.");
815+
816+
id sessionMock = OCMClassMock([AVAudioSession class]);
817+
OCMStub([sessionMock sharedInstance]).andReturn(sessionMock);
818+
OCMStub([sessionMock category]).andReturn(AVAudioSessionCategoryPlayAndRecord);
819+
OCMStub([sessionMock categoryOptions])
820+
.andReturn(AVAudioSessionCategoryOptionMixWithOthers |
821+
AVAudioSessionCategoryOptionDefaultToSpeaker);
822+
OCMReject([sessionMock setCategory:OCMOCK_ANY withOptions:0 error:[OCMArg setTo:nil]])
823+
.ignoringNonObjectArgs();
824+
825+
[videoPlayerPlugin setMixWithOthers:true error:&error];
826+
}
827+
794828
- (void)validateTransformFixForOrientation:(UIImageOrientation)orientation {
795829
AVAssetTrack *track = [[FakeAVAssetTrack alloc] initWithOrientation:orientation];
796830
CGAffineTransform t = FVPGetStandardizedTransformForTrack(track);

packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,7 @@ - (int64_t)onPlayerSetup:(FVPVideoPlayer *)player frameUpdater:(FVPFrameUpdater
708708
- (void)initialize:(FlutterError *__autoreleasing *)error {
709709
#if TARGET_OS_IOS
710710
// Allow audio playback when the Ring/Silent switch is set to silent
711-
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
711+
upgradeAudioSessionCategory(AVAudioSessionCategoryPlayback, 0, 0);
712712
#endif
713713

714714
[self.playersByTextureId
@@ -813,17 +813,57 @@ - (void)pausePlayer:(NSInteger)textureId error:(FlutterError **)error {
813813
[player pause];
814814
}
815815

816+
// do not overwrite PlayAndRecord with Playback which causes inability to record
817+
// audio, do not overwrite all options, only change category if it is considered
818+
// as upgrade which means it can only enable ability to play in silent mode or
819+
// ability to record audio but never disables it, that could affect other plugins
820+
// which depend on this global state, only change category or options if there is
821+
// change to prevent unnecessary route changes which can cause lags
822+
// https://github.com/flutter/flutter/issues/131553
823+
#if TARGET_OS_IOS
824+
static void upgradeAudioSessionCategory(AVAudioSessionCategory category,
825+
AVAudioSessionCategoryOptions options,
826+
AVAudioSessionCategoryOptions clearOptions) {
827+
if (!NSThread.isMainThread) {
828+
dispatch_sync(dispatch_get_main_queue(), ^{
829+
upgradeAudioSessionCategory(category, options, clearOptions);
830+
});
831+
return;
832+
}
833+
NSSet *playCategories = [NSSet
834+
setWithObjects:AVAudioSessionCategoryPlayback, AVAudioSessionCategoryPlayAndRecord, nil];
835+
NSSet *recordCategories =
836+
[NSSet setWithObjects:AVAudioSessionCategoryRecord, AVAudioSessionCategoryPlayAndRecord, nil];
837+
NSSet *categories = [NSSet setWithObjects:category, AVAudioSession.sharedInstance.category, nil];
838+
BOOL needPlay = [categories intersectsSet:playCategories];
839+
BOOL needRecord = [categories intersectsSet:recordCategories];
840+
if (needPlay && needRecord) {
841+
category = AVAudioSessionCategoryPlayAndRecord;
842+
} else if (needPlay) {
843+
category = AVAudioSessionCategoryPlayback;
844+
} else if (needRecord) {
845+
category = AVAudioSessionCategoryRecord;
846+
}
847+
options = (AVAudioSession.sharedInstance.categoryOptions & ~clearOptions) | options;
848+
if ([category isEqualToString:AVAudioSession.sharedInstance.category] &&
849+
options == AVAudioSession.sharedInstance.categoryOptions) {
850+
return;
851+
}
852+
[AVAudioSession.sharedInstance setCategory:category withOptions:options error:nil];
853+
}
854+
#endif
855+
816856
- (void)setMixWithOthers:(BOOL)mixWithOthers
817857
error:(FlutterError *_Nullable __autoreleasing *)error {
818858
#if TARGET_OS_OSX
819859
// AVAudioSession doesn't exist on macOS, and audio always mixes, so just no-op.
820860
#else
821861
if (mixWithOthers) {
822-
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
823-
withOptions:AVAudioSessionCategoryOptionMixWithOthers
824-
error:nil];
862+
upgradeAudioSessionCategory(AVAudioSession.sharedInstance.category,
863+
AVAudioSessionCategoryOptionMixWithOthers, 0);
825864
} else {
826-
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
865+
upgradeAudioSessionCategory(AVAudioSession.sharedInstance.category, 0,
866+
AVAudioSessionCategoryOptionMixWithOthers);
827867
}
828868
#endif
829869
}

packages/video_player/video_player_avfoundation/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: video_player_avfoundation
22
description: iOS and macOS implementation of the video_player plugin.
33
repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_avfoundation
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
5-
version: 2.6.1
5+
version: 2.6.2
66

77
environment:
88
sdk: ^3.2.3

0 commit comments

Comments
 (0)