1
1
import React , { useState , useEffect , useRef } from "react" ;
2
- import Link from "next/link" ;
3
2
import Head from "../components/head" ;
4
3
import Container from "@material-ui/core/Container" ;
5
- import Chip from "@material-ui/core/Chip" ;
6
- import Autocomplete from "@material-ui/lab/Autocomplete" ;
7
- import TextField from "@material-ui/core/TextField" ;
8
- import Snackbar from "@material-ui/core/Snackbar" ;
9
- import MuiAlert from "@material-ui/lab/Alert" ;
10
4
import Box from "@material-ui/core/Box" ;
11
5
import Grid from "@material-ui/core/Grid" ;
12
6
import Button from "@material-ui/core/Button" ;
13
- import IconButton from "@material-ui/core/IconButton" ;
14
- import LibraryMusicIcon from "@material-ui/icons/LibraryMusic" ;
7
+ import Notice from "../components/notice" ;
8
+ import UploadButton from "../components/uploadButton" ;
9
+ import TagField from "../components/tagField" ;
10
+ import TranscriptField from "../components/transcriptField" ;
15
11
16
12
const Home = ( ) => {
13
+ // 音声認識インスタンス
17
14
const recognizerRef = useRef ( ) ;
18
- const inputRef = useRef ( ) ;
19
- const [ finalText , setFinalText ] = useState ( "" ) ;
20
- const [ transcript , setTranscript ] = useState ( "ボタンを押して検知開始" ) ;
21
- const initialTagValues = [ "年収" ] ;
22
- const [ tagValues , setTagValues ] = useState ( initialTagValues ) ;
23
- const [ detecting , setDetecting ] = useState ( false ) ;
24
- const candidates = [ "年収" , "自由" , "成功" ] ;
25
- const [ alertOpen , setAlertOpen ] = useState ( false ) ;
26
- const [ fileLoaded , setFileLoaded ] = useState ( false ) ;
27
- const [ userMusic , setUserMusic ] = useState ( null ) ;
28
- const [ userMusicName , setUserMusicName ] = useState ( "" ) ;
15
+ // スナックバー表示
16
+ const [ alertOpen , setAlertOpen ] = useState ( false ) ; // 自慢検知アラート
17
+ const [ fileLoaded , setFileLoaded ] = useState ( false ) ; // ファイル読み込み完了
18
+ // 音声認識
19
+ const [ detecting , setDetecting ] = useState ( false ) ; // 音声認識ステータス
20
+ const [ finalText , setFinalText ] = useState ( "" ) ; // 確定された文章
21
+ const [ transcript , setTranscript ] = useState ( "ボタンを押して検知開始" ) ; // 認識中の文章
22
+ // 単語検知
23
+ const initialTagValues = [ "年収" ] ; // デフォルト検知単語
24
+ const candidates = [ "年収" , "自由" , "成功" ] ; // 検知単語候補
25
+ const [ tagValues , setTagValues ] = useState ( initialTagValues ) ; // 検知単語一覧
26
+ // 効果音
27
+ const [ userMusic , setUserMusic ] = useState ( null ) ; // ユーザー追加音
28
+ const [ userMusicName , setUserMusicName ] = useState ( "" ) ; // ファイル名
29
29
30
30
useEffect ( ( ) => {
31
- const music = new Audio ( "/static/warning01.mp3" ) ;
31
+ const music = new Audio ( "/static/warning01.mp3" ) ; // デフォルト音
32
+ // NOTE: Web Speech APIが使えるブラウザか判定
33
+ // https://developer.mozilla.org/ja/docs/Web/API/Web_Speech_API
32
34
if ( ! window . SpeechRecognition && ! window . webkitSpeechRecognition ) {
33
35
alert ( "お使いのブラウザには未対応です" ) ;
34
36
return ;
35
37
}
38
+ // NOTE: 将来的にwebkit prefixが取れる可能性があるため
36
39
const SpeechRecognition =
37
40
window . SpeechRecognition || window . webkitSpeechRecognition ;
38
41
recognizerRef . current = new SpeechRecognition ( ) ;
@@ -49,12 +52,15 @@ const Home = () => {
49
52
[ ...event . results ] . slice ( event . resultIndex ) . forEach ( result => {
50
53
const transcript = result [ 0 ] . transcript ;
51
54
if ( result . isFinal ) {
55
+ // 音声認識が完了して文章が確定
52
56
setFinalText ( prevState => {
53
57
return prevState + transcript ;
54
58
} ) ;
55
59
setTranscript ( "" ) ;
56
60
} else {
61
+ // 音声認識の途中経過
57
62
if ( tagValues . some ( value => transcript . includes ( value ) ) ) {
63
+ // NOTE: ユーザーが効果音を追加しなければデフォルトを鳴らす
58
64
( userMusic || music ) . play ( ) ;
59
65
setAlertOpen ( true ) ;
60
66
}
@@ -66,124 +72,66 @@ const Home = () => {
66
72
67
73
return (
68
74
< div >
69
- < Head title = "Home " />
70
- < Snackbar
75
+ < Head title = "自慢ディテクター " />
76
+ < Notice
71
77
open = { alertOpen }
72
- autoHideDuration = { 6000 }
78
+ severity = "error"
73
79
onClose = { ( ) => {
74
80
setAlertOpen ( false ) ;
75
81
} }
76
82
>
77
- < MuiAlert
78
- elevation = { 6 }
79
- variant = "filled"
80
- onClose = { ( ) => {
81
- setAlertOpen ( false ) ;
82
- } }
83
- severity = "error"
84
- >
85
- 自慢を検知しました
86
- </ MuiAlert >
87
- </ Snackbar >
88
- < Snackbar
83
+ 自慢を検知しました
84
+ </ Notice >
85
+ < Notice
89
86
open = { fileLoaded }
90
- autoHideDuration = { 6000 }
87
+ severity = "success"
91
88
onClose = { ( ) => {
92
89
setFileLoaded ( false ) ;
93
90
} }
94
91
>
95
- < MuiAlert
96
- elevation = { 6 }
97
- variant = "filled"
98
- onClose = { ( ) => {
99
- setFileLoaded ( false ) ;
100
- } }
101
- severity = "success"
102
- >
103
- { userMusicName } を読み込みました
104
- </ MuiAlert >
105
- </ Snackbar >
92
+ { userMusicName } を読み込みました
93
+ </ Notice >
106
94
< Container >
107
95
< Grid container alignItems = "center" justify = "center" >
108
96
< Grid item >
109
- < img src = "/static/logo.png" height = "200px" />
97
+ < img src = "/static/logo.png" height = "200px" alt = "自慢ディテクター" />
110
98
</ Grid >
111
99
</ Grid >
112
100
< Box fontSize = { 25 } >
113
- < p >
114
- { finalText }
115
- < span style = { { color : alertOpen ? "#f00" : "#aaa" } } >
116
- { transcript }
117
- </ span >
118
- </ p >
119
- < div id = "result-div" > </ div >
101
+ < TranscriptField
102
+ finalText = { finalText }
103
+ transcript = { transcript }
104
+ isMatch = { alertOpen }
105
+ />
120
106
</ Box >
121
107
< Grid container spacing = { 2 } >
122
108
< Grid item xs = { 11 } >
123
- < Autocomplete
109
+ < TagField
124
110
disabled = { detecting }
125
- multiple
126
- id = "tags-filled"
127
111
options = { candidates }
128
- freeSolo
129
112
defaultValue = { initialTagValues }
130
- onChange = { ( event , values ) => {
113
+ label = "反応する単語"
114
+ placeholder = "単語を追加 +"
115
+ onTagChange = { values => {
131
116
setTagValues ( values ) ;
132
117
} }
133
- renderTags = { ( value , getTagProps ) =>
134
- value . map ( ( option , index ) => (
135
- < Chip
136
- variant = "outlined"
137
- label = { option }
138
- { ...getTagProps ( { index } ) }
139
- />
140
- ) )
141
- }
142
- renderInput = { params => {
143
- return (
144
- < TextField
145
- { ...params }
146
- variant = "outlined"
147
- label = "反応する単語"
148
- placeholder = "単語を追加 +"
149
- />
150
- ) ;
151
- } }
152
118
/>
153
119
</ Grid >
154
120
< Grid item >
155
- < input
156
- ref = { inputRef }
157
- accept = "audio/*"
158
- id = "file-input"
159
- multiple
160
- type = "file"
161
- style = { { display : "none" } }
162
- onChange = { event => {
163
- const file = event . target . files [ 0 ] ;
164
- if ( ! ( file instanceof File ) ) return ;
165
- if ( file . type . indexOf ( "audio" ) === - 1 ) {
166
- alert ( "オーディオファイルを選択してください" ) ;
167
- return ;
168
- }
121
+ < UploadButton
122
+ disabled = { detecting }
123
+ fileType = "audio"
124
+ onFileChange = { file => {
169
125
const src = window . URL . createObjectURL ( file ) ;
170
126
const audio = new Audio ( src ) ;
171
127
setUserMusic ( audio ) ;
172
128
setUserMusicName ( file . name ) ;
173
129
setFileLoaded ( true ) ;
174
130
} }
131
+ onInvalidFileError = { ( ) => {
132
+ alert ( "オーディオファイルを選択してください" ) ;
133
+ } }
175
134
/>
176
- < label htmlFor = "file-input" >
177
- < IconButton
178
- color = "primary"
179
- size = "large"
180
- disabled = { detecting }
181
- aria-label = "upload audio"
182
- component = "span"
183
- >
184
- < LibraryMusicIcon />
185
- </ IconButton >
186
- </ label >
187
135
</ Grid >
188
136
</ Grid >
189
137
< Box m = { 2 } >
0 commit comments