From 95e69100a7ec9a835f8d2f0152e1d019cc7bff67 Mon Sep 17 00:00:00 2001 From: Filip Hracek Date: Wed, 20 Mar 2024 15:23:17 +0100 Subject: [PATCH 1/3] Deprecate shutdown() --- example/lib/main.dart | 2 +- example/lib/main_wav_stream.dart | 2 +- example/tests/tests.dart | 15 +- lib/fix_data.yaml | 11 ++ lib/src/exceptions/exceptions_from_dart.dart | 16 +- lib/src/soloud.dart | 149 ++++--------------- test_fixes/soloud_disposal.dart | 1 + test_fixes/soloud_disposal.dart.expect | 5 +- 8 files changed, 59 insertions(+), 142 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index d56927a..d38801f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -55,7 +55,7 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { @override void dispose() { - SoLoud.instance.shutdown(); + SoLoud.instance.deinit(); super.dispose(); } diff --git a/example/lib/main_wav_stream.dart b/example/lib/main_wav_stream.dart index f33b919..01fc5f1 100644 --- a/example/lib/main_wav_stream.dart +++ b/example/lib/main_wav_stream.dart @@ -110,7 +110,7 @@ class _MyHomePageState extends State { } Future stop() async { - await SoLoud.instance.shutdown(); + SoLoud.instance.deinit(); SoLoudCapture.instance.stopCapture(); sounds.clear(); } diff --git a/example/tests/tests.dart b/example/tests/tests.dart index cca0c8a..ed7be48 100644 --- a/example/tests/tests.dart +++ b/example/tests/tests.dart @@ -152,7 +152,7 @@ Future test6() async { 'The protected song has been stopped!', ); - await dispose(); + dispose(); } /// Test allInstancesFinished stream @@ -203,7 +203,7 @@ Future test5() async { assert(explosionDisposed, "Explosion sound wasn't disposed."); assert(songDisposed, "Song sound wasn't disposed."); - await dispose(); + dispose(); } /// Test synchronous `deinit()` @@ -333,7 +333,7 @@ Future test3() async { await delay(300); } - await dispose(); + dispose(); } /// Test play, pause, seek, position @@ -365,7 +365,7 @@ Future test2() async { assert(position == wantedPosition, 'getPosition() failed!'); } - await dispose(); + dispose(); } /// Test start/stop isolate, load, play and events from sound @@ -420,7 +420,7 @@ Future test1() async { { /// Stop player and see in log: /// "@@@@@@@@@@@ SOUND EVENT: SoundEvent.soundDisposed .*" - await dispose(); + dispose(); assert( output == 'SoundEvent.soundDisposed', 'Sound end playback event not triggered!', @@ -433,9 +433,8 @@ Future initialize() async { await SoLoud.instance.initialize(); } -Future dispose() async { - final ret = await SoLoud.instance.shutdown(); - assert(ret, 'dispose() failed!'); +void dispose() { + SoLoud.instance.deinit(); } Future loadAsset() async { diff --git a/lib/fix_data.yaml b/lib/fix_data.yaml index 231dd9c..1720c45 100644 --- a/lib/fix_data.yaml +++ b/lib/fix_data.yaml @@ -9,6 +9,17 @@ version: 1 transforms: + # SoLoud.shutdown => SoLoud.deinit + - title: "Rename to 'deinit'" + date: 2024-03-11 + element: + uris: [ 'flutter_soloud.dart', 'package:flutter_soloud/flutter_soloud.dart' ] + method: 'shutdown' + inClass: 'SoLoud' + changes: + - kind: 'rename' + newName: 'deinit' + # AudioSource.handle => AudioSource.handles - title: "Rename to 'handles'" date: 2024-03-11 diff --git a/lib/src/exceptions/exceptions_from_dart.dart b/lib/src/exceptions/exceptions_from_dart.dart index e409ea1..884c91f 100644 --- a/lib/src/exceptions/exceptions_from_dart.dart +++ b/lib/src/exceptions/exceptions_from_dart.dart @@ -38,18 +38,14 @@ class SoLoudInitializationTimedOutException extends SoLoudDartException { String get description => 'The engine took too long to initialize.'; } -/// An exception that is thrown when the SoLoud engine fails to shutdown. -/// This is not thrown during normal shutdown, but it _can_ be thrown if -/// `initialize()` was called at a time of shutdown, and that shutdown failed. -class ShutdownFailedException extends SoLoudDartException { - /// Creates a new [ShutdownFailedException]. - const ShutdownFailedException([super.message]); +/// An exception that is thrown when the SoLoud engine initialization +/// is cut short by a call to `SoLoud.deinit()`. +class SoLoudInitializationStoppedByDeinitException extends SoLoudDartException { + /// Creates a new [SoLoudInitializationStoppedByDeinitException]. + const SoLoudInitializationStoppedByDeinitException([super.message]); @override - String get description => 'The engine failed to shut down. ' - 'This is not thrown during normal shutdown, but it _can_ be thrown if ' - '`initialize()` was called at a time of a shutdown in progress, ' - 'and that shutdown failed.'; + String get description => 'SoLoud.deinit() was called during initialization.'; } /// An exception that is thrown when the temporary folder fails to be created diff --git a/lib/src/soloud.dart b/lib/src/soloud.dart index 5f6c7c7..35eef81 100644 --- a/lib/src/soloud.dart +++ b/lib/src/soloud.dart @@ -148,11 +148,6 @@ interface class SoLoud { /// This is `null` when the engine is not currently being initialized. Completer? _initializeCompleter; - /// The completer for a shutdown in progress. - /// - /// This is `null` when the engine is not currently being shut down. - Completer? _shutdownCompleter; - /// A [Future] that returns `true` when the audio engine is initialized /// (and ready to play sounds, for example). /// @@ -222,8 +217,10 @@ interface class SoLoud { /// more general initialization process, this field is only an internal /// control mechanism. Users should use [initialized] instead. /// - /// The field is useful in [disposeAllSound], which is called from [shutdown] + /// The field is useful in [disposeAllSound], which is called from `shutdown` /// (so [isInitialized] is already `false` at that point). + /// + // TODO(filiph): check if still needed bool _isEngineInitialized = false; /// status of capture @@ -305,11 +302,6 @@ interface class SoLoud { /// unnecessary, as the amount of data will be finite. /// The default is `false`. /// - /// It is safe to call this function even if the engine is currently being - /// shut down. In that case, the function will wait for [shutdown] - /// to properly complete before initializing again. In case the shutting - /// down doesn't complete succesfully, [ShutdownFailedException] is thrown. - /// /// (This method was formerly called `startIsolate()`.) Future initialize({ Duration timeout = const Duration(seconds: 10), @@ -339,19 +331,6 @@ interface class SoLoud { final completer = Completer(); _initializeCompleter = completer; - if (_shutdownCompleter != null) { - // We are in the middle of shutting down the engine. - // We should wait for that to complete before initializing again. - final success = await _shutdownCompleter!.future; - if (!success) { - // The engine failed to shut down. We can't initialize it. - _initializeCompleter = null; - throw const ShutdownFailedException( - 'initialize() called while the engine is shutting down ' - 'but the shutdown failed'); - } - } - _isolateToMainStream = ReceivePort(); _returnedEvent = StreamController.broadcast(); @@ -366,8 +345,23 @@ interface class SoLoud { '_isInitialized should be false at this point. ' 'There might be a bug in the code that tries to prevent ' 'multiple concurrent initializations.'); - assert(_initializeCompleter == completer, - '_initializeCompleter has been reassigned during initialization'); + if (_initializeCompleter == null) { + _log.warning( + '_initializeCompleter was set to null during initialization. ' + 'This might mean that deinit() was called while the engine ' + 'was still being initialized.'); + _cleanUpUnsuccessfulInitialization(); + assert(completer.isCompleted, + 'Deinit() should have completed the future'); + return; + } + + assert( + _initializeCompleter == completer, + '_initializeCompleter has been reassigned ' + 'during initialization. This is probably a bug in ' + 'the flutter_soloud package. There should always be at most ' + 'one _initializeCompleter running at any given time.'); if (error == PlayerErrors.noError) { // ignore: deprecated_member_use_from_same_package @@ -478,95 +472,12 @@ interface class SoLoud { @Deprecated('use dispose() instead') Future stopIsolate() => shutdown(); - /// Stops the engine and disposes of all resources, including sounds - /// and the audio isolate. - /// - /// Returns `true` when everything has been disposed. Returns `false` - /// if there was nothing to dispose (e.g. the engine hasn't ever been - /// successfully initialized). - /// - /// It is safe to call this function even if the engine is currently being - /// initialized. In that case, the function will wait for [initialize] - /// to properly complete before shutting down. - /// - /// (This method was formerly called `stopIsolate()`.) + /// Deprecated alias of [deinit]. + @Deprecated("Use 'deinit()' instead") Future shutdown() async { _log.finest('shutdown() called'); - - if (_initializeCompleter != null) { - // We are in the middle of initializing the engine. - // We should wait for that to complete before disposing. - assert(!_isInitialized, - '_isInitialized should be false before initialization completes'); - try { - await _initializeCompleter!.future; - } on SoLoudException catch (e) { - // The engine failed to initialize. Nothing to shut down. - _log.warning( - 'shutdown() called while the engine is initializing ' - 'but the initialization failed.', - e); - assert(!_isInitialized, - '_isInitialized should be false when initialization fails'); - return false; - } - } - - if (_shutdownCompleter != null) { - // We are already in the middle of shutting down the engine. - assert( - !_isInitialized, '_isInitialized should be false when shutting down'); - return _shutdownCompleter!.future; - } - - if (!_isInitialized) { - // The engine isn't initialized. - _log.warning('shutdown() called when the engine is not initialized'); - return false; - } - - _isInitialized = false; - - final completer = Completer(); - _shutdownCompleter = completer; - - try { - await disposeAllSound(); - } on SoLoudException catch (e) { - _log.severe('disposeAllSound() failed during shutdown', e); - } - - try { - await _stopLoop(); - } on SoLoudException catch (e) { - _log.severe('stopLoop() failed during shutdown', e); - } - - // Engine will be disposed below when the audio isolate exits, - // so just set this variable to false. - _isEngineInitialized = false; - _mainToIsolateStream?.send( - { - 'event': MessageEvents.exitIsolate, - 'args': (), - }, - ); - await _waitForEvent(MessageEvents.exitIsolate, ()); - await _returnedEvent?.close(); - _returnedEvent = null; - _isolateToMainStream?.close(); - _isolateToMainStream = null; - _isolate?.kill(); - _isolate = null; - // ignore: deprecated_member_use_from_same_package - audioEvent.add(AudioEvent.isolateStopped); - - assert(_shutdownCompleter == completer, - '_shutdownCompleter has been reassigned'); - _shutdownCompleter = null; - - completer.complete(true); - return completer.future; + deinit(); + return true; } /// Stops the engine and disposes of all resources, including sounds @@ -576,14 +487,14 @@ interface class SoLoud { /// within the `dispose()` of the uppermost widget in the tree /// or inside [AppLifecycleListener.onExitRequested]. /// - /// During the normal app life cycle and if you want to shutdown the player, - /// please use [shutdown] which safer and it is meant to throw errors. + /// (This method was formerly called `stopIsolate()`.) void deinit() { _log.finest('deinit() called'); /// check if we are in the middle of an initialization. if (_initializeCompleter != null) { - _initializeCompleter?.complete(); + _initializeCompleter + ?.completeError(const SoLoudInitializationStoppedByDeinitException()); _initializeCompleter = null; } @@ -1187,9 +1098,7 @@ interface class SoLoud { /// Disposes all sounds already loaded. Complete silence. /// /// No need to call this method when shutting down the engine. - /// (It is automatically called from within [shutdown].) /// Throws [SoLoudNotInitializedException] if the engine is not initialized. - /// Future disposeAllSound() async { if (!_isEngineInitialized) { throw const SoLoudNotInitializedException(); @@ -1450,8 +1359,8 @@ interface class SoLoud { /// This will most likely be your background music. This can be worked /// around by protecting the sound. /// If all voices are protected, the result will be undefined. - /// The number of protected entries is inclusive in the - /// max number active voice count [getMaxActiveVoiceCount]. + /// The number of protected entries is inclusive in the + /// max number active voice count [getMaxActiveVoiceCount]. /// For example when having 16 max active voice count set to 16, and /// you want to play 20 other sounds, the protected voice will still play /// but you will hear only 15 of the other 20. diff --git a/test_fixes/soloud_disposal.dart b/test_fixes/soloud_disposal.dart index c12bb73..29db4e0 100644 --- a/test_fixes/soloud_disposal.dart +++ b/test_fixes/soloud_disposal.dart @@ -3,4 +3,5 @@ import 'package:flutter_soloud/flutter_soloud.dart'; Future main() async { await SoLoud.instance.stopIsolate(); await SoLoud.instance.dispose(); + await SoLoud.instance.shutdown(); } diff --git a/test_fixes/soloud_disposal.dart.expect b/test_fixes/soloud_disposal.dart.expect index 7602b67..b89b580 100644 --- a/test_fixes/soloud_disposal.dart.expect +++ b/test_fixes/soloud_disposal.dart.expect @@ -1,6 +1,7 @@ import 'package:flutter_soloud/flutter_soloud.dart'; Future main() async { - await SoLoud.instance.shutdown(); - await SoLoud.instance.shutdown(); + SoLoud.instance.deinit(); + SoLoud.instance.deinit(); + SoLoud.instance.deinit(); } From 2ef753092fc21c8c435b3a650439b7280d86bf0e Mon Sep 17 00:00:00 2001 From: Filip Hracek Date: Wed, 20 Mar 2024 15:26:35 +0100 Subject: [PATCH 2/3] Rename initialize() to init() --- example/lib/main.dart | 2 +- example/lib/main_wav_stream.dart | 2 +- example/tests/tests.dart | 10 ++++---- lib/fix_data.yaml | 13 ++++++++++- lib/src/soloud.dart | 24 +++++++++++++------- test_fixes/soloud_initialization.dart | 1 + test_fixes/soloud_initialization.dart.expect | 3 ++- 7 files changed, 38 insertions(+), 17 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index d38801f..6f8b97e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -30,7 +30,7 @@ void main() async { }); /// Initialize the player - await SoLoud.instance.initialize().then( + await SoLoud.instance.init().then( (_) { Logger('main').info('player started'); SoLoud.instance.setVisualizationEnabled(true); diff --git a/example/lib/main_wav_stream.dart b/example/lib/main_wav_stream.dart index 01fc5f1..d98cfdc 100644 --- a/example/lib/main_wav_stream.dart +++ b/example/lib/main_wav_stream.dart @@ -70,7 +70,7 @@ class _MyHomePageState extends State { Future start() async { try { - await SoLoud.instance.initialize(); + await SoLoud.instance.init(); } catch (e) { debugPrint('isolate starting error: $e'); return; diff --git a/example/tests/tests.dart b/example/tests/tests.dart index ed7be48..a2b371a 100644 --- a/example/tests/tests.dart +++ b/example/tests/tests.dart @@ -212,7 +212,7 @@ Future test4() async { for (var t = 100; t >= 0; t -= 5) { /// Initialize the player var error = ''; - await SoLoud.instance.initialize().then( + await SoLoud.instance.init().then( (_) {}, onError: (Object e) { e = 'TEST FAILED delay: $t. Player starting error: $e'; @@ -242,7 +242,7 @@ Future test4() async { /// waiting for `initialize()` to finish for (var t = 50; t >= 0; t -= 2) { /// Initialize the player - unawaited(SoLoud.instance.initialize()); + unawaited(SoLoud.instance.init()); /// wait for [t] ms and deinit() await Future.delayed(Duration(milliseconds: t), () {}); @@ -258,7 +258,7 @@ Future test4() async { } /// Try init-play-deinit and again init-play without disposing the sound - await SoLoud.instance.initialize(); + await SoLoud.instance.init(); await loadAsset(); await SoLoud.instance.play(currentSound!); @@ -273,7 +273,7 @@ Future test4() async { /// Initialize again and check if the sound has been /// disposed correctly by `deinit()` - await SoLoud.instance.initialize(); + await SoLoud.instance.init(); assert( SoLoudController() .soLoudFFI @@ -430,7 +430,7 @@ Future test1() async { /// Common methods Future initialize() async { - await SoLoud.instance.initialize(); + await SoLoud.instance.init(); } void dispose() { diff --git a/lib/fix_data.yaml b/lib/fix_data.yaml index 1720c45..d62a638 100644 --- a/lib/fix_data.yaml +++ b/lib/fix_data.yaml @@ -9,9 +9,20 @@ version: 1 transforms: + # SoLoud.initialize => SoLoud.init + - title: "Rename to 'init'" + date: 2024-03-20 + element: + uris: [ 'flutter_soloud.dart', 'package:flutter_soloud/flutter_soloud.dart' ] + method: 'initialize' + inClass: 'SoLoud' + changes: + - kind: 'rename' + newName: 'init' + # SoLoud.shutdown => SoLoud.deinit - title: "Rename to 'deinit'" - date: 2024-03-11 + date: 2024-03-20 element: uris: [ 'flutter_soloud.dart', 'package:flutter_soloud/flutter_soloud.dart' ] method: 'shutdown' diff --git a/lib/src/soloud.dart b/lib/src/soloud.dart index 35eef81..47d0629 100644 --- a/lib/src/soloud.dart +++ b/lib/src/soloud.dart @@ -167,14 +167,14 @@ interface class SoLoud { /// or it had failed to initialize (`false`), /// or it was already shut down (`false`), /// or it is _being_ shut down (`false`), - /// or when there wasn't ever a call to [initialize] at all (`false`). + /// or when there wasn't ever a call to [init] at all (`false`). /// /// If the engine is in the middle of initializing, the future will complete /// when the initialization is done. It will be `true` if the initialization /// was successful, and `false` if it failed. The future will never throw. /// - /// It is _not_ needed to await this future after a call to [initialize]. - /// The [initialize] method already returns a future, and it is the + /// It is _not_ needed to await this future after a call to [init]. + /// The [init] method already returns a future, and it is the /// same future that this getter returns. /// /// ```dart @@ -271,10 +271,18 @@ interface class SoLoud { /// Initializes the audio engine. /// - /// Use [initialize] instead. This method is simply an alias for [initialize] + /// Use [init] instead. This method is simply an alias for [init] /// for backwards compatibility. It will be removed in a future version. - @Deprecated('use initialize() instead') - Future startIsolate() => initialize(); + @Deprecated('use init() instead') + Future startIsolate() => init(); + + /// Deprecated alias of [init]. + @Deprecated("Use 'init()' instead") + Future initialize({ + Duration timeout = const Duration(seconds: 10), + bool automaticCleanup = false, + }) => + init(timeout: timeout, automaticCleanup: automaticCleanup); /// Initializes the audio engine. /// @@ -303,7 +311,7 @@ interface class SoLoud { /// The default is `false`. /// /// (This method was formerly called `startIsolate()`.) - Future initialize({ + Future init({ Duration timeout = const Duration(seconds: 10), bool automaticCleanup = false, }) async { @@ -571,7 +579,7 @@ interface class SoLoud { /// A deprecated method that manually starts the engine. /// - /// Do not use. The engine is fully started with [initialize]. + /// Do not use. The engine is fully started with [init]. /// This method will be removed in a future version. @Deprecated('Use initialize() instead') Future initEngine() => _initEngine(); diff --git a/test_fixes/soloud_initialization.dart b/test_fixes/soloud_initialization.dart index 9aff22b..6885aae 100644 --- a/test_fixes/soloud_initialization.dart +++ b/test_fixes/soloud_initialization.dart @@ -3,4 +3,5 @@ import 'package:flutter_soloud/flutter_soloud.dart'; Future main() async { final soloud = SoLoud(); await soloud.startIsolate(); + await soloud.initialize(); } diff --git a/test_fixes/soloud_initialization.dart.expect b/test_fixes/soloud_initialization.dart.expect index eca434c..ed34b20 100644 --- a/test_fixes/soloud_initialization.dart.expect +++ b/test_fixes/soloud_initialization.dart.expect @@ -2,5 +2,6 @@ import 'package:flutter_soloud/flutter_soloud.dart'; Future main() async { final soloud = SoLoud(); - await soloud.initialize(); + await soloud.init(); + await soloud.init(); } From 7dbf7303f34f64d38446b4cf3d0404ba36c88557 Mon Sep 17 00:00:00 2001 From: Filip Hracek Date: Wed, 20 Mar 2024 15:28:31 +0100 Subject: [PATCH 3/3] Update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbf8c73..286d7fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,12 @@ to `2.0.0-pre.2` and beyond. ); soloud.play(source); ``` + +- Deprecated `shutdown()`. Replaced with the synchronous `deinit()`. + Quick fix available. +- Renamed `initialize()` to `init()`, in order to come closer to the original + C++ API, and also to have a symmetry (`init`/`deinit`). + Quick fix available. #### 2.0.0-pre.1 (12 Mar 2024) - added `looping` and `loopingStartAt` properties to `SoLoud.play()` and `SoLoud.play3d()`.