File size: 11,167 Bytes
b412841
6fc04a2
f525763
d45f43f
 
f525763
 
 
 
 
 
 
 
 
 
 
 
de16ffe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f525763
 
 
1afd5a5
 
 
 
 
 
f525763
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37782cb
1afd5a5
 
 
f525763
 
 
 
 
 
 
 
1afd5a5
 
f525763
 
 
 
 
 
 
 
de16ffe
 
 
 
 
 
 
 
 
 
f525763
 
 
 
de16ffe
f525763
 
 
 
de16ffe
f525763
1afd5a5
 
 
 
 
 
f525763
 
 
 
 
 
de16ffe
1dfab95
d45f43f
 
de16ffe
f525763
1afd5a5
f525763
1afd5a5
f525763
1afd5a5
 
 
 
 
 
 
f525763
 
 
 
 
 
1afd5a5
 
 
 
 
f525763
 
de16ffe
 
1dfab95
de16ffe
 
f525763
1afd5a5
f525763
 
 
 
1afd5a5
f525763
1afd5a5
 
 
f525763
7811276
f525763
1afd5a5
f525763
 
 
 
 
 
 
 
 
 
 
de16ffe
 
 
 
 
 
 
 
1dfab95
 
de16ffe
 
 
 
 
 
 
 
 
 
f525763
1afd5a5
f525763
1afd5a5
f525763
 
 
6fc04a2
1afd5a5
de16ffe
 
 
 
 
f525763
 
d45f43f
 
f525763
6fc04a2
 
f525763
 
1afd5a5
f525763
 
1afd5a5
de16ffe
1dfab95
 
 
de16ffe
1afd5a5
f525763
1afd5a5
f525763
 
 
1afd5a5
 
 
 
 
1dfab95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1afd5a5
d45f43f
1dfab95
 
 
 
 
 
 
 
d45f43f
1dfab95
1afd5a5
1dfab95
1afd5a5
 
 
 
 
d45f43f
1afd5a5
 
 
 
1dfab95
1afd5a5
 
 
 
 
 
 
f525763
 
 
 
 
 
 
1afd5a5
f525763
 
 
 
1afd5a5
f525763
 
 
d45f43f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Voice Recorder Interface</title>
    <style>
      body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        height: 100vh;
        margin: 0;
        background-color: #121212;
        color: white;
      }
      /* トグルスイッチ(基準音声保存用) */
      .toggle-container {
        display: flex;
        align-items: center;
        margin-bottom: 20px;
      }
      .toggle-label {
        margin-right: 10px;
      }
      .toggle-switch {
        position: relative;
        display: inline-block;
        width: 50px;
        height: 24px;
      }
      .toggle-switch input {
        opacity: 0;
        width: 0;
        height: 0;
      }
      .slider {
        position: absolute;
        cursor: pointer;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: #757575;
        transition: 0.2s;
        border-radius: 34px;
      }
      .slider::before {
        content: "";
        position: absolute;
        height: 18px;
        width: 18px;
        left: 4px;
        bottom: 3px;
        background-color: white;
        transition: 0.2s;
        border-radius: 50%;
      }
      input:checked + .slider {
        background-color: #4caf50;
      }
      input:checked + .slider::before {
        transform: translateX(26px);
      }
      /* チャートのスタイル */
      .chart {
        width: 300px;
        height: 300px;
        margin-bottom: 20px; /* 円グラフとボタンの間隔を狭く */
      }
      .controls {
        display: flex;
        flex-direction: column;
        align-items: center;
      }
      .record-button {
        width: 80px;
        height: 80px;
        background-color: transparent;
        border-radius: 50%;
        border: 4px solid white;
        display: flex;
        justify-content: center;
        align-items: center;
        cursor: pointer;
        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
        transition: all 0.2s ease;
      }
      .record-icon {
        width: 60px;
        height: 60px;
        background-color: #d32f2f;
        border-radius: 50%;
        transition: all 0.2s ease;
      }
      .recording .record-icon {
        width: 40px;
        height: 40px;
        border-radius: 10%;
      }
      .result-button {
        margin-left: 10px;

        margin-top: 20px;
        padding: 10px 20px;
        background-color: #4caf50;
        border: none;
        border-radius: 5px;
        color: white;
        cursor: pointer;
        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
      }
      .result {
        display: flex;
      }
      .result-button:hover {
        background-color: #388e3c;
      }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  </head>
  <body>
    <!-- トグルスイッチ:基準音声保存モード -->
    <div class="toggle-container">
      <span class="toggle-label">基準音声を保存</span>
      <label class="toggle-switch">
        <input type="checkbox" id="baseVoiceToggle" />
        <span class="slider"></span>
      </label>
    </div>

    <!-- チャート表示部 -->
    <div class="chart">
      <canvas id="speechChart"></canvas>
    </div>

    <!-- 録音ボタン -->
    <button class="record-button" id="recordButton" onclick="toggleRecording()">
      <div class="record-icon" id="recordIcon"></div>
    </button>

    <!-- 結果ボタン -->
    <div class="result-buttons">
      <button class="result-button" id="historyButton" onclick="showHistory()">
        会話履歴を表示
      </button>
      <button class="result-button" id="feedbackButton" onclick="showResults()">
        フィードバック画面を表示
      </button>
    </div>

    <script>
      let isRecording = false;
      let mediaRecorder;
      let audioChunks = [];
      let recordingInterval; // 通常モードでの10秒周期用
      let baseTimeout; // 基準音声モード用のタイマー
      let count_voice = 0;
      let before_rate = 0;

      // Chart.js の初期化
      const ctx = document.getElementById("speechChart").getContext("2d");
      const speechChart = new Chart(ctx, {
        type: "doughnut",
        data: {
          labels: ["自分", "他の人"],
          datasets: [
            {
              data: [30, 70],
              backgroundColor: ["#4caf50", "#757575"],
            },
          ],
        },
        options: {
          responsive: true,
          plugins: {
            legend: {
              display: true,
              position: "bottom",
              labels: { color: "white" },
            },
          },
        },
      });

      // トグルの状態を取得する関数
      function isBaseVoiceMode() {
        return document.getElementById("baseVoiceToggle").checked;
      }

      async function toggleRecording() {
        const recordButton = document.getElementById("recordButton");

        if (!isRecording) {
          // 録音開始
          isRecording = true;
          recordButton.classList.add("recording");
          try {
            const stream = await navigator.mediaDevices.getUserMedia({
              audio: true,
            });
            mediaRecorder = new MediaRecorder(stream);
            audioChunks = [];

            mediaRecorder.ondataavailable = (event) => {
              if (event.data.size > 0) {
                audioChunks.push(event.data);
              }
            };

            mediaRecorder.onstop = () => {
              sendAudioChunks([...audioChunks]);
              audioChunks = [];
            };

            mediaRecorder.start();

            if (isBaseVoiceMode()) {
              // 基準音声モード:10秒後に自動停止するタイマーをセット
              baseTimeout = setTimeout(() => {
                if (mediaRecorder && mediaRecorder.state === "recording") {
                  mediaRecorder.stop();
                  // 10秒経過しても録音ボタンがONなら強制的に停止&トグルをオフにする
                  isRecording = false;
                  recordButton.classList.remove("recording");
                  document.getElementById("baseVoiceToggle").checked = false;
                }
              }, 10000);
            } else {
              // 通常モード:10秒ごとに自動停止して送信、継続録音する処理
              recordingInterval = setInterval(() => {
                if (mediaRecorder && mediaRecorder.state === "recording") {
                  mediaRecorder.stop();
                }
              }, 10000);
            }
          } catch (error) {
            console.error("マイクへのアクセスに失敗しました:", error);
            isRecording = false;
            recordButton.classList.remove("recording");
          }
        } else {
          // 手動停止
          isRecording = false;
          recordButton.classList.remove("recording");
          if (isBaseVoiceMode()) {
            clearTimeout(baseTimeout);
          } else {
            clearInterval(recordingInterval);
          }
          if (mediaRecorder && mediaRecorder.state === "recording") {
            mediaRecorder.stop();
            count_voice = 0;
            before_rate = 0;
          }
        }
      }

      function sendAudioChunks(chunks) {
        const audioBlob = new Blob(chunks, { type: "audio/wav" });
        const reader = new FileReader();
        reader.onloadend = () => {
          const base64String = reader.result.split(",")[1]; // Base64エンコードされた音声データ
          // エンドポイントの選択:基準音声モードなら '/upload_base_audio'
          const endpoint = isBaseVoiceMode()
            ? "/upload_base_audio"
            : "/upload_audio";
          fetch(endpoint, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({ audio_data: base64String }),
          })
            .then((response) => response.json())
            .then((data) => {
              if (data.error) {
                alert("エラー: " + data.error);
                console.error(data.details);
              } else if (data.rate !== undefined && !isBaseVoiceMode()) {
                // 通常モードの場合、解析結果をチャートに反映
                if (count_voice === 0) {
                  speechChart.data.datasets[0].data = [
                    data.rate,
                    100 - data.rate,
                  ];
                  before_rate = data.rate;
                } else if (count_voice === 1) {
                  let tmp_rate = (data.rate + before_rate) / 2;
                  speechChart.data.datasets[0].data = [
                    tmp_rate,
                    100 - tmp_rate,
                  ];
                  before_rate = tmp_rate;
                } else {
                  let tmp_rate = (data.rate + before_rate * 2) / 3;
                  speechChart.data.datasets[0].data = [
                    tmp_rate,
                    100 - tmp_rate,
                  ];
                  before_rate = tmp_rate;
                }
                count_voice++;
                speechChart.update();
              } else {
                // 基準音声モードまたは解析結果がない場合
                if (isBaseVoiceMode()) {
                  //alert('基準音声が保存されました。');
                  // トグルをリセット
                  document.getElementById("baseVoiceToggle").checked = false;
                } else {
                  //alert('音声がバックエンドに送信されました。');
                }
              }
              // 通常モードの場合、録音が継続中なら次の録音を開始(自動連続録音)
              if (
                !isBaseVoiceMode() &&
                isRecording &&
                mediaRecorder &&
                mediaRecorder.state === "inactive"
              ) {
                mediaRecorder.start();
              }
            })
            .catch((error) => {
              console.error("エラー:", error);
              if (
                !isBaseVoiceMode() &&
                isRecording &&
                mediaRecorder &&
                mediaRecorder.state === "inactive"
              ) {
                mediaRecorder.start();
              }
            });
        };
        reader.readAsDataURL(audioBlob);
      }

      function showHistory() {
        // 会話履歴表示の画面があれば、そのページへ遷移する例
        // window.location.href = 'history';
        alert("会話履歴を表示する機能は未実装です。");
      }

      function showResults() {
        // フィードバック画面へ遷移
        window.location.href = "feedback";
      }
    </script>
  </body>
</html>