-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
102 lines (92 loc) · 3.61 KB
/
main.py
File metadata and controls
102 lines (92 loc) · 3.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse
from playwright.async_api import async_playwright, ViewportSize, Page, Browser
import asyncio
import json
import os
app = FastAPI()
browser: Browser = None
sessions: dict[str, Page] = {}
session_locks: dict[str, asyncio.Lock] = {}
WIDTH, HEIGHT = (1280, 720)
FPS = 30 # VERY LOW so Render doesn't commit die when tryna run this
LIVE_FEED_QUALITY = 60 # Also quite low (just over a quarter) to satisfy Render's bad VM
@app.on_event("startup")
async def startup():
global browser
playwright = await async_playwright().start()
browser = await playwright.chromium.launch(
headless=True,
chromium_sandbox=False,
args=[
"--disable-gpu",
"--no-sandbox",
"--disable-dev-shm-usage",
"--disable-background-timer-throttling",
"--disable-renderer-backgrounding",
"--disable-extensions",
"--disable-infobars",
"--disable-accelerated-2d-canvas",
"--disable-software-rasterizer"
]
)
async def get_or_create_page(sid: str):
lock = session_locks.setdefault(sid, asyncio.Lock())
async with lock:
if sid not in sessions:
page = await browser.new_page(
viewport=ViewportSize(width=WIDTH, height=HEIGHT),
)
await page.goto("https://www.google.com")
await page.wait_for_load_state("networkidle")
sessions[sid] = page
return sessions[sid]
@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
await ws.accept()
sid = ws.query_params.get("sid")
page = await get_or_create_page(sid)
async def send_frames():
try:
while True:
buf = await page.screenshot(type="jpeg", quality=LIVE_FEED_QUALITY)
await ws.send_bytes(buf) # send as binary
await asyncio.sleep(1 / FPS)
except Exception as e:
print(f"Frame sender stopped: {e}")
# run frame sender in background
frame_task = asyncio.create_task(send_frames())
try:
async for message in ws.iter_text():
data = json.loads(message)
if data["type"] == "navigate":
url = data["url"]
if ":" not in url:
url = f"http://{url}"
await page.goto(url)
await page.wait_for_load_state("networkidle")
elif data["type"] == "mousemove":
scaled_x = data["x"] * (WIDTH / data["img_width"])
scaled_y = data["y"] * (HEIGHT / data["img_height"])
await page.mouse.move(x=scaled_x, y=scaled_y)
elif data["type"] == "click":
scaled_x = data["x"] * (WIDTH / data["img_width"])
scaled_y = data["y"] * (HEIGHT / data["img_height"])
await page.mouse.click(x=scaled_x, y=scaled_y)
elif data["type"] == "rightClick":
scaled_x = data["x"] * (WIDTH / data["img_width"])
scaled_y = data["y"] * (HEIGHT / data["img_height"])
await page.mouse.click(x=scaled_x, y=scaled_y, button="right")
elif data["type"] == "keypress":
await page.keyboard.press(data["key"])
elif data["type"] == "wheel":
await page.mouse.wheel(data["deltaX"], data["deltaY"])
finally:
frame_task.cancel()
await page.close()
sessions.pop(sid, None)
session_locks.pop(sid, None)
@app.get("/")
def index():
with open("index.html") as f:
return HTMLResponse(f.read())