* Initializes main-thread video recording with worker frame transfer * Worker captures frames as ImageBitmap and sends to main thread * Main thread draws to mirror canvas and uses MediaRecorder (where it's available) * @param {HTMLElement} recordButton - The record button element
(recordButton)
| 1300 | * @param {HTMLElement} recordButton - The record button element |
| 1301 | */ |
| 1302 | function initializeWorkerVideoRecorder(recordButton) { |
| 1303 | console.log('Initializing video recorder for worker mode'); |
| 1304 | |
| 1305 | // Check MediaRecorder support |
| 1306 | if (typeof MediaRecorder === 'undefined') { |
| 1307 | console.warn('MediaRecorder API not supported, video recording disabled'); |
| 1308 | recordButton.style.display = 'none'; |
| 1309 | return; |
| 1310 | } |
| 1311 | |
| 1312 | // Make record button visible |
| 1313 | recordButton.classList.add('visible'); |
| 1314 | console.log('Record button made visible'); |
| 1315 | |
| 1316 | // Try direct capture from the main canvas. After transferControlToOffscreen(), |
| 1317 | // the OffscreenCanvas content auto-syncs to the original HTMLCanvasElement. |
| 1318 | // captureStream() can capture this displayed content directly, eliminating the |
| 1319 | // expensive ImageBitmap→postMessage→mirror canvas pipeline. |
| 1320 | const mainCanvas = document.getElementById('myCanvas'); |
| 1321 | let useDirectCapture = false; |
| 1322 | |
| 1323 | if (mainCanvas) { |
| 1324 | try { |
| 1325 | const testStream = /** @type {HTMLCanvasElement} */ (mainCanvas).captureStream(0); |
| 1326 | if (testStream && testStream.getVideoTracks().length > 0) { |
| 1327 | testStream.getTracks().forEach((t) => t.stop()); |
| 1328 | useDirectCapture = true; |
| 1329 | console.log('Direct canvas capture supported - using optimized recording pipeline (no ImageBitmap transfer)'); |
| 1330 | } |
| 1331 | } catch (e) { |
| 1332 | console.log('Direct canvas capture not supported, falling back to mirror canvas:', e.message); |
| 1333 | } |
| 1334 | } |
| 1335 | |
| 1336 | if (!useDirectCapture) { |
| 1337 | // Fall back to mirror canvas approach (worker sends ImageBitmap frames) |
| 1338 | setupWorkerFrameListener(); |
| 1339 | } |
| 1340 | |
| 1341 | let isRecording = false; |
| 1342 | |
| 1343 | /** |
| 1344 | * Updates record button UI state |
| 1345 | * @param {boolean} recording - Whether currently recording |
| 1346 | */ |
| 1347 | function updateRecordButtonUI(recording) { |
| 1348 | isRecording = recording; |
| 1349 | |
| 1350 | if (recording) { |
| 1351 | recordButton.classList.add('recording'); |
| 1352 | const recordIcon = /** @type {HTMLElement|null} */ (recordButton.querySelector('.record-icon')); |
| 1353 | const stopIcon = /** @type {HTMLElement|null} */ (recordButton.querySelector('.stop-icon')); |
| 1354 | if (recordIcon) recordIcon.style.display = 'none'; |
| 1355 | if (stopIcon) stopIcon.style.display = 'block'; |
| 1356 | } else { |
| 1357 | recordButton.classList.remove('recording'); |
| 1358 | const recordIcon = /** @type {HTMLElement|null} */ (recordButton.querySelector('.record-icon')); |
| 1359 | const stopIcon = /** @type {HTMLElement|null} */ (recordButton.querySelector('.stop-icon')); |
no test coverage detected