bluefireteam / audioplayers

A Flutter package to play multiple audio files simultaneously (Android/iOS/web/Linux/Windows/macOS)
https://pub.dartlang.org/packages/audioplayers
MIT License
1.99k stars 844 forks source link

AudioPlayer._onPrepared Null check operator used on a null value problem #1640

Open merterkoc opened 1 year ago

merterkoc commented 1 year ago

Checklist

Current bug behaviour

l``Some users are receiving this error in our live application. This error occurs as a result of a null coming from the natived channel. We get this error on the same line on iOS and Android sides. We will open pr to solve this error.

Fatal Exception: FlutterError
Fatal Exception: FlutterError
0  App                            0x4b2973 AudioPlayer._onPrepared.<anonymous closure> (#2) + 128 (audioplayer.dart:128)
1  App                            0x593f3 Provider._inheritedElementOf + 343 (provider.dart:343)
2  App                            0x58e17 Provider.of + 293 (provider.dart:293)
3  App                            0x58d7b ReadContext.read + 649 (provider.dart:649)
4  App                            0x2739b3 WebViewState.build.<anonymous closure>.<anonymous closure> + 714 (add_credit_card.dart:714)
5  App                            0x57241b WebKitWebViewController._webView.<anonymous closure>.<anonymous closure> + 255 (webkit_webview_controller.dart:255)

Expected behaviour

The function named "_onPrepared" of the "AudioPlayer" class should successfully process the data coming from the native channel and should not crash with any null value.

Steps to reproduce

We tried to recreate it but I couldn't. However, this problem was seen in more than 400 users in versions 5.0.0 and 5.1.0.

Code sample

No response

Affected platforms

Android, iOS

Platform details

It's a platform and device independent bug. We get it on both platforms and a wide variety of devices. We get this error on iOS, Android and various devices.

AudioPlayers Version

5.0.0, 5.1.0

Build mode

debug, profile, release

Audio Files/URLs/Sources

masterpass_success.wav.zip

Screenshots

No response

Logs

No response

Related issues / more information

No response

Working on PR

yeah

Gustl22 commented 1 year ago

@merterkoc Are you sure it's the null value which is causing the error? Can you post a separate log for each platform? There's another issue regarding double fulfillment of the preparedCompleter but that's not the same thing.

merterkoc commented 1 year ago

I am sharing an example crash log.

AudioPlayer onPrepared Crash Stacktrace.txt

image

Gustl22 commented 1 year ago

@merterkoc I cannot explain, how this bug could happen. I reevaluated the iOS platform multiple times on the native side. It would be really helpful, if you somehow can reproduce this bug with a minimal example. The thing is, just removing the null check operator just hides the deeper problem on the lib or the native side, which maybe would then never be resolved. I saw you also have a Logs tab, maybe you can find out more about the actual error description instead of the Stacktrace :)

tentenponce commented 6 months ago

Hi, is there any update on this? There are 8.7k crash events affecting 6.3k users on our app with this same error. Attaching our crash log as this might help identifying the issue. We cannot replicate it on our end as well.

audioplayers-onprepared-crash.txt

Fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: Null check operator used on a null value. Error thrown while finalizing the widget tree.
       at AudioPlayer._onPrepared.<fn>(audioplayer.dart:129)
       at Element.findAncestorWidgetOfExactType(framework.dart:4635)
       at StackRouterScope.of(controller_scope.dart:108)
       at AutoRouter.of(auto_router.dart:82)
       at AutoRouterX.router(auto_router_x.dart:8)
       at _RejectJobViewState.dispose(job_bottom_sheet_view.dart:395)
......

I tried investigating the value that is being null coming from the native side, but it seems that it cannot be null because it is not nullable in the first place (at least for Android): https://github.com/bluefireteam/audioplayers/blob/main/packages/audioplayers_android/android/src/main/kotlin/xyz/luan/audioplayers/AudioplayersPlugin.kt#L226

Baesd on this, the problem might be lying on the EventChannel itself, when receiving the stream from the native side, though it is able to parse event type properly, as it was being mapped to the proper switch case: https://github.com/bluefireteam/audioplayers/blob/main/packages/audioplayers_platform_interface/lib/src/audioplayers_platform.dart#L260

I am stuck after reaching this investigation.

merterkoc commented 6 months ago

In the last 30 days, there have been 68 bug reports for iOS and 11 for Android. This is a very low number compared to the number of users. But I must say the problem remains

Gustl22 commented 6 months ago

@merterkoc if you have something reproducible I'm happy to help. Otherwise we cannot make sure the issue is resolved. Can you contact one of your users to reproduce the issue?

Yaseendev commented 5 months ago

I think the problem is here (Line 129 in audioplayers.dart):

Stream get _onPrepared => eventStream .where((event) => event.eventType == AudioEventType.prepared) .map((event) => event.isPrepared!);

"isPrepared" property here has a null value

We can modify the code so that it returns false when its null like this: (event) => event.isPrepared ?? false

Gustl22 commented 5 months ago

@Yaseendev no that's not the solution. See https://github.com/bluefireteam/audioplayers/issues/1640#issuecomment-1751367875

If you can reproduce the problem, let me know, so we can fix it on the native platform.

firgia commented 5 days ago

Hi, any news here? I'm still getting the same issue.

Screenshot 2024-10-08 at 12 47 32

I'm using audioplayers: ^5.0.0, this is my AudioplayerService code:

import 'dart:async';

import 'package:audioplayers/audioplayers.dart';

class AudioPlayerService {
  late AudioPlayer audio;
  StreamController<PlayerState> playerStateStream;

  AudioPlayerService({
    required this.playerStateStream,
  }) {
    _createAudioPlayer();
  }

  Future<Duration?> getCurrentPosition() {
    return audio.getCurrentPosition();
  }

  Future<bool> play(
    String source, {
    double playbackRate = 1.0,
  }) async {
    await audio.setPlaybackRate(playbackRate);
    return audio
        .play(source.startsWith("http")
            ? UrlSource(source)
            : DeviceFileSource(source))
        .then((_) {
      _addPlayerStateChanged(PlayerState.playing);

      return true;
    }).catchError((_) async {
      _addPlayerStateChanged(PlayerState.stopped);
      return false;
    });
  }

  Future<void> stop() async {
    await audio.stop();
    _addPlayerStateChanged(PlayerState.stopped);
  }

  void _createAudioPlayer() {
    audio = AudioPlayer(playerId: DateTime.now().toIso8601String());
    audio.onPlayerStateChanged.listen((event) {
      _addPlayerStateChanged(event);
    });
  }

  void _addPlayerStateChanged(PlayerState playerState) {
    if (!playerStateStream.isClosed) {
      playerStateStream.sink.add(playerState);
    }
  }

  Future<void> dispose() => audio.dispose();
}