's'
This commit is contained in:
78
web.py
78
web.py
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from contextlib import asynccontextmanager
|
||||
@@ -9,7 +10,7 @@ from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from fastapi import FastAPI, Query, Request
|
||||
from fastapi import FastAPI, Query, Request, WebSocket
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse, JSONResponse, Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
@@ -28,6 +29,7 @@ from src.android_control import (
|
||||
android_ui_nodes,
|
||||
list_android_devices,
|
||||
)
|
||||
from src.scrcpy_stream import cleanup_scrcpy_session, open_scrcpy_h264_stream, scrcpy_stream_info
|
||||
from src.control_plane import (
|
||||
delete_root_file,
|
||||
list_root_files,
|
||||
@@ -480,6 +482,80 @@ async def get_android_display_metrics(serial: str = Query(..., description="Andr
|
||||
return JSONResponse({"error": str(exc)}, status_code=400)
|
||||
|
||||
|
||||
@app.get("/android/scrcpy/info")
|
||||
async def get_android_scrcpy_info():
|
||||
"""是否具备 scrcpy-server 与版本提示(真 · WebSocket 流依赖此 jar)。"""
|
||||
return scrcpy_stream_info()
|
||||
|
||||
|
||||
@app.websocket("/android/scrcpy/ws")
|
||||
async def android_scrcpy_ws(websocket: WebSocket):
|
||||
"""浏览器 WebSocket:推送设备端 scrcpy-server raw_stream H.264 字节流(与官方 scrcpy 同源编码)。"""
|
||||
await websocket.accept()
|
||||
serial = (websocket.query_params.get("serial") or "").strip()
|
||||
ms_raw = (websocket.query_params.get("max_size") or "1920").strip()
|
||||
try:
|
||||
max_size = max(0, min(int(ms_raw), 8192))
|
||||
except ValueError:
|
||||
max_size = 1920
|
||||
|
||||
writer: asyncio.StreamWriter | None = None
|
||||
port: int | None = None
|
||||
try:
|
||||
port, _scid, reader, writer = await open_scrcpy_h264_stream(serial, max_size=max_size)
|
||||
except ValueError as exc:
|
||||
await websocket.send_text(json.dumps({"error": str(exc), "phase": "serial"}))
|
||||
await websocket.close(code=4400)
|
||||
return
|
||||
except Exception as exc:
|
||||
await websocket.send_text(json.dumps({"error": str(exc), "phase": "setup"}))
|
||||
await websocket.close(code=4500)
|
||||
return
|
||||
|
||||
async def pump() -> None:
|
||||
try:
|
||||
while True:
|
||||
chunk = await reader.read(64 * 1024)
|
||||
if not chunk:
|
||||
break
|
||||
await websocket.send_bytes(chunk)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def wait_client_disconnect() -> None:
|
||||
try:
|
||||
while True:
|
||||
msg = await websocket.receive()
|
||||
if msg.get("type") == "websocket.disconnect":
|
||||
return
|
||||
except Exception:
|
||||
return
|
||||
|
||||
pump_task = asyncio.create_task(pump())
|
||||
disc_task = asyncio.create_task(wait_client_disconnect())
|
||||
try:
|
||||
_done, pending = await asyncio.wait(
|
||||
{pump_task, disc_task},
|
||||
return_when=asyncio.FIRST_COMPLETED,
|
||||
)
|
||||
for t in pending:
|
||||
t.cancel()
|
||||
await asyncio.gather(*pending, return_exceptions=True)
|
||||
finally:
|
||||
if not pump_task.done():
|
||||
pump_task.cancel()
|
||||
await asyncio.gather(pump_task, return_exceptions=True)
|
||||
if not disc_task.done():
|
||||
disc_task.cancel()
|
||||
await asyncio.gather(disc_task, return_exceptions=True)
|
||||
if port is not None:
|
||||
await cleanup_scrcpy_session(port, writer)
|
||||
try:
|
||||
await websocket.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@app.post("/android/action")
|
||||
async def post_android_action(request: Request):
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user