Files

90 lines
2.8 KiB
Python

from urllib.parse import urlsplit
import httpx
from fastapi import APIRouter, HTTPException
from fastapi.responses import Response, StreamingResponse
from pydantic import BaseModel
from server.schemas import DeviceBody, device_payload
from server.services.device import DeviceProfile
from server.services.http import http
from server.services import playlist as playlists
from server.services.sessions import sessions
router = APIRouter(prefix="/api/play")
class PlayBody(BaseModel):
token: str = ""
device: DeviceBody | None = None
vid: int
def _device(token: str, raw: dict | None) -> DeviceProfile | None:
if not token:
return None
if raw:
return sessions.bind(token, DeviceProfile.merge(raw))
return sessions.device_for(token)
def _public_url(url: str) -> str:
parts = urlsplit(url)
if parts.scheme not in ("http", "https") or not parts.hostname:
raise HTTPException(status_code=400, detail="播放地址无效")
return url
@router.post("/episodes")
def list_episodes(body: PlayBody):
device = _device(body.token, device_payload(body.device))
try:
rows = playlists.load_episodes(body.token, device, body.vid)
except RuntimeError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return {
"episodes": [
{
"cid": ep["cid"],
"idx": ep["idx"],
"title": ep["title"],
"playable": bool(ep.get("src")),
}
for ep in rows
]
}
@router.get("/segment")
def play_segment(url: str):
target = _public_url(url)
try:
upstream = http.open_stream(target)
except (httpx.HTTPError, TimeoutError, OSError, RuntimeError) as exc:
raise HTTPException(status_code=502, detail="分片读取失败") from exc
media = upstream.headers.get("content-type", "video/mp2t").split(";", 1)[0].strip() or "video/mp2t"
def chunks():
try:
for chunk in upstream.iter_bytes(64 * 1024):
if chunk:
yield chunk
finally:
upstream.close()
return StreamingResponse(chunks(), media_type=media)
@router.get("/{vid}/{cid}/index.m3u8")
def episode_playlist(vid: int, cid: int):
token = sessions.active_token()
device = sessions.device_for(token) if token else None
src = playlists.episode_src(token, device, vid, cid)
if not src:
raise HTTPException(status_code=404, detail="这一集还没有可播放的地址")
try:
text = playlists.playback_playlist(src)
except RuntimeError as exc:
raise HTTPException(status_code=502, detail=str(exc)) from exc
return Response(text, media_type="application/vnd.apple.mpegurl", headers={"Cache-Control": "no-store"})