File size: 9,072 Bytes
509a107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
import { useState, useCallback, useRef } from 'react'
import './ScreenCapture.css'

const ScreenCapture = ({ onCapture, onError }) => {
  const [isCapturing, setIsCapturing] = useState(false)
  const [permissionState, setPermissionState] = useState('prompt') // 'prompt', 'granted', 'denied'
  const [errorMessage, setErrorMessage] = useState(null)
  const [stream, setStream] = useState(null)
  const videoRef = useRef(null)
  const canvasRef = useRef(null)

  const checkBrowserSupport = () => {
    if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
      return {
        supported: false,
        message: 'Screen capture is not supported in your browser. Please use Chrome, Edge, or Firefox.'
      }
    }
    return { supported: true }
  }

  const handlePermissionError = (error) => {
    console.error('Screen capture error:', error)
    
    let userMessage = ''
    let developerInfo = ''
    
    if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
      userMessage = 'Screen capture permission was denied. Please click "Allow" when prompted to share your screen.'
      developerInfo = 'User denied permission'
      setPermissionState('denied')
    } else if (error.name === 'NotFoundError') {
      userMessage = 'No screen capture sources available. Please make sure you have a display connected.'
      developerInfo = 'No capture sources found'
    } else if (error.name === 'NotReadableError') {
      userMessage = 'Screen capture source is currently in use by another application. Please close other screen recording applications and try again.'
      developerInfo = 'Hardware or OS constraint'
    } else if (error.name === 'OverconstrainedError') {
      userMessage = 'The requested screen capture settings are not supported. Trying with default settings...'
      developerInfo = 'Constraint error'
    } else if (error.name === 'TypeError') {
      userMessage = 'Screen capture API error. Please refresh the page and try again.'
      developerInfo = 'API usage error'
    } else if (error.name === 'AbortError') {
      userMessage = 'Screen capture was cancelled.'
      developerInfo = 'User aborted'
    } else {
      userMessage = `Screen capture failed: ${error.message || 'Unknown error'}`
      developerInfo = error.toString()
    }
    
    setErrorMessage(userMessage)
    
    if (onError) {
      onError({
        userMessage,
        technicalDetails: {
          name: error.name,
          message: error.message,
          info: developerInfo
        }
      })
    }
    
    return userMessage
  }

  const startCapture = useCallback(async () => {
    const support = checkBrowserSupport()
    if (!support.supported) {
      setErrorMessage(support.message)
      if (onError) {
        onError({
          userMessage: support.message,
          technicalDetails: { name: 'BrowserNotSupported' }
        })
      }
      return
    }

    setIsCapturing(true)
    setErrorMessage(null)
    
    try {
      // Configure capture options with fallbacks
      const displayMediaOptions = {
        video: {
          displaySurface: 'browser', // Prefer browser tab
          logicalSurface: true,
          cursor: 'always',
          width: { ideal: 1920 },
          height: { ideal: 1080 }
        },
        audio: false,
        preferCurrentTab: false,
        selfBrowserSurface: 'exclude',
        surfaceSwitching: 'include',
        systemAudio: 'exclude'
      }

      // Try to get display media with full options
      let mediaStream
      try {
        mediaStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions)
      } catch (err) {
        console.warn('Failed with full options, trying minimal options:', err)
        // Fallback to minimal options
        mediaStream = await navigator.mediaDevices.getDisplayMedia({
          video: true,
          audio: false
        })
      }

      setStream(mediaStream)
      setPermissionState('granted')
      
      // Set up video element to display the stream
      if (videoRef.current) {
        videoRef.current.srcObject = mediaStream
        await videoRef.current.play()
      }

      // Listen for stream end (user stops sharing)
      mediaStream.getVideoTracks()[0].addEventListener('ended', () => {
        stopCapture()
        setErrorMessage('Screen sharing was stopped.')
      })

      // Capture a frame after a short delay to ensure video is ready
      setTimeout(() => captureFrame(mediaStream), 500)
      
    } catch (error) {
      handlePermissionError(error)
    } finally {
      setIsCapturing(false)
    }
  }, [])

  const captureFrame = useCallback((mediaStream) => {
    if (!videoRef.current || !canvasRef.current) {
      setErrorMessage('Unable to capture frame. Video elements not ready.')
      return
    }

    try {
      const video = videoRef.current
      const canvas = canvasRef.current
      const context = canvas.getContext('2d')
      
      // Set canvas size to match video
      canvas.width = video.videoWidth
      canvas.height = video.videoHeight
      
      // Draw video frame to canvas
      context.drawImage(video, 0, 0, canvas.width, canvas.height)
      
      // Convert to blob
      canvas.toBlob((blob) => {
        if (blob && onCapture) {
          // Convert blob to base64 for sending to backend
          const reader = new FileReader()
          reader.onloadend = () => {
            onCapture({
              dataUrl: reader.result,
              blob: blob,
              width: canvas.width,
              height: canvas.height,
              timestamp: new Date().toISOString()
            })
          }
          reader.readAsDataURL(blob)
        }
      }, 'image/png', 0.9)
      
    } catch (error) {
      console.error('Error capturing frame:', error)
      setErrorMessage('Failed to capture frame from screen.')
    }
  }, [onCapture])

  const stopCapture = useCallback(() => {
    if (stream) {
      stream.getTracks().forEach(track => track.stop())
      setStream(null)
    }
    if (videoRef.current) {
      videoRef.current.srcObject = null
    }
  }, [stream])

  const retryCapture = useCallback(() => {
    setErrorMessage(null)
    setPermissionState('prompt')
    startCapture()
  }, [startCapture])

  return (
    <div className="screen-capture-container">
      {errorMessage && (
        <div className="error-banner">
          <div className="error-content">
            <svg className="error-icon" viewBox="0 0 24 24" width="20" height="20">
              <path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
            </svg>
            <span className="error-message">{errorMessage}</span>
          </div>
          {permissionState === 'denied' && (
            <button className="retry-button" onClick={retryCapture}>
              Try Again
            </button>
          )}
        </div>
      )}
      
      <div className="capture-controls">
        <button 
          onClick={startCapture}
          disabled={isCapturing || stream}
          className={`capture-button ${isCapturing ? 'capturing' : ''}`}
        >
          {isCapturing ? (
            <>
              <span className="spinner"></span>
              Requesting Permission...
            </>
          ) : stream ? (
            <>
              <span className="recording-dot"></span>
              Screen Sharing Active
            </>
          ) : (
            <>
              <svg className="capture-icon" viewBox="0 0 24 24" width="20" height="20">
                <path fill="currentColor" d="M21 3H3c-1.11 0-2 .89-2 2v14c0 1.11.89 2 2 2h18c1.11 0 2-.89 2-2V5c0-1.11-.89-2-2-2zm0 16H3V5h18v14z"/>
                <path fill="currentColor" d="M15 11l-4-2v6z"/>
              </svg>
              Capture Screen
            </>
          )}
        </button>
        
        {stream && (
          <button onClick={stopCapture} className="stop-button">
            Stop Sharing
          </button>
        )}
      </div>

      {stream && (
        <button 
          onClick={() => captureFrame(stream)}
          className="snapshot-button"
        >
          Take Screenshot
        </button>
      )}

      {/* Hidden video and canvas elements for capture */}
      <video 
        ref={videoRef}
        style={{ display: 'none' }}
        autoPlay
        playsInline
      />
      <canvas 
        ref={canvasRef}
        style={{ display: 'none' }}
      />
      
      {/* Browser compatibility notice */}
      <div className="compatibility-info">
        <details>
          <summary>Browser Compatibility</summary>
          <ul>
            <li>βœ… Chrome 72+</li>
            <li>βœ… Edge 79+</li>
            <li>βœ… Firefox 66+</li>
            <li>βœ… Safari 13+ (macOS)</li>
            <li>❌ Internet Explorer</li>
            <li>⚠️ Mobile browsers have limited support</li>
          </ul>
        </details>
      </div>
    </div>
  )
}

export default ScreenCapture