let isRecording = false; let mediaRecorder; let audioChunks = []; let recordingInterval; let count_voice = 0; let before_rate = []; const RECORDING_INTERVAL_MS = 5000; // 5秒 // メンバーとチャートの初期化 let members = []; let voiceData = []; let baseMemberColors = ["#4caf50", "#007bff", "#ffc107", "#dc3545", "#28a745", "#9c27b0", "#ff9800"]; // Chart.js の初期化 const ctx = document.getElementById("speechChart").getContext("2d"); const speechChart = new Chart(ctx, { type: "doughnut", data: { labels: members, datasets: [ { data: voiceData, backgroundColor: getMemberColors(members.length), }, ], }, options: { responsive: true, plugins: { legend: { display: true, position: "bottom", labels: { color: "white" }, }, }, }, }); // サーバーからメンバー情報を取得してチャートを更新する関数 async function updateChartFrom() { try { const response = await fetch("/confirm"); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (!data || !data.members || !Array.isArray(data.members)) { console.error("Invalid member data received:", data); members = ["member1"]; voiceData = [50, 50]; updateChart(); return; } members = data.members; voiceData = []; for (let i = 0; i < members.length; i++) { voiceData.push(100 / members.length); } updateChart(); } catch (error) { console.error("Failed to fetch member data:", error); members = ["member1"]; voiceData = [50, 50]; updateChart(); } } function updateChart() { // 一人モードの場合は、ユーザーとグレー(無音)の比率をチャートに表示 if (members.length === 1) { const userName = members[0]; speechChart.data.labels = [userName, "無音"]; speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"]; } else { // 複数メンバーの場合は通常通りの処理 speechChart.data.labels = members; speechChart.data.datasets[0].backgroundColor = getMemberColors(members.length); } speechChart.data.datasets[0].data = voiceData; speechChart.update(); } // ページ読み込み時にチャート情報を更新 updateChartFrom(); // 録音ボタンの録音開始/停止処理 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(); // 5秒ごとに録音を停止して送信するインターバルを設定 recordingInterval = setInterval(() => { if (mediaRecorder && mediaRecorder.state === "recording") { mediaRecorder.stop(); mediaRecorder.start(); } }, RECORDING_INTERVAL_MS); } catch (error) { console.error("マイクへのアクセスに失敗しました:", error); isRecording = false; recordButton.classList.remove("recording"); } } else { // 録音停止 isRecording = false; recordButton.classList.remove("recording"); if (mediaRecorder && mediaRecorder.state === "recording") { mediaRecorder.stop(); } clearInterval(recordingInterval); count_voice = 0; //before_rate = []; } } function sendAudioChunks(chunks) { const audioBlob = new Blob(chunks, { type: "audio/wav" }); const reader = new FileReader(); reader.onloadend = () => { const base64String = reader.result.split(",")[1]; const form = document.getElementById("recordForm"); const nameInput = form.querySelector('input[name="name"]'); const name = nameInput ? nameInput.value : "unknown"; fetch("/upload_audio", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ audio_data: base64String, name: name }), }) .then((response) => response.json()) .then((data) => { if (data.error) { alert("エラー: " + data.error); console.error(data.details); } else if (data.rate !== undefined) { updateChartData(data.rate); } else if (data.rates !== undefined) { updateChartData(data.rates); } }) .catch((error) => { console.error("エラー:", error); }); }; reader.readAsDataURL(audioBlob); } function getMemberColors(memberCount) { // 一人モードの場合は特別な処理をしない(updateChartで処理するため) if (memberCount <= 1) { return ["#4caf50", "#757575"]; } else { let colors = []; for (let i = 0; i < memberCount; i++) { colors.push(baseMemberColors[i % baseMemberColors.length]); } return colors; } } function updateChartData(newRate) { // 一人モードの時の処理 if (members.length === 1) { if (count_voice === 0) { speechChart.data.datasets[0].data = [newRate, 100 - newRate]; before_rate = [newRate]; } else { // 一人モードでは、過去のデータと現在のデータを加重平均する let tmp_rate = (newRate + before_rate[0] * count_voice) / (count_voice + 1); speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate]; before_rate = [tmp_rate]; } count_voice++; // 一人モードでは常に緑色とグレーの組み合わせを使用 speechChart.data.labels = [members[0], "無音"]; speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"]; } else { console.log(before_rate) // 複数人モードの処理 if (!Array.isArray(newRate)) { console.error("newRate is not an array:", newRate); return; } if (newRate.length !== members.length) { console.error( "newRate length does not match members length:", newRate, members ); return; } let averagedRates = new Array(newRate.length); for (let i = 0; i < newRate.length; i++) { let tmp_rate; if (count_voice === 0) { // 初回はそのまま tmp_rate = newRate[i]; } else { // 2回目以降は、過去の平均値と現在の値を加重平均する tmp_rate = (newRate[i] + before_rate[i] * count_voice) / (count_voice + 1); } averagedRates[i] = tmp_rate; } // before_rateを更新 before_rate = averagedRates; //グラフに反映 speechChart.data.datasets[0].data = averagedRates; count_voice++; speechChart.data.datasets[0].backgroundColor = getMemberColors( members.length ); } speechChart.update(); }