Add mobile microphone HTTPS hints and Permissions-Policy header.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-12 15:11:31 +08:00
parent 90e77f8f70
commit 984f2e03a4
2 changed files with 92 additions and 2 deletions
+40 -2
View File
@@ -174,7 +174,44 @@ server {
--- ---
## 九、HTTP 内网直连(不装 App ## 十、手机麦克风无法使用?
电脑能看到麦克风按钮,手机提示「检测不到麦克风」,通常是以下原因:
### 1. 未使用 HTTPS(最常见)
手机浏览器规定:`getUserMedia`(麦克风)**仅在 HTTPS / localhost 下可用**。
| 访问方式 | 电脑 | 手机 |
|----------|------|------|
| `http://192.168.x.x:5683` | 可能显示按钮但录音失败 | ❌ 检测不到麦克风 |
| `https://你的域名`NPS+反代) | ✅ | ✅ |
**解决:** 按本文档配置 NPS + 云服务器 HTTPS,用手机访问域名。
### 2. 云反代未放行麦克风权限
在云服务器的 Nginx / 面板反代中,确认响应头包含(教程示例,在你自己的反代里配置):
```nginx
add_header Permissions-Policy "microphone=(self), camera=(self)" always;
```
Trading Studio 应用层也会发送该头;若反代覆盖了响应头,需在反代侧补上。
### 3. iOS / 微信浏览器限制
- **iOS**:请用 **Safari** 打开 HTTPS 域名,首次点击麦克风时点「允许」
- **微信内置浏览器**:通常**不支持**网页录音 → 右上角「在浏览器中打开」
- **已安装 PWA**:从桌面图标启动后,在系统设置中检查 Safari/Chrome 麦克风权限
### 4. 临时替代:上传录音文件
在 HTTP 内网或无法授权麦克风时,点击音频区域的 **「上传」** 标签,选择手机「语音备忘录」导出的 `.m4a` / `.wav` 文件,功能与现场录音相同。
---
## 十一、HTTP 内网直连(不装 App)
局域网内 `http://192.168.x.x:5683` 可正常使用全部功能,仅 **PWA 安装** 受限。 局域网内 `http://192.168.x.x:5683` 可正常使用全部功能,仅 **PWA 安装** 受限。
点击页面「安装 App」会提示需 HTTPS;功能不受影响。 点击页面「安装 App」会提示需 HTTPS;功能不受影响。
@@ -184,4 +221,5 @@ server {
## 相关文档 ## 相关文档
- 内网部署:`DEPLOY.md` - 内网部署:`DEPLOY.md`
- 服务器更新:`bash server-update.sh` - 服务器更新:`bash server-update.sh`
- 麦克风问题:见上文 **第十节**
+52
View File
@@ -289,11 +289,28 @@ PWA_HEAD = """
return; return;
} }
btn.style.display = "inline-flex"; btn.style.display = "inline-flex";
// 非 HTTPS 时标记页面,用于显示麦克风提示
if (!isSecure()) {
document.documentElement.classList.add("insecure-context");
}
}); });
})(); })();
</script> </script>
""" """
MIC_HINT_HTML = """
<div class="mic-hint">
<strong>📱 手机 / 平板录音说明</strong>
<ul>
<li><strong>HTTPS 域名</strong>访问才能使用麦克风(HTTP 内网 IP 仅支持上传文件)</li>
<li>iOS 请用 <strong>Safari</strong> 打开,微信内置浏览器通常无法录音</li>
<li>首次使用请允许浏览器的「麦克风」权限</li>
<li>穿透方案见 <code>PWA_NPS.md</code></li>
</ul>
</div>
"""
INSTALL_APP_BUTTON_HTML = """ INSTALL_APP_BUTTON_HTML = """
<button id="pwa-install-btn" class="pwa-install-btn" type="button" title="安装到手机/平板/电脑桌面"> <button id="pwa-install-btn" class="pwa-install-btn" type="button" title="安装到手机/平板/电脑桌面">
<img src="/pwa/icons/install.svg" alt="" class="pwa-install-icon" width="28" height="28"/> <img src="/pwa/icons/install.svg" alt="" class="pwa-install-icon" width="28" height="28"/>
@@ -719,6 +736,31 @@ gradio-app,
} }
footer { visibility: hidden; } footer { visibility: hidden; }
/* 手机端麦克风提示(HTTP 下强制显示) */
.mic-hint {
display: none;
background: #1e3a5f !important;
border: 1px solid #3b82f6 !important;
border-radius: 10px !important;
padding: 12px 16px !important;
margin: 8px 0 14px 0 !important;
color: #dbeafe !important;
font-size: 0.9rem !important;
line-height: 1.6 !important;
}
.mic-hint strong { color: #ffffff !important; }
.mic-hint ul { margin: 8px 0 0 0 !important; padding-left: 20px !important; }
.mic-hint code {
background: #0f172a !important;
color: #93c5fd !important;
padding: 2px 6px !important;
border-radius: 4px !important;
}
@media (max-width: 1024px) {
.mic-hint { display: block; }
}
html.insecure-context .mic-hint { display: block !important; }
""" """
@@ -843,6 +885,7 @@ def build_app() -> gr.Blocks:
with gr.Tabs(): with gr.Tabs():
# ---- Tab 1: 音色锁定 ---- # ---- Tab 1: 音色锁定 ----
with gr.Tab("🎙️ 音色锁定"): with gr.Tab("🎙️ 音色锁定"):
gr.HTML(MIC_HINT_HTML)
gr.HTML( gr.HTML(
f'<div class="hint-box">' f'<div class="hint-box">'
f'上传 <strong>10-30 秒</strong> 干净人声样本,系统将提取 Speaker Embedding ' f'上传 <strong>10-30 秒</strong> 干净人声样本,系统将提取 Speaker Embedding '
@@ -873,6 +916,7 @@ def build_app() -> gr.Blocks:
# ---- Tab 2: 分步操作 ---- # ---- Tab 2: 分步操作 ----
with gr.Tab("🔧 分步流水线"): with gr.Tab("🔧 分步流水线"):
gr.HTML(MIC_HINT_HTML)
with gr.Row(elem_classes=["pipeline-steps"]): with gr.Row(elem_classes=["pipeline-steps"]):
with gr.Column(scale=1): with gr.Column(scale=1):
gr.Markdown("### Step 1 · 音频极速识别") gr.Markdown("### Step 1 · 音频极速识别")
@@ -911,6 +955,7 @@ def build_app() -> gr.Blocks:
# ---- Tab 3: 一键生产 ---- # ---- Tab 3: 一键生产 ----
with gr.Tab("🚀 一键生产"): with gr.Tab("🚀 一键生产"):
gr.HTML(MIC_HINT_HTML)
gr.Markdown( gr.Markdown(
"上传碎碎念录音,系统自动完成 **识别 → 润色 → 合成** 全流程。" "上传碎碎念录音,系统自动完成 **识别 → 润色 → 合成** 全流程。"
) )
@@ -953,6 +998,13 @@ def create_fastapi_app():
fastapi_app = FastAPI(title="Trading Studio", docs_url=None, redoc_url=None) fastapi_app = FastAPI(title="Trading Studio", docs_url=None, redoc_url=None)
@fastapi_app.middleware("http")
async def add_media_permissions(request, call_next):
"""允许浏览器在 HTTPS 下请求麦克风(配合云反代使用)。"""
response = await call_next(request)
response.headers["Permissions-Policy"] = "microphone=(self), camera=(self)"
return response
if PWA_DIR.is_dir(): if PWA_DIR.is_dir():
fastapi_app.mount("/pwa", StaticFiles(directory=str(PWA_DIR)), name="pwa") fastapi_app.mount("/pwa", StaticFiles(directory=str(PWA_DIR)), name="pwa")