This commit is contained in:
sneha kumari 2025-03-06 21:21:05 +05:30
parent 9a1171bb35
commit 140b57aea1
6 changed files with 553 additions and 290 deletions

77
README.md Normal file
View File

@ -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 Edges 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 dont sue me if your avatar starts vibing too hard. 😆
---

View File

@ -1,29 +1,59 @@
<!-- //camera.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
background-color: rgb(168, 29, 203);
margin: 0;
padding: 0;
}
div#camera {
width: 200px;
height: 200px;
overflow: hidden;
}
</style>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Camera</title>
<style>
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: transparent;
}
#camera {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
border-radius: 50%;
}
#avatar {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
}
#cam {
width: 100%;
height: 100%;
object-fit: cover;
transform: scaleX(-1);
}
.avatar-fallback {
width: 100%;
height: 100%;
background-color: #5d3fbd;
color: white;
display: flex;
justify-content: center;
align-items: center;
font-family: Arial, sans-serif;
font-size: 60px;
font-weight: bold;
}
</style>
</head>
<body>
<div id="camera"></div>
<script src="camera.js"></script>
<div id="camera"></div>
<script src="camera.js"></script>
</body>
</html>

117
camera.js
View File

@ -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();
document.addEventListener('DOMContentLoaded', runCode);

View File

@ -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);
};
}
}
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);
}
}

View File

@ -16,7 +16,7 @@
<style>
body {
width: 300px;
height: 320px;
height: 340px;
font-family: Poppins, sans-serif;
background: #251f38;
--gap: 5em;
@ -54,22 +54,27 @@
background-color: rgba(255, 255, 255, 0.1);
cursor: pointer;
transition: all 0.3s;
flex: 1;
}
.mode-button:hover {
background-color: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}
.mode-button.active {
background-color: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.avatar-upload {
display: none;
flex-direction: column;
align-items: center;
gap: 10px;
gap: 15px;
margin-bottom: 20px;
animation: fadeIn 0.3s ease;
}
.avatar-preview {
@ -81,6 +86,13 @@
display: flex;
align-items: center;
justify-content: center;
border: 3px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
transition: all 0.3s;
}
.avatar-preview:hover {
transform: scale(1.05);
}
.avatar-preview img {
@ -97,17 +109,85 @@
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 8px;
}
.upload-btn:hover {
background-color: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}
.record-options {
display: flex;
flex-direction: row;
gap: 25px;
justify-content: center;
margin-bottom: 15px;
}
.record-btn {
border: none;
color: white;
text-align: center;
border-radius: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 15px;
background-color: rgba(255, 255, 255, 0.1);
cursor: pointer;
transition: all 0.3s;
width: 80px;
height: 80px;
}
.record-btn:hover {
background-color: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}
.record-btn p {
margin: 5px 0 0 0;
font-size: 12px;
}
.quality-select {
background-color: rgba(255, 255, 255, 0.1);
color: white;
border: 1.5px solid rgb(255, 255, 255);
font-family: Poppins;
padding: 8px 15px;
border-radius: 5px;
margin-top: 10px;
}
.quality-select option {
color: black;
font-size: 0.75rem;
padding: 8px 2px;
cursor: pointer;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.icon-container {
display: flex;
align-items: center;
justify-content: center;
}
</style>
</head>
<body>
<div>
<h1 style="font-weight: 400; margin-bottom:20px;"> Recorder </h1>
<h1 style="font-weight: 500; margin-bottom:20px; text-align: center;">BUBBLE CAM</h1>
<!-- Camera/Avatar Mode Selector -->
<div class="mode-selector">
@ -124,44 +204,51 @@
<!-- Avatar Upload Section -->
<div id="avatar-section" class="avatar-upload">
<div class="avatar-preview">
<img id="avatar-img" src="default-avatar.png" alt="Avatar">
<img id="avatar-img" src="avatar.png" alt="Avatar">
</div>
<input type="file" id="avatar-input" accept="image/*" style="display: none;">
<button class="upload-btn" onclick="document.getElementById('avatar-input').click()">
<!-- Make the file input directly visible -->
<label for="avatar-input" class="upload-btn">
<i class="fas fa-upload"></i>
Upload Avatar
</button>
</label>
<input type="file" id="avatar-input" accept="image/*" style="position: absolute; opacity: 0; width: 0; height: 0;">
</div>
<!-- Recording Options -->
<div style="display: flex; flex-direction: row; gap: 25px;">
<div style="display: flex; color: white;">
<button id="tab"
style="border: none; gap:10px; color: white; text-align: center; border-radius: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; padding:8px 10px; background-color: transparent; cursor: pointer;">
<i id="tab-icon" style="color: white;" class="fa-regular fa-window-maximize fa-2xl"></i>
<div class="record-options">
<div class="record-option">
<button id="tab" class="record-btn">
<div class="icon-container">
<i id="tab-icon" class="fa-regular fa-window-maximize fa-xl"></i>
</div>
<p>Window Tab</p>
</button>
</div>
<div style="display: flex; color: white;">
<button id="screen"
style="border: none; color: white; gap:10px; text-align: center; border-radius: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; padding:8px 10px; background-color: transparent; cursor: pointer;">
<i id="screen-icon" style="color: white;" class="fa-solid fa-display fa-2xl"></i>
<div class="record-option">
<button id="screen" class="record-btn">
<div class="icon-container">
<i id="screen-icon" class="fa-solid fa-display fa-xl"></i>
</div>
<p>Screen</p>
</button>
</div>
</div>
<div id="options">
<br>
<select id="quality"
style="background-color: transparent; color: white; border: 1.5px solid rgb(255, 255, 255);font-family: Poppins; padding: 3px; border-radius: 5px;"
required>
<option style="color: black; font-size:0.75rem; padding:8px 2px; cursor:pointer;" value="high" selected>
High (1080p, 60fps)</option>
<div id="options" style="display: flex; justify-content: center;">
<select id="quality" class="quality-select" required>
<option value="high" selected>High (1080p, 60fps)</option>
<option value="medium">Medium (720p, 30fps)</option>
<option value="low">Low (480p, 30fps)</option>
</select>
</div>
</div>
<script src="popup.js"></script>
<script>
document.getElementById('upload-avatar-btn').addEventListener('click', function() {
document.getElementById('avatar-input').click();
});
</script>
</body>
</html>

107
popup.js
View File

@ -1,12 +1,10 @@
//popup.js
document.addEventListener('DOMContentLoaded', function() {
const cameraMode = document.getElementById('camera-mode');
const avatarMode = document.getElementById('avatar-mode');
const avatarSection = document.getElementById('avatar-section');
const avatarInput = document.getElementById('avatar-input');
const avatarPreview = document.getElementById('avatar-img');
// Mode switching
cameraMode.addEventListener('click', () => {
cameraMode.classList.add('active');
avatarMode.classList.remove('active');
@ -23,31 +21,86 @@ document.addEventListener('DOMContentLoaded', function() {
chrome.runtime.sendMessage({ type: 'mode-change', mode: 'avatar' });
});
// Avatar upload handling
// Fix the avatar upload functionality
avatarInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const avatarData = e.target.result;
avatarPreview.src = avatarData;
chrome.storage.local.set({ avatarData: avatarData });
console.log("File input changed"); // Add this debug line
const file = e.target.files[0];
if (!file) {
console.log("No file selected");
return;
}
console.log("File selected:", file.name); // Add this debug line
if (!file.type.match('image.*')) {
alert('Please select an image file');
return;
}
if (file.size > 2 * 1024 * 1024) {
alert('Image is too large. Please select an image under 2MB.');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
const avatarData = e.target.result;
console.log("File loaded successfully"); // Add this debug line
avatarPreview.src = avatarData;
avatarPreview.style.display = 'block';
// Store the avatar data in Chrome storage
chrome.storage.local.set({ avatarData: avatarData }, function() {
if (chrome.runtime.lastError) {
console.error('Error saving avatar:', chrome.runtime.lastError);
return;
}
console.log("Avatar saved to storage"); // Add this debug line
// Notify any active content scripts about the avatar change
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
if (tabs[0]?.id) {
chrome.runtime.sendMessage({
type: 'avatar-update',
avatarData: avatarData
type: 'avatar-update',
avatarData: avatarData
});
};
reader.readAsDataURL(file);
}
});
});
} catch (error) {
console.error('Error processing avatar:', error);
alert('There was an error processing your image. Please try another.');
}
};
reader.onerror = function() {
console.error('Error reading file');
alert('Error reading the image file. Please try again.');
};
reader.readAsDataURL(file);
});
// Load saved mode and avatar
chrome.storage.local.get(['mode', 'avatarData'], function(data) {
if (data.mode === 'avatar') {
avatarMode.click();
if (data.avatarData) {
avatarPreview.src = data.avatarData;
}
} else {
cameraMode.click();
}
if (data.avatarData) {
avatarPreview.src = data.avatarData;
avatarPreview.style.display = 'block';
} else {
avatarPreview.src = 'avatar.png';
avatarPreview.style.display = 'block';
}
});
});
@ -68,11 +121,10 @@ const injectCamera = async () => {
});
};
// Initialize camera bubble when popup opens
injectCamera();
const removeCamera = async () => {
const tab = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab) return;
@ -80,18 +132,13 @@ const removeCamera = async () => {
console.log("inject into tab", tabId);
await chrome.scripting.executeScript({
func: () => {
const camera = document.querySelector("#purple-camera");
if (!camera) return;
camera.remove();
document.querySelector("#purple-camera").style.display = "none";
const camera = document.querySelector("#camera-wrapper");
if (camera) camera.remove();
},
target: { tabId },
});
};
const checkRecording = async () => {
const recording = await chrome.storage.local.get(["recording", "type"]);
const recordingStatus = recording.recording || false;
@ -130,7 +177,6 @@ const init = async () => {
const recordingState = await checkRecording();
if (recordingState[0] === true) {
chrome.runtime.sendMessage({ type: "stop-recording" });
removeCamera();
} else {
@ -142,7 +188,6 @@ const init = async () => {
injectCamera();
}
setTimeout(() => {
window.close();
}, 100);
@ -159,4 +204,4 @@ const init = async () => {
});
};
init();
init();