ryanheise / just_audio

Audio Player
1.02k stars 639 forks source link

Switching rapidly between audio sources leads to `PlatformException` #1078

Open rubenferreira97 opened 11 months ago

rubenferreira97 commented 11 months ago

Which API doesn't behave as documented, and how does it misbehave? Calling .setAudioSource with preload: false throws PlatformException. However in the docs only states:

 /// When [preload] is `true`, this method may throw:
  ///
  /// * [Exception] if no audio source has been previously set.
  /// * [PlayerException] if the audio source was unable to be loaded.
  /// * [PlayerInterruptedException] if another audio source was loaded before
  /// this call completed or the player was stopped or disposed of before the
  /// call completed.

Minimal reproduction project https://github.com/rubenferreira97/just_audio_bug

To Reproduce (i.e. user steps, not code) Steps to reproduce the behavior:

  1. Run the project
  2. Switch fast between the two audios.
  3. See error

Error messages

E/flutter (11925): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(abort, Loading interrupted, null, null)
E/flutter (11925): #0      AudioPlayer._setPlatformActive.checkInterruption (package:just_audio/just_audio.dart:1247:7)
E/flutter (11925): #1      AudioPlayer._setPlatformActive.setPlatform (package:just_audio/just_audio.dart:1358:11)
E/flutter (11925): <asynchronous suspension>
E/flutter (11925): 
I/flutter (11925): Error PlatformException(abort, Loading interrupted, null, null)
E/flutter (11925): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(Platform player 7154b6f3-01e4-4a0c-897d-2be091c3b53a already exists, null, null, null)
E/flutter (11925): #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:652:7)
E/flutter (11925): #1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:310:18)
E/flutter (11925): <asynchronous suspension>
E/flutter (11925): #2      MethodChannelJustAudio.init (package:just_audio_platform_interface/method_channel_just_audio.dart:13:5)
E/flutter (11925): <asynchronous suspension>
E/flutter (11925): #3      AudioPlayer._setPlatformActive.setPlatform (package:just_audio/just_audio.dart:1341:13)
E/flutter (11925): <asynchronous suspension>

Expected behavior Should switch audio and play it without crashes/exceptions.

Screenshots If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

Smartphone (please complete the following information):

Flutter SDK version

Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 3.13.5, on Microsoft Windows [Version 10.0.22621.2283], locale pt-PT)
[√] Windows Version (Installed version of Windows is version 10 or higher)
[√] Android toolchain - develop for Android devices (Android SDK version 34.0.0-rc2)
[√] Chrome - develop for the web
[X] Visual Studio - develop Windows apps
    X Visual Studio not installed; this is necessary to develop Windows apps.
      Download at https://visualstudio.microsoft.com/downloads/.
      Please install the "Desktop development with C++" workload, including all of its default components
[√] Android Studio (version 2022.3)
[√] VS Code (version 1.82.3)
[√] Connected device (4 available)
[√] Network resources

! Doctor found issues in 1 category.

Additional context Add any other context about the problem here.

ryanheise commented 11 months ago

Which API doesn't behave as documented, and how does it misbehave? Calling .stop, .setAudioSource and .play too fast may cause issues.

Hi, the idea is to explain how it doesn't behave as "documented" which I'll include below:

  /// When [preload] is `true`, this method may throw:
  ///
  /// * [Exception] if no audio source has been previously set.
  /// * [PlayerException] if the audio source was unable to be loaded.
  /// * [PlayerInterruptedException] if another audio source was loaded before
  /// this call completed or the player was stopped or disposed of before the
  /// call completed.

I think the bug you might be getting at is that the expected behaviour is point 3 above, but the actual behaviour is an unhandled exception.

rubenferreira97 commented 11 months ago

I think it is a bug because subsequent calls break completely the player. The players become unresponsive after the first crash with this exception: PlatformException(Platform player 7154b6f3-01e4-4a0c-897d-2be091c3b53a already exists, null, null, null)

Did you try the reproducible example?

ryanheise commented 11 months ago

I'm just trying to help you fill in the first section of the bug report. The exception is expected and actually right there in the documentation (which I'm saying you need to reference when filling in the first section), and hence the problem is not the exception, but rather that the exception is unhandled.

rubenferreira97 commented 11 months ago

@ryanheise Sorry to bother, I edited my initial issue. The exact same exceptions throw when preload = false so I guess that's not documented. The player becomes broken afterwards, I still think this is a underlying issue. Even if an exception is thrown I think the player should not be in a inconsistent state.

ryanheise commented 11 months ago

The underlying problem "is" that the exception is unhandled, since that means that the exception isn't actually being thrown by the API that purports to throw that exception. if it were thrown, then it would be in the correct state, allowing you to set a new audio source etc.

firgia commented 4 months ago

I had the same issue, Any news here?

johnareid54 commented 4 months ago

@firgia "I had the same issue, Any news here?" I had the same fault today, spent ages trying to find the cause, as a workaround I used different AudioPlayer();

afl-dev commented 3 months ago

same

Anshuman115 commented 2 months ago

The issue is still present, if anyone has any fix, do share.

The issue is reproducible by rapidly changing the audio sources and trying to play, but the issue is catching the error to fix it.

firgia commented 2 months ago

This issue is not fully resolved. I have fixed this issue by adding debounce at least 500ms and never see this issue anymore at the moment

Anshuman115 commented 2 months ago

This issue is not fully resolved. I have fixed this issue by adding debounce at least 500ms and never see this issue anymore at the moment

Can you share the code please ?

kingrmb commented 1 month ago

Can you share the code please ? @firgia

firgia commented 1 month ago

@Anshuman115 @kingrmb


import 'package:just_audio/just_audio.dart';

class JustAudioService {
  static final JustAudioService _singleton = JustAudioService._internal();

  factory JustAudioService() => _singleton;

  JustAudioService._internal();

  AudioPlayer audio = AudioPlayer();
  Timer? _debounce;

  Future<bool> play(String source) async {
    Completer<bool> completer = Completer<bool>();
    await stop();

    bool isAlreadyPlay = false;
    if (_debounce == null) {
      isAlreadyPlay = true;

      _playSource(source).then(
        (value) {
          if (!completer.isCompleted) completer.complete(value);
        },
      );
    }

    if (_debounce?.isActive ?? false) _debounce?.cancel();
    _debounce = Timer(const Duration(milliseconds: 500), () async {
      if (!isAlreadyPlay) {
        _playSource(source).then(
          (value) {
            if (!completer.isCompleted) completer.complete(value);
          },
        );
      }
      _debounce = null;
    });

    return completer.future;
  }

  Future<bool> _playSource(String source) async {
    try {
      late AudioSource audioSource;
      if (source.startsWith("http")) {
        audioSource = LockCachingAudioSource(Uri.parse(source));
      } else {
        audioSource = AudioSource.file(source);
      }

      await audio.setAudioSource(audioSource);
      await audio.play();

      return true;
    } catch (e) {
      return false;
    }
  }

  Future<void> stop() => audio.stop();
}
richanshah commented 1 month ago

any complete solution to this ?