0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flutterでテキスト読み上げ

0
Posted at

Flutter を使ったテキスト読み上げ機能を紹介します。
TTS → text to speech と呼ばれています。

読み上げアプリの必要性

多機能 → シンプル → 読み上げ

スマホアプリは画面の小ささからシンプルさが求められました。
しかし頻繁に見なければいけないという問題があります。
シンプルな通知なら耳で行えばいいのでは。
最近はカーフ型イヤホンも普及して、眼鏡の人もイヤホンを付けられるようになりました。
読み上げアプリは黎明期です。そこに期待があるのです。

サンプルアプリ

Flutter で実装しました。

ソースコード

pubspec.yaml

dart
  flutter_tts: ^4.2.5

main.dart

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

void main() {
  runApp(
    MaterialApp(
      home: TtsScreen(),
    ),
  );
}

class TtsScreen extends StatefulWidget {
  @override
  TtsState createState() => TtsState();
}

class TtsState extends State<TtsScreen> {
  TtsController ctrl = TtsController();

  String text = """
ヒトは発声器官を通じて音声を生成し、コミュニケーションを行なう。
この音声を人工的に生成するタスクが音声合成である。合成された音声を合成音声と呼ぶ。
音声合成は様々な手法で実現できる。ある種の楽器は人の声に似た音を発し、
また人の喉を模倣した機械に風を吹き込むことで人の声に似た音が生成できる。
コンピューターを用い、音声情報処理の一種としてデジタル的に音声を合成することもできる。
""";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        padding: EdgeInsets.fromLTRB(30, 70, 30, 20),
        child: Column(
          children: [
            Row(children: [
              IconButton(
                icon: Icon(Icons.volume_up),
                iconSize: 32,
                onPressed: () => ctrl.startSpeaking(text),
              ),
              SizedBox(width: 10),
              IconButton(
                icon: Icon(Icons.stop),
                iconSize: 32,
                onPressed: () => ctrl.stopSpeaking(),
              ),
            ]),
            SizedBox(height: 20),
            Text(text),
          ],
        ),
      ),
    );
  }
}

tts_controller.dart

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

class TtsController {
  TtsController() {}

  FlutterTts flutterTts = FlutterTts();
  bool isSpeaking = false;
  int index = 0;
  List<String> sentences = [];

  Future startSpeaking(String text) async {
    if (isSpeaking) return;

    flutterTts.setCompletionHandler(() async {
      completeSpeaking();
    });

    await flutterTts.setSpeechRate(0.5); // 速さ 0.5
    await flutterTts.setVolume(1.0); // 音量 1.0
    await flutterTts.setPitch(1.0); // ピッチ 1.0
    await flutterTts.setLanguage("ja-JP");
    await flutterTts.setVoice({"name": "O-ren", "locale": "ja-JP"}); // オーレン女性

    // 文章を。で区切る
    String s = text.replaceAll('\n', '');
    sentences = s.split('。');
    sentences.removeWhere((value) => value == '');

    index = 0;
    isSpeaking = true;
    speak();
  }

  Future speak() async {
    if (isSpeaking) {
      if (index < sentences.length) {
        String s = sentences[index];
        await Future.delayed(Duration(milliseconds: 1000));
        await flutterTts.speak(s);
      } else {
        isSpeaking = false;
      }
    }
  }

  // 一文終了のコールバック
  completeSpeaking() {
    index++;
    speak();
  }

  Future stopSpeaking() async {
    isSpeaking = false;
    flutterTts.stop();
  }
}

解説

読み上げ関数

読み上げ自体は超シンプルです。

await flutterTts.speak('あいうえお');

「。」で待つ処理

「。」で待つ処理は自前でやります。「。」で区切りリスト化します。一文ごとに1000ミリ秒待ってから次の文を読み上げます。

漢字の読み方

漢字の読み方に対して表示用と読み上げ用のひらがなテキストの二種類を作る必要があります。

ルビ(ふりがな)
正攻法でHTMLのRUBYを使うと処理が大変です。

獅子しし

<ruby><rb>獅子</rb><rp>(</rp><rt>しし</rt><rp>)</rp></ruby>

小説投稿サイトでは「 | 」「《 》」で囲むとルビになります。

↓ 投稿例
突然|獅子《しし》が吠えた。

↓ 実際の表示
突然獅子ししが吠えた。

自作アプリ

自作の読書アプリをリリースしています。
アプリ名「一日一冊」 iOSのみ 無料

ルビ(ふりがな)に対応しています。ひらがなだけを抽出して表示用・読み上げ用を作ります。
一度出たルビは覚える機能があります。
読んでいる文だけハイライトします。そのため文章をHTMLのIDで囲っています。
セリフ「」内は声の高さを少し上げています。



ボイス一覧サンプル

各OSに入っているボイス一覧を出力するサンプルコードです。

dart
  // ボイス一覧サンプル
  Future showVoices() async {
    List voices = await flutterTts.getVoices;
    for (var item in voices) {
      var map = item as Map<Object?, Object?>;
      if (map["gender"].toString() != "unspecified") {
        if (map["locale"].toString() == "ja-JP") {
          print('${map["name"]}  ${map["locale"]}  ${map["gender"]}');
        }
        if (map["locale"].toString().contains("en-US")) {
          print('${map["name"]}  ${map["locale"]}  ${map["gender"]}');
        }
      }
    }
  }

iOS の実行結果

  Fred  en-US  male
  Nicky  en-US  female
  Aaron  en-US  male
  Samantha  en-US  female
  Hattori  ja-JP  male
  Kyoko  ja-JP  female
  O-ren  ja-JP  female

iOSでは男性1人女性2人の声が入っています。他にも機械の音声などがあります。

まとめ

Flutter の読み上げアプリを紹介しました。「。」で待つ処理は自前で行います。特定の漢字の読みも自前で行います。
エンジニアの仕事は新しいパイを持ってくることです。パイを奪い合うことではありません。読み上げ機能にはチャンスがあります。いつチャンスが来てもいいように準備しておきましょう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?