This commit is contained in:
eric
2026-07-10 05:15:29 -05:00
parent a0104f6517
commit 924d05d06e
71 changed files with 6815 additions and 680 deletions

View File

@@ -1,20 +1,150 @@
"""YouTube 开播前网络预检(参照 sh2 network_preflightWindows 版仅检 Google 可达)。"""
"""YouTube 开播前网络预检(参照 d2ypp2 network_preflightWindows/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 "网络预检未通过")