简体   繁体   中英

How to synchronize text to speech and displayed text in Flutter with flutter_tts?

I need some text to be read to the user while it is displayed on screen. Each time the text has finished to be read, the next one needs to be displayed and read until no more texts are available. I managed to do it by fiddling with FlutterTts.setCompletionHandler and setState, but I might have found the most cumbersome way of doing it. Here is a minimal working code snippet:

import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: WaitingRoom(),
    );
  }
}

class WaitingRoom extends StatefulWidget {
  @override
  _WaitingRoomState createState() => _WaitingRoomState();
}

class _WaitingRoomState extends State<WaitingRoom> {
  FlutterTts flutterTts = FlutterTts();

  String speechBubbleText = 'Bonjour. Joue avec moi !';
  List<Widget> actions = [];
  bool alreadyDelayed = false;

  @override
  Widget build(BuildContext context) {
    if (!alreadyDelayed) {
      flutterTts.setCompletionHandler(() {
        youWon();
      });
      alreadyDelayed = true;
    }

    flutterTts.speak(speechBubbleText);

    return Scaffold(
        appBar: AppBar(),
        body: Column(
          children: [
            Container(
              child: Text(
                speechBubbleText,
              ),
            ),
            Row(
              children: actions,
            ),
          ],
        ));
  }

  void youWon() {
    setState(() {
      speechBubbleText = 'Bravo, tu as gagné !'
          'Le médecin devrait arriver bientôt';
      actions = [];
    });

    flutterTts.setCompletionHandler(() {
      setState(() {
        speechBubbleText = 'Ca va se passer comme ça :';
      });
      flutterTts.setCompletionHandler(() {
        setState(() {
          speechBubbleText = 'Tu vas attendre le médecin';
          actions = [];
          actions.add(Text('test'));
        });
        flutterTts.setCompletionHandler(() {
          setState(() {
            speechBubbleText = 'Il viendra te chercher pour la consultation';
            actions = [];
            actions.add(Text('test'));
          });
          flutterTts.setCompletionHandler(() {
            setState(() {
              speechBubbleText =
                  'Quand ce sera fini, tu pourras rentrer à la maison !';
              actions = [];
              actions.add(Text('test'));
            });
            flutterTts.setCompletionHandler(() {
              setState(() {
                speechBubbleText = 'Maintenant, on attend le médecin';
                actions = [];
                actions.add(Text('test'));
              });
              flutterTts.setCompletionHandler(() {});
            });
          });
        });
      });
    });
  }
}

flutter_tts version in pubspec.yaml: flutter_tts: ^2.0.0

As you can see, youWon() looks like a callback hell. I could extract functions, but I got the feeling I'm doing this wrong. The documentation of flutter_tts mentions the command await flutterTts.awaitSpeakCompletion(true); , but while it awaits for the text to be read, the screen still displays all the texts without waiting.

What am I missing here?

Thank you in advance for your help.

This should work for you:

import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: WaitingRoom(),
    );
  }
}

class WaitingRoom extends StatefulWidget {
  @override
  _WaitingRoomState createState() => _WaitingRoomState();
}

class _WaitingRoomState extends State<WaitingRoom> {
  late FlutterTts flutterTts;

  String speechBubbleText = 'Bonjour. Joue avec moi !';
  List<Widget> actions = [];
  bool alreadyDelayed = false;

  @override
  initState() {
    super.initState();
    initTts();
  }

  initTts() async {
    flutterTts = FlutterTts();

    await flutterTts.awaitSpeakCompletion(true);
    await youWon();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: Column(
          children: [
            Container(
              child: Text(
                speechBubbleText,
              ),
            ),
            Row(
              children: actions,
            ),
          ],
        ));
  }

  Future<void> youWon() async {
    await flutterTts.speak(speechBubbleText);
    setState(() {
      speechBubbleText = 'Bravo, tu as gagné !'
          'Le médecin devrait arriver bientôt';
      actions = [];
    });
    await flutterTts.speak(speechBubbleText);

    setState(() {
      speechBubbleText = 'Ca va se passer comme ça :';
    });
    await flutterTts.speak(speechBubbleText);

    setState(() {
      speechBubbleText = 'Tu vas attendre le médecin';
      actions = [];
      actions.add(Text('test'));
    });
    await flutterTts.speak(speechBubbleText);

    setState(() {
      speechBubbleText = 'Il viendra te chercher pour la consultation';
      actions = [];
      actions.add(Text('test'));
    });
    await flutterTts.speak(speechBubbleText);

    setState(() {
      speechBubbleText = 'Quand ce sera fini, tu pourras rentrer à la maison !';
      actions = [];
      actions.add(Text('test'));
    });
    await flutterTts.speak(speechBubbleText);

    setState(() {
      speechBubbleText = 'Maintenant, on attend le médecin';
      actions = [];
      actions.add(Text('test'));
    });
    await flutterTts.speak(speechBubbleText);
  }
}

I finally found a solution that only requires to duplicate the flutterTts.speak(speechBubbleText) line of code. This is a great gain compared to a callback hell!

import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: WaitingRoom(),
    );
  }
}

class WaitingRoom extends StatefulWidget {
  @override
  _WaitingRoomState createState() => _WaitingRoomState();
}

class _WaitingRoomState extends State<WaitingRoom> {
  FlutterTts flutterTts = FlutterTts();

  String speechBubbleText = 'Bonjour. Joue avec moi !';
  List<Widget> actions = [];
  bool alreadyDelayed = false;

  @override
  Widget build(BuildContext context) {
    if (!alreadyDelayed) {
      flutterTts.speak(speechBubbleText);
      flutterTts.setCompletionHandler(() {
        youWon();
      });
      alreadyDelayed = true;
    }

    return Scaffold(
        appBar: AppBar(),
        body: Column(
          children: [
            Container(
              child: Text(
                speechBubbleText,
              ),
            ),
            Row(
              children: actions,
            ),
          ],
        ));
  }

  Future<void> youWon() async {
    flutterTts.setCompletionHandler(() {});
    setState(() {
      speechBubbleText = 'Bravo, tu as gagné !'
          'Le médecin devrait arriver bientôt';
      actions = [];
    });
    await flutterTts.speak(speechBubbleText);

    setState(() {
      speechBubbleText = 'Ca va se passer comme ça :';
    });
    await flutterTts.speak(speechBubbleText);

    setState(() {
      speechBubbleText = 'Tu vas attendre le médecin';
      actions = [];
      actions.add(Text('test'));
    });
    await flutterTts.speak(speechBubbleText);

    setState(() {
      speechBubbleText = 'Il viendra te chercher pour la consultation';
      actions = [];
      actions.add(Text('test'));
    });
    await flutterTts.speak(speechBubbleText);

    setState(() {
      speechBubbleText =
      'Quand ce sera fini, tu pourras rentrer à la maison !';
      actions = [];
      actions.add(Text('test'));
    });
    await flutterTts.speak(speechBubbleText);

    setState(() {
      speechBubbleText = 'Maintenant, on attend le médecin';
      actions = [];
      actions.add(Text('test'));
    });
    await flutterTts.speak(speechBubbleText);
  }
}

Interestingly, await flutterTts.awaitSpeakCompletion(true); is useless as it is set to true by default. Setting it to false ignores all the texts until the last one.

I am still using setCompletionHandler() in a way it wasn't created for so this is a partial answer, but at least it gets rid of the callback hell. I'll give an update if I find a better way of doing it, please feel free to comment or give a better answer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM