flame icon indicating copy to clipboard operation
flame copied to clipboard

audio_pool calls stop() when player is not in currentPlayers

Open pgainullin opened this issue 2 years ago • 1 comments

Current bug behaviour

Error is triggered when stop() is called on a player that is no longer in memory.

Expected behaviour

stop() is only called on players in memory.

Steps to reproduce

Create a static class method which returns an audio_pool and call that instance many times over.

Flutter doctor output

[√] Flutter (Channel beta, 2.5.0-5.1.pre, on Microsoft Windows [Version 10.0.19043.1165], locale en-GB)
    • Flutter version 2.5.0-5.1.pre at C:\src\flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 65cf7de5f4 (8 days ago), 2021-08-11 15:48:01 -0700
    • Engine revision fbeb7e22bd
    • Dart version 2.14.0 (build 2.14.0-377.4.beta)

[√] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
    • Android SDK at C:\Users\Pjotr\AppData\Local\Android\Sdk
    • Platform android-31, build-tools 31.0.0
    • ANDROID_HOME = C:\Users\Pjotr\AppData\Local\Android\Sdk\
    • ANDROID_SDK_ROOT = C:\Users\Pjotr\AppData\Local\Android\Sdk\
    • Java binary at: C:\src\Android Studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7249189)
    • All Android licenses accepted.

[√] Chrome - develop for the web
    • Chrome at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe

[√] Visual Studio - develop for Windows (Visual Studio Community 2019 16.9.0)
    • Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
    • Visual Studio Community 2019 version 16.9.31025.194
    • Windows 10 SDK version 10.0.19041.0

[√] Android Studio (version 2020.3)
    • Android Studio at C:\src\Android Studio
    • Flutter plugin can be installed from:
       https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
       https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7249189)

[√] Connected device (3 available)
    • Windows (desktop) • windows • windows-x64    • Microsoft Windows [Version 10.0.19043.1165]
    • Chrome (web)      • chrome  • web-javascript • Google Chrome 92.0.4515.159
    • Edge (web)        • edge    • web-javascript • Microsoft Edge 92.0.902.73

• No issues found!

More environment information

  • Flame version: 0.29.3
  • Platform affected: android emulator

Log information

E/flutter ( 8365): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: NoSuchMethodError: The method 'stop' was called on null.
E/flutter ( 8365): Receiver: null
E/flutter ( 8365): Tried calling: stop()
E/flutter ( 8365): #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:63:5)
E/flutter ( 8365): #1      AudioPool.start.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:flame/audio_pool.dart:54:19)
E/flutter ( 8365): #2      AudioPool.start.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:flame/audio_pool.dart:51:28)
E/flutter ( 8365): #3      BasicLock.synchronized (package:synchronized/src/basic_lock.dart:32:26)

More information

In my project I solved this issue by forking audio_pool and rewriting it as follows. I'm not sure if my solution has performance issues, but if you are happy with it I'd be happy to create a PR based on it:

//@dart=2.13

import 'dart:async';

import 'package:audioplayers/audio_cache.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:synchronized/synchronized.dart';

// typedef void Stoppable();

/// An AudioPool is a provider of AudioPlayers that leaves them pre-loaded to minimize delays.
///
/// All AudioPlayers loaded are for the same [sound]. If you want multiple sounds use multiple [AudioPool].
/// Use this class if you'd like have extremely quick firing, repetitive and simultaneous sounds, like shooting a laser in a fast-paced spaceship game.
class ToAudioPool {
  late AudioCache cache;
  // Map<String, AudioPlayer> currentPlayers = {};
  Set<AudioPlayer> currentPlayers = {};
  Map<AudioPlayer, StreamSubscription<void>> subscriptions = {};
  Set<AudioPlayer> availablePlayers = {};

  String sound;
  bool repeating;
  int minPlayers, maxPlayers;

  final Lock _lock = Lock();

  ToAudioPool(this.sound,
      {this.repeating = false,
        this.maxPlayers = 1,
        this.minPlayers = 1,
        String prefix = 'assets/audio/sfx/'}) {
    cache = AudioCache(prefix: prefix);
  }

  Future init() async {
    for (int i = 0; i < minPlayers; i++) {
      availablePlayers.add(await _createNewAudioPlayer());
    }
  }

  Future<void> start({double volume = 1.0}) async {
    return _lock.synchronized(() async {
      if (availablePlayers.isEmpty) {
        availablePlayers.add(await _createNewAudioPlayer());
      }
      final AudioPlayer player = availablePlayers.elementAt(0);
      availablePlayers.remove(player);
      currentPlayers.add(player);

      await player.setVolume(volume);
      await player.resume();

      // subscription
      subscriptions[player]= player.onPlayerCompletion.listen((_) {
        if (repeating) {
          player.resume();
        } else {
          stopPlayer(player);
        }
      });

    });
  }

  void stopPlayer(AudioPlayer player){
    _lock.synchronized(() async {
      currentPlayers.remove(player);
      subscriptions[player]?.cancel();
      await player.stop();
      if (availablePlayers.length >= maxPlayers) {
        await player.release();
      } else {
        availablePlayers.add(player);
      }
    });
  }

  Future<AudioPlayer> _createNewAudioPlayer() async {
    final AudioPlayer player = AudioPlayer();
    final String url = (await cache.load(sound)).path;
    await player.setUrl(url);
    await player.setReleaseMode(ReleaseMode.STOP);
    return player;
  }
}

pgainullin avatar Aug 20 '21 09:08 pgainullin

A PR would be awesome! Then it would be easier to compare both versions to see what was the issue and the changes proposed.

luanpotter avatar Nov 18 '21 02:11 luanpotter

I'm closing this as inactive, it's most likely solved in the new version of AudioPlayers anyways.

spydon avatar Sep 17 '22 13:09 spydon