Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
"version": "0.11.0",
"manifest_version": 3,
"icons": {
"16": "./static/logo/Logo_16.png",
"32": "./static/logo/Logo_32.png",
"48": "./static/logo/Logo_48.png",
"128": "./static/logo/Logo_128.png",
"256": "./static/logo/Logo_256.png"
"16": "./static/Logo_16.png",
"32": "./static/Logo_32.png",
"48": "./static/Logo_48.png",
"128": "./static/Logo_128.png",
"256": "./static/Logo_256.png"
},
"action": {
"default_popup": "./popup.html"
Expand Down
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
6 changes: 3 additions & 3 deletions src/app/messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export enum MessageType {
CameraBubbleShow = "CameraBubbleShow",
CameraBubbleHide = "CameraBubbleHide",
RecordingStart = "RecordingStart",
RecordingStop = "RecordingStop",
RecordingComplete = "RecordingComplete",
RecordingPause = "RecordingPause",
RecordingResume = "RecordingResume",
RecordingCancel = "RecordingCancel",
Expand Down Expand Up @@ -65,9 +65,9 @@ export const sender = {
target: "background",
});
},
recordingStop: () => {
recordingComplete: () => {
return chrome.runtime.sendMessage<Message, MessageResponse>({
type: MessageType.RecordingStop,
type: MessageType.RecordingComplete,
target: "background",
});
},
Expand Down
5 changes: 5 additions & 0 deletions src/app/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export enum StorageKey {
DevicesVideoId = "devices.video.id",
DevicesVideoName = "devices.video.name",
RecordingState = "recording.state",
RecordingDownloadId = "recording.download_id",
UiCameraBubbleEnabled = "ui.cameraBubble.enabled",
UiCameraBubblePosition = "ui.cameraBubble.position",
UiCameraBubbleSize = "ui.cameraBubble.size",
Expand All @@ -18,6 +19,8 @@ export enum StorageKey {
export enum RecordingState {
NotStarted = "NotStarted",
InProgress = "InProgress",
Completed = "Completed",
Downloading = "Downloading",
OnPause = "OnPause",
}

Expand All @@ -32,6 +35,7 @@ type StorageValueTypeMap = {
[StorageKey.DevicesVideoId]: string;
[StorageKey.DevicesVideoName]: string;
[StorageKey.RecordingState]: RecordingState;
[StorageKey.RecordingDownloadId]: number;
[StorageKey.UiCameraBubbleEnabled]: boolean;
[StorageKey.UiCameraBubblePosition]: { x: number; y: number };
[StorageKey.UiCameraBubbleSize]: { width: number; height: number };
Expand Down Expand Up @@ -83,6 +87,7 @@ export const storage = {
},
recording: {
state: createStorageSetterGetter(StorageKey.RecordingState),
downloadId: createStorageSetterGetter(StorageKey.RecordingDownloadId),
},
ui: {
cameraBubble: {
Expand Down
21 changes: 15 additions & 6 deletions src/background/controllers/recorder_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class Recorder {
#chunks: BlobPart[];
#isRecordingCanceled: boolean;
#mediaRecorder?: MediaRecorder;
#downloadUrl?: string;

constructor(mimeType: string) {
console.log(`Recorder.constructor(mimeType='${mimeType}')`);
Expand All @@ -24,7 +25,7 @@ class Recorder {

#onTrackEnded() {
(async () => {
await sender.background.recordingStop();
await sender.background.recordingComplete();
})().catch((err) => {
console.error(
`Recorder.#onTrackEnded error: ${(err as Error).toString()}`,
Expand All @@ -48,17 +49,15 @@ class Recorder {
return;
}

const downloadUrl = URL.createObjectURL(
this.#downloadUrl = URL.createObjectURL(
new Blob(this.#chunks, {
type: event.data.type,
}),
);

await sender.background.recordingSave({
recordingUrl: downloadUrl,
recordingUrl: this.#downloadUrl,
});

URL.revokeObjectURL(downloadUrl);
})().catch((err) => {
console.error(
`Recorder.#onMediaRecorderDataAvailable error: ${(err as Error).toString()}`,
Expand All @@ -78,7 +77,7 @@ class Recorder {
});

for (const track of this.#mediaStream.getTracks()) {
track.addEventListener("ended", this.#onTrackEnded.bind(this));
track.addEventListener("ended", this.#onTrackEnded);
}

this.#mediaRecorder.addEventListener(
Expand All @@ -95,6 +94,7 @@ class Recorder {
}
this.#mediaRecorder.stop();
for (const track of this.#mediaStream.getTracks()) {
track.removeEventListener("ended", this.#onTrackEnded);
track.stop();
}
}
Expand All @@ -120,6 +120,14 @@ class Recorder {
this.#isRecordingCanceled = true;
this.stop();
}

cleanup() {
if (!this.#downloadUrl) {
return;
}
URL.revokeObjectURL(this.#downloadUrl);
this.#downloadUrl = "";
}
}

class RecorderController {
Expand Down Expand Up @@ -207,6 +215,7 @@ class RecorderController {
console.error("Recorder is not created");
return;
}
RecorderController.#recorder.cleanup();
RecorderController.#recorder = undefined;
}
}
Expand Down
55 changes: 47 additions & 8 deletions src/background/controllers/recording_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,13 @@ class RecordingController {
await storage.recording.state.set(RecordingState.InProgress);
}

static async stop() {
console.log("RecordingController.stop()");
static async complete() {
console.log("RecordingController.complete()");
if (!(await chrome.offscreen.hasDocument())) {
return;
}
await sender.offscreen.recorderStop();
await sender.offscreen.recorderDelete();
await chrome.offscreen.closeDocument();
await storage.recording.state.set(RecordingState.NotStarted);
await storage.recording.state.set(RecordingState.Completed);
}

static async pause() {
Expand Down Expand Up @@ -85,9 +83,24 @@ class RecordingController {
if (!(await chrome.offscreen.hasDocument())) {
return;
}
await chrome.downloads.download({
const downloadId = await chrome.downloads.download({
url: options.recordingUrl,
});
await storage.recording.downloadId.set(downloadId);
await storage.recording.state.set(RecordingState.Downloading);
console.log(
`RecordingController.save(): Downloading started with id=${downloadId}`,
);
}

static async saveComplete() {
console.log("RecordingController.saveComplete()");
if (!(await chrome.offscreen.hasDocument())) {
return;
}
await sender.offscreen.recorderDelete();
await storage.recording.state.set(RecordingState.NotStarted);
await storage.recording.downloadId.set(0);
}
}

Expand All @@ -106,8 +119,8 @@ chrome.runtime.onMessage.addListener(
case MessageType.RecordingStart:
await RecordingController.start();
break;
case MessageType.RecordingStop:
await RecordingController.stop();
case MessageType.RecordingComplete:
await RecordingController.complete();
break;
case MessageType.RecordingPause:
await RecordingController.pause();
Expand Down Expand Up @@ -141,3 +154,29 @@ chrome.runtime.onMessage.addListener(
return true;
},
);

chrome.downloads.onChanged.addListener((downloadDelta) => {
(async () => {
const downloadId = await storage.recording.downloadId.get();
if (!downloadId || downloadDelta.id !== downloadId) {
return;
}
const { state } = downloadDelta;
// NOTE: We may receive filename update here without `state` on downloading,
// so we can just ignore such case
if (!state) {
return;
}
if (state?.current === "complete") {
await RecordingController.saveComplete();
return;
}
console.error(
`Problem with recording downloading: ${JSON.stringify(downloadDelta)}`,
);
})().catch((err) => {
console.error(
`Error in 'chrome.downloads.onChanged' handler: ${(err as Error).toString()}`,
);
});
});
18 changes: 9 additions & 9 deletions src/ui/pages/popup/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
IconTrash,
IconUserCircle,
} from "@tabler/icons-react";
import background from "/assets/Background.svg";
import lookup128 from "/assets/Lookup_128.png";
import background from "/static/Background.svg";
import lookup128 from "/static/Lookup_128.png";

const Settings = () => {
const [cameraBubbleEnabled] = useStorageValue(
Expand Down Expand Up @@ -119,13 +119,13 @@ const RecordingControl = () => {
className="h-[28px] w-[28px] stroke-black transition-colors hover:stroke-[#00d492]"
stroke={2}
onClick={() => {
sender.background
.recordingStop()
.catch((err) =>
console.error(
`Can't stop recording: ${(err as Error).toString()}`,
),
);
(async () => {
await sender.background.recordingComplete();
})().catch((err) =>
console.error(
`Can't complete and download recording: ${(err as Error).toString()}`,
),
);
}}
/>
<PlayButton
Expand Down
3 changes: 1 addition & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
"paths": {
/* Specify a set of entries that re-map imports to additional lookup locations. */
"@/*": ["./*"],
"/static/*": ["../public/static/*"],
"/assets/*": ["./ui/assets/*"]
"/static/*": ["../public/static/*"]
},
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
"typeRoots": [
Expand Down