StateError (Bad state: Cannot add new events after calling close)

Viewed 2842

I'm using this package: https://pub.dev/packages/dart_vlc for streaming multiple cameras on a page of my Flutter App. But I'm facing a problem, when I change page and I go to another page I will get the error below:

Unhandled Exception :StateError (Bad state: Cannot add new events after calling close)

Below there is the code of my Streaming page:

class CameraScreen extends StatefulWidget {
  CameraScreen(
      {required Key key,
      required this.onPageChange,
      required this.onLogout,
      required this.id})
      : super(key: key);
  final Function(String, String) onPageChange;
  final VoidCallback onLogout;
  final String id;
  @override
  CameraScreenState createState() => CameraScreenState();
}

class CameraScreenState extends State<CameraScreen>
    with TickerProviderStateMixin {
  PageConfig pageConfig = PageConfig();
  String project = '';
  List<ResponsiveGridCol> children = [];
  List<Player> players = [];
  List<dynamic> items = [];
  double initialSliderValue = 0;
  Player playerOfTheExpandedImg =
      Player(id: 1000, videoWidth: 1200, videoHeight: 800);

  @override
  void initState() {
    project = Project.getCurrentProject();
    pageConfig = Project.getProjectPageDetails(widget.id);
    getData();
    super.initState();
  }

  @override
  void dispose() {
    stopAndDisposePlayers();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return CustomScaffold(
      text: pageConfig.name!,
      onPageChange: widget.onPageChange,
      onLogout: widget.onLogout,
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: SingleChildScrollView(
          child: ResponsiveGridRow(
            children: children,
          ),
        ),
      ),
    );
  }

  getData() async {
    File cameraFile = File(AppStrings.CONFIG_DIRECTORY_PATH +
        project +
        AppStrings.CAMERA_CONFIG_FILE_NAME);
    items = jsonDecode(cameraFile.readAsStringSync());
    int i = 0;
    List<ResponsiveGridCol> widgets = [];
    for (var item in items) {
      var cameraConfig = Camera.fromJson(item);
      Player player = Player(id: i, videoWidth: 400, videoHeight: 300);
      var media = await Media.network(cameraConfig.url,
          parse: true, timeout: Duration(seconds: 2));
      await player.open(media);
      await player.play();
      players.add(player);
      widgets.add(
        ResponsiveGridCol(
          lg: 6,
          child: Card(
            key: Key('$i'),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Text(cameraConfig.title, style: AppStyles.BOLD),
                Text('Telecamere'),
                Video(
                  playerId: i,
                  width: 500,
                  height: 375,
                  showControls: false,
                ),
                Divider(),
                IconButton(
                  icon: Icon(Icons.open_in_browser),
                  onPressed: () async {
                    var stream = await createStreamForExpandedImg(item);
                    showDialog(
                      context: context,
                      builder: (BuildContext context) {
                        return ExpandedCamImg(
                            player: playerOfTheExpandedImg,
                            stream: stream,
                            cameraConfig: cameraConfig);
                      },
                    ).whenComplete(() async {
                      await playerOfTheExpandedImg.stop();
                      await playerOfTheExpandedImg.dispose();
                    });
                  },
                )
              ],
            ),
          ),
        ),
      );
      i++;
    }
    if (mounted) {
      setState(() {
        children = widgets;
      });
    } else
      return;
  }

  Future<Video> createStreamForExpandedImg(dynamic item) async {
    playerOfTheExpandedImg =
        Player(id: 1000, videoWidth: 1200, videoHeight: 800);
    var stream = Video(
      playerId: 1000,
      width: 1200,
      height: 800,
      showControls: false,
    );
    var cameraConfig = Camera.fromJson(item);
    var media = await Media.network(cameraConfig.url,
        parse: true, timeout: Duration(seconds: 2));
    await playerOfTheExpandedImg.open(media);
    await playerOfTheExpandedImg.play();
    return stream;
  }

  stopAndDisposePlayers() async {
    for (var player in players) {
      await player.stop();
      await player.dispose();
    }
  }
}

I suppose it is a problem that when dispose() is called all players are not closed and destroyed properly.

1 Answers

The cause of this issue is the Stream Controller is being accessed after it has been closed. Likely due to player.dispose(). What you can do here is add a checker to ensure that no new events will be added after dispose() has been called.

bool _isDisposed = false;

stopAndDisposePlayers() async {
  _isDisposed = true;
  
  ...
}

You can then use _isDisposed to check before starting the Stream.

Related