Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android]: IllegalStateException #1708

Open
2 tasks done
giantss opened this issue Nov 23, 2023 · 7 comments
Open
2 tasks done

[Android]: IllegalStateException #1708

giantss opened this issue Nov 23, 2023 · 7 comments
Labels
bug platform-android Affects the android platform

Comments

@giantss
Copy link

giantss commented Nov 23, 2023

Checklist

  • I read the troubleshooting guide before raising this issue
  • I made sure that the issue I am raising doesn't already exist

Current bug behaviour

android crash,There are captured messages in the screenshots.

Expected behaviour

no crash.

Steps to reproduce

  1. Execute flutter run on the code sample
  2. ...
  3. ...

Code sample

Code sample
void main() {
}

Affected platforms

Android

Platform details

android 13

AudioPlayers Version

audioplayers: ^5.2.1

Build mode

release

Audio Files/URLs/Sources

SM-G9860

Screenshots

image

Logs

my relevant logs
Full Logs
my full logs or a link to a gist

Flutter doctor:

Output of: flutter doctor -v

Related issues / more information

No response

Working on PR

no way

@giantss giantss added the bug label Nov 23, 2023
@Gustl22
Copy link
Collaborator

Gustl22 commented Nov 23, 2023

We need precise steps to reproduce. Otherwise we cannot help :/

@giantss
Copy link
Author

giantss commented Nov 23, 2023

I'm very sorry, I can't provide the steps. This is a crash that occurs when users use it. It is captured and uploaded to the sentry backend. @Gustl22

@Gustl22
Copy link
Collaborator

Gustl22 commented Nov 23, 2023

You may could ask one of your customers to reproduce it?

@giantss
Copy link
Author

giantss commented Nov 23, 2023

image

MI 10 also has the same problem
android 13

@Gustl22
Copy link
Collaborator

Gustl22 commented Nov 23, 2023

Sorry. We already have enough reports of this error:
https://github.com/bluefireteam/audioplayers/issues?q=is%3Aissue+is%3Aopen+Illegalstateexception

But no reproduction steps. Reporting more errors doesn't help without being able to reproduce it, e.g. with a code snipped or a repository. Unfortunately we cannot invest time in something, we can solve only by guessing.

Also note we want to use #1526 which hopefully solves some of these errors.

@Gustl22 Gustl22 added platform-android Affects the android platform changes-requested The issue or merge request doesn't fulfill the conventions and has to be changed by the author labels Nov 23, 2023
@giantss
Copy link
Author

giantss commented Nov 23, 2023

@Gustl22 thank you for your reply.

Due to business needs, I need to generate more of this control (for example, 15) and place it in the pageView. When the user switches the pageView, the voice of the previous pageView interface will be paused, and it will automatically jump to the next interface and automatically play the interface. voice.

I tried to reproduce this crash, and I have made some progress so far. There is a certain probability that this crash will occur when the user quickly clicks to switch pageView.

This is a voice playback control I customized, the code is as follows.

import 'dart:async';

import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:tk_hsk/app_theme.dart';
import 'package:tk_hsk/common/component/my_space.dart';
import 'package:tk_hsk/common/global.dart';

import '../../utils/log_util.dart';

class CusAudioPlayer extends StatefulWidget {
  final String? headPath;
  final String path;
  final int? sourceType; /// 1 for SD card, 2 for project directory, 3 for network
  final bool showProgressBar;
  final bool? resetHeardPathPlayed;  // Reset the played flag for the header audio
  final bool? isContinue;  // Whether to continue playing
  final bool? autoPlay;  // Whether to autoplay
  final bool? paused;  // Whether it is paused
  final bool? isPlayHead;  // Whether to play the number
  final Function? playerCompleteCallback;
  final Function? onTapIcon;
  final Function(PlayerState)? playerStateChangedCallback;

  const CusAudioPlayer({
    Key? key,
    required this.path,
    this.headPath = "",
    this.showProgressBar = true,
    this.autoPlay = false,
    this.paused = false,
    this.resetHeardPathPlayed = false,
    this.isPlayHead = false,
    this.sourceType = 1,
    this.playerCompleteCallback,
    this.onTapIcon,
    this.playerStateChangedCallback,
    this.isContinue = false
  }) : super(key: key);

  @override
  State<CusAudioPlayer> createState() => _CusAudioPlayerState();
}

class _CusAudioPlayerState extends State<CusAudioPlayer> with WidgetsBindingObserver {
  final AudioPlayer audioPlayer = AudioPlayer();
  Duration _duration = const Duration(milliseconds: 0);
  PlayerState _playerState = PlayerState.stopped;
  Duration _position = const Duration(milliseconds: 0);
  bool heardPathPlayed = false;
  String playingPath = '';

  String get _durationText => _duration.toString().split('.').first;
  String get _positionText => _position.toString().split('.').first;
  Timer? _timer;
  List<MenuItem> menuItems = [
    MenuItem("x0.5", 0.5),
    MenuItem("x0.75", 0.75),
    MenuItem("x1.0", 1.0),
    MenuItem("x1.25", 1.25),
    MenuItem("x1.5", 1.5),
    MenuItem("x2.0", 2.0),
  ];

  double dropValue = 1.0;

  @override
  void initState() {
    WidgetsBinding.instance.addObserver(this);
    audioPlayer.setPlaybackRate(global.playbackRate);
    dropValue = global.playbackRate;
    audioPlayer.onDurationChanged.listen((Duration d) {
      if (mounted) {
        if(!widget.isPlayHead!){
          setState(() => _duration = d);
        } else {
          if(widget.headPath!.isEmpty){
            setState(() => _duration = d);
          } else {
            if(heardPathPlayed){
              setState(() => _duration = d);
            }
          }
        }
      }
    });
    audioPlayer.onPositionChanged.listen((Duration  p) {
      // if (mounted) {
      //   setState(() => _position = p);
      // }
    });

    audioPlayer.onPlayerStateChanged.listen((s) {
      if (mounted) {
        if(s == PlayerState.playing){
          startTimer();
        } else if(s == PlayerState.stopped ||s == PlayerState.completed){
          endTimer();
        }
      }
    });

    audioPlayer.onPlayerComplete.listen((event) {
      if(widget.isContinue!){
        play();
      } else {
        if( widget.playerCompleteCallback != null){
          if(!widget.isPlayHead!){
            widget.playerCompleteCallback!();
            setPlayerState(PlayerState.stopped);
          } else {
            if(widget.headPath!.isEmpty){
              widget.playerCompleteCallback!();
              setPlayerState(PlayerState.stopped);
            } else {
              if(heardPathPlayed){
                widget.playerCompleteCallback!();
                setPlayerState(PlayerState.stopped);
                heardPathPlayed = false;
              } else {
                heardPathPlayed = true;
                play();
              }
            }
          }
        }
      }
    });

    if(widget.autoPlay!){
      if(_playerState == PlayerState.playing){
        return;
      }
      play();
    } else {
      audioPlayer.pause();
    }

    super.initState();
  }

  startTimer(){
    _timer = Timer.periodic(const Duration(milliseconds: 15), (timer) {
      audioPlayer.getCurrentPosition().then((p) {
        if (mounted) {
          if(p != null){
            if(!widget.isPlayHead!){
              setState(() => _position = p);
            } else {
              if(widget.headPath!.isEmpty){
                setState(() => _position = p);
              } else {
                if(heardPathPlayed){
                  setState(() => _position = p);
                }
              }
            }
          }
        }
      });
    });
  }

  endTimer(){
    _timer?.cancel();
  }

  @override
  void didUpdateWidget(covariant CusAudioPlayer oldWidget) {
    if(widget.resetHeardPathPlayed!){
      heardPathPlayed = false;
      audioPlayer.stop();
      _position = const Duration(milliseconds: 0);
    }

    if(widget.paused!){
      audioPlayer.pause();
      setPlayerState(PlayerState.paused);
      return;
    }

    if(widget.autoPlay!){
      play();
    } else {
      audioPlayer.pause();
      setPlayerState(PlayerState.paused);
    }
    super.didUpdateWidget(oldWidget);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) async {
    if (state == AppLifecycleState.resumed) {
      // Log.logs("audio App entered the foreground");
    } else if (state == AppLifecycleState.inactive || state == AppLifecycleState.paused){
      Log.logs("audio App entered the background");
      if(_playerState == PlayerState.playing){
        await audioPlayer.pause();
        setPlayerState(PlayerState.paused);
      }
    }

    super.didChangeAppLifecycleState(state);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    audioPlayer.release();
    super.dispose();
  }

  play() async {
    if(widget.path.isEmpty){
      return;
    }
    if(!widget.isPlayHead!){
      playAudio(widget.path);
    } else {
      if(widget.headPath != null){
        if(widget.headPath!.isNotEmpty && !heardPathPlayed){
          playAudio(widget.headPath!);
        } else {
          playAudio(widget.path);
        }
      } else {
        playAudio(widget.path);
      }
    }
  }

  setPlayerState(PlayerState ps){
    if(mounted){
      setState(() {
        _playerState = ps;
      });
    }
  }

  playAudio(String path) async {
    try{
      if(audioPlayer.state == PlayerState.playing){
        return;
      }
      if(playingPath == path){
        audioPlayer.resume();
      }
      if(widget.sourceType == 1){
        await audioPlayer.play(DeviceFileSource("${global.destPath}$path"));
      } else if(widget.sourceType == 2){
        await audioPlayer.play(AssetSource(path));
      } else if(widget.sourceType == 3){
        await audioPlayer.play(UrlSource(path));
      }
      playingPath = path;
      setPlayerState(PlayerState.playing);
    } catch(e){
      Log.logs("audio play failed $e");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 10),
      child: widget.showProgressBar ? Row(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          IconButton(
            padding: EdgeInsets.zero,
            constraints: const BoxConstraints(
              minWidth: 40,
              minHeight: 40
            ),
            onPressed: () async {
              if(_playerState == PlayerState.playing){
                await audioPlayer.pause();
                setPlayerState(PlayerState.paused);
              } else {
                play();
                if(widget.onTapIcon != null){
                  widget.onTapIcon!();
                }
              }
            },
            icon: _playerState == PlayerState.paused ? Icon(Icons.play_circle_outline,color: AppTheme.bg_yellowf2, size: 45, ) :  Icon( _playerState == PlayerState.playing ?
              Icons.pause_circle_outline :
              heardPathPlayed ? Icons.pause_circle_outline : Icons.play_circle_outline,
              color: AppTheme.bg_yellowf2, size: 45,)
          ),
          const MySpace(horizontal: 15,),
          Expanded(
            flex: 1,
            child: Column(
              children: [
                const MySpace(vertical: 17,),
                SliderTheme(
                  data:  SliderThemeData(
                    thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 10),
                    overlayShape: SliderComponentShape.noOverlay,
                  ),
                  child: Slider(
                    thumbColor: AppTheme.bg_yellowf2,
                    activeColor: AppTheme.bg_grayd9,
                    inactiveColor: AppTheme.bg_grayf0,
                    onChanged: (v){
                      final duration = _duration;
                      final position = v * duration.inMilliseconds;
                      audioPlayer.seek(Duration(milliseconds: position.round()));
                    },
                    value: (_position.inMilliseconds > 0 &&
                        _position.inMilliseconds < _duration.inMilliseconds)
                        ? _position.inMilliseconds / _duration.inMilliseconds
                        : 0.0,
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.only(top: 2, left: 8, right: 10),
                  child:Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Text(_positionText, style: TextStyle(fontSize: 11, color: AppTheme.font_gray70),),
                      Text(_durationText, style: TextStyle(fontSize: 11, color: AppTheme.font_gray70),),
                    ],
                  ),
                )
              ],
            )
          ),
          global.pageId == 2 ? SizedBox() : DropdownButtonHideUnderline(
            child: DropdownButton(
              alignment: AlignmentDirectional.center,
              value: dropValue,
              items: [
                for(int i=0; i<menuItems.length; i++)
                  DropdownMenuItem(
                    child: Text(menuItems[i].name, style: TextStyle(fontSize: 11, color: AppTheme.font_gray70),),
                    value: menuItems[i].value,
                  )
              ],
              onChanged: (value) {
                setState(() {
                  dropValue = double.parse(value.toString());
                  global.playbackRate = dropValue;
                  audioPlayer.setPlaybackRate(dropValue);
                  play();
                });
              }
            ),
          )
        ],
      ) : IconButton(
        padding: EdgeInsets.all(4),
        onPressed: () async {
          if(_playerState == PlayerState.playing){
            await audioPlayer.stop();
            setPlayerState(PlayerState.stopped);
          } else {
            await play();
            widget.onTapIcon!();
          }
        },
        icon: _playerState == PlayerState.playing ?
        const Icon(Icons.pause_circle_outline, color: AppTheme.bg_yellowf2, size: 45,) :
        const Icon(Icons.play_circle_outline, color:AppTheme.bg_yellowf2, size: 45,)
      ),
    );
  }
}

class MenuItem {
  late String name;
  late double value;

  MenuItem(String name, double value){
    this.name = name;
    this.value = value;
  }
}

@Gustl22
Copy link
Collaborator

Gustl22 commented Nov 23, 2023

Thank you for sharing the code. It is quite complex though. Try to leave out any parts, which don't contribute reproducing the error.

Also I do not currently work on Android bugs, which are expected to be solved by #1526, as the time is much better invested in having this released, although it could take a while.

But we're happy to review any merge requests which solve these errors.

@Gustl22 Gustl22 removed the changes-requested The issue or merge request doesn't fulfill the conventions and has to be changed by the author label Nov 23, 2023
@Gustl22 Gustl22 changed the title android crash [Android]: IllegalStateException Nov 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug platform-android Affects the android platform
Projects
None yet
Development

No branches or pull requests

2 participants