javplayer / main.py
bigbossmonster's picture
Update main.py
9ee80cc verified
import uvicorn
import httpx
from fastapi import FastAPI, Response, Request, HTTPException # Added Request
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
# --- Configuration ---
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Referer": "https://media.javtrailers.com/",
}
# Global client variable
http_client: httpx.AsyncClient = None
# --- Lifespan Management ---
@asynccontextmanager
async def lifespan(app: FastAPI):
global http_client
http_client = httpx.AsyncClient(
headers=HEADERS,
timeout=httpx.Timeout(10.0, read=30.0),
follow_redirects=True
)
yield
await http_client.aclose()
app = FastAPI(lifespan=lifespan)
# Allow CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# --- Helpers ---
def rewrite_m3u8_content(content_text: str, base_url: str) -> str:
lines = content_text.splitlines()
new_lines = []
for line in lines:
line = line.strip()
if not line:
continue
if line.startswith("#"):
new_lines.append(line)
elif line.startswith("http://") or line.startswith("https://"):
new_lines.append(line)
else:
new_lines.append(f"/proxy-segment?base={base_url}&segment={line}")
return "\n".join(new_lines)
async def stream_generator(response: httpx.Response):
"""Safely iterates over the stream, handling upstream disconnects."""
try:
async for chunk in response.aiter_raw():
yield chunk
except (httpx.ReadError, httpx.RemoteProtocolError, httpx.ConnectError) as e:
print(f"Warning: Stream interrupted by upstream: {e}")
finally:
await response.aclose()
# --- Routes ---
@app.get("/")
def home():
return {
"status": "Running",
"usage_hls": "/proxy-playlist?url=...",
"usage_mp4": "/proxy-video?url=..."
}
# ---------------------------------------------------------
# NEW: MP4 Proxy Support (Supports Seeking/Scrubbing)
# ---------------------------------------------------------
@app.get("/proxy-video")
async def proxy_video(url: str, request: Request):
try:
# 1. Forward the 'Range' header if the browser sent one.
# This allows the browser to request specific byte chunks (seeking).
req_headers = {}
if "range" in request.headers:
req_headers["Range"] = request.headers["range"]
# 2. Build the request manually
req = http_client.build_request("GET", url, headers=req_headers)
# 3. Send request (stream=True)
r = await http_client.send(req, stream=True)
# 4. Handle errors
if r.status_code >= 400:
await r.aclose()
return Response(content=f"Upstream Error {r.status_code}", status_code=r.status_code)
# 5. Prepare Response Headers
# We must forward Content-Range/Length so the browser knows the video size.
resp_headers = {
"Accept-Ranges": "bytes", # Tell browser we support seeking
"Content-Type": r.headers.get("Content-Type", "video/mp4"),
}
# Only add these if they exist in the upstream response
if "Content-Length" in r.headers:
resp_headers["Content-Length"] = r.headers["Content-Length"]
if "Content-Range" in r.headers:
resp_headers["Content-Range"] = r.headers["Content-Range"]
# 6. Return StreamingResponse
# We pass r.status_code (usually 206 or 200) directly.
return StreamingResponse(
stream_generator(r),
status_code=r.status_code,
headers=resp_headers,
media_type="video/mp4"
)
except httpx.RequestError as e:
return Response(content=f"Video Error: {e}", status_code=502)
# --- Existing HLS Routes ---
@app.get("/proxy-playlist")
async def proxy_playlist(url: str):
try:
resp = await http_client.get(url)
resp.raise_for_status()
base_url = str(resp.url).rsplit('/', 1)[0] + '/'
modified_m3u8 = rewrite_m3u8_content(resp.text, base_url)
return Response(content=modified_m3u8, media_type="application/vnd.apple.mpegurl")
except httpx.RequestError as e:
return Response(content=f"Proxy Error: {e}", status_code=502)
@app.get("/proxy-segment")
async def proxy_segment(base: str, segment: str):
full_url = base + segment
# CASE 1: Nested Playlist (.m3u8)
if segment.split('?')[0].endswith('.m3u8'):
try:
resp = await http_client.get(full_url)
resp.raise_for_status()
new_base = str(resp.url).rsplit('/', 1)[0] + '/'
modified_m3u8 = rewrite_m3u8_content(resp.text, new_base)
return Response(content=modified_m3u8, media_type="application/vnd.apple.mpegurl")
except httpx.RequestError as e:
return Response(content=f"Playlist Error: {e}", status_code=502)
# CASE 2: Video Segment (.ts)
try:
req = http_client.build_request("GET", full_url)
r = await http_client.send(req, stream=True)
if r.status_code >= 400:
await r.aclose()
return Response(content=f"Upstream Error {r.status_code}", status_code=r.status_code)
return StreamingResponse(
stream_generator(r),
media_type="video/MP2T"
)
except httpx.RequestError as e:
print(f"Connection failed for {segment}: {e}")
return Response(content="Segment unavailable", status_code=502)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=7860)