From ad9e4518cf69f55df50c3e1105d0d1635c898085 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Sat, 10 Oct 2020 15:46:14 +1100 Subject: [PATCH] Add disposePlayer to platform interface. Retain player.position after dispose. --- just_audio/darwin/Classes/AudioPlayer.m | 12 +-- just_audio/darwin/Classes/JustAudioPlugin.m | 19 ++++- just_audio/example/ios/Podfile | 76 ++++--------------- just_audio/example/ios/Podfile.lock | 14 +--- .../Flutter/GeneratedPluginRegistrant.swift | 4 +- just_audio/ios/Classes/AudioPlayer.h | 3 +- just_audio/lib/just_audio.dart | 9 ++- just_audio/macos/Classes/AudioPlayer.h | 3 +- just_audio/pubspec.yaml | 4 +- .../lib/just_audio_platform_interface.dart | 27 +++++++ .../lib/method_channel_just_audio.dart | 13 +++- 11 files changed, 87 insertions(+), 97 deletions(-) diff --git a/just_audio/darwin/Classes/AudioPlayer.m b/just_audio/darwin/Classes/AudioPlayer.m index 4812daf..ba660d5 100644 --- a/just_audio/darwin/Classes/AudioPlayer.m +++ b/just_audio/darwin/Classes/AudioPlayer.m @@ -37,17 +37,15 @@ FlutterResult _playResult; id _timeObserver; BOOL _automaticallyWaitsToMinimizeStalling; - BOOL _configuredSession; BOOL _playing; float _speed; } -- (instancetype)initWithRegistrar:(NSObject *)registrar playerId:(NSString*)idParam configuredSession:(BOOL)configuredSession { +- (instancetype)initWithRegistrar:(NSObject *)registrar playerId:(NSString*)idParam { self = [super init]; NSAssert(self, @"super init cannot be nil"); _registrar = registrar; _playerId = idParam; - _configuredSession = configuredSession; _methodChannel = [FlutterMethodChannel methodChannelWithName:[NSMutableString stringWithFormat:@"com.ryanheise.just_audio.methods.%@", _playerId] binaryMessenger:[registrar messenger]]; @@ -113,9 +111,6 @@ [self seek:position index:request[@"index"] completionHandler:^(BOOL finished) { result(@{}); }]; - } else if ([@"dispose" isEqualToString:call.method]) { - [self dispose]; - result(@{}); } else if ([@"concatenatingInsertAll" isEqualToString:call.method]) { [self concatenatingInsertAll:(NSString *)request[@"id"] index:[request[@"index"] intValue] sources:(NSArray *)request[@"children"]]; result(@{}); @@ -908,11 +903,6 @@ _playResult = result; } _playing = YES; -#if TARGET_OS_IPHONE - if (_configuredSession) { - [[AVAudioSession sharedInstance] setActive:YES error:nil]; - } -#endif _player.rate = _speed; [self updatePosition]; if (@available(macOS 10.12, iOS 10.0, *)) {} diff --git a/just_audio/darwin/Classes/JustAudioPlugin.m b/just_audio/darwin/Classes/JustAudioPlugin.m index d38418f..db5663b 100644 --- a/just_audio/darwin/Classes/JustAudioPlugin.m +++ b/just_audio/darwin/Classes/JustAudioPlugin.m @@ -5,7 +5,7 @@ @implementation JustAudioPlugin { NSObject* _registrar; - BOOL _configuredSession; + NSMutableDictionary *_players; } + (void)registerWithRegistrar:(NSObject*)registrar { @@ -20,6 +20,7 @@ self = [super init]; NSAssert(self, @"super init cannot be nil"); _registrar = registrar; + _players = [[NSMutableDictionary alloc] init]; return self; } @@ -27,11 +28,25 @@ if ([@"init" isEqualToString:call.method]) { NSDictionary *request = (NSDictionary *)call.arguments; NSString *playerId = request[@"id"]; - /*AudioPlayer* player =*/ [[AudioPlayer alloc] initWithRegistrar:_registrar playerId:playerId configuredSession:_configuredSession]; + AudioPlayer* player = [[AudioPlayer alloc] initWithRegistrar:_registrar playerId:playerId]; + [_players setValue:player forKey:playerId]; result(nil); + } else if ([@"disposePlayer" isEqualToString:call.method]) { + NSDictionary *request = (NSDictionary *)call.arguments; + NSString *playerId = request[@"id"]; + [_players[playerId] dispose]; + [_players setValue:nil forKey:playerId]; + result(@{}); } else { result(FlutterMethodNotImplemented); } } +- (void)dealloc { + for (NSString *playerId in _players) { + [_players[playerId] dispose]; + } + [_players removeAllObjects]; +} + @end diff --git a/just_audio/example/ios/Podfile b/just_audio/example/ios/Podfile index 5a69b89..f7d6a5e 100644 --- a/just_audio/example/ios/Podfile +++ b/just_audio/example/ios/Podfile @@ -10,75 +10,29 @@ project 'Runner', { 'Release' => :release, } -def parse_KV_file(file, separator='=') - file_abs_path = File.expand_path(file) - if !File.exists? file_abs_path - return []; +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end - generated_key_values = {} - skip_line_start_symbols = ["#", "/"] - File.foreach(file_abs_path) do |line| - next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } - plugin = line.split(pattern=separator) - if plugin.length == 2 - podname = plugin[0].strip() - path = plugin[1].strip() - podpath = File.expand_path("#{path}", file_abs_path) - generated_key_values[podname] = podpath - else - puts "Invalid plugin specification: #{line}" - end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches end - generated_key_values + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + target 'Runner' do - # Flutter Pod - - copied_flutter_dir = File.join(__dir__, 'Flutter') - copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') - copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') - unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) - # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. - # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. - # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. - - generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') - unless File.exist?(generated_xcode_build_settings_path) - raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) - cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; - - unless File.exist?(copied_framework_path) - FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) - end - unless File.exist?(copied_podspec_path) - FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) - end - end - - # Keep pod path relative so it can be checked into Podfile.lock. - pod 'Flutter', :path => 'Flutter' - - # Plugin Pods - - # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock - # referring to absolute paths on developers' machines. - system('rm -rf .symlinks') - system('mkdir -p .symlinks/plugins') - plugin_pods = parse_KV_file('../.flutter-plugins') - plugin_pods.each do |name, path| - symlink = File.join('.symlinks', 'plugins', name) - File.symlink(path, symlink) - pod name, :path => File.join(symlink, 'ios') - end + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['ENABLE_BITCODE'] = 'NO' - end + flutter_additional_ios_build_settings(target) end end diff --git a/just_audio/example/ios/Podfile.lock b/just_audio/example/ios/Podfile.lock index 67d740d..af27c5e 100644 --- a/just_audio/example/ios/Podfile.lock +++ b/just_audio/example/ios/Podfile.lock @@ -6,18 +6,12 @@ PODS: - Flutter - path_provider (0.0.1): - Flutter - - path_provider_linux (0.0.1): - - Flutter - - path_provider_macos (0.0.1): - - Flutter DEPENDENCIES: - audio_session (from `.symlinks/plugins/audio_session/ios`) - Flutter (from `Flutter`) - just_audio (from `.symlinks/plugins/just_audio/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - - path_provider_linux (from `.symlinks/plugins/path_provider_linux/ios`) - - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) EXTERNAL SOURCES: audio_session: @@ -28,19 +22,13 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/just_audio/ios" path_provider: :path: ".symlinks/plugins/path_provider/ios" - path_provider_linux: - :path: ".symlinks/plugins/path_provider_linux/ios" - path_provider_macos: - :path: ".symlinks/plugins/path_provider_macos/ios" SPEC CHECKSUMS: audio_session: 4f3e461722055d21515cf3261b64c973c062f345 Flutter: 0e3d915762c693b495b44d77113d4970485de6ec just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c - path_provider_linux: 4d630dc393e1f20364f3e3b4a2ff41d9674a84e4 - path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 -PODFILE CHECKSUM: f32fb4e7c14f8b3ca19a369d7be425dd9241af27 +PODFILE CHECKSUM: 8e679eca47255a8ca8067c4c67aab20e64cb974d COCOAPODS: 1.9.3 diff --git a/just_audio/example/macos/Flutter/GeneratedPluginRegistrant.swift b/just_audio/example/macos/Flutter/GeneratedPluginRegistrant.swift index 0ae8b66..c891b67 100644 --- a/just_audio/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/just_audio/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,12 @@ import FlutterMacOS import Foundation -import path_provider_macos import audio_session import just_audio +import path_provider_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/just_audio/ios/Classes/AudioPlayer.h b/just_audio/ios/Classes/AudioPlayer.h index 1a985f3..42d81e1 100644 --- a/just_audio/ios/Classes/AudioPlayer.h +++ b/just_audio/ios/Classes/AudioPlayer.h @@ -2,7 +2,8 @@ @interface AudioPlayer : NSObject -- (instancetype)initWithRegistrar:(NSObject *)registrar playerId:(NSString*)idParam configuredSession:(BOOL)configuredSession; +- (instancetype)initWithRegistrar:(NSObject *)registrar playerId:(NSString*)idParam; +- (void)dispose; @end diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index b1dbe2a..949d8cb 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -324,7 +324,6 @@ class AudioPlayer { /// The current position of the player. Duration get position { - if (_disposed) return null; if (playing && processingState == ProcessingState.ready) { final result = _playbackEvent.updatePosition + (DateTime.now().difference(_playbackEvent.updateTime)) * speed; @@ -682,7 +681,13 @@ class AudioPlayer { Future dispose() async { if (_disposed) return; _disposed = true; - await (await _platform).dispose(DisposeRequest()); + try { + await JustAudioPlatform.instance + .disposePlayer(DisposePlayerRequest(id: _id)); + } catch (e) { + print("disposePlayer() not implemented. Falling back to dispose()"); + await (await _platform).dispose(DisposeRequest()); + } _audioSource = null; _audioSources.values.forEach((s) => s._dispose()); _audioSources.clear(); diff --git a/just_audio/macos/Classes/AudioPlayer.h b/just_audio/macos/Classes/AudioPlayer.h index d64e13d..1ce2109 100644 --- a/just_audio/macos/Classes/AudioPlayer.h +++ b/just_audio/macos/Classes/AudioPlayer.h @@ -2,7 +2,8 @@ @interface AudioPlayer : NSObject -- (instancetype)initWithRegistrar:(NSObject *)registrar playerId:(NSString*)idParam configuredSession:(BOOL)configuredSession; +- (instancetype)initWithRegistrar:(NSObject *)registrar playerId:(NSString*)idParam; +- (void)dispose; @end diff --git a/just_audio/pubspec.yaml b/just_audio/pubspec.yaml index 81dd931..f05363f 100644 --- a/just_audio/pubspec.yaml +++ b/just_audio/pubspec.yaml @@ -8,7 +8,9 @@ environment: flutter: ">=1.12.13+hotfix.5" dependencies: - just_audio_platform_interface: ^1.0.0 + # just_audio_platform_interface: ^1.0.0 + just_audio_platform_interface: + path: ../just_audio_platform_interface audio_session: ^0.0.7 rxdart: ^0.24.1 path: ^1.6.4 diff --git a/just_audio_platform_interface/lib/just_audio_platform_interface.dart b/just_audio_platform_interface/lib/just_audio_platform_interface.dart index 31efda9..16dd44c 100644 --- a/just_audio_platform_interface/lib/just_audio_platform_interface.dart +++ b/just_audio_platform_interface/lib/just_audio_platform_interface.dart @@ -41,6 +41,11 @@ abstract class JustAudioPlatform extends PlatformInterface { Future init(InitRequest request) { throw UnimplementedError('init() has not been implemented.'); } + + /// Disposes of a platform player. + Future disposePlayer(DisposePlayerRequest request) { + throw UnimplementedError('disposePlayer() has not been implemented.'); + } } /// A nested platform interface for communicating with a particular player @@ -53,6 +58,10 @@ abstract class JustAudioPlatform extends PlatformInterface { /// `implements` this interface will be broken by newly added /// [AudioPlayerPlatform] methods. abstract class AudioPlayerPlatform { + final String id; + + AudioPlayerPlatform(this.id); + Stream get playbackEventMessageStream { throw UnimplementedError( 'playbackEventMessageStream has not been implemented.'); @@ -103,6 +112,9 @@ abstract class AudioPlayerPlatform { "setAndroidAudioAttributes() has not been implemented."); } + /// This method has been superceded by [JustAudioPlatform.disposePlayer]. + /// For backward compatibility, this method will still be called as a + /// fallback if [JustAudioPlatform.disposePlayer] is not implemented. Future dispose(DisposeRequest request) { throw UnimplementedError("dispose() has not been implemented."); } @@ -244,6 +256,21 @@ class InitRequest { }; } +class DisposePlayerRequest { + final String id; + + DisposePlayerRequest({@required this.id}); + + Map toMap() => { + 'id': id, + }; +} + +class DisposePlayerResponse { + static DisposePlayerResponse fromMap(Map map) => + DisposePlayerResponse(); +} + class LoadRequest { final AudioSourceMessage audioSourceMessage; diff --git a/just_audio_platform_interface/lib/method_channel_just_audio.dart b/just_audio_platform_interface/lib/method_channel_just_audio.dart index ba053c3..c8b7a2a 100644 --- a/just_audio_platform_interface/lib/method_channel_just_audio.dart +++ b/just_audio_platform_interface/lib/method_channel_just_audio.dart @@ -13,15 +13,22 @@ class MethodChannelJustAudio extends JustAudioPlatform { await _mainChannel.invokeMethod('init', request.toMap()); return MethodChannelAudioPlayer(request.id); } + + @override + Future disposePlayer( + DisposePlayerRequest request) async { + return DisposePlayerResponse.fromMap( + await _mainChannel.invokeMethod('disposePlayer', request.toMap())); + } } /// An implementation of [AudioPlayerPlatform] that uses method channels. class MethodChannelAudioPlayer extends AudioPlayerPlatform { - final String id; final MethodChannel _channel; - MethodChannelAudioPlayer(this.id) - : _channel = MethodChannel('com.ryanheise.just_audio.methods.$id'); + MethodChannelAudioPlayer(String id) + : _channel = MethodChannel('com.ryanheise.just_audio.methods.$id'), + super(id); @override Stream get playbackEventMessageStream =>