feat: implement screen recording via chrome.tabCapture with offscreen document

- Use chrome.tabCapture.getMediaStreamId() from service worker
- Pass stream ID to offscreen document for MediaRecorder
- Recording survives page navigation (unlike getDisplayMedia)
- Requires user to click extension icon first (activeTab permission)
- Add esbuild to build offscreen.ts separately
- Update prompt docs with recording instructions
This commit is contained in:
Tommy D. Rossi
2026-01-23 16:57:51 +01:00
parent e56e27e5e5
commit 74497c2a1a
8 changed files with 210 additions and 278 deletions
+9 -9
View File
@@ -5,14 +5,14 @@
* - Future: audio processing, canvas operations, etc.
*/
interface RecordingState {
recorder: MediaRecorder | null
stream: MediaStream | null
interface OffscreenRecordingState {
recorder: MediaRecorder
stream: MediaStream
startedAt: number
tabId: number
}
let recording: RecordingState | null = null
let recording: OffscreenRecordingState | null = null
// Message types
type StartRecordingMessage = {
@@ -119,8 +119,8 @@ async function handleStartRecording(params: StartRecordingMessage): Promise<any>
}
}
recorder.onerror = (event: any) => {
console.error('MediaRecorder error:', event.error)
recorder.onerror = (event: Event) => {
console.error('MediaRecorder error:', (event as ErrorEvent).error)
handleCancelRecording()
}
@@ -149,7 +149,7 @@ async function handleStopRecording(): Promise<any> {
// Stop recorder and wait for final data
await new Promise<void>((resolve) => {
const originalOnStop = recorder.onstop
recorder.onstop = (event) => {
recorder.onstop = (event: Event) => {
if (originalOnStop) {
originalOnStop.call(recorder, event)
}
@@ -163,7 +163,7 @@ async function handleStopRecording(): Promise<any> {
})
// Stop all tracks
stream.getTracks().forEach((track) => track.stop())
stream.getTracks().forEach((track: MediaStreamTrack) => { track.stop() })
const duration = Date.now() - startedAt
@@ -205,7 +205,7 @@ function handleCancelRecording(): any {
if (recorder.state !== 'inactive') {
recorder.stop()
}
stream.getTracks().forEach((track) => track.stop())
stream.getTracks().forEach((track: MediaStreamTrack) => { track.stop() })
chrome.runtime.sendMessage({
action: 'recordingCancelled',