Add mobile microphone HTTPS hints and Permissions-Policy header.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+40
-2
@@ -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`
|
||||||
|
- 麦克风问题:见上文 **第十节**
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user