diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3d85844
--- /dev/null
+++ b/README.md
@@ -0,0 +1,77 @@
+# π Bubblecam - Your Webcam, Your Way!
+
+## π₯ What's Bubblecam?
+Bubblecam lets you control your screen recoding like a boss. Use your **real camera** or switch to a **custom avatar**, drag it anywhere on your screen, and even **record your screen**! Works like magic in Microsoft Edge. π
+
+---
+
+## π₯ Key Features
+β
**Toggle Between Camera & Avatar** - Be on cam or flex your avatar, your choice!
+β
**Custom Avatar Upload** - Upload your own avatar to replace your webcam feed.
+β
**Draggable Camera Bubble** - Move it **anywhere** on the screen, no restrictions!
+β
**Screen Recording** - Capture your screen directly from the pop-up.
+β
**Easy to Use** - No complicated setup, just load & go!
+
+---
+
+## π οΈ Tech Stack
+- **HTML** (for the UI)
+- **JavaScript** (for all the cool interactivity)
+
+---
+
+## π Project Structure
+```
+bubblecam/
+β
+βββ icons/ # All the cool icons
+β
+βββ camera.html # Main camera interface
+βββ camera.js # Handles camera logic
+βββ content.js # Manages draggable camera bubble
+βββ desktopRecord.html # UI for screen recording
+βββ desktopRecord.js # Logic for screen recording
+βββ offscreen.html # Off-screen camera handling
+βββ offscreen.js # Off-screen magic happens here
+βββ popup.html # Pop-up UI
+βββ popup.js # Controls pop-up window
+βββ service-worker.js # Injects & removes camera based on activity
+```
+
+---
+
+## ποΈ How to Install & Use in Microsoft Edge (100% Free!)
+### **1οΈβ£ Download & Extract the ZIP**
+- Hit **Download ZIP** (if it's from GitHub, click the green `<> Code` button > `Download ZIP`).
+- Extract the folder to your PC.
+
+### **2οΈβ£ Load It as an Unpacked Extension in Edge**
+1. Open **Microsoft Edge** (yep, only Edge, sorry Chrome gang π« ).
+2. Go to `edge://extensions/`.
+3. Toggle **Developer Mode** (top-right corner).
+4. Click **"Load unpacked"**.
+5. Select the **bubblecam** folder (the one you extracted).
+6. Boom! Bubblecam is installed. π
+
+### **3οΈβ£ How to Use Bubblecam**
+1. Click on the **Bubblecam icon** in Edgeβs toolbar.
+2. Choose **Camera Mode** or **Avatar Mode**.
+3. **Upload Your Avatar** (if using avatar mode).
+4. Drag your camera bubble anywhere you want! π
+5. Click **Record** to start screen recording.
+6. Have fun being the director of your own screen. π¬
+
+---
+
+## β‘ Prerequisites
+- A PC with **Microsoft Edge** installed.
+- Camera & microphone permissions enabled (duh, you need a cam!).
+
+---
+
+## π License
+Licensed under the **MIT License** β basically, you're free to use it however you want! Just donβt sue me if your avatar starts vibing too hard. π
+
+---
+
+
diff --git a/camera.html b/camera.html
index b88d535..20a2283 100644
--- a/camera.html
+++ b/camera.html
@@ -1,29 +1,59 @@
-
-
-
-
- Document
-
+
+
+ Camera
+
-
-
-
+
+
-
\ No newline at end of file
diff --git a/camera.js b/camera.js
index 857d230..9078d6b 100644
--- a/camera.js
+++ b/camera.js
@@ -6,60 +6,66 @@ const runCode = async () => {
const cameraElement = document.querySelector("#camera");
const startCamera = async () => {
- // Check permissions first
- const permissions = await navigator.permissions.query({
- name: "camera",
- });
-
- if (permissions.state === "prompt") {
- await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
- return;
- }
-
- if (permissions.state === "denied") {
- alert("Camera permissions denied");
- return;
- }
-
- // Only proceed with camera if we're in camera mode
- if (currentMode === 'avatar') {
- displayAvatar();
- return;
- }
-
- const videoElement = document.createElement("video");
- videoElement.setAttribute("id", "cam");
- videoElement.setAttribute(
- "style",
- `
- height: 200px;
- border-radius: 100px;
- transform: scaleX(-1);
- `
- );
- videoElement.setAttribute("autoplay", true);
- videoElement.setAttribute("muted", true);
try {
+ const permissions = await navigator.permissions.query({
+ name: "camera",
+ });
+
+ if (permissions.state === "prompt") {
+ await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
+ return;
+ }
+
+ if (permissions.state === "denied") {
+ console.log("Camera permissions denied, falling back to avatar");
+ currentMode = 'avatar';
+ displayAvatar();
+ return;
+ }
+
+
+ if (currentMode === 'avatar') {
+ displayAvatar();
+ return;
+ }
+
+ const videoElement = document.createElement("video");
+ videoElement.setAttribute("id", "cam");
+ videoElement.setAttribute(
+ "style",
+ `
+ height: 200px;
+ width: 200px;
+ border-radius: 100px;
+ transform: scaleX(-1);
+ object-fit: cover;
+ `
+ );
+ videoElement.setAttribute("autoplay", true);
+ videoElement.setAttribute("muted", true);
+
cameraStream = await navigator.mediaDevices.getUserMedia({
audio: false,
- video: true,
+ video: {
+ width: { ideal: 400 },
+ height: { ideal: 400 },
+ facingMode: "user"
+ }
});
videoElement.srcObject = cameraStream;
- // Clear any existing content
cameraElement.innerHTML = '';
cameraElement.appendChild(videoElement);
} catch (err) {
console.error("Error accessing camera:", err);
- // Fallback to avatar if camera fails
+ currentMode = 'avatar';
displayAvatar();
}
};
const displayAvatar = () => {
- // Stop any existing camera stream
if (cameraStream) {
stopCamera();
}
@@ -73,15 +79,42 @@ const runCode = async () => {
width: 200px;
border-radius: 100px;
object-fit: cover;
+ background-color: #5d3fbd;
`
);
- avatarImg.src = avatarData || 'default-avatar.png';
- // Clear any existing content
+
+ if (avatarData) {
+ avatarImg.src = avatarData;
+ } else {
+
+ avatarImg.src = 'avatar.png';
+ avatarImg.onerror = () => {
+
+ avatarImg.style.backgroundColor = '#5d3fbd';
+ avatarImg.style.display = 'flex';
+ avatarImg.style.justifyContent = 'center';
+ avatarImg.style.alignItems = 'center';
+ avatarImg.src = '';
+
+ // Create a text element for initials
+ const initialsElem = document.createElement('div');
+ initialsElem.textContent = 'BC';
+ initialsElem.style.color = 'white';
+ initialsElem.style.fontSize = '60px';
+ initialsElem.style.fontWeight = 'bold';
+
+ cameraElement.innerHTML = '';
+ cameraElement.appendChild(initialsElem);
+ };
+ }
+
+
cameraElement.innerHTML = '';
cameraElement.appendChild(avatarImg);
};
+
const stopCamera = () => {
if (cameraStream) {
cameraStream.getTracks().forEach(track => {
@@ -101,7 +134,6 @@ const runCode = async () => {
}
};
- // Message listeners for camera/avatar control
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "stop-camera") {
stopCamera();
@@ -125,7 +157,7 @@ const runCode = async () => {
}
});
- // Initialize based on saved mode
+
chrome.storage.local.get(['mode', 'avatarData'], function(data) {
currentMode = data.mode || 'camera';
avatarData = data.avatarData;
@@ -138,4 +170,5 @@ const runCode = async () => {
});
};
-runCode();
\ No newline at end of file
+
+document.addEventListener('DOMContentLoaded', runCode);
\ No newline at end of file
diff --git a/offscreen.js b/offscreen.js
index 8b5d092..a25347b 100644
--- a/offscreen.js
+++ b/offscreen.js
@@ -1,182 +1,173 @@
//offscreen.js
chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
- console.log("[offscreen] message received", message, sender);
-
- switch (message.type) {
- case "start-recording":
- console.log("start recording received in offscreen.js");
-
- await startRecording(message.data, message.quality);
- sendResponse({ status: "recording-started" });
+ console.log("[offscreen] message received", message, sender);
+
+ switch (message.type) {
+ case "start-recording":
+ console.log("start recording received in offscreen.js");
+
+ await startRecording(message.data, message.quality);
+ sendResponse({ status: "recording-started" });
+ break;
+ case "stop-recording":
+ console.log("stop recording received in offscreen.js");
+
+ await stopRecording();
+ sendResponse({ status: "recording-stopped" });
+ break;
+ default:
+ console.log("default");
+ sendResponse({ status: "unknown-message" });
+ }
+
+ return true;
+});
+
+let recorder;
+let data = [];
+
+async function stopRecording() {
+ console.log("Entered stopRecording");
+
+ if (recorder?.state === "recording") {
+ console.log("Recorder state is 'recording', stopping...");
+ recorder.stop();
+
+ // Send a message to the content script to stop the camera
+ chrome.runtime.sendMessage({ type: "stop-camera" }, response => {
+ if (response?.status === "camera-stopped") {
+ console.log("Camera has been successfully stopped.");
+ } else {
+ console.log("Failed to stop the camera.");
+ }
+ });
+ } else {
+ console.log("No active recording found or recorder is not in 'recording' state.");
+ }
+
+ console.log("Stopped the recording");
+}
+
+function stopAllMediaStreams(media, microphone) {
+ media.getTracks().forEach((track) => {
+ track.stop();
+ console.log("Media Track stopped:", track);
+ });
+
+ microphone.getTracks().forEach((track) => {
+ track.stop();
+ console.log("Microphone Track stopped", track);
+ });
+}
+
+async function startRecording(streamId, quality) {
+ try {
+ if (recorder?.state === "recording") {
+ throw new Error("Called startRecording while recording is in progress.");
+ }
+
+ console.log("start recording", streamId);
+ console.log("qaulity inside offfscreen.js", quality);
+
+ let videoConstraints;
+
+ switch (quality) {
+ case "low":
+ videoConstraints = {
+ mandatory: {
+ chromeMediaSource: "tab",
+ chromeMediaSourceId: streamId,
+ maxWidth: 640,
+ maxHeight: 480,
+ minWidth: 640,
+ minHeight: 480,
+ maxFrameRate: 15,
+ },
+ };
break;
- case "stop-recording":
- console.log("stop recording received in offscreen.js");
-
- await stopRecording();
- sendResponse({ status: "recording-stopped" });
+ case "medium":
+ videoConstraints = {
+ mandatory: {
+ chromeMediaSource: "tab",
+ chromeMediaSourceId: streamId,
+ maxWidth: 1280,
+ maxHeight: 720,
+ minWidth: 1280,
+ minHeight: 720,
+ maxFrameRate: 30,
+ },
+ };
+ break;
+ case "high":
+ videoConstraints = {
+ mandatory: {
+ chromeMediaSource: "tab",
+ chromeMediaSourceId: streamId,
+ maxWidth: 1920,
+ maxHeight: 1080,
+ minWidth: 1920,
+ minHeight: 1080,
+ maxFrameRate: 60,
+ },
+ };
break;
default:
- console.log("default");
- sendResponse({ status: "unknown-message" });
- }
-
- return true;
- });
-
-
- let recorder;
- let data = [];
-
- async function stopRecording() {
- console.log("Entered stopRecording");
-
- if (recorder?.state === "recording") {
- console.log("Recorder state is 'recording', stopping...");
- recorder.stop();
-
- // Send a message to the content script to stop the camera
- chrome.runtime.sendMessage({ type: "stop-camera" }, response => {
- if (response?.status === "camera-stopped") {
- console.log("Camera has been successfully stopped.");
- } else {
- console.log("Failed to stop the camera.");
- }
- });
- } else {
- console.log("No active recording found or recorder is not in 'recording' state.");
- }
-
- console.log("Stopped the recording");
- }
-
- function stopAllMediaStreams(media, microphone) {
- media.getTracks().forEach((track) => {
- track.stop();
- console.log("Media Track stopped:", track);
- });
-
- microphone.getTracks().forEach((track) => {
- track.stop();
- console.log("Microphone Track stopped", track);
- });
- }
-
- async function startRecording(streamId, quality) {
- try {
- if (recorder?.state === "recording") {
- throw new Error("Called startRecording while recording is in progress.");
- }
-
- console.log("start recording", streamId);
- console.log("qaulity inside offfscreen.js", quality);
-
-
- let videoConstraints;
-
- switch (quality) {
- case "low":
- videoConstraints = {
- mandatory: {
- chromeMediaSource: "tab",
- chromeMediaSourceId: streamId,
- maxWidth: 640,
- maxHeight: 480,
- minWidth: 640,
- minHeight: 480,
- maxFrameRate: 15,
- },
- };
- break;
- case "medium":
- videoConstraints = {
- mandatory: {
- chromeMediaSource: "tab",
- chromeMediaSourceId: streamId,
- maxWidth: 1280,
- maxHeight: 720,
- minWidth: 1280,
- minHeight: 720,
- maxFrameRate: 30,
- },
- };
- break;
- case "high":
- videoConstraints = {
- mandatory: {
- chromeMediaSource: "tab",
- chromeMediaSourceId: streamId,
- maxWidth: 1920,
- maxHeight: 1080,
- minWidth: 1920,
- minHeight: 1080,
- maxFrameRate: 60,
- },
- };
- break;
- default:
- videoConstraints = {
- mandatory: {
- chromeMediaSource: "tab",
- chromeMediaSourceId: streamId,
- },
- };
- }
-
-
-
- const media = await navigator.mediaDevices.getUserMedia({
- audio: {
+ videoConstraints = {
mandatory: {
chromeMediaSource: "tab",
chromeMediaSourceId: streamId,
},
- },
- video: videoConstraints
- });
-
-
- const microphone = await navigator.mediaDevices.getUserMedia({
- audio: { echoCancellation: false },
- });
-
- const mixedContext = new AudioContext();
- const mixedDest = mixedContext.createMediaStreamDestination();
-
- mixedContext.createMediaStreamSource(microphone).connect(mixedDest);
- mixedContext.createMediaStreamSource(media).connect(mixedDest);
-
- const combinedStream = new MediaStream([
- media.getVideoTracks()[0],
- mixedDest.stream.getTracks()[0],
- ]);
-
- recorder = new MediaRecorder(combinedStream, { mimeType: "video/webm" });
-
-
- recorder.ondataavailable = (event) => {
- console.log("data available", event);
- data.push(event.data);
- };
-
-
- recorder.onstop = async () => {
- console.log("recording stopped");
- // send the data to the service worker
- console.log("sending data to service worker");
- stopAllMediaStreams(media, microphone);
-
- recorder = null;
-
-
- const blob = new Blob(data, { type: "video/webm" });
- const url = URL.createObjectURL(blob);
-
- chrome.runtime.sendMessage({ type: "open-tab", url });
- };
-
-
- recorder.start();
- } catch (err) {
- console.log("error", err);
+ };
}
- }
\ No newline at end of file
+
+ const media = await navigator.mediaDevices.getUserMedia({
+ audio: {
+ mandatory: {
+ chromeMediaSource: "tab",
+ chromeMediaSourceId: streamId,
+ },
+ },
+ video: videoConstraints
+ });
+
+ const microphone = await navigator.mediaDevices.getUserMedia({
+ audio: { echoCancellation: false },
+ });
+
+ const mixedContext = new AudioContext();
+ const mixedDest = mixedContext.createMediaStreamDestination();
+
+ mixedContext.createMediaStreamSource(microphone).connect(mixedDest);
+ mixedContext.createMediaStreamSource(media).connect(mixedDest);
+
+ const combinedStream = new MediaStream([
+ media.getVideoTracks()[0],
+ mixedDest.stream.getTracks()[0],
+ ]);
+
+ recorder = new MediaRecorder(combinedStream, { mimeType: "video/webm" });
+
+ recorder.ondataavailable = (event) => {
+ console.log("data available", event);
+ data.push(event.data);
+ };
+
+ recorder.onstop = async () => {
+ console.log("recording stopped");
+ // send the data to the service worker
+ console.log("sending data to service worker");
+ stopAllMediaStreams(media, microphone);
+
+ recorder = null;
+
+ const blob = new Blob(data, { type: "video/webm" });
+ const url = URL.createObjectURL(blob);
+
+ chrome.runtime.sendMessage({ type: "open-tab", url });
+ };
+
+ recorder.start();
+ } catch (err) {
+ console.log("error", err);
+ }
+}
\ No newline at end of file
diff --git a/popup.html b/popup.html
index 988cc55..fcd1697 100644
--- a/popup.html
+++ b/popup.html
@@ -16,7 +16,7 @@
-
Recorder
+
BUBBLE CAM
@@ -124,44 +204,51 @@
-
-
-
-
+
+
-
-
-
-
-
- High (1080p, 60fps)
+
+
+ High (1080p, 60fps)
+ Medium (720p, 30fps)
+ Low (480p, 30fps)
+