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; FlutterResult _playResult;
id _timeObserver; id _timeObserver;
BOOL _automaticallyWaitsToMinimizeStalling; BOOL _automaticallyWaitsToMinimizeStalling;
BOOL _configuredSession;
BOOL _playing; BOOL _playing;
float _speed; float _speed;
} }
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar playerId:(NSString*)idParam configuredSession:(BOOL)configuredSession { - (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar playerId:(NSString*)idParam {
self = [super init]; self = [super init];
NSAssert(self, @"super init cannot be nil"); NSAssert(self, @"super init cannot be nil");
_registrar = registrar; _registrar = registrar;
_playerId = idParam; _playerId = idParam;
_configuredSession = configuredSession;
_methodChannel = _methodChannel =
[FlutterMethodChannel methodChannelWithName:[NSMutableString stringWithFormat:@"com.ryanheise.just_audio.methods.%@", _playerId] [FlutterMethodChannel methodChannelWithName:[NSMutableString stringWithFormat:@"com.ryanheise.just_audio.methods.%@", _playerId]
binaryMessenger:[registrar messenger]]; binaryMessenger:[registrar messenger]];
@ -113,9 +111,6 @@
[self seek:position index:request[@"index"] completionHandler:^(BOOL finished) { [self seek:position index:request[@"index"] completionHandler:^(BOOL finished) {
result(@{}); result(@{});
}]; }];
} else if ([@"dispose" isEqualToString:call.method]) {
[self dispose];
result(@{});
} else if ([@"concatenatingInsertAll" isEqualToString:call.method]) { } else if ([@"concatenatingInsertAll" isEqualToString:call.method]) {
[self concatenatingInsertAll:(NSString *)request[@"id"] index:[request[@"index"] intValue] sources:(NSArray *)request[@"children"]]; [self concatenatingInsertAll:(NSString *)request[@"id"] index:[request[@"index"] intValue] sources:(NSArray *)request[@"children"]];
result(@{}); result(@{});
@ -908,11 +903,6 @@
_playResult = result; _playResult = result;
} }
_playing = YES; _playing = YES;
#if TARGET_OS_IPHONE
if (_configuredSession) {
[[AVAudioSession sharedInstance] setActive:YES error:nil];
}
#endif
_player.rate = _speed; _player.rate = _speed;
[self updatePosition]; [self updatePosition];
if (@available(macOS 10.12, iOS 10.0, *)) {} if (@available(macOS 10.12, iOS 10.0, *)) {}

View File

@ -5,7 +5,7 @@
@implementation JustAudioPlugin { @implementation JustAudioPlugin {
NSObject<FlutterPluginRegistrar>* _registrar; NSObject<FlutterPluginRegistrar>* _registrar;
BOOL _configuredSession; NSMutableDictionary<NSString *, AudioPlayer *> *_players;
} }
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar { + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
@ -20,6 +20,7 @@
self = [super init]; self = [super init];
NSAssert(self, @"super init cannot be nil"); NSAssert(self, @"super init cannot be nil");
_registrar = registrar; _registrar = registrar;
_players = [[NSMutableDictionary alloc] init];
return self; return self;
} }
@ -27,11 +28,25 @@
if ([@"init" isEqualToString:call.method]) { if ([@"init" isEqualToString:call.method]) {
NSDictionary *request = (NSDictionary *)call.arguments; NSDictionary *request = (NSDictionary *)call.arguments;
NSString *playerId = request[@"id"]; 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); 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 { } else {
result(FlutterMethodNotImplemented); result(FlutterMethodNotImplemented);
} }
} }
- (void)dealloc {
for (NSString *playerId in _players) {
[_players[playerId] dispose];
}
[_players removeAllObjects];
}
@end @end

View File

@ -10,75 +10,29 @@ project 'Runner', {
'Release' => :release, 'Release' => :release,
} }
def parse_KV_file(file, separator='=') def flutter_root
file_abs_path = File.expand_path(file) generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
if !File.exists? file_abs_path unless File.exist?(generated_xcode_build_settings_path)
return []; raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end end
generated_key_values = {}
skip_line_start_symbols = ["#", "/"] File.foreach(generated_xcode_build_settings_path) do |line|
File.foreach(file_abs_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/)
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } return matches[1].strip if matches
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
end 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 end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do target 'Runner' do
# Flutter Pod flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
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
end end
post_install do |installer| post_install do |installer|
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|
target.build_configurations.each do |config| flutter_additional_ios_build_settings(target)
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end end
end end

View File

@ -6,18 +6,12 @@ PODS:
- Flutter - Flutter
- path_provider (0.0.1): - path_provider (0.0.1):
- Flutter - Flutter
- path_provider_linux (0.0.1):
- Flutter
- path_provider_macos (0.0.1):
- Flutter
DEPENDENCIES: DEPENDENCIES:
- audio_session (from `.symlinks/plugins/audio_session/ios`) - audio_session (from `.symlinks/plugins/audio_session/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- just_audio (from `.symlinks/plugins/just_audio/ios`) - just_audio (from `.symlinks/plugins/just_audio/ios`)
- path_provider (from `.symlinks/plugins/path_provider/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: EXTERNAL SOURCES:
audio_session: audio_session:
@ -28,19 +22,13 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/just_audio/ios" :path: ".symlinks/plugins/just_audio/ios"
path_provider: path_provider:
:path: ".symlinks/plugins/path_provider/ios" :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: SPEC CHECKSUMS:
audio_session: 4f3e461722055d21515cf3261b64c973c062f345 audio_session: 4f3e461722055d21515cf3261b64c973c062f345
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
path_provider_linux: 4d630dc393e1f20364f3e3b4a2ff41d9674a84e4
path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0
PODFILE CHECKSUM: f32fb4e7c14f8b3ca19a369d7be425dd9241af27 PODFILE CHECKSUM: 8e679eca47255a8ca8067c4c67aab20e64cb974d
COCOAPODS: 1.9.3 COCOAPODS: 1.9.3

View File

@ -5,12 +5,12 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import path_provider_macos
import audio_session import audio_session
import just_audio import just_audio
import path_provider_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
} }

View File

@ -2,7 +2,8 @@
@interface AudioPlayer : NSObject<FlutterStreamHandler> @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 @end

View File

@ -324,7 +324,6 @@ class AudioPlayer {
/// The current position of the player. /// The current position of the player.
Duration get position { Duration get position {
if (_disposed) return null;
if (playing && processingState == ProcessingState.ready) { if (playing && processingState == ProcessingState.ready) {
final result = _playbackEvent.updatePosition + final result = _playbackEvent.updatePosition +
(DateTime.now().difference(_playbackEvent.updateTime)) * speed; (DateTime.now().difference(_playbackEvent.updateTime)) * speed;
@ -682,7 +681,13 @@ class AudioPlayer {
Future<void> dispose() async { Future<void> dispose() async {
if (_disposed) return; if (_disposed) return;
_disposed = true; _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; _audioSource = null;
_audioSources.values.forEach((s) => s._dispose()); _audioSources.values.forEach((s) => s._dispose());
_audioSources.clear(); _audioSources.clear();

View File

@ -2,7 +2,8 @@
@interface AudioPlayer : NSObject<FlutterStreamHandler> @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 @end

View File

@ -8,7 +8,9 @@ environment:
flutter: ">=1.12.13+hotfix.5" flutter: ">=1.12.13+hotfix.5"
dependencies: 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 audio_session: ^0.0.7
rxdart: ^0.24.1 rxdart: ^0.24.1
path: ^1.6.4 path: ^1.6.4

View File

@ -41,6 +41,11 @@ abstract class JustAudioPlatform extends PlatformInterface {
Future<AudioPlayerPlatform> init(InitRequest request) { Future<AudioPlayerPlatform> init(InitRequest request) {
throw UnimplementedError('init() has not been implemented.'); 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 /// 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 /// `implements` this interface will be broken by newly added
/// [AudioPlayerPlatform] methods. /// [AudioPlayerPlatform] methods.
abstract class AudioPlayerPlatform { abstract class AudioPlayerPlatform {
final String id;
AudioPlayerPlatform(this.id);
Stream<PlaybackEventMessage> get playbackEventMessageStream { Stream<PlaybackEventMessage> get playbackEventMessageStream {
throw UnimplementedError( throw UnimplementedError(
'playbackEventMessageStream has not been implemented.'); 'playbackEventMessageStream has not been implemented.');
@ -103,6 +112,9 @@ abstract class AudioPlayerPlatform {
"setAndroidAudioAttributes() has not been implemented."); "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) { Future<DisposeResponse> dispose(DisposeRequest request) {
throw UnimplementedError("dispose() has not been implemented."); 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 { class LoadRequest {
final AudioSourceMessage audioSourceMessage; final AudioSourceMessage audioSourceMessage;

View File

@ -13,15 +13,22 @@ class MethodChannelJustAudio extends JustAudioPlatform {
await _mainChannel.invokeMethod('init', request.toMap()); await _mainChannel.invokeMethod('init', request.toMap());
return MethodChannelAudioPlayer(request.id); 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. /// An implementation of [AudioPlayerPlatform] that uses method channels.
class MethodChannelAudioPlayer extends AudioPlayerPlatform { class MethodChannelAudioPlayer extends AudioPlayerPlatform {
final String id;
final MethodChannel _channel; final MethodChannel _channel;
MethodChannelAudioPlayer(this.id) MethodChannelAudioPlayer(String id)
: _channel = MethodChannel('com.ryanheise.just_audio.methods.$id'); : _channel = MethodChannel('com.ryanheise.just_audio.methods.$id'),
super(id);
@override @override
Stream<PlaybackEventMessage> get playbackEventMessageStream => Stream<PlaybackEventMessage> get playbackEventMessageStream =>