autoglm-gui 0.4.11__py3-none-any.whl → 0.4.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. AutoGLM_GUI/__init__.py +8 -0
  2. AutoGLM_GUI/__main__.py +29 -34
  3. AutoGLM_GUI/adb_plus/__init__.py +6 -0
  4. AutoGLM_GUI/adb_plus/device.py +50 -0
  5. AutoGLM_GUI/adb_plus/ip.py +78 -0
  6. AutoGLM_GUI/adb_plus/serial.py +35 -0
  7. AutoGLM_GUI/api/__init__.py +8 -0
  8. AutoGLM_GUI/api/agents.py +76 -67
  9. AutoGLM_GUI/api/devices.py +96 -6
  10. AutoGLM_GUI/api/media.py +12 -235
  11. AutoGLM_GUI/config_manager.py +538 -97
  12. AutoGLM_GUI/exceptions.py +7 -0
  13. AutoGLM_GUI/platform_utils.py +19 -0
  14. AutoGLM_GUI/schemas.py +35 -2
  15. AutoGLM_GUI/scrcpy_protocol.py +46 -0
  16. AutoGLM_GUI/scrcpy_stream.py +192 -307
  17. AutoGLM_GUI/server.py +7 -2
  18. AutoGLM_GUI/socketio_server.py +125 -0
  19. AutoGLM_GUI/static/assets/{about-wSo3UgQ-.js → about-kgOkkOWe.js} +1 -1
  20. AutoGLM_GUI/static/assets/chat-CZV3RByK.js +149 -0
  21. AutoGLM_GUI/static/assets/{index-B5u1xtK1.js → index-BPYHsweG.js} +1 -1
  22. AutoGLM_GUI/static/assets/index-Beu9cbSy.css +1 -0
  23. AutoGLM_GUI/static/assets/index-DfI_Z1Cx.js +10 -0
  24. AutoGLM_GUI/static/assets/worker-D6BRitjy.js +1 -0
  25. AutoGLM_GUI/static/index.html +2 -2
  26. {autoglm_gui-0.4.11.dist-info → autoglm_gui-0.4.12.dist-info}/METADATA +2 -1
  27. autoglm_gui-0.4.12.dist-info/RECORD +56 -0
  28. AutoGLM_GUI/resources/apks/ADBKeyBoard.LICENSE.txt +0 -339
  29. AutoGLM_GUI/resources/apks/ADBKeyBoard.README.txt +0 -1
  30. AutoGLM_GUI/resources/apks/ADBKeyboard.apk +0 -0
  31. AutoGLM_GUI/static/assets/chat-BcY2K0yj.js +0 -25
  32. AutoGLM_GUI/static/assets/index-CHrYo3Qj.css +0 -1
  33. AutoGLM_GUI/static/assets/index-D5BALRbT.js +0 -10
  34. autoglm_gui-0.4.11.dist-info/RECORD +0 -52
  35. {autoglm_gui-0.4.11.dist-info → autoglm_gui-0.4.12.dist-info}/WHEEL +0 -0
  36. {autoglm_gui-0.4.11.dist-info → autoglm_gui-0.4.12.dist-info}/entry_points.txt +0 -0
  37. {autoglm_gui-0.4.11.dist-info → autoglm_gui-0.4.12.dist-info}/licenses/LICENSE +0 -0
AutoGLM_GUI/api/media.py CHANGED
@@ -1,52 +1,28 @@
1
- """Media routes: screenshot, video stream, stream reset."""
1
+ """Media routes: screenshot and stream reset."""
2
2
 
3
- import asyncio
4
- import os
5
- from pathlib import Path
3
+ from __future__ import annotations
6
4
 
7
- from fastapi import APIRouter, WebSocket, WebSocketDisconnect
5
+ from fastapi import APIRouter
8
6
 
9
7
  from AutoGLM_GUI.adb_plus import capture_screenshot
10
8
  from AutoGLM_GUI.logger import logger
11
9
  from AutoGLM_GUI.schemas import ScreenshotRequest, ScreenshotResponse
12
- from AutoGLM_GUI.scrcpy_stream import ScrcpyStreamer
13
- from AutoGLM_GUI.state import scrcpy_locks, scrcpy_streamers
10
+ from AutoGLM_GUI.socketio_server import stop_streamers
14
11
 
15
12
  router = APIRouter()
16
13
 
17
- # Debug configuration: Set DEBUG_SAVE_VIDEO_STREAM=1 to save streams to debug_streams/
18
- DEBUG_SAVE_STREAM = os.getenv("DEBUG_SAVE_VIDEO_STREAM", "0") == "1"
19
-
20
14
 
21
15
  @router.post("/api/video/reset")
22
16
  async def reset_video_stream(device_id: str | None = None) -> dict:
23
- """Reset video stream (cleanup scrcpy server,多设备支持)."""
17
+ """Reset active scrcpy streams (Socket.IO)."""
18
+ stop_streamers(device_id=device_id)
24
19
  if device_id:
25
- if device_id in scrcpy_locks:
26
- async with scrcpy_locks[device_id]:
27
- if device_id in scrcpy_streamers:
28
- logger.info(f"Stopping streamer for device {device_id}")
29
- scrcpy_streamers[device_id].stop()
30
- del scrcpy_streamers[device_id]
31
- logger.info(f"Streamer reset for device {device_id}")
32
- return {
33
- "success": True,
34
- "message": f"Video stream reset for device {device_id}",
35
- }
36
- return {
37
- "success": True,
38
- "message": f"No active video stream for device {device_id}",
39
- }
40
- return {"success": True, "message": f"No video stream for device {device_id}"}
41
-
42
- device_ids = list(scrcpy_streamers.keys())
43
- for dev_id in device_ids:
44
- if dev_id in scrcpy_locks:
45
- async with scrcpy_locks[dev_id]:
46
- if dev_id in scrcpy_streamers:
47
- scrcpy_streamers[dev_id].stop()
48
- del scrcpy_streamers[dev_id]
49
- logger.info("All streamers reset")
20
+ logger.info("Video stream reset for device %s", device_id)
21
+ return {
22
+ "success": True,
23
+ "message": f"Video stream reset for device {device_id}",
24
+ }
25
+ logger.info("All video streams reset")
50
26
  return {"success": True, "message": "All video streams reset"}
51
27
 
52
28
 
@@ -71,202 +47,3 @@ def take_screenshot(request: ScreenshotRequest) -> ScreenshotResponse:
71
47
  is_sensitive=False,
72
48
  error=str(e),
73
49
  )
74
-
75
-
76
- @router.websocket("/api/video/stream")
77
- async def video_stream_ws(
78
- websocket: WebSocket,
79
- device_id: str | None = None,
80
- ):
81
- """Stream real-time H.264 video from scrcpy server via WebSocket(多设备支持)."""
82
- await websocket.accept()
83
-
84
- if not device_id:
85
- await websocket.send_json({"error": "device_id is required"})
86
- return
87
-
88
- logger.info(f"WebSocket connection for device {device_id}")
89
-
90
- # Debug: Save stream to file for analysis (controlled by DEBUG_SAVE_VIDEO_STREAM env var)
91
- debug_file = None
92
- if DEBUG_SAVE_STREAM:
93
- debug_dir = Path("debug_streams")
94
- debug_dir.mkdir(exist_ok=True)
95
- debug_file_path = (
96
- debug_dir / f"{device_id}_{int(__import__('time').time())}.h264"
97
- )
98
- debug_file = open(debug_file_path, "wb")
99
- logger.debug(f"DEBUG: Saving stream to {debug_file_path}")
100
-
101
- if device_id not in scrcpy_locks:
102
- scrcpy_locks[device_id] = asyncio.Lock()
103
-
104
- async with scrcpy_locks[device_id]:
105
- if device_id not in scrcpy_streamers:
106
- logger.info(f"Creating streamer for device {device_id}")
107
- scrcpy_streamers[device_id] = ScrcpyStreamer(
108
- device_id=device_id, max_size=1280, bit_rate=4_000_000
109
- )
110
-
111
- try:
112
- logger.info(f"Starting scrcpy server for device {device_id}")
113
- await scrcpy_streamers[device_id].start()
114
- logger.info(f"Scrcpy server started for device {device_id}")
115
-
116
- # Read NAL units until we have SPS, PPS, and IDR
117
- streamer = scrcpy_streamers[device_id]
118
-
119
- logger.debug("Reading NAL units for initialization...")
120
- for attempt in range(20): # Max 20 NAL units for initialization
121
- try:
122
- nal_unit = await streamer.read_nal_unit(auto_cache=True)
123
- nal_type = nal_unit[4] & 0x1F if len(nal_unit) > 4 else -1
124
- nal_type_names = {5: "IDR", 7: "SPS", 8: "PPS"}
125
- logger.debug(
126
- f"Read NAL unit: type={nal_type_names.get(nal_type, nal_type)}, size={len(nal_unit)} bytes"
127
- )
128
-
129
- # Check if we have all required parameter sets
130
- if (
131
- streamer.cached_sps
132
- and streamer.cached_pps
133
- and streamer.cached_idr
134
- ):
135
- logger.debug(
136
- f"✓ Initialization complete: SPS={len(streamer.cached_sps)}B, PPS={len(streamer.cached_pps)}B, IDR={len(streamer.cached_idr)}B"
137
- )
138
- break
139
- except Exception as e:
140
- logger.warning(f"Failed to read NAL unit: {e}")
141
- await asyncio.sleep(0.5)
142
- continue
143
-
144
- # Get initialization data (SPS + PPS + IDR)
145
- init_data = streamer.get_initialization_data()
146
- if not init_data:
147
- raise RuntimeError(
148
- "Failed to get initialization data (missing SPS/PPS/IDR)"
149
- )
150
-
151
- # Send initialization data as ONE message (SPS+PPS+IDR combined)
152
- await websocket.send_bytes(init_data)
153
- logger.debug(
154
- f"✓ Sent initialization data to first client: {len(init_data)} bytes total"
155
- )
156
-
157
- # Debug: Save to file
158
- if debug_file:
159
- debug_file.write(init_data)
160
- debug_file.flush()
161
-
162
- except Exception as e:
163
- logger.exception(f"Failed to start streamer: {e}")
164
- scrcpy_streamers[device_id].stop()
165
- del scrcpy_streamers[device_id]
166
- try:
167
- await websocket.send_json({"error": str(e)})
168
- except Exception:
169
- pass
170
- return
171
- else:
172
- logger.info(f"Reusing streamer for device {device_id}")
173
-
174
- streamer = scrcpy_streamers[device_id]
175
- # CRITICAL: Send complete initialization data (SPS+PPS+IDR)
176
- # Without IDR frame, decoder cannot start and will show black screen
177
-
178
- # Wait for initialization data to be ready (max 5 seconds)
179
- init_data = None
180
- for attempt in range(10):
181
- init_data = streamer.get_initialization_data()
182
- if init_data:
183
- break
184
- logger.debug(
185
- f"Waiting for initialization data (attempt {attempt + 1}/10)..."
186
- )
187
- await asyncio.sleep(0.5)
188
-
189
- if init_data:
190
- # Log what we're sending
191
- logger.debug(
192
- f"✓ Sending cached initialization data for device {device_id}:"
193
- )
194
- logger.debug(
195
- f" - SPS: {len(streamer.cached_sps) if streamer.cached_sps else 0}B"
196
- )
197
- logger.debug(
198
- f" - PPS: {len(streamer.cached_pps) if streamer.cached_pps else 0}B"
199
- )
200
- logger.debug(
201
- f" - IDR: {len(streamer.cached_idr) if streamer.cached_idr else 0}B"
202
- )
203
- logger.debug(f" - Total: {len(init_data)} bytes")
204
-
205
- await websocket.send_bytes(init_data)
206
- logger.debug("✓ Initialization data sent successfully")
207
-
208
- # Debug: Save to file
209
- if debug_file:
210
- debug_file.write(init_data)
211
- debug_file.flush()
212
- else:
213
- error_msg = f"Initialization data not ready for device {device_id} after 5 seconds"
214
- logger.error(f"ERROR: {error_msg}")
215
- try:
216
- await websocket.send_json({"error": error_msg})
217
- except Exception:
218
- pass
219
- return
220
-
221
- streamer = scrcpy_streamers[device_id]
222
-
223
- stream_failed = False
224
- try:
225
- nal_count = 0
226
- while True:
227
- try:
228
- # Read one complete NAL unit
229
- # Each WebSocket message = one complete NAL unit (clear semantic boundary)
230
- nal_unit = await streamer.read_nal_unit(auto_cache=True)
231
- await websocket.send_bytes(nal_unit)
232
-
233
- # Debug: Save to file
234
- if debug_file:
235
- debug_file.write(nal_unit)
236
- debug_file.flush()
237
-
238
- nal_count += 1
239
- if nal_count % 100 == 0:
240
- logger.debug(f"Device {device_id}: Sent {nal_count} NAL units")
241
- except ConnectionError as e:
242
- logger.warning(f"Device {device_id}: Connection error: {e}")
243
- stream_failed = True
244
- try:
245
- await websocket.send_json({"error": f"Stream error: {str(e)}"})
246
- except Exception:
247
- pass
248
- break
249
-
250
- except WebSocketDisconnect:
251
- logger.info(f"Device {device_id}: Client disconnected")
252
- except Exception as e:
253
- logger.exception(f"Device {device_id}: Error: {e}")
254
- stream_failed = True
255
- try:
256
- await websocket.send_json({"error": str(e)})
257
- except Exception:
258
- pass
259
-
260
- if stream_failed:
261
- async with scrcpy_locks[device_id]:
262
- if device_id in scrcpy_streamers:
263
- logger.info(f"Resetting streamer for device {device_id}")
264
- scrcpy_streamers[device_id].stop()
265
- del scrcpy_streamers[device_id]
266
-
267
- # Debug: Close file
268
- if debug_file:
269
- debug_file.close()
270
- logger.debug("DEBUG: Closed debug file")
271
-
272
- logger.info(f"Device {device_id}: Stream ended")