's'
This commit is contained in:
@@ -1,20 +1,150 @@
|
||||
"""YouTube 开播前网络预检(参照 sh2 network_preflight,Windows 版仅检 Google 可达)。"""
|
||||
"""YouTube 开播前网络预检(参照 d2ypp2 network_preflight,Windows/Electron 适配)。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from web2_network import CONNECT_TIMEOUT, GOOGLE_CHECK_URL, READ_TIMEOUT, _latency_get
|
||||
from web2_network import (
|
||||
CONNECT_TIMEOUT,
|
||||
DOUYIN_CHECK_URL,
|
||||
GOOGLE_CHECK_URL,
|
||||
READ_TIMEOUT,
|
||||
_latency_get,
|
||||
)
|
||||
from web2_proxy_config import read_proxy_config
|
||||
|
||||
YOUTUBE_CHECK_URL = "https://www.youtube.com/generate_204"
|
||||
|
||||
_PROBE_TARGETS: tuple[dict[str, str], ...] = (
|
||||
{"id": "google", "label": "Google", "url": GOOGLE_CHECK_URL, "role": "global"},
|
||||
{"id": "youtube", "label": "YouTube", "url": YOUTUBE_CHECK_URL, "role": "global"},
|
||||
{"id": "douyin", "label": "抖音源站", "url": DOUYIN_CHECK_URL, "role": "source"},
|
||||
)
|
||||
|
||||
|
||||
def _preflight_disabled() -> bool:
|
||||
raw = os.environ.get("YOUTUBE_NETWORK_PREFLIGHT", "1").strip().lower()
|
||||
return raw in {"0", "false", "no", "off"}
|
||||
|
||||
|
||||
def _global_ok(probes: list[dict[str, Any]]) -> bool:
|
||||
return any(p.get("ok") for p in probes if p.get("role") == "global")
|
||||
|
||||
|
||||
def _douyin_ok(probes: list[dict[str, Any]]) -> bool:
|
||||
return any(p.get("ok") for p in probes if p.get("id") == "douyin")
|
||||
|
||||
|
||||
async def _probe_all(client: httpx.AsyncClient) -> list[dict[str, Any]]:
|
||||
rows: list[dict[str, Any]] = []
|
||||
for target in _PROBE_TARGETS:
|
||||
ok, ms, err = await _latency_get(client, target["url"])
|
||||
rows.append(
|
||||
{
|
||||
**target,
|
||||
"ok": ok,
|
||||
"latency_ms": round(ms, 1) if ms >= 0 else None,
|
||||
"error": err,
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
async def run_youtube_preflight() -> dict[str, Any]:
|
||||
"""返回完整预检报告,供 /youtube/preflight 与开播拦截共用。"""
|
||||
if _preflight_disabled():
|
||||
return {
|
||||
"ok": True,
|
||||
"mode": "disabled",
|
||||
"message": "网络预检已关闭(YOUTUBE_NETWORK_PREFLIGHT=0)",
|
||||
"checks": {},
|
||||
"direct": [],
|
||||
"proxy": [],
|
||||
"proxy_config": read_proxy_config(),
|
||||
}
|
||||
|
||||
timeout = httpx.Timeout(READ_TIMEOUT, connect=CONNECT_TIMEOUT)
|
||||
proxy_cfg = read_proxy_config()
|
||||
|
||||
async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client:
|
||||
direct = await _probe_all(client)
|
||||
|
||||
proxy_rows: list[dict[str, Any]] = []
|
||||
proxy_http = (proxy_cfg.get("http") or "").strip()
|
||||
if proxy_cfg.get("enabled") and proxy_http:
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
timeout=timeout,
|
||||
follow_redirects=True,
|
||||
proxy=proxy_http,
|
||||
) as client:
|
||||
proxy_rows = await _probe_all(client)
|
||||
except Exception as e:
|
||||
proxy_rows = [
|
||||
{**t, "ok": False, "latency_ms": None, "error": str(e)}
|
||||
for t in _PROBE_TARGETS
|
||||
]
|
||||
|
||||
global_direct = _global_ok(direct)
|
||||
global_proxy = _global_ok(proxy_rows)
|
||||
douyin_direct = _douyin_ok(direct)
|
||||
|
||||
ok = bool(global_direct or global_proxy)
|
||||
mode = "direct" if global_direct else ("proxy" if global_proxy else "failed")
|
||||
|
||||
checks = {
|
||||
"google": next((p for p in direct if p.get("id") == "google"), {}).get("ok", False),
|
||||
"youtube": next((p for p in direct if p.get("id") == "youtube"), {}).get("ok", False),
|
||||
"douyin": douyin_direct,
|
||||
"global_direct": global_direct,
|
||||
"global_proxy": global_proxy,
|
||||
"proxy_configured": bool(proxy_cfg.get("enabled") and proxy_http),
|
||||
}
|
||||
|
||||
if ok:
|
||||
if global_direct:
|
||||
message = "直连可达 Google/YouTube,可尝试开播"
|
||||
else:
|
||||
message = f"直连失败,但录制代理 {proxy_http} 可达 Google/YouTube"
|
||||
if not douyin_direct:
|
||||
message += ";抖音源站当前不可达,可能无法拉流"
|
||||
else:
|
||||
parts = []
|
||||
if not global_direct:
|
||||
parts.append("Google/YouTube 直连不可达")
|
||||
if proxy_cfg.get("enabled"):
|
||||
if not global_proxy:
|
||||
parts.append("录制代理也未打通 Google/YouTube")
|
||||
else:
|
||||
parts.append("未启用录制代理")
|
||||
if not douyin_direct:
|
||||
parts.append("抖音源站不可达")
|
||||
message = ";".join(parts)
|
||||
|
||||
blocking_message: Optional[str] = None
|
||||
if not ok:
|
||||
blocking_message = (
|
||||
f"{message}。请先在「网络」页检测并配置代理,或在下方「录制代理」中填写 Clash 混合端口后再开播。"
|
||||
)
|
||||
|
||||
return {
|
||||
"ok": ok,
|
||||
"mode": mode,
|
||||
"message": message,
|
||||
"blocking_message": blocking_message,
|
||||
"checks": checks,
|
||||
"direct": direct,
|
||||
"proxy": proxy_rows,
|
||||
"proxy_config": proxy_cfg,
|
||||
"douyin_direct_ok": douyin_direct,
|
||||
}
|
||||
|
||||
|
||||
async def youtube_start_preflight_message() -> str | None:
|
||||
timeout = httpx.Timeout(READ_TIMEOUT, connect=CONNECT_TIMEOUT)
|
||||
async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client:
|
||||
ok, _, err = await _latency_get(client, GOOGLE_CHECK_URL)
|
||||
if ok:
|
||||
result = await run_youtube_preflight()
|
||||
if result.get("ok"):
|
||||
return None
|
||||
detail = err or "timeout"
|
||||
return (
|
||||
f"Google 出口不可达({detail}),YouTube 推流可能失败。"
|
||||
"请先在「网络」页检测并配置代理/TUN,再开始直播。"
|
||||
)
|
||||
return str(result.get("blocking_message") or result.get("message") or "网络预检未通过")
|
||||
|
||||
Reference in New Issue
Block a user