File size: 7,424 Bytes
399b80c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import aiohttp
from typing import Optional
from openspace.utils.logging import Logger
from .config import get_client_base_url

logger = Logger.get_logger(__name__)


class RecordingClient:
    """
    Client for screen recording via HTTP API.
    
    This client directly calls the local server's recording endpoints:
    - POST /start_recording
    - POST /end_recording
    """
    
    def __init__(
        self,
        base_url: Optional[str] = None,
        timeout: int = 30
    ):
        """
        Initialize recording client.
        
        Args:
            base_url: Base URL of the local server
                     (default: read from local_server/config.json or env LOCAL_SERVER_URL)
            timeout: Request timeout in seconds
        """
        # Get base_url: priority is explicit > env > config file
        if base_url is None:
            base_url = get_client_base_url()
        
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self._session: Optional[aiohttp.ClientSession] = None
    
    async def _get_session(self) -> aiohttp.ClientSession:
        """Get or create aiohttp session."""
        if self._session is None or self._session.closed:
            self._session = aiohttp.ClientSession(
                timeout=aiohttp.ClientTimeout(total=self.timeout)
            )
        return self._session
    
    async def start_recording(self, auto_cleanup: bool = True) -> bool:
        """
        Start screen recording.
        
        Args:
            auto_cleanup: If True, automatically end previous recording if one is in progress
        """
        try:
            session = await self._get_session()
            url = f"{self.base_url}/start_recording"
            
            async with session.post(url) as response:
                if response.status == 200:
                    logger.info("Screen recording started")
                    return True
                elif response.status == 400 and auto_cleanup:
                    # Check if error is due to recording already in progress
                    error_text = await response.text()
                    if "already in progress" in error_text.lower():
                        logger.warning("Recording already in progress, stopping previous recording...")
                        
                        # Try to end the previous recording
                        video_bytes = await self.end_recording()
                        if video_bytes:
                            logger.info("Previous recording ended successfully, retrying start...")
                        else:
                            logger.warning("Failed to end previous recording, but will retry start anyway...")
                        
                        # Retry starting recording (without auto_cleanup to avoid infinite loop)
                        return await self.start_recording(auto_cleanup=False)
                    else:
                        logger.error(f"Failed to start recording: HTTP {response.status} - {error_text}")
                        return False
                else:
                    error_text = await response.text()
                    logger.error(f"Failed to start recording: HTTP {response.status} - {error_text}")
                    return False
        
        except Exception as e:
            logger.error(f"Failed to start recording: {e}")
            return False
    
    async def end_recording(self, dest: Optional[str] = None) -> Optional[bytes]:
        """
        End screen recording and optionally save to file.
        """
        try:
            session = await self._get_session()
            url = f"{self.base_url}/end_recording"
            
            # Use longer timeout for end_recording (file may be large)
            async with session.post(url, timeout=aiohttp.ClientTimeout(total=60)) as response:
                if response.status == 200:
                    video_bytes = await response.read()
                    
                    # Save to file if destination provided
                    if dest:
                        try:
                            with open(dest, "wb") as f:
                                f.write(video_bytes)
                            logger.info(f"Recording saved to: {dest}")
                        except Exception as e:
                            logger.error(f"Failed to save recording file: {e}")
                            return None
                    
                    logger.info("Screen recording ended")
                    return video_bytes
                else:
                    error_text = await response.text()
                    logger.error(f"Failed to end recording: HTTP {response.status} - {error_text}")
                    return None
        
        except Exception as e:
            logger.error(f"Failed to end recording: {e}")
            return None
    
    async def close(self):
        """Close the HTTP session."""
        if self._session and not self._session.closed:
            await self._session.close()
            # Give aiohttp time to finish cleanup callbacks
            import asyncio
            await asyncio.sleep(0.25)
            logger.debug("Recording client session closed")
    
    async def __aenter__(self):
        """Context manager entry."""
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit."""
        await self.close()
        return False


class RecordingContextManager:

    def __init__(
        self,
        base_url: Optional[str] = None,
        output_path: Optional[str] = None,
        timeout: Optional[int] = None
    ):
        """
        Initialize recording context manager.
        
        Args:
            base_url: Base URL of the local server (default: from config)
            output_path: Path to save recording (default: from config)
            timeout: Request timeout in seconds (default: from config)
        """
        # Load output_path from config if not provided
        if output_path is None:
            try:
                from openspace.config import get_config
                config = get_config()
                if config.recording.screen_recording_path:
                    output_path = config.recording.screen_recording_path
            except Exception:
                pass
        
        self.client = RecordingClient(base_url=base_url, timeout=timeout)
        self.output_path = output_path
        self.recording_started = False
    
    async def __aenter__(self) -> RecordingClient:
        """Start recording on context entry."""
        success = await self.client.start_recording()
        if success:
            self.recording_started = True
            logger.info("Recording context started")
        else:
            logger.warning("Failed to start recording in context")
        
        return self.client
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Stop recording on context exit."""
        if self.recording_started:
            try:
                await self.client.end_recording(dest=self.output_path)
                logger.info("Recording context ended")
            except Exception as e:
                logger.error(f"Failed to end recording in context: {e}")
        
        await self.client.close()
        return False