|
| 1 | +import os |
| 2 | + |
| 3 | +os.environ["QT_QPA_PLATFORM"] = "xcb" |
| 4 | + |
1 | 5 | import asyncio |
2 | 6 | import json |
| 7 | + |
| 8 | +import cv2 |
| 9 | +import websockets |
3 | 10 | from aiortc import RTCPeerConnection, RTCSessionDescription |
4 | | -from intercomclient.token_store import TokenStore |
| 11 | + |
5 | 12 | from intercomclient.config import Config |
6 | | -import websockets |
| 13 | +from intercomclient.token_store import TokenStore |
| 14 | + |
7 | 15 |
|
| 16 | +class TestClient: |
| 17 | + def __init__(self): |
| 18 | + self.pc = RTCPeerConnection() |
| 19 | + self.pc.addTransceiver("video", direction="recvonly") |
| 20 | + self.config = Config() |
| 21 | + self.output_dir = "/tmp/intercom_client_testing/frames/" |
| 22 | + if not os.path.exists(self.output_dir): |
| 23 | + os.makedirs(self.output_dir) |
| 24 | + self.device_code = TokenStore(self.config, verify=False).load_tokens()[ |
| 25 | + "device_code" |
| 26 | + ] |
| 27 | + self.websocket_api_url = ( |
| 28 | + f"{self.config.websocket_api_base_url}/{self.device_code}/" |
| 29 | + ) |
| 30 | + self.remote_video = None |
| 31 | + |
| 32 | + async def show_frame(self, frame): |
| 33 | + await asyncio.to_thread(cv2.imshow, "Remote CCTV Feed", frame) |
| 34 | + |
| 35 | + async def display_remote_video(self): |
| 36 | + print("Displaying remote video track...") |
| 37 | + track = self.remote_video |
| 38 | + self.frame_count = 0 |
| 39 | + while True: |
| 40 | + track = self.remote_video |
| 41 | + if track is None: |
| 42 | + await asyncio.sleep(1) |
| 43 | + print("Waiting for remote video track to be available...") |
| 44 | + continue |
| 45 | + print("Waiting for video frame...") |
| 46 | + frame = await track.recv() # receive an AV frame |
| 47 | + print("Video frame received, converting to OpenCV format...") |
| 48 | + img = frame.to_ndarray(format="bgr24") # convert to OpenCV format |
| 49 | + print("Displaying video frame...") |
| 50 | + frame_path = os.path.join(self.output_dir, "frame.jpg") |
| 51 | + if self.frame_count % 3 == 0: # every 3rd frame |
| 52 | + cv2.imwrite(frame_path, img) |
| 53 | + self.frame_count += 1 |
| 54 | + |
| 55 | + async def test_frame(self): |
| 56 | + @self.pc.on("track") |
| 57 | + async def on_track(track): |
| 58 | + print("Track received:", track.kind) |
8 | 59 |
|
9 | | -async def test_frame(): |
10 | | - pc = RTCPeerConnection() |
11 | | - config = Config() |
12 | | - device_code = TokenStore(config, verify=False).load_tokens()["device_code"] |
13 | | - websocket_api_url = f"{config.websocket_api_base_url}/{device_code}/" |
14 | | - |
15 | | - # # Optionally add a dummy track if server expects one |
16 | | - # class DummyTrack(VideoStreamTrack): |
17 | | - # async def recv(self): |
18 | | - # # just return None for testing |
19 | | - # return None |
20 | | - |
21 | | - # pc.addTrack(DummyTrack()) |
22 | | - |
23 | | - async with websockets.connect(websocket_api_url) as ws: |
24 | | - # Create offer |
25 | | - offer = await pc.createOffer() |
26 | | - await pc.setLocalDescription(offer) |
27 | | - |
28 | | - await ws.send(json.dumps({"type": "offer", "sdp": pc.localDescription.sdp})) |
29 | | - |
30 | | - # Wait for answer from server |
31 | | - async for message in ws: |
32 | | - print(f"Test client - Received message: {message}") |
33 | | - data = json.loads(message) |
34 | | - if data["type"] == "answer": |
35 | | - await pc.setRemoteDescription( |
36 | | - RTCSessionDescription(sdp=data["sdp"], type="answer") |
37 | | - ) |
38 | | - elif data["type"] == "ice": |
39 | | - await pc.addIceCandidate(data["candidate"]) |
40 | | - |
41 | | - print("WebRTC handshake done!") |
42 | | - |
43 | | - # Wait for first frame from server |
44 | | - @pc.on("track") |
45 | | - def on_track(track): |
46 | | - print(f"Track received: {track.kind}") |
47 | 60 | if track.kind == "video": |
48 | | - # grab a single frame |
49 | | - async def grab_frame(): |
50 | | - frame = await track.recv() |
51 | | - print(f"Received frame: {frame}") |
52 | | - await pc.close() |
53 | | - return frame |
| 61 | + self.remote_video = track |
| 62 | + |
| 63 | + async with websockets.connect(self.websocket_api_url) as ws: |
| 64 | + # Create offer |
| 65 | + offer = await self.pc.createOffer() |
| 66 | + await self.pc.setLocalDescription(offer) |
| 67 | + |
| 68 | + await ws.send( |
| 69 | + json.dumps({"type": "offer", "sdp": self.pc.localDescription.sdp}) |
| 70 | + ) |
| 71 | + |
| 72 | + # Wait for answer from server |
| 73 | + async for message in ws: |
| 74 | + print(f"Test client - Received message: {message}") |
| 75 | + data = json.loads(message) |
| 76 | + if data["type"] == "answer": |
| 77 | + await self.pc.setRemoteDescription( |
| 78 | + RTCSessionDescription(sdp=data["sdp"], type="answer") |
| 79 | + ) |
| 80 | + elif data["type"] == "ice": |
| 81 | + await self.pc.addIceCandidate(data["candidate"]) |
| 82 | + print("WebRTC handshake done!") |
| 83 | + |
| 84 | + # Keep the event loop alive until frame is received |
| 85 | + await asyncio.sleep(5) |
| 86 | + |
| 87 | + |
| 88 | +async def main(): |
| 89 | + test_client = TestClient() |
54 | 90 |
|
55 | | - asyncio.create_task(grab_frame()) |
| 91 | + signaling_task = asyncio.create_task(test_client.test_frame()) |
| 92 | + display_task = asyncio.create_task(test_client.display_remote_video()) |
56 | 93 |
|
57 | | - # Keep the event loop alive until frame is received |
58 | | - await asyncio.sleep(5) |
| 94 | + await asyncio.gather(signaling_task, display_task) |
59 | 95 |
|
60 | 96 |
|
61 | | -asyncio.run(test_frame()) |
| 97 | +asyncio.run(main()) |
0 commit comments