alnitak / flutter_soloud

Flutter low-level audio plugin using SoLoud C++ library and FFI
MIT License
156 stars 15 forks source link

fix: Init error on hot restart #81

Closed daniloapr closed 1 month ago

daniloapr commented 2 months ago

Description

I'm instantiating the SoLoud.instance in a singleton class and calling init() when the singleton's constructor. When I hot reload the application, it throws a SoLoudPlayerAlreadyInitializedException (logs below). However, when I try to load assets, it throws the exception SoLoudNotInitializedException. This is preventing me from using the library.

Steps To Reproduce

  1. Create a singleton class and call SoLoud.instance.init()
  2. Hot restart the app

Expected Behavior

The previous instance should be refreshed and no init() error should occur

Logs

flutter: FINEST: 2024-04-27 12:22:15.502315: initialize() called
flutter: INFO: 2024-04-27 12:22:15.526642: Temporary directory initialized at /var/mobile/Containers/Data/Application/843B8881-FF5A-4429-AE75-B41CF54866E8/Library/Caches/SoLoudLoader-Temp-Files
flutter: FINEST: 2024-04-27 12:22:15.694999: _initEngine() called
flutter: FINEST: 2024-04-27 12:22:15.704450: main isolate received: {event: MessageEvents.initEngine, args: (), return: PlayerErrors.playerAlreadyInited (The player has already been inited!)}
flutter: SEVERE: 2024-04-27 12:22:15.706821: _initEngine() result: PlayerErrors.playerAlreadyInited (The player has already been inited!)
flutter: SEVERE: 2024-04-27 12:22:15.709483: _initEngine() failed with error: PlayerErrors.playerAlreadyInited (The player has already been inited!)
SoLoudNotInitializedException: SoLoud has not been initialized yet. Call `SoLoud.initialize()` first and await it, or await the `SoLoud.initialized` Future elsewhere. Alternately, you can check the synchronous `SoLoud.isInitialized` bool just before calling any SoLoud method

Code

// on app initialization
GetIt.instance.registerSingleton<SoLoudImpl>(SoLoudImpl());
import 'package:flutter/foundation.dart';
import 'package:flutter_soloud/flutter_soloud.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

class SoLoudImpl {
  final player = SoLoud.instance;

  SoLoudImpl() {
    try {
      player.init();
    } catch (e) {
      debugPrint(e.toString());
      Sentry.captureException(e);
    }
  }

  AudioSource? beep;

  /// Load assets
  Future<void> init() async {
    try {
      beep = await player.loadAsset(Audios.normalBeep);
    } catch (e) {
      debugPrint(e.toString());
    }
  }

  void dispose() {
    player.disposeAllSources();
  }

  void _play(AudioSource? source) {
    assert(source != null, 'AudioSource not initialized');
    if (source != null) {
      player.play(source);
    }
  }

  @override
  void playBeep() {
    _play(beep);
  }
}
alnitak commented 2 months ago

Hi @daniloapr, thanks for your findings!

I did some tests and figured out that there was a problem when doing a hot reload restart. This is a complete minimal example done with your code:

code:
```dart import 'dart:developer' as dev; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_soloud/flutter_soloud.dart'; import 'package:logging/logging.dart'; void main() async { Logger.root.level = kDebugMode ? Level.FINE : Level.INFO; Logger.root.onRecord.listen((record) { dev.log( record.message, time: record.time, level: record.level.value, name: record.loggerName, zone: record.zone, error: record.error, stackTrace: record.stackTrace, ); }); WidgetsFlutterBinding.ensureInitialized(); runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => _MyAppState(); } class _MyAppState extends State { late AudioSource sound; final impl = SoLoudImpl.instance; @override void dispose() { impl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( themeMode: ThemeMode.dark, darkTheme: ThemeData.dark(useMaterial3: true), home: Scaffold( body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ OutlinedButton( onPressed: impl.init, child: const Text('init'), ), OutlinedButton( onPressed: impl.playBeep, child: const Text('beep'), ), ], ), ), ), ); } } class SoLoudImpl { SoLoudImpl._() { try { /// You should fix this because the `init()` must be awaited player.init(); } catch (e) { debugPrint(e.toString()); } } static final SoLoudImpl instance = SoLoudImpl._(); final player = SoLoud.instance; AudioSource? beep; Future init() async { try { beep = await player.loadAsset('assets/audio/explosion.mp3'); } catch (e) { debugPrint(e.toString()); } } void dispose() { /// Instead of `disposeAllSources()` I would use `deinit()` which also disposes all sources // player.disposeAllSources(); player.deinit(); } void _play(AudioSource? source) { assert(source != null, 'AudioSource not initialized'); if (source != null) { player.play(source); } } void playBeep() { _play(beep); } } ```

A side note on the code:

I did a change in the plugin. Can you please try it and tell me if it works for you? You can try by replacing

dependencies:
  flutter_soloud: ^2.0.0

with

dependencies:
  flutter_soloud:
    git:
      url: https://github.com/alnitak/flutter_soloud.git
      ref: dev
novas1r1 commented 2 months ago

Hey, I had the same initializing error and with your new version it worked :)! Thanks for the fast fix!

alnitak commented 1 month ago

I have pushed version 2.0.1 with the fix for this issue. Also, thanks @novas1r1 for the feedback.