Search code examples
flutterdarttext-to-speechstatus

Playing or not playing in text to speech


I have a program that translates a text into speech, in flutter, so far so good, the question is How to make the program show a text that says the status of "playing" and "not playing" in the program, that is, while the voice is playing, the "playing" status is displayed.

I would really appreciate your response.

This is my code:

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

class TextVoicePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TextVoice(),
    );
  }
}

class TextVoice extends StatefulWidget {
  @override
  _Speak createState() => _Speak();
}

class _Speak extends State<TextVoice> {
  final FlutterTts flutterTts = FlutterTts();

  @override
  Widget build(BuildContext context) {
    bool speaking = false;
    TextEditingController textEditingController = TextEditingController();

    Future _speak(String text) async {
      await flutterTts.setLanguage("es-MX");
      await flutterTts.setPitch(1);
      await flutterTts.speak(text);
    }

    Future _stop() async {
      await flutterTts.stop();
    }

    return Scaffold(
        body: Stack(
          children: <Widget>[
            Padding(
              padding: EdgeInsets.all(30.0),
              child: TextFormField(
                controller: textEditingController,
              ),
            ),
          ],
        ),
        floatingActionButtonLocation:
            FloatingActionButtonLocation.miniCenterDocked,
        floatingActionButton: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              FloatingActionButton(
                backgroundColor: Colors.green,
                foregroundColor: Colors.white,
                heroTag: "btn",
                onPressed: () => _speak(textEditingController.text),
                child: Icon(Icons.play_arrow),
              ),
              SizedBox(
                width: 130,
              ),
              FloatingActionButton(
                foregroundColor: Colors.white,
                backgroundColor: Colors.red,
                heroTag: "btn2",
                onPressed: () => _stop(),
                child: Icon(Icons.stop),
              )
            ],
          ),
        ));
  }
}

Solution

  • I've made some changes to the code and added the behavior you were looking for. I'll enumerate the changes:

    1- Moved the variables speaking and textEditingController as attributes of the class, as this is the correct place to define them.

    2- I included an initState() to initialize textEditingController and, as I found in flutter_tts documentation, I added two handler methods to make the switch of the text from "Playing" to "Not playing".

    3- Also added a dispose() method too. Mainly because TextEditingController should be disposed when the widget lifecycle ends (you could read more about Flutter lifecycle in the official documentation or in this great article).

    4- I wrapped the Stack with a Column and appended a SizedBox with a Text to display the status (I think that the Stack widget may not be the best option to use in this situation. I would switch it to a simple Container maybe).

    5- Finally, I moved _speak and _stop methods out of the build function, and placed them as methods of the class.

    You might also want to take a look at the official flutter documentation about the use of setState.

    I've pointed out the changes in the code below:

    import 'package:flutter/material.dart';
    import 'package:flutter_tts/flutter_tts.dart';
    
    class TextVoicePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: TextVoice(),
        );
      }
    }
    
    class TextVoice extends StatefulWidget {
      @override
      _Speak createState() => _Speak();
    }
    
    class _Speak extends State<TextVoice> {
      final FlutterTts flutterTts = FlutterTts();
      ///1
      bool speaking = false;
      TextEditingController textEditingController;
    
      @override ///2
      void initState() {
        textEditingController = TextEditingController();
        flutterTts.setStartHandler(() {
          ///This is called when the audio starts
          setState(() {
            speaking = true;
          });
        });
    
        flutterTts.setCompletionHandler(() {
          ///This is called when the audio ends
          setState(() {
            speaking = false;
          });
        });
        super.initState();
      }
    
      @override ///3
      void dispose() {
        textEditingController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            body: Column(
              children: [
                Stack(
                  children: <Widget>[
                    Padding(
                      padding: EdgeInsets.all(30.0),
                      child: TextFormField(
                        controller: textEditingController,
                      ),
                    ),
                  ],
                ),
                SizedBox( ///4
                  child: Text(
                    speaking ? "Playing" : "Not playing"
                  ),
                )
              ],
            ),
            floatingActionButtonLocation:
            FloatingActionButtonLocation.miniCenterDocked,
            floatingActionButton: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  FloatingActionButton(
                    backgroundColor: Colors.green,
                    foregroundColor: Colors.white,
                    heroTag: "btn",
                    onPressed: () => _speak(textEditingController.text),
                    child: Icon(Icons.play_arrow),
                  ),
                  SizedBox(
                    width: 130,
                  ),
                  FloatingActionButton(
                    foregroundColor: Colors.white,
                    backgroundColor: Colors.red,
                    heroTag: "btn2",
                    onPressed: () => _stop(),
                    child: Icon(Icons.stop),
                  )
                ],
              ),
            ));
      }
    
      ///5
      Future _speak(String text) async {
        await flutterTts.setLanguage("es-MX");
        await flutterTts.setPitch(1);
        await flutterTts.speak(text);
      }
    
      Future _stop() async {
        await flutterTts.stop();
      }
    }