readme
This commit is contained in:
parent
9a1171bb35
commit
140b57aea1
77
README.md
Normal file
77
README.md
Normal 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 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. 😆
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
50
camera.html
50
camera.html
@ -1,29 +1,59 @@
|
|||||||
<!-- //camera.html -->
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Document</title>
|
<title>Camera</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body, html {
|
||||||
background-color: rgb(168, 29, 203);
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#camera {
|
#camera {
|
||||||
width: 200px;
|
width: 100%;
|
||||||
height: 200px;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
overflow: hidden;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="camera"></div>
|
<div id="camera"></div>
|
||||||
<script src="camera.js"></script>
|
<script src="camera.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
59
camera.js
59
camera.js
@ -6,7 +6,8 @@ const runCode = async () => {
|
|||||||
const cameraElement = document.querySelector("#camera");
|
const cameraElement = document.querySelector("#camera");
|
||||||
|
|
||||||
const startCamera = async () => {
|
const startCamera = async () => {
|
||||||
// Check permissions first
|
|
||||||
|
try {
|
||||||
const permissions = await navigator.permissions.query({
|
const permissions = await navigator.permissions.query({
|
||||||
name: "camera",
|
name: "camera",
|
||||||
});
|
});
|
||||||
@ -17,11 +18,13 @@ const runCode = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (permissions.state === "denied") {
|
if (permissions.state === "denied") {
|
||||||
alert("Camera permissions denied");
|
console.log("Camera permissions denied, falling back to avatar");
|
||||||
|
currentMode = 'avatar';
|
||||||
|
displayAvatar();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only proceed with camera if we're in camera mode
|
|
||||||
if (currentMode === 'avatar') {
|
if (currentMode === 'avatar') {
|
||||||
displayAvatar();
|
displayAvatar();
|
||||||
return;
|
return;
|
||||||
@ -33,33 +36,36 @@ const runCode = async () => {
|
|||||||
"style",
|
"style",
|
||||||
`
|
`
|
||||||
height: 200px;
|
height: 200px;
|
||||||
|
width: 200px;
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
transform: scaleX(-1);
|
transform: scaleX(-1);
|
||||||
|
object-fit: cover;
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
videoElement.setAttribute("autoplay", true);
|
videoElement.setAttribute("autoplay", true);
|
||||||
videoElement.setAttribute("muted", true);
|
videoElement.setAttribute("muted", true);
|
||||||
|
|
||||||
try {
|
|
||||||
cameraStream = await navigator.mediaDevices.getUserMedia({
|
cameraStream = await navigator.mediaDevices.getUserMedia({
|
||||||
audio: false,
|
audio: false,
|
||||||
video: true,
|
video: {
|
||||||
|
width: { ideal: 400 },
|
||||||
|
height: { ideal: 400 },
|
||||||
|
facingMode: "user"
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
videoElement.srcObject = cameraStream;
|
videoElement.srcObject = cameraStream;
|
||||||
|
|
||||||
// Clear any existing content
|
|
||||||
cameraElement.innerHTML = '';
|
cameraElement.innerHTML = '';
|
||||||
cameraElement.appendChild(videoElement);
|
cameraElement.appendChild(videoElement);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error accessing camera:", err);
|
console.error("Error accessing camera:", err);
|
||||||
// Fallback to avatar if camera fails
|
currentMode = 'avatar';
|
||||||
displayAvatar();
|
displayAvatar();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const displayAvatar = () => {
|
const displayAvatar = () => {
|
||||||
// Stop any existing camera stream
|
|
||||||
if (cameraStream) {
|
if (cameraStream) {
|
||||||
stopCamera();
|
stopCamera();
|
||||||
}
|
}
|
||||||
@ -73,15 +79,42 @@ const runCode = async () => {
|
|||||||
width: 200px;
|
width: 200px;
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
object-fit: cover;
|
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.innerHTML = '';
|
||||||
cameraElement.appendChild(avatarImg);
|
cameraElement.appendChild(avatarImg);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const stopCamera = () => {
|
const stopCamera = () => {
|
||||||
if (cameraStream) {
|
if (cameraStream) {
|
||||||
cameraStream.getTracks().forEach(track => {
|
cameraStream.getTracks().forEach(track => {
|
||||||
@ -101,7 +134,6 @@ const runCode = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Message listeners for camera/avatar control
|
|
||||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
if (message.type === "stop-camera") {
|
if (message.type === "stop-camera") {
|
||||||
stopCamera();
|
stopCamera();
|
||||||
@ -125,7 +157,7 @@ const runCode = async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize based on saved mode
|
|
||||||
chrome.storage.local.get(['mode', 'avatarData'], function(data) {
|
chrome.storage.local.get(['mode', 'avatarData'], function(data) {
|
||||||
currentMode = data.mode || 'camera';
|
currentMode = data.mode || 'camera';
|
||||||
avatarData = data.avatarData;
|
avatarData = data.avatarData;
|
||||||
@ -138,4 +170,5 @@ const runCode = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
runCode();
|
|
||||||
|
document.addEventListener('DOMContentLoaded', runCode);
|
@ -23,7 +23,6 @@ chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
let recorder;
|
let recorder;
|
||||||
let data = [];
|
let data = [];
|
||||||
|
|
||||||
@ -70,7 +69,6 @@ chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
|
|||||||
console.log("start recording", streamId);
|
console.log("start recording", streamId);
|
||||||
console.log("qaulity inside offfscreen.js", quality);
|
console.log("qaulity inside offfscreen.js", quality);
|
||||||
|
|
||||||
|
|
||||||
let videoConstraints;
|
let videoConstraints;
|
||||||
|
|
||||||
switch (quality) {
|
switch (quality) {
|
||||||
@ -122,8 +120,6 @@ chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const media = await navigator.mediaDevices.getUserMedia({
|
const media = await navigator.mediaDevices.getUserMedia({
|
||||||
audio: {
|
audio: {
|
||||||
mandatory: {
|
mandatory: {
|
||||||
@ -134,7 +130,6 @@ chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
|
|||||||
video: videoConstraints
|
video: videoConstraints
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const microphone = await navigator.mediaDevices.getUserMedia({
|
const microphone = await navigator.mediaDevices.getUserMedia({
|
||||||
audio: { echoCancellation: false },
|
audio: { echoCancellation: false },
|
||||||
});
|
});
|
||||||
@ -152,13 +147,11 @@ chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
|
|||||||
|
|
||||||
recorder = new MediaRecorder(combinedStream, { mimeType: "video/webm" });
|
recorder = new MediaRecorder(combinedStream, { mimeType: "video/webm" });
|
||||||
|
|
||||||
|
|
||||||
recorder.ondataavailable = (event) => {
|
recorder.ondataavailable = (event) => {
|
||||||
console.log("data available", event);
|
console.log("data available", event);
|
||||||
data.push(event.data);
|
data.push(event.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
recorder.onstop = async () => {
|
recorder.onstop = async () => {
|
||||||
console.log("recording stopped");
|
console.log("recording stopped");
|
||||||
// send the data to the service worker
|
// send the data to the service worker
|
||||||
@ -167,14 +160,12 @@ chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
|
|||||||
|
|
||||||
recorder = null;
|
recorder = null;
|
||||||
|
|
||||||
|
|
||||||
const blob = new Blob(data, { type: "video/webm" });
|
const blob = new Blob(data, { type: "video/webm" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
chrome.runtime.sendMessage({ type: "open-tab", url });
|
chrome.runtime.sendMessage({ type: "open-tab", url });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
recorder.start();
|
recorder.start();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("error", err);
|
console.log("error", err);
|
||||||
|
133
popup.html
133
popup.html
@ -16,7 +16,7 @@
|
|||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 320px;
|
height: 340px;
|
||||||
font-family: Poppins, sans-serif;
|
font-family: Poppins, sans-serif;
|
||||||
background: #251f38;
|
background: #251f38;
|
||||||
--gap: 5em;
|
--gap: 5em;
|
||||||
@ -54,22 +54,27 @@
|
|||||||
background-color: rgba(255, 255, 255, 0.1);
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-button:hover {
|
.mode-button:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.2);
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-button.active {
|
.mode-button.active {
|
||||||
background-color: rgba(255, 255, 255, 0.3);
|
background-color: rgba(255, 255, 255, 0.3);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-upload {
|
.avatar-upload {
|
||||||
display: none;
|
display: none;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 15px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-preview {
|
.avatar-preview {
|
||||||
@ -81,6 +86,13 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: 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 {
|
.avatar-preview img {
|
||||||
@ -97,17 +109,85 @@
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-btn:hover {
|
.upload-btn:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.2);
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<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 -->
|
<!-- Camera/Avatar Mode Selector -->
|
||||||
<div class="mode-selector">
|
<div class="mode-selector">
|
||||||
@ -124,44 +204,51 @@
|
|||||||
<!-- Avatar Upload Section -->
|
<!-- Avatar Upload Section -->
|
||||||
<div id="avatar-section" class="avatar-upload">
|
<div id="avatar-section" class="avatar-upload">
|
||||||
<div class="avatar-preview">
|
<div class="avatar-preview">
|
||||||
<img id="avatar-img" src="default-avatar.png" alt="Avatar">
|
<img id="avatar-img" src="avatar.png" alt="Avatar">
|
||||||
</div>
|
</div>
|
||||||
<input type="file" id="avatar-input" accept="image/*" style="display: none;">
|
<!-- Make the file input directly visible -->
|
||||||
<button class="upload-btn" onclick="document.getElementById('avatar-input').click()">
|
<label for="avatar-input" class="upload-btn">
|
||||||
|
<i class="fas fa-upload"></i>
|
||||||
Upload Avatar
|
Upload Avatar
|
||||||
</button>
|
</label>
|
||||||
|
<input type="file" id="avatar-input" accept="image/*" style="position: absolute; opacity: 0; width: 0; height: 0;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Recording Options -->
|
<!-- Recording Options -->
|
||||||
<div style="display: flex; flex-direction: row; gap: 25px;">
|
<div class="record-options">
|
||||||
<div style="display: flex; color: white;">
|
<div class="record-option">
|
||||||
<button id="tab"
|
<button id="tab" class="record-btn">
|
||||||
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;">
|
<div class="icon-container">
|
||||||
<i id="tab-icon" style="color: white;" class="fa-regular fa-window-maximize fa-2xl"></i>
|
<i id="tab-icon" class="fa-regular fa-window-maximize fa-xl"></i>
|
||||||
|
</div>
|
||||||
<p>Window Tab</p>
|
<p>Window Tab</p>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; color: white;">
|
<div class="record-option">
|
||||||
<button id="screen"
|
<button id="screen" class="record-btn">
|
||||||
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;">
|
<div class="icon-container">
|
||||||
<i id="screen-icon" style="color: white;" class="fa-solid fa-display fa-2xl"></i>
|
<i id="screen-icon" class="fa-solid fa-display fa-xl"></i>
|
||||||
|
</div>
|
||||||
<p>Screen</p>
|
<p>Screen</p>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="options">
|
<div id="options" style="display: flex; justify-content: center;">
|
||||||
<br>
|
<select id="quality" class="quality-select" required>
|
||||||
<select id="quality"
|
<option value="high" selected>High (1080p, 60fps)</option>
|
||||||
style="background-color: transparent; color: white; border: 1.5px solid rgb(255, 255, 255);font-family: Poppins; padding: 3px; border-radius: 5px;"
|
<option value="medium">Medium (720p, 30fps)</option>
|
||||||
required>
|
<option value="low">Low (480p, 30fps)</option>
|
||||||
<option style="color: black; font-size:0.75rem; padding:8px 2px; cursor:pointer;" value="high" selected>
|
|
||||||
High (1080p, 60fps)</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="popup.js"></script>
|
<script src="popup.js"></script>
|
||||||
|
<script>
|
||||||
|
document.getElementById('upload-avatar-btn').addEventListener('click', function() {
|
||||||
|
document.getElementById('avatar-input').click();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
85
popup.js
85
popup.js
@ -1,4 +1,3 @@
|
|||||||
//popup.js
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const cameraMode = document.getElementById('camera-mode');
|
const cameraMode = document.getElementById('camera-mode');
|
||||||
const avatarMode = document.getElementById('avatar-mode');
|
const avatarMode = document.getElementById('avatar-mode');
|
||||||
@ -6,7 +5,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const avatarInput = document.getElementById('avatar-input');
|
const avatarInput = document.getElementById('avatar-input');
|
||||||
const avatarPreview = document.getElementById('avatar-img');
|
const avatarPreview = document.getElementById('avatar-img');
|
||||||
|
|
||||||
// Mode switching
|
|
||||||
cameraMode.addEventListener('click', () => {
|
cameraMode.addEventListener('click', () => {
|
||||||
cameraMode.classList.add('active');
|
cameraMode.classList.add('active');
|
||||||
avatarMode.classList.remove('active');
|
avatarMode.classList.remove('active');
|
||||||
@ -23,31 +21,86 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
chrome.runtime.sendMessage({ type: 'mode-change', mode: 'avatar' });
|
chrome.runtime.sendMessage({ type: 'mode-change', mode: 'avatar' });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Avatar upload handling
|
// Fix the avatar upload functionality
|
||||||
avatarInput.addEventListener('change', function(e) {
|
avatarInput.addEventListener('change', function(e) {
|
||||||
|
console.log("File input changed"); // Add this debug line
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
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();
|
const reader = new FileReader();
|
||||||
reader.onload = function(e) {
|
reader.onload = function(e) {
|
||||||
|
try {
|
||||||
const avatarData = e.target.result;
|
const avatarData = e.target.result;
|
||||||
|
console.log("File loaded successfully"); // Add this debug line
|
||||||
|
|
||||||
avatarPreview.src = avatarData;
|
avatarPreview.src = avatarData;
|
||||||
chrome.storage.local.set({ avatarData: 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({
|
chrome.runtime.sendMessage({
|
||||||
type: 'avatar-update',
|
type: 'avatar-update',
|
||||||
avatarData: avatarData
|
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) {
|
chrome.storage.local.get(['mode', 'avatarData'], function(data) {
|
||||||
|
|
||||||
if (data.mode === 'avatar') {
|
if (data.mode === 'avatar') {
|
||||||
avatarMode.click();
|
avatarMode.click();
|
||||||
|
} else {
|
||||||
|
cameraMode.click();
|
||||||
|
}
|
||||||
|
|
||||||
if (data.avatarData) {
|
if (data.avatarData) {
|
||||||
avatarPreview.src = 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();
|
injectCamera();
|
||||||
|
|
||||||
const removeCamera = async () => {
|
const removeCamera = async () => {
|
||||||
|
|
||||||
const tab = await chrome.tabs.query({ active: true, currentWindow: true });
|
const tab = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
if (!tab) return;
|
if (!tab) return;
|
||||||
|
|
||||||
@ -80,18 +132,13 @@ const removeCamera = async () => {
|
|||||||
console.log("inject into tab", tabId);
|
console.log("inject into tab", tabId);
|
||||||
await chrome.scripting.executeScript({
|
await chrome.scripting.executeScript({
|
||||||
func: () => {
|
func: () => {
|
||||||
const camera = document.querySelector("#purple-camera");
|
const camera = document.querySelector("#camera-wrapper");
|
||||||
if (!camera) return;
|
if (camera) camera.remove();
|
||||||
camera.remove();
|
|
||||||
document.querySelector("#purple-camera").style.display = "none";
|
|
||||||
},
|
},
|
||||||
target: { tabId },
|
target: { tabId },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const checkRecording = async () => {
|
const checkRecording = async () => {
|
||||||
const recording = await chrome.storage.local.get(["recording", "type"]);
|
const recording = await chrome.storage.local.get(["recording", "type"]);
|
||||||
const recordingStatus = recording.recording || false;
|
const recordingStatus = recording.recording || false;
|
||||||
@ -130,7 +177,6 @@ const init = async () => {
|
|||||||
const recordingState = await checkRecording();
|
const recordingState = await checkRecording();
|
||||||
|
|
||||||
if (recordingState[0] === true) {
|
if (recordingState[0] === true) {
|
||||||
|
|
||||||
chrome.runtime.sendMessage({ type: "stop-recording" });
|
chrome.runtime.sendMessage({ type: "stop-recording" });
|
||||||
removeCamera();
|
removeCamera();
|
||||||
} else {
|
} else {
|
||||||
@ -142,7 +188,6 @@ const init = async () => {
|
|||||||
injectCamera();
|
injectCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.close();
|
window.close();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user