Add disposePlayer to platform interface. Retain player.position after dispose.

This commit is contained in:
Ryan Heise 2020-10-10 15:46:14 +11:00
parent 35a6e4810b
commit ad9e4518cf
11 changed files with 87 additions and 97 deletions

View File

@ -37,17 +37,15 @@
FlutterResult _playResult;
id _timeObserver;
BOOL _automaticallyWaitsToMinimizeStalling;
BOOL _configuredSession;
BOOL _playing;
float _speed;
}
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar playerId:(NSString*)idParam configuredSession:(BOOL)configuredSession {
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)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, *)) {}

View File

@ -5,7 +5,7 @@
@implementation JustAudioPlugin {
NSObject<FlutterPluginRegistrar>* _registrar;
BOOL _configuredSession;
NSMutableDictionary<NSString *, AudioPlayer *> *_players;
}
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)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

View File

@ -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

View File

@ -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

View File

@ -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"))
}

View File

@ -2,7 +2,8 @@
@interface AudioPlayer : NSObject<FlutterStreamHandler>
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar playerId:(NSString*)idParam configuredSession:(BOOL)configuredSession;
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar playerId:(NSString*)idParam;
- (void)dispose;
@end

View File

@ -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<void> 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();

View File

@ -2,7 +2,8 @@
@interface AudioPlayer : NSObject<FlutterStreamHandler>
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar playerId:(NSString*)idParam configuredSession:(BOOL)configuredSession;
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar playerId:(NSString*)idParam;
- (void)dispose;
@end

View File

@ -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

View File

@ -41,6 +41,11 @@ abstract class JustAudioPlatform extends PlatformInterface {
Future<AudioPlayerPlatform> init(InitRequest request) {
throw UnimplementedError('init() has not been implemented.');
}
/// Disposes of a platform player.
Future<DisposePlayerResponse> 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<PlaybackEventMessage> 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<DisposeResponse> 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<dynamic, dynamic> toMap() => {
'id': id,
};
}
class DisposePlayerResponse {
static DisposePlayerResponse fromMap(Map<dynamic, dynamic> map) =>
DisposePlayerResponse();
}
class LoadRequest {
final AudioSourceMessage audioSourceMessage;

View File

@ -13,15 +13,22 @@ class MethodChannelJustAudio extends JustAudioPlatform {
await _mainChannel.invokeMethod('init', request.toMap());
return MethodChannelAudioPlayer(request.id);
}
@override
Future<DisposePlayerResponse> 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<PlaybackEventMessage> get playbackEventMessageStream =>