just_audio icon indicating copy to clipboard operation
just_audio copied to clipboard

Switching rapidly between audio sources leads to `PlatformException`

Open rubenferreira97 opened this issue 1 year ago • 14 comments

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):

  • OS: Windows 11
  • Browser: Android

Smartphone (please complete the following information):

  • Device: Redmi Note 9S
  • OS: Android 12

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.

rubenferreira97 avatar Oct 03 '23 13:10 rubenferreira97

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.

ryanheise avatar Oct 03 '23 14:10 ryanheise

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?

rubenferreira97 avatar Oct 03 '23 14:10 rubenferreira97

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.

ryanheise avatar Oct 03 '23 14:10 ryanheise

@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.

rubenferreira97 avatar Oct 03 '23 15:10 rubenferreira97

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.

ryanheise avatar Oct 03 '23 15:10 ryanheise

I had the same issue, Any news here?

firgia avatar Apr 08 '24 08:04 firgia

@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();

johnareid54 avatar Apr 09 '24 14:04 johnareid54

same

afl-dev avatar May 21 '24 14:05 afl-dev

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.

Anshuman115 avatar Jun 12 '24 09:06 Anshuman115

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

firgia avatar Jun 12 '24 10:06 firgia

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 ?

Anshuman115 avatar Jun 12 '24 10:06 Anshuman115

Can you share the code please ? @firgia

kingrmb avatar Jul 04 '24 02:07 kingrmb

@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();
}

firgia avatar Jul 04 '24 03:07 firgia

any complete solution to this ?

richanshah avatar Jul 25 '24 11:07 richanshah