diff --git a/packages/audiofileplayer/CHANGELOG.md b/packages/audiofileplayer/CHANGELOG.md index 0e44eb2..f7ed87f 100644 --- a/packages/audiofileplayer/CHANGELOG.md +++ b/packages/audiofileplayer/CHANGELOG.md @@ -5,3 +5,7 @@ ## 1.0.1 - 21 Nov 2019 * Fix new build issues in podfile, add pubspec.yaml dependency versions + +## 1.0.1 - 21 Nov 2019 + + * Fix background audio on iOS, add ability to specify iOS audio category (which defaults to 'playback'). diff --git a/packages/audiofileplayer/example/android/gradle.properties b/packages/audiofileplayer/example/android/gradle.properties index 8bd86f6..7be3d8b 100644 --- a/packages/audiofileplayer/example/android/gradle.properties +++ b/packages/audiofileplayer/example/android/gradle.properties @@ -1 +1,2 @@ org.gradle.jvmargs=-Xmx1536M +android.enableR8=true diff --git a/packages/audiofileplayer/example/ios/Runner.xcodeproj/project.pbxproj b/packages/audiofileplayer/example/ios/Runner.xcodeproj/project.pbxproj index 7bd4cce..8b5e450 100644 --- a/packages/audiofileplayer/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/audiofileplayer/example/ios/Runner.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ 283EA6CAD6FBBC952F6CE58F /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 48D486519172122DC77807F5 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -55,6 +56,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C1B130F899C72F55F2976942 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + EC8F38AE54ABD5EB70CDAE54 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -74,6 +77,9 @@ 73E993770646A7EF8D98E434 /* Pods */ = { isa = PBXGroup; children = ( + C1B130F899C72F55F2976942 /* Pods-Runner.debug.xcconfig */, + EC8F38AE54ABD5EB70CDAE54 /* Pods-Runner.release.xcconfig */, + 48D486519172122DC77807F5 /* Pods-Runner.profile.xcconfig */, ); name = Pods; sourceTree = ""; @@ -178,6 +184,11 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + SystemCapabilities = { + com.apple.BackgroundModes = { + enabled = 1; + }; + }; }; }; }; @@ -186,6 +197,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -267,16 +279,13 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ diff --git a/packages/audiofileplayer/example/ios/Runner/Info.plist b/packages/audiofileplayer/example/ios/Runner/Info.plist index 9c2ec66..6be6eb7 100644 --- a/packages/audiofileplayer/example/ios/Runner/Info.plist +++ b/packages/audiofileplayer/example/ios/Runner/Info.plist @@ -22,6 +22,10 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIBackgroundModes + + audio + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/packages/audiofileplayer/example/lib/main.dart b/packages/audiofileplayer/example/lib/main.dart index 816f56e..ec8f77e 100644 --- a/packages/audiofileplayer/example/lib/main.dart +++ b/packages/audiofileplayer/example/lib/main.dart @@ -27,6 +27,9 @@ class _MyAppState extends State { bool _remoteAudioLoading = false; String _remoteErrorMessage; + // The iOS audio category dropdown item in the fourth card. + IosAudioCategory _iosAudioCategory = IosAudioCategory.playback; + @override void initState() { super.initState(); @@ -57,7 +60,7 @@ class _MyAppState extends State { child: Column( children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 4.0), child: RaisedButton( onPressed: onTap, child: isPlaying @@ -198,9 +201,34 @@ class _MyAppState extends State { }), _remoteErrorMessage != null ? Text(_remoteErrorMessage, - style: TextStyle(color: const Color(0xFFFF0000))) + style: const TextStyle(color: const Color(0xFFFF0000))) : Text(_remoteAudioLoading ? 'loading...' : 'loaded') ]), + _cardWrapper([ + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + const Text('Enable background playback:'), + Checkbox( + value: Audio.shouldPlayWhileAppPaused, + onChanged: (bool isOn) => + setState(() => Audio.shouldPlayWhileAppPaused = isOn)) + ]), + const Text('(iOS only) iOS audio category:'), + DropdownButton( + value: _iosAudioCategory, + onChanged: (IosAudioCategory newValue) { + setState(() { + _iosAudioCategory = newValue; + Audio.setIosAudioCategory(_iosAudioCategory); + }); + }, + items: IosAudioCategory.values.map((IosAudioCategory category) { + return DropdownMenuItem( + value: category, + child: Text(category.toString()), + ); + }).toList(), + ) + ]), ]), )); } diff --git a/packages/audiofileplayer/ios/Classes/AudiofileplayerPlugin.m b/packages/audiofileplayer/ios/Classes/AudiofileplayerPlugin.m index 87107ac..96f0342 100644 --- a/packages/audiofileplayer/ios/Classes/AudiofileplayerPlugin.m +++ b/packages/audiofileplayer/ios/Classes/AudiofileplayerPlugin.m @@ -1,3 +1,4 @@ +#import #import "AudiofileplayerPlugin.h" #import "ManagedPlayer.h" @@ -23,6 +24,12 @@ static NSString *const kPositionSeconds = @"position_seconds"; static NSString *const kErrorCode = @"AudioPluginError"; +static NSString *const kAudioCategoryMethod = @"iosAudioCategory"; +static NSString *const kAudioCategory = @"iosAudioCategory"; +static NSString *const kAudioCategoryAmbientSolo = @"iosAudioCategoryAmbientSolo"; +static NSString *const kAudioCategoryAmbientMixed = @"iosAudioCategoryAmbientMixed"; +static NSString *const kAudioCategoryPlayback = @"iosAudioCategoryPlayback"; + @interface AudiofileplayerPlugin () @end @@ -48,13 +55,33 @@ - (instancetype)initWithRegistrar:(NSObject *)registrar _registrar = registrar; _channel = channel; _playersDict = [NSMutableDictionary dictionary]; + // Set audio category to initial default of 'playback'. + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback + error:nil]; } return self; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSLog(@"handleMethodCall: method = %@", call.method); - if ([call.method isEqualToString:@"load"]) { + + // Setting the audio category. + if ([call.method isEqualToString:kAudioCategoryMethod]) { + NSString *categoryString = call.arguments[kAudioCategory]; + AVAudioSessionCategory category; + if ([categoryString isEqualToString:kAudioCategoryAmbientSolo]) { + category = AVAudioSessionCategorySoloAmbient; + } else if ([categoryString isEqualToString:kAudioCategoryAmbientMixed]) { + category = AVAudioSessionCategoryAmbient; + } if ([categoryString isEqualToString:kAudioCategoryPlayback]) { + category = AVAudioSessionCategoryPlayback; + } + [[AVAudioSession sharedInstance] setCategory:category error:nil]; + return; + } + + // Loading an audio instance. + if ([call.method isEqualToString:kLoadMethod]) { [self handleLoadWithCall:call result:result]; return; } diff --git a/packages/audiofileplayer/lib/audiofileplayer.dart b/packages/audiofileplayer/lib/audiofileplayer.dart index 36261ab..5906df6 100644 --- a/packages/audiofileplayer/lib/audiofileplayer.dart +++ b/packages/audiofileplayer/lib/audiofileplayer.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io' show Platform; import 'dart:typed_data'; import 'dart:ui' show AppLifecycleState; @@ -33,6 +34,39 @@ const String onPositionCallback = 'onPosition'; const String positionSecondsKey = 'position_seconds'; const String errorCode = 'AudioPluginError'; +const String iosAudioCategoryMethod = 'iosAudioCategory'; +const String iosAudioCategoryKey = 'iosAudioCategory'; +const String iosAudioCategoryAmbientSolo = 'iosAudioCategoryAmbientSolo'; +const String iosAudioCategoryAmbientMixed = 'iosAudioCategoryAmbientMixed'; +const String iosAudioCategoryPlayback = 'iosAudioCategoryPlayback'; + +/// Represents audio playback category on iOS. +/// +/// An 'ambient' category should be used for tasks like game audio, whereas +/// the [playback] category should be used for tasks like music player playback. +/// +/// Note that for background audio, the [shouldPlayWhileAppPaused] flag must +/// also be set. +/// +/// See +/// https://developer.apple.com/documentation/avfoundation/avaudiosessioncategory +/// for more information. +enum IosAudioCategory { + /// Audio is silenced by screen lock and the silent switch; audio will not mix + /// with other apps' audio. + ambientSolo, + + /// Audio is silenced by screen lock and the silent switch; audio will mix + /// with other apps' (mixable) audio. + ambientMixed, + + /// Audio is not silenced by screen lock or silent switch; audio will not mix + /// with other apps' audio. + /// + /// The default value. + playback +} + /// A plugin for audio playback. /// /// Example usage: @@ -170,7 +204,7 @@ class Audio with WidgetsBindingObserver { } @visibleForTesting - static final MethodChannel channel = MethodChannel(channelName) + static final MethodChannel channel = const MethodChannel(channelName) ..setMethodCallHandler(handleMethodCall); static final Uuid _uuid = Uuid(); @@ -503,6 +537,25 @@ class Audio with WidgetsBindingObserver { } } + /// Sets the iOS audio category. + /// + /// Only communicates with the underlying plugin on iOS; no-op otherwise. + static Future setIosAudioCategory(IosAudioCategory category) async { + const Map categoryToString = + { + IosAudioCategory.ambientSolo: iosAudioCategoryAmbientSolo, + IosAudioCategory.ambientMixed: iosAudioCategoryAmbientMixed, + IosAudioCategory.playback: iosAudioCategoryPlayback + }; + if (!Platform.isIOS) return; + try { + await channel.invokeMethod(iosAudioCategoryMethod, + {iosAudioCategoryKey: categoryToString[category]}); + } on PlatformException catch (e) { + _logger.severe('setIosAudioCategory error, category: $category', e); + } + } + /// Sends method call for starting playback. Future _playNative(bool playFromStart, double endpointSeconds) async { try { diff --git a/packages/audiofileplayer/pubspec.yaml b/packages/audiofileplayer/pubspec.yaml index d9c443c..68ca123 100644 --- a/packages/audiofileplayer/pubspec.yaml +++ b/packages/audiofileplayer/pubspec.yaml @@ -1,6 +1,6 @@ name: audiofileplayer description: A Flutter plugin for audio playback. -version: 1.0.1 +version: 1.0.2 homepage: https://github.com/google/flutter.plugins/tree/master/packages/audiofileplayer environment: