Skip to content

Commit 9d1871d

Browse files
committed
Improved recording stability
1 parent cb39a0a commit 9d1871d

19 files changed

Lines changed: 1443 additions & 156 deletions

File tree

src/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "__MSG_extName__",
44
"description": "__MSG_extDesc__",
55
"default_locale": "en",
6-
"version": "4.2.0",
6+
"version": "4.2.2",
77
"background": {
88
"service_worker": "background.bundle.js"
99
},

src/pages/Background/listeners/onTabActivatedListener.js

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,37 @@ import { sendMessageTab } from "../tabManagement";
22

33
export const handleTabActivation = async (activeInfo) => {
44
try {
5-
const { recordingStartTime } = await chrome.storage.local.get([
5+
const {
6+
recordingStartTime,
7+
recording,
8+
restarting,
9+
pendingRecording,
10+
recorderSession,
11+
} = await chrome.storage.local.get([
612
"recordingStartTime",
13+
"recording",
14+
"restarting",
15+
"pendingRecording",
16+
"recorderSession",
717
]);
818

919
// Get the activated tab
1020
const tab = await chrome.tabs.get(activeInfo.tabId);
1121

12-
// Check if currently recording or restarting
13-
const { recording } = await chrome.storage.local.get(["recording"]);
14-
const { restarting } = await chrome.storage.local.get(["restarting"]);
15-
const { pendingRecording } = await chrome.storage.local.get([
16-
"pendingRecording",
17-
]);
22+
// Check both recording flag AND recorderSession to avoid race conditions
23+
// recorderSession persists even if the SW restarts
24+
const isActivelyRecording =
25+
recording || (recorderSession && recorderSession.status === "recording");
1826

19-
if (recording) {
27+
if (isActivelyRecording) {
2028
// Check if region recording and if the current tab is the recording tab
21-
const { tabRecordedID } = await chrome.storage.local.get([
22-
"tabRecordedID",
23-
]);
29+
const { tabRecordedID, region, customRegion, recordingType } =
30+
await chrome.storage.local.get([
31+
"tabRecordedID",
32+
"region",
33+
"customRegion",
34+
"recordingType",
35+
]);
2436
if (tabRecordedID && tabRecordedID !== activeInfo.tabId) {
2537
sendMessageTab(activeInfo.tabId, { type: "hide-popup-recording" });
2638
} else if (
@@ -34,18 +46,13 @@ export const handleTabActivation = async (activeInfo) => {
3446
}
3547

3648
// Check if it's region or customRegion recording
37-
const { region } = await chrome.storage.local.get(["region"]);
38-
const { customRegion } = await chrome.storage.local.get(["customRegion"]);
39-
const { recordingType } = await chrome.storage.local.get([
40-
"recordingType",
41-
]);
4249
if (!region && !customRegion && recordingType !== "region") {
4350
sendMessageTab(activeInfo.tabId, {
4451
type: "recording-check",
4552
recordingStartTime,
4653
});
4754
}
48-
} else if (!recording && !restarting && !pendingRecording) {
55+
} else if (!isActivelyRecording && !restarting && !pendingRecording) {
4956
sendMessageTab(activeInfo.tabId, { type: "recording-ended" });
5057
}
5158

src/pages/Background/listeners/onTabRemovedListener.js

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { sendMessageTab, focusTab } from "../tabManagement";
2+
import { sendMessageRecord } from "../recording/sendMessageRecord";
23

34
/**
45
* Listener for when a tab is removed.
@@ -14,35 +15,61 @@ export const onTabRemovedListener = () => {
1415
restarting,
1516
recordingTab,
1617
tabRecordedID,
18+
recorderSession,
1719
} = await chrome.storage.local.get([
1820
"region",
1921
"customRegion",
2022
"recording",
2123
"restarting",
2224
"recordingTab",
2325
"tabRecordedID",
26+
"recorderSession",
2427
]);
2528

2629
const isRegionMode = region || customRegion;
2730
const recordedTabId = tabRecordedID || recordingTab;
2831

29-
if (!recording || restarting) return;
32+
// Check both recording flag AND recorderSession
33+
const isActivelyRecording =
34+
recording ||
35+
(recorderSession && recorderSession.status === "recording");
3036

31-
// If the removed tab is the one being recorded
37+
if (!isActivelyRecording || restarting) return;
38+
39+
// If the removed tab is the one being recorded (for tab capture)
3240
if (tabId === recordedTabId) {
3341
// Clear reference to the removed tab
3442
chrome.storage.local.set({ recordingTab: null, tabRecordedID: null });
3543

36-
const { activeTab } = await chrome.storage.local.get(["activeTab"]);
37-
44+
// Send stop directly to the recorder, not through content script
45+
// This is more reliable as the content script tab may not exist
3846
try {
39-
if (activeTab) {
40-
await sendMessageTab(activeTab, { type: "stop-recording-tab" });
41-
}
47+
await sendMessageRecord({
48+
type: "stop-recording-tab",
49+
reason: "recorded-tab-closed",
50+
});
4251
} catch (err) {
43-
console.warn("Could not message active tab to stop recording:", err);
52+
console.warn("Could not message recorder to stop:", err);
4453
}
4554

55+
// Also try to notify the active tab for UI cleanup
56+
const { activeTab } = await chrome.storage.local.get(["activeTab"]);
57+
if (activeTab && activeTab !== tabId) {
58+
sendMessageTab(activeTab, { type: "stop-pending" }).catch(() => {});
59+
}
60+
61+
chrome.action.setIcon({ path: "assets/icon-34.png" });
62+
}
63+
64+
// If the CloudRecorder tab itself was closed, that's a critical failure
65+
if (tabId === recordingTab && recordingTab !== recordedTabId) {
66+
console.error("CloudRecorder tab was closed during recording!");
67+
chrome.storage.local.set({
68+
recording: false,
69+
recorderSession: recorderSession
70+
? { ...recorderSession, status: "crashed", crashedAt: Date.now() }
71+
: null,
72+
});
4673
chrome.action.setIcon({ path: "assets/icon-34.png" });
4774
}
4875
} catch (error) {

src/pages/Background/listeners/onTabUpdatedListener.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,32 @@ import { sendMessageTab } from "../tabManagement";
33
export const handleTabUpdate = async (tabId, changeInfo, tab) => {
44
try {
55
if (changeInfo.status === "complete") {
6-
const { recording } = await chrome.storage.local.get(["recording"]);
7-
const { restarting } = await chrome.storage.local.get(["restarting"]);
8-
const { tabRecordedID } = await chrome.storage.local.get([
6+
const {
7+
recording,
8+
restarting,
9+
tabRecordedID,
10+
pendingRecording,
11+
recordingStartTime,
12+
recorderSession,
13+
} = await chrome.storage.local.get([
14+
"recording",
15+
"restarting",
916
"tabRecordedID",
10-
]);
11-
const { pendingRecording } = await chrome.storage.local.get([
1217
"pendingRecording",
13-
]);
14-
const { recordingStartTime } = await chrome.storage.local.get([
1518
"recordingStartTime",
19+
"recorderSession",
1620
]);
1721

18-
if (!recording && !restarting && !pendingRecording) {
22+
// Check both recording flag AND recorderSession to avoid race conditions
23+
// recorderSession persists even if the SW restarts
24+
const isActivelyRecording =
25+
recording ||
26+
(recorderSession && recorderSession.status === "recording");
27+
const isPendingOrRestarting = restarting || pendingRecording;
28+
29+
if (!isActivelyRecording && !isPendingOrRestarting) {
1930
sendMessageTab(tabId, { type: "recording-ended" });
20-
} else if (recording) {
31+
} else if (isActivelyRecording) {
2132
if (tabRecordedID && tabRecordedID === tabId) {
2233
sendMessageTab(tabId, {
2334
type: "recording-check",

src/pages/Background/messaging/handlers.js

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,32 @@ const API_BASE = process.env.SCREENITY_API_BASE_URL;
5757
const CLOUD_FEATURES_ENABLED =
5858
process.env.SCREENITY_ENABLE_CLOUD_FEATURES === "true";
5959

60+
let activeRecordingSession = null;
61+
let recordingTabListener = null;
62+
63+
const clearRecordingSession = () => {
64+
activeRecordingSession = null;
65+
if (recordingTabListener) {
66+
chrome.tabs.onRemoved.removeListener(recordingTabListener);
67+
recordingTabListener = null;
68+
}
69+
};
70+
71+
const registerRecordingTabListener = (tabId) => {
72+
if (!tabId || recordingTabListener) return;
73+
recordingTabListener = (closedTabId) => {
74+
if (closedTabId === tabId) {
75+
chrome.runtime.sendMessage({
76+
type: "stop-recording-tab",
77+
reason: "recording-tab-closed",
78+
tabId: closedTabId,
79+
});
80+
clearRecordingSession();
81+
}
82+
};
83+
chrome.tabs.onRemoved.addListener(recordingTabListener);
84+
};
85+
6086
export const copyToClipboard = (text) => {
6187
if (!text) return;
6288
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
@@ -104,7 +130,29 @@ export const setupHandlers = () => {
104130
async (message, sender) => await handleGetStreamingData(message, sender)
105131
);
106132
registerMessage("cancel-recording", (message) => cancelRecording(message));
107-
registerMessage("stop-recording-tab", (message, sendResponse) => {
133+
registerMessage("stop-recording-tab", (message, sender, sendResponse) => {
134+
try {
135+
const reason = message?.reason || "unknown";
136+
const senderTabId = message?.tabId || sender?.tab?.id || null;
137+
const senderUrl = sender?.url || null;
138+
const stack = new Error().stack;
139+
console.warn("[Screenity][BG] stop-recording-tab received", {
140+
reason,
141+
senderTabId,
142+
senderUrl,
143+
});
144+
chrome.storage.local.set({
145+
lastStopRecordingEvent: {
146+
reason,
147+
senderTabId,
148+
senderUrl,
149+
stack,
150+
ts: Date.now(),
151+
},
152+
});
153+
} catch (err) {
154+
console.warn("[Screenity][BG] stop-recording-tab logging failed", err);
155+
}
108156
handleStopRecordingTab(message);
109157
sendResponse({ ok: true });
110158
return true;
@@ -236,6 +284,21 @@ export const setupHandlers = () => {
236284
}
237285
);
238286
registerMessage("is-pinned", async () => await isPinned());
287+
288+
// Prevent Chrome from discarding the CloudRecorder tab during recording
289+
registerMessage("set-tab-auto-discardable", async ({ payload }, sender) => {
290+
try {
291+
const tabId = sender?.tab?.id;
292+
if (tabId) {
293+
await chrome.tabs.update(tabId, {
294+
autoDiscardable: payload.discardable,
295+
});
296+
}
297+
} catch (err) {
298+
console.warn("Failed to set tab autoDiscardable:", err);
299+
}
300+
});
301+
239302
registerMessage(
240303
"save-to-drive",
241304
async (message) => await handleSaveToDrive(message, false)
@@ -911,4 +974,49 @@ export const setupHandlers = () => {
911974
});
912975
return true;
913976
});
977+
registerMessage(
978+
"register-recording-session",
979+
(message, sender, sendResponse) => {
980+
const incoming = message.session || {};
981+
if (
982+
activeRecordingSession &&
983+
activeRecordingSession.id &&
984+
incoming.id &&
985+
activeRecordingSession.id !== incoming.id
986+
) {
987+
sendResponse({
988+
ok: false,
989+
error: "Another recording session is already active",
990+
activeRecordingSession,
991+
});
992+
return true;
993+
}
994+
995+
const tabId = incoming.tabId || sender?.tab?.id || null;
996+
activeRecordingSession = { ...incoming, tabId };
997+
registerRecordingTabListener(tabId);
998+
sendResponse({ ok: true, session: activeRecordingSession });
999+
return true;
1000+
}
1001+
);
1002+
1003+
registerMessage(
1004+
"clear-recording-session",
1005+
(message, sender, sendResponse) => {
1006+
clearRecordingSession();
1007+
sendResponse({ ok: true });
1008+
return true;
1009+
}
1010+
);
1011+
1012+
registerMessage(
1013+
"restore-recording-session",
1014+
async (message, sender, sendResponse) => {
1015+
const { recorderSession } = await chrome.storage.local.get([
1016+
"recorderSession",
1017+
]);
1018+
sendResponse({ recorderSession: recorderSession || null });
1019+
return true;
1020+
}
1021+
);
9141022
};

src/pages/Background/offscreen/offscreenDocument.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ const openRecorderTab = async (
3838
active: switchTab,
3939
})
4040
.then((tab) => {
41+
// Prevent Chrome from discarding this tab during recording
42+
chrome.tabs.update(tab.id, { autoDiscardable: false }).catch(() => {});
43+
4144
chrome.storage.local.set({
4245
recordingTab: tab.id,
4346
offscreen: false,

src/pages/Background/recording/recordingHelpers.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,19 @@ export const handleRecordingComplete = async () => {
7979
};
8080

8181
export const handleRecordingError = async (request) => {
82+
console.log("handleRecordingError called with request:", request);
8283
const { activeTab } = await chrome.storage.local.get(["activeTab"]);
8384

85+
// For stream-ended, we just notify the user but DON'T stop the recording
86+
// The user can decide whether to continue or stop
87+
if (request.error === "stream-ended") {
88+
sendMessageTab(activeTab, {
89+
type: "stream-ended-warning",
90+
message: request.why || "Screen sharing stopped unexpectedly.",
91+
}).catch(() => {});
92+
return; // Don't continue with the normal error handling
93+
}
94+
8495
sendMessageRecord({ type: "recording-error" }).then(() => {
8596
sendMessageTab(activeTab, { type: "stop-pending" });
8697
focusTab(activeTab);

src/pages/Background/recording/restoreRecording.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export const checkRestore = async () => {
1515
};
1616

1717
export const restoreRecording = async () => {
18-
const hasWebCodecs = supportsWebCodecs();
18+
//const hasWebCodecs = supportsWebCodecs();
19+
const hasWebCodecs = false; // FLAG: Force old FFMPEG for now
1920

2021
let editorUrl, messageType;
2122

0 commit comments

Comments
 (0)