2025-08-22 08:01:36 +08:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="zh-CN">
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
2025-08-22 11:24:40 +08:00
|
|
|
<title>学伴小智</title>
|
2025-08-22 08:01:36 +08:00
|
|
|
<style>
|
|
|
|
.model-selector {
|
2025-08-22 08:06:17 +08:00
|
|
|
position: fixed; top: 20px; left: 20px; z-index: 1000;
|
|
|
|
padding: 10px; background-color: rgba(255, 255, 255, 0.8);
|
|
|
|
border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
2025-08-22 08:23:08 +08:00
|
|
|
display: flex; flex-direction: column; gap: 15px;
|
2025-08-22 08:01:36 +08:00
|
|
|
}
|
2025-08-22 08:06:17 +08:00
|
|
|
select { padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; background-color: white; font-size: 14px; }
|
|
|
|
.recording-controls {
|
2025-08-22 08:23:08 +08:00
|
|
|
position: static;
|
2025-08-22 08:06:17 +08:00
|
|
|
display: flex; flex-direction: column; gap: 10px;
|
2025-08-22 08:23:08 +08:00
|
|
|
margin: 0;
|
2025-08-22 08:01:36 +08:00
|
|
|
}
|
2025-08-22 08:06:17 +08:00
|
|
|
.record-button {
|
|
|
|
width: 70px; height: 70px; border-radius: 50%;
|
|
|
|
background-color: #dc3545; border: none; color: white;
|
|
|
|
font-size: 16px; cursor: pointer; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
|
|
display: flex; align-items: center; justify-content: center;
|
2025-08-22 08:18:29 +08:00
|
|
|
/* 确保按钮始终可见 */
|
|
|
|
opacity: 1 !important;
|
|
|
|
visibility: visible !important;
|
2025-08-22 08:01:36 +08:00
|
|
|
}
|
2025-08-22 08:06:17 +08:00
|
|
|
.record-button:hover { background-color: #c82333; }
|
|
|
|
.stop-button {
|
|
|
|
width: 70px; height: 70px; border-radius: 50%;
|
|
|
|
background-color: #6c757d; border: none; color: white;
|
|
|
|
font-size: 16px; cursor: pointer; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
|
|
display: flex; align-items: center; justify-content: center;
|
2025-08-22 08:01:36 +08:00
|
|
|
display: none;
|
2025-08-22 08:18:29 +08:00
|
|
|
/* 确保按钮始终可见 */
|
|
|
|
opacity: 1 !important;
|
|
|
|
visibility: visible !important;
|
2025-08-22 08:01:36 +08:00
|
|
|
}
|
2025-08-22 08:06:17 +08:00
|
|
|
.stop-button:hover { background-color: #5a6268; }
|
|
|
|
.recording-indicator {
|
|
|
|
position: fixed; bottom: 20px; left: 20px; z-index: 1000;
|
|
|
|
padding: 10px 15px; background-color: rgba(220, 53, 69, 0.9);
|
|
|
|
color: white; border-radius: 20px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
|
|
display: none; align-items: center;
|
2025-08-22 08:01:36 +08:00
|
|
|
}
|
2025-08-22 08:06:17 +08:00
|
|
|
.recording-dot {
|
|
|
|
width: 10px; height: 10px; background-color: white;
|
|
|
|
border-radius: 50%; margin-right: 8px; animation: pulse 1.5s infinite;
|
2025-08-22 08:01:36 +08:00
|
|
|
}
|
2025-08-22 08:06:17 +08:00
|
|
|
@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
|
2025-08-22 10:22:27 +08:00
|
|
|
|
|
|
|
/* 添加思考中动画样式 */
|
|
|
|
.thinking-indicator {
|
|
|
|
position: fixed; bottom: 20px; right: 120px; z-index: 1000;
|
|
|
|
padding: 10px 15px; background-color: rgba(0, 123, 255, 0.9);
|
|
|
|
color: white; border-radius: 20px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
|
|
display: none; align-items: center;
|
|
|
|
}
|
|
|
|
.thinking-dots {
|
|
|
|
display: flex; gap: 4px; margin-right: 8px;
|
|
|
|
}
|
|
|
|
.thinking-dot {
|
|
|
|
width: 8px; height: 8px; background-color: white;
|
|
|
|
border-radius: 50%;
|
|
|
|
}
|
|
|
|
.thinking-dot:nth-child(1) { animation: pulse 1.5s infinite 0s; }
|
|
|
|
.thinking-dot:nth-child(2) { animation: pulse 1.5s infinite 0.3s; }
|
|
|
|
.thinking-dot:nth-child(3) { animation: pulse 1.5s infinite 0.6s; }
|
|
|
|
|
|
|
|
/* 新增样式 */
|
|
|
|
.result-container {
|
|
|
|
position: fixed; bottom: 80px; left: 20px; z-index: 1000;
|
|
|
|
padding: 15px; background-color: rgba(255, 255, 255, 0.95);
|
|
|
|
border-radius: 8px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
|
|
|
|
max-width: 400px; width: 90%;
|
|
|
|
display: none; flex-direction: column; gap: 10px;
|
|
|
|
}
|
|
|
|
.result-header {
|
|
|
|
font-weight: bold; color: #495057; margin-bottom: 5px;
|
|
|
|
display: flex; align-items: center; gap: 5px;
|
|
|
|
}
|
|
|
|
.result-header.asr { color: #007bff; }
|
|
|
|
.result-header.feedback { color: #28a745; }
|
|
|
|
.result-text {
|
|
|
|
color: #333; line-height: 1.5; max-height: 200px; overflow-y: auto;
|
|
|
|
padding-right: 5px; word-break: break-all;
|
|
|
|
}
|
|
|
|
.audio-player-container {
|
|
|
|
margin-top: 10px; display: flex; align-items: center; gap: 10px;
|
|
|
|
}
|
|
|
|
.play-button {
|
|
|
|
background-color: #28a745; color: white; border: none;
|
|
|
|
border-radius: 50%; width: 40px; height: 40px;
|
|
|
|
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
|
|
|
display: flex; /* 始终显示播放按钮 */
|
|
|
|
}
|
|
|
|
.play-button:hover { background-color: #218838; }
|
|
|
|
.audio-progress {
|
|
|
|
flex-grow: 1; height: 6px; background-color: #e9ecef; border-radius: 3px;
|
|
|
|
overflow: hidden; cursor: pointer;
|
|
|
|
}
|
|
|
|
.progress-bar {
|
|
|
|
height: 100%; background-color: #28a745; width: 0%;
|
|
|
|
}
|
|
|
|
.audio-time {
|
|
|
|
font-size: 12px; color: #6c757d;
|
|
|
|
}
|
2025-08-22 08:01:36 +08:00
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div class="model-selector">
|
|
|
|
<label for="model-select">选择学伴:</label>
|
|
|
|
<select id="model-select">
|
|
|
|
<option value="shizuku">小智</option>
|
|
|
|
<option value="koharu">小荷</option>
|
|
|
|
<option value="wanko">汪喵</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="recording-indicator" id="recordingIndicator">
|
|
|
|
<div class="recording-dot"></div>
|
|
|
|
<span>正在录音...</span>
|
|
|
|
</div>
|
|
|
|
|
2025-08-22 10:22:27 +08:00
|
|
|
<!-- 添加思考中指示器 -->
|
|
|
|
<div class="thinking-indicator" id="thinkingIndicator">
|
|
|
|
<div class="thinking-dots">
|
|
|
|
<div class="thinking-dot"></div>
|
|
|
|
<div class="thinking-dot"></div>
|
|
|
|
<div class="thinking-dot"></div>
|
|
|
|
</div>
|
|
|
|
<span>学伴正在思考中...</span>
|
|
|
|
</div>
|
|
|
|
|
2025-08-22 08:27:45 +08:00
|
|
|
<!-- 在看板娘附近添加录音按钮 -->
|
|
|
|
<div class="recording-controls" style="position: fixed; right:37px; bottom: 230px; z-index: 998;">
|
2025-08-22 10:22:27 +08:00
|
|
|
<button class="record-button" id="startRecordBtn" style="font-size: 14px;">和我讲话</button>
|
|
|
|
<button class="stop-button" id="stopRecordBtn" style="font-size: 14px;">停止讲话</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 新增结果显示容器 -->
|
|
|
|
<div class="result-container" id="resultContainer">
|
|
|
|
<div>
|
|
|
|
<div class="result-header asr">
|
|
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
|
<path d="M12 1H4C2.89 1 2 1.9 2 3V13C2 14.1 2.89 15 4 15H12C13.11 15 14 14.1 14 13V3C14 1.9 13.11 1 12 1ZM12 13H4V3H12V13Z" fill="#007bff"/>
|
|
|
|
<path d="M8 4C6.34 4 5 5.34 5 7C5 8.66 6.34 10 8 10C9.66 10 11 8.66 11 7C11 5.34 9.66 4 8 4ZM8 8.5C7.17 8.5 6.5 7.83 6.5 7C6.5 6.17 7.17 5.5 8 5.5C8.83 5.5 9.5 6.17 9.5 7C9.5 7.83 8.83 8.5 8 8.5Z" fill="#007bff"/>
|
|
|
|
<path d="M8 11C5.79 11 4 12.79 4 15V16H12V15C12 12.79 10.21 11 8 11ZM8 13C9.1 13 10 13.9 10 15H6C6 13.9 6.9 13 8 13Z" fill="#007bff"/>
|
|
|
|
</svg>
|
|
|
|
你讲的话
|
|
|
|
</div>
|
|
|
|
<div class="result-text" id="asrResultText"></div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<div class="result-header feedback">
|
|
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
|
<path d="M1 4C1 2.9 1.9 2 3 2H13C14.1 2 15 2.9 15 4V11C15 12.1 14.1 13 13 13H9L5 16V13H3C1.9 13 1 12.1 1 11V4Z" fill="#28a745"/>
|
|
|
|
<path d="M8 8H6V5H8V8ZM11 8H9V5H11V8ZM11 11H6V9H11V11Z" fill="#28a745"/>
|
|
|
|
</svg>
|
|
|
|
学伴回复
|
|
|
|
</div>
|
|
|
|
<div class="result-text" id="feedbackResultText"></div>
|
|
|
|
</div>
|
|
|
|
<div class="audio-player-container">
|
|
|
|
<button class="play-button" id="playAudioBtn">
|
|
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
|
<path d="M8 5V19L19 12L8 5Z" fill="white"/>
|
|
|
|
</svg>
|
|
|
|
</button>
|
|
|
|
<div class="audio-progress" id="audioProgress">
|
|
|
|
<div class="progress-bar" id="progressBar"></div>
|
|
|
|
</div>
|
|
|
|
<div class="audio-time" id="audioTime">00:00 / 00:00</div>
|
|
|
|
</div>
|
2025-08-22 08:27:45 +08:00
|
|
|
</div>
|
2025-08-22 08:01:36 +08:00
|
|
|
|
2025-08-22 13:04:16 +08:00
|
|
|
<div>
|
|
|
|
有权限问题的话,请点击<a href="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/11%E3%80%81%E5%AD%A6%E4%BC%B4Chrome%E5%BD%95%E9%9F%B3%E9%85%8D%E7%BD%AE%E4%BF%AE%E6%94%B9.pdf">这里</a>
|
|
|
|
</div>
|
2025-08-22 08:01:36 +08:00
|
|
|
<script src="https://l2dwidget.js.org/lib/L2Dwidget.min.js"></script>
|
|
|
|
<script>
|
2025-08-22 08:18:29 +08:00
|
|
|
// 模型配置 - 使用与Sample.html相同的CDN链接
|
2025-08-22 08:01:36 +08:00
|
|
|
const models = {
|
2025-08-22 08:18:29 +08:00
|
|
|
shizuku: { jsonPath: "https://unpkg.com/live2d-widget-model-shizuku@1.0.5/assets/shizuku.model.json", name: "小智" },
|
|
|
|
koharu: { jsonPath: "https://unpkg.com/live2d-widget-model-koharu@1.0.5/assets/koharu.model.json", name: "小荷" },
|
2025-08-22 08:06:17 +08:00
|
|
|
wanko: { jsonPath: "https://unpkg.com/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json", name: "汪喵" }
|
2025-08-22 08:01:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
// 录音相关变量
|
2025-08-22 08:06:17 +08:00
|
|
|
let mediaRecorder; let audioChunks = []; let isRecording = false;
|
2025-08-22 10:22:27 +08:00
|
|
|
// 音频播放相关变量
|
|
|
|
let audioElement = null; let isPlaying = false;
|
2025-08-22 08:01:36 +08:00
|
|
|
|
|
|
|
// 获取URL参数
|
|
|
|
function getUrlParam(name) {
|
|
|
|
const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
|
|
|
|
const r = window.location.search.substr(1).match(reg);
|
2025-08-22 08:06:17 +08:00
|
|
|
return r ? unescape(r[2]) : null;
|
2025-08-22 08:01:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 开始录音
|
|
|
|
function startRecording() {
|
|
|
|
if (isRecording) return;
|
|
|
|
|
2025-08-22 08:06:17 +08:00
|
|
|
console.log("尝试开始录音");
|
2025-08-22 08:01:36 +08:00
|
|
|
navigator.mediaDevices.getUserMedia({ audio: true })
|
|
|
|
.then(stream => {
|
|
|
|
mediaRecorder = new MediaRecorder(stream);
|
|
|
|
audioChunks = [];
|
|
|
|
|
|
|
|
mediaRecorder.ondataavailable = event => {
|
2025-08-22 08:06:17 +08:00
|
|
|
if (event.data.size > 0) audioChunks.push(event.data);
|
2025-08-22 08:01:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
mediaRecorder.onstop = () => {
|
|
|
|
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
|
|
|
|
console.log("录音完成,音频数据大小:", audioBlob.size);
|
|
|
|
const audioUrl = URL.createObjectURL(audioBlob);
|
|
|
|
console.log("录音URL:", audioUrl);
|
2025-08-22 08:06:17 +08:00
|
|
|
// 这里可以调用ASR服务
|
2025-08-22 08:53:10 +08:00
|
|
|
uploadAudioToServer(audioBlob);
|
2025-08-22 08:01:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
mediaRecorder.start();
|
|
|
|
isRecording = true;
|
|
|
|
document.getElementById('recordingIndicator').style.display = 'flex';
|
2025-08-22 08:06:17 +08:00
|
|
|
document.getElementById('startRecordBtn').style.display = 'none';
|
|
|
|
document.getElementById('stopRecordBtn').style.display = 'flex';
|
|
|
|
console.log("开始录音成功");
|
2025-08-22 08:01:36 +08:00
|
|
|
|
|
|
|
// 设置最长录音时间为60秒
|
|
|
|
setTimeout(stopRecording, 60000);
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
console.error("获取麦克风权限失败:", error);
|
|
|
|
alert("请授权麦克风权限以使用录音功能");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// 停止录音
|
|
|
|
function stopRecording() {
|
|
|
|
if (!isRecording || !mediaRecorder) return;
|
|
|
|
|
|
|
|
mediaRecorder.stop();
|
|
|
|
isRecording = false;
|
|
|
|
document.getElementById('recordingIndicator').style.display = 'none';
|
2025-08-22 08:06:17 +08:00
|
|
|
document.getElementById('startRecordBtn').style.display = 'flex';
|
|
|
|
document.getElementById('stopRecordBtn').style.display = 'none';
|
2025-08-22 08:01:36 +08:00
|
|
|
console.log("停止录音");
|
|
|
|
|
|
|
|
if (mediaRecorder.stream) {
|
|
|
|
mediaRecorder.stream.getTracks().forEach(track => track.stop());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-22 08:53:10 +08:00
|
|
|
// 上传音频到服务器
|
|
|
|
function uploadAudioToServer(audioBlob) {
|
|
|
|
console.log("开始上传音频到服务器");
|
2025-08-22 10:22:27 +08:00
|
|
|
// 显示思考中动画
|
|
|
|
document.getElementById('thinkingIndicator').style.display = 'flex';
|
|
|
|
|
2025-08-22 08:53:10 +08:00
|
|
|
const formData = new FormData();
|
|
|
|
formData.append('file', audioBlob, 'recording.wav');
|
|
|
|
|
|
|
|
fetch('/api/xueban/upload-audio', {
|
|
|
|
method: 'POST',
|
|
|
|
body: formData
|
|
|
|
})
|
|
|
|
.then(response => {
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('服务器响应错误');
|
|
|
|
}
|
|
|
|
return response.json();
|
|
|
|
})
|
|
|
|
.then(data => {
|
2025-08-22 10:22:27 +08:00
|
|
|
console.log("处理结果:", data);
|
|
|
|
// 隐藏思考中动画
|
|
|
|
document.getElementById('thinkingIndicator').style.display = 'none';
|
|
|
|
|
2025-08-22 08:53:10 +08:00
|
|
|
if (data.success) {
|
2025-08-22 10:22:27 +08:00
|
|
|
showResults(data.data);
|
2025-08-22 08:53:10 +08:00
|
|
|
} else {
|
2025-08-22 10:22:27 +08:00
|
|
|
alert('音频处理失败: ' + data.message);
|
2025-08-22 08:53:10 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
console.error("上传音频失败:", error);
|
2025-08-22 10:22:27 +08:00
|
|
|
// 隐藏思考中动画
|
|
|
|
document.getElementById('thinkingIndicator').style.display = 'none';
|
|
|
|
|
2025-08-22 08:53:10 +08:00
|
|
|
alert('上传音频失败: ' + error.message);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-08-22 10:22:27 +08:00
|
|
|
// 显示ASR识别结果和反馈
|
|
|
|
function showResults(data) {
|
|
|
|
// 更新结果显示容器
|
|
|
|
const resultContainer = document.getElementById('resultContainer');
|
|
|
|
resultContainer.style.display = 'flex';
|
|
|
|
|
|
|
|
// 显示ASR结果
|
|
|
|
document.getElementById('asrResultText').textContent = data.asr_text || '未识别到内容';
|
|
|
|
|
|
|
|
// 显示反馈文本
|
|
|
|
document.getElementById('feedbackResultText').textContent = data.feedback_text || '无反馈内容';
|
|
|
|
|
|
|
|
// 准备音频播放
|
|
|
|
if (data.audio_url) {
|
|
|
|
if (audioElement) {
|
|
|
|
audioElement.pause();
|
|
|
|
audioElement = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
audioElement = new Audio(data.audio_url);
|
|
|
|
audioElement.onloadedmetadata = function() {
|
|
|
|
updateAudioTimeDisplay();
|
|
|
|
// 音频加载完成后自动播放
|
|
|
|
try {
|
|
|
|
audioElement.play();
|
|
|
|
isPlaying = true;
|
|
|
|
updatePlayButton();
|
|
|
|
} catch (e) {
|
|
|
|
console.error("自动播放失败:", e);
|
|
|
|
}
|
|
|
|
// 无论自动播放是否成功,都显示播放按钮
|
|
|
|
document.getElementById('playAudioBtn').style.display = 'flex';
|
|
|
|
};
|
|
|
|
|
|
|
|
audioElement.ontimeupdate = function() {
|
|
|
|
updateAudioProgress();
|
|
|
|
updateAudioTimeDisplay();
|
|
|
|
};
|
|
|
|
|
|
|
|
audioElement.onended = function() {
|
|
|
|
isPlaying = false;
|
|
|
|
updatePlayButton();
|
|
|
|
};
|
|
|
|
|
|
|
|
// 绑定播放按钮事件
|
|
|
|
document.getElementById('playAudioBtn').onclick = togglePlayAudio;
|
|
|
|
|
|
|
|
// 绑定进度条点击事件
|
|
|
|
document.getElementById('audioProgress').onclick = function(e) {
|
|
|
|
if (!audioElement) return;
|
|
|
|
|
|
|
|
const progressBar = document.getElementById('audioProgress');
|
|
|
|
const rect = progressBar.getBoundingClientRect();
|
|
|
|
const clickPosition = (e.clientX - rect.left) / rect.width;
|
|
|
|
audioElement.currentTime = clickPosition * audioElement.duration;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 切换音频播放/暂停
|
|
|
|
function togglePlayAudio() {
|
|
|
|
if (!audioElement) return;
|
|
|
|
|
|
|
|
if (isPlaying) {
|
|
|
|
audioElement.pause();
|
|
|
|
} else {
|
|
|
|
audioElement.play();
|
2025-08-22 08:53:10 +08:00
|
|
|
}
|
2025-08-22 10:22:27 +08:00
|
|
|
isPlaying = !isPlaying;
|
|
|
|
updatePlayButton();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 更新播放按钮状态
|
|
|
|
function updatePlayButton() {
|
|
|
|
const playButton = document.getElementById('playAudioBtn');
|
|
|
|
if (isPlaying) {
|
|
|
|
playButton.innerHTML = `
|
|
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
|
<path d="M6 19H10V5H6V19ZM14 19H18V5H14V19Z" fill="white"/>
|
|
|
|
</svg>
|
|
|
|
`;
|
|
|
|
} else {
|
|
|
|
playButton.innerHTML = `
|
|
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
|
<path d="M8 5V19L19 12L8 5Z" fill="white"/>
|
|
|
|
</svg>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
}
|
2025-08-22 08:53:10 +08:00
|
|
|
|
2025-08-22 10:22:27 +08:00
|
|
|
// 更新音频进度条
|
|
|
|
function updateAudioProgress() {
|
|
|
|
if (!audioElement || !audioElement.duration) return;
|
|
|
|
|
|
|
|
const progress = (audioElement.currentTime / audioElement.duration) * 100;
|
|
|
|
document.getElementById('progressBar').style.width = `${progress}%`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 更新音频时间显示
|
|
|
|
function updateAudioTimeDisplay() {
|
|
|
|
if (!audioElement || !audioElement.duration) return;
|
|
|
|
|
|
|
|
const currentTime = formatTime(audioElement.currentTime);
|
|
|
|
const duration = formatTime(audioElement.duration);
|
|
|
|
document.getElementById('audioTime').textContent = `${currentTime} / ${duration}`;
|
|
|
|
}
|
2025-08-22 08:53:10 +08:00
|
|
|
|
2025-08-22 10:22:27 +08:00
|
|
|
// 格式化时间为 MM:SS
|
|
|
|
function formatTime(seconds) {
|
|
|
|
const mins = Math.floor(seconds / 60);
|
|
|
|
const secs = Math.floor(seconds % 60);
|
|
|
|
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
2025-08-22 08:53:10 +08:00
|
|
|
}
|
|
|
|
|
2025-08-22 08:18:29 +08:00
|
|
|
// 初始化看板娘 - 简化为Sample.html的工作版本
|
2025-08-22 08:01:36 +08:00
|
|
|
function initL2Dwidget() {
|
2025-08-22 08:30:36 +08:00
|
|
|
const modelId = getUrlParam('id') || 'koharu';
|
|
|
|
const model = models[modelId] || models.koharu;
|
2025-08-22 08:01:36 +08:00
|
|
|
|
|
|
|
document.getElementById('model-select').value = modelId;
|
|
|
|
console.log('加载模型:', model.name, model.jsonPath);
|
|
|
|
|
2025-08-22 08:18:29 +08:00
|
|
|
// 初始化模型 - 与Sample.html相同的配置
|
2025-08-22 08:01:36 +08:00
|
|
|
L2Dwidget.init({
|
2025-08-22 08:06:17 +08:00
|
|
|
"model": { "jsonPath": model.jsonPath, "scale": 1 },
|
2025-08-22 08:18:29 +08:00
|
|
|
"display": {
|
|
|
|
"position": "right",
|
|
|
|
"width": 150,
|
|
|
|
"height": 300,
|
2025-08-22 08:23:08 +08:00
|
|
|
"hOffset": 0, // 重置水平偏移
|
|
|
|
"vOffset": -20 // 重置垂直偏移
|
2025-08-22 08:18:29 +08:00
|
|
|
},
|
2025-08-22 08:06:17 +08:00
|
|
|
"mobile": { "show": true, "scale": 0.5 },
|
|
|
|
"react": { "opacityDefault": 0.8, "opacityOnHover": 1 },
|
|
|
|
"dialog": { "enable": true, "script": {
|
|
|
|
'tap body': `你好啊,我是${model.name}。`,
|
|
|
|
'tap face': '有什么问题或者烦心事都可以和我聊聊~'
|
|
|
|
}}
|
2025-08-22 08:01:36 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// 页面加载完成后初始化
|
|
|
|
window.onload = function() {
|
2025-08-22 08:18:29 +08:00
|
|
|
// 直接初始化看板娘,不添加额外延迟
|
2025-08-22 08:01:36 +08:00
|
|
|
initL2Dwidget();
|
2025-08-22 08:18:29 +08:00
|
|
|
|
|
|
|
// 监听下拉框变化
|
|
|
|
document.getElementById('model-select').addEventListener('change', function() {
|
|
|
|
window.location.search = '?id=' + this.value;
|
|
|
|
});
|
|
|
|
|
|
|
|
// 绑定录音按钮事件
|
|
|
|
document.getElementById('startRecordBtn').addEventListener('click', startRecording);
|
|
|
|
document.getElementById('stopRecordBtn').addEventListener('click', stopRecording);
|
|
|
|
|
2025-08-22 08:06:17 +08:00
|
|
|
// 页面加载时请求麦克风权限
|
|
|
|
navigator.mediaDevices.getUserMedia({ audio: true })
|
|
|
|
.then(stream => {
|
|
|
|
console.log("麦克风权限已授予");
|
|
|
|
// 立即停止流,只获取权限
|
|
|
|
stream.getTracks().forEach(track => track.stop());
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
console.error("获取麦克风权限失败:", error);
|
|
|
|
alert("请授权麦克风权限以使用录音功能");
|
|
|
|
});
|
2025-08-22 08:01:36 +08:00
|
|
|
};
|
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|