BubbleCam/video.js
2025-02-16 15:10:07 +05:30

454 lines
13 KiB
JavaScript

//video.js
let videoBlob = null;
let videoDuration = 0;
let mediaRecorder = null;
let recordedChunks = [];
let currentVideoUrl = null;
const timeline = document.getElementById('timeline');
const trimStart = document.getElementById('trim-start');
const trimEnd = document.getElementById('trim-end');
const videoElement = document.querySelector("#recorded-video");
const downloadBtn = document.querySelector("#download-btn");
const trimBtn = document.querySelector("#trim-btn");
let isDraggingStart = false;
let isDraggingEnd = false;
trimStart.style.left = '0%';
trimEnd.style.left = '100%';
trimBtn.disabled = true;
trimStart.addEventListener('mousedown', startDragStart);
trimEnd.addEventListener('mousedown', startDragEnd);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', stopDrag);
function startDragStart(e) {
isDraggingStart = true;
e.preventDefault();
}
function startDragEnd(e) {
isDraggingEnd = true;
e.preventDefault();
}
function drag(e) {
if (!isDraggingStart && !isDraggingEnd) return;
const timelineRect = timeline.getBoundingClientRect();
let newPosition = ((e.clientX - timelineRect.left) / timelineRect.width) * 100;
newPosition = Math.max(0, Math.min(newPosition, 100));
if (isDraggingStart) {
const endPosition = parseFloat(trimEnd.style.left) || 100;
if (newPosition >= endPosition) return;
trimStart.style.left = `${newPosition}%`;
updateVideoTime(newPosition, 'start');
}
if (isDraggingEnd) {
const startPosition = parseFloat(trimStart.style.left) || 0;
if (newPosition <= startPosition) return;
trimEnd.style.left = `${newPosition}%`;
updateVideoTime(newPosition, 'end');
}
}
function updateVideoTime(position, type) {
if (!videoDuration || !isFinite(videoDuration)) return;
const timeInSeconds = (position / 100) * videoDuration;
if (isFinite(timeInSeconds) && type === 'start') {
videoElement.currentTime = timeInSeconds;
}
}
function stopDrag() {
isDraggingStart = false;
isDraggingEnd = false;
}
async function waitForVideoDuration() {
return new Promise((resolve) => {
const checkDuration = () => {
if (videoElement.readyState >= 2 && isFinite(videoElement.duration)) {
videoDuration = videoElement.duration;
resolve(videoDuration);
} else {
setTimeout(checkDuration, 100);
}
};
checkDuration();
});
}
async function trimVideo(startTime, endTime) {
// Validate inputs
if (!isFinite(startTime) || !isFinite(endTime) || startTime < 0 || endTime <= startTime) {
throw new Error('Invalid trim times');
}
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set canvas dimensions to match video
canvas.width = videoElement.videoWidth;
canvas.height = videoElement.videoHeight;
// Create a new MediaRecorder
const stream = canvas.captureStream();
try {
mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp8',
videoBitsPerSecond: 2500000
});
} catch (e) {
mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm',
videoBitsPerSecond: 2500000
});
}
recordedChunks = [];
mediaRecorder.ondataavailable = (e) => {
if (e.data.size > 0) {
recordedChunks.push(e.data);
}
};
mediaRecorder.onstop = () => {
const blob = new Blob(recordedChunks, { type: 'video/webm' });
resolve(blob);
};
// Start recording
mediaRecorder.start(100);
// Set video to start time
videoElement.currentTime = startTime;
const drawFrame = () => {
if (videoElement.currentTime >= endTime) {
mediaRecorder.stop();
return;
}
ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
videoElement.currentTime += 1/30;
requestAnimationFrame(drawFrame);
};
videoElement.onseeked = () => {
if (Math.abs(videoElement.currentTime - startTime) < 0.1) {
drawFrame();
videoElement.onseeked = null;
}
};
});
}
async function playVideo(message) {
const url = message?.videoUrl || message?.base64;
if (!url) {
console.error('No video URL provided');
return;
}
try {
// Clean up previous video if it exists
if (currentVideoUrl) {
URL.revokeObjectURL(currentVideoUrl);
currentVideoUrl = null;
}
// Reset video element
videoElement.pause();
videoElement.currentTime = 0;
videoElement.src = '';
// Clear previous video data
videoBlob = null;
videoDuration = 0;
// Load new video blob
const response = await fetch(url);
videoBlob = await response.blob();
// Create and store new object URL
currentVideoUrl = URL.createObjectURL(videoBlob);
videoElement.src = currentVideoUrl;
// Wait for video metadata and duration
await waitForVideoDuration();
console.log('Video duration:', videoDuration);
// Reset trim handles
trimStart.style.left = '0%';
trimEnd.style.left = '100%';
// Enable trim button
trimBtn.disabled = false;
// Clear any stored video URL
if (chrome?.storage?.local) {
chrome.storage.local.remove("videoUrl");
}
// Store new video URL
if (url !== currentVideoUrl) {
saveVideo(url);
}
} catch (error) {
console.error('Error loading video:', error);
// Clean up on error
if (currentVideoUrl) {
URL.revokeObjectURL(currentVideoUrl);
currentVideoUrl = null;
}
return;
}
// Update download functionality
// Update download functionality
downloadBtn.onclick = () => {
if (videoBlob) {
try {
// Create a download link
const a = document.createElement('a');
a.href = currentVideoUrl;
// Set a default filename with timestamp
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
a.download = `recorded_video_${timestamp}.webm`;
// Make sure the link is hidden
a.style.display = 'none';
// Add to document, click it, and remove it
document.body.appendChild(a);
a.click();
// Small timeout before removing the element
setTimeout(() => {
document.body.removeChild(a);
}, 100);
} catch (error) {
console.error('Download error:', error);
alert('Error downloading video. Please try again.');
}
} else {
alert('No video available to download');
}
};
// Update trim functionality
trimBtn.onclick = async () => {
if (!videoBlob || !videoDuration) {
console.error('Video not properly loaded');
return;
}
try {
const startPercent = parseFloat(trimStart.style.left) || 0;
const endPercent = parseFloat(trimEnd.style.left) || 100;
const startTime = (startPercent / 100) * videoDuration;
const endTime = (endPercent / 100) * videoDuration;
if (!isFinite(startTime) || !isFinite(endTime)) {
throw new Error('Invalid trim times calculated');
}
console.log(`Trimming video from ${startTime}s to ${endTime}s`);
trimBtn.disabled = true;
trimBtn.textContent = 'Trimming...';
const trimmedBlob = await trimVideo(startTime, endTime);
// Clean up previous video
if (currentVideoUrl) {
URL.revokeObjectURL(currentVideoUrl);
}
// Set up new video
videoBlob = trimmedBlob;
currentVideoUrl = URL.createObjectURL(trimmedBlob);
videoElement.src = currentVideoUrl;
console.log('Video trimmed successfully');
} catch (error) {
console.error('Error trimming video:', error.message);
} finally {
trimBtn.disabled = false;
trimBtn.textContent = 'Trim Video';
}
};
}
// Storage and message handling
const saveVideo = (videoUrl) => {
if (chrome?.storage?.local) {
chrome.storage.local.set({ videoUrl }, () => {
if (chrome.runtime.lastError) {
console.error('Error saving video URL:', chrome.runtime.lastError);
}
});
}
};
// Listen for stored video on load
if (chrome?.storage?.local) {
chrome.storage.local.get(["videoUrl"], (result) => {
if (result.videoUrl) {
playVideo(result);
}
});
}
// Listen for messages from service worker
if (chrome?.runtime?.onMessage) {
chrome.runtime.onMessage.addListener((message) => {
switch (message.type) {
case "play-video":
playVideo(message);
break;
default:
console.log("Unknown message type");
}
});
}
window.addEventListener('unload', () => {
if (currentVideoUrl) {
URL.revokeObjectURL(currentVideoUrl);
}
});
// Add these variables at the top of video.js
// Add these variables at the top of video.js
let embedCode = '';
// Make sure this is defined
// Function to generate embed code
function generateEmbedCode(videoUrl) {
const embedWidth = 640;
const embedHeight = 360;
return `<iframe width="${embedWidth}" height="${embedHeight}" src="${videoUrl}" frameborder="0" allowfullscreen></iframe>`;
}
// Function to show embed modal
function showEmbedModal() {
const embedModal = document.getElementById('embed-modal');
const embedCodeTextarea = document.getElementById('embed-code');
const copyEmbedBtn = document.getElementById('copy-embed');
const closeEmbedModalBtn = document.getElementById('close-embed-modal');
if (videoBlob) {
const videoUrl = URL.createObjectURL(videoBlob);
embedCode = generateEmbedCode(videoUrl);
embedCodeTextarea.value = embedCode;
embedModal.style.display = 'flex';
// Copy button functionality
copyEmbedBtn.onclick = () => {
embedCodeTextarea.select();
document.execCommand('copy');
copyEmbedBtn.innerHTML = '<i class="fas fa-check"></i> Copied!';
setTimeout(() => {
copyEmbedBtn.innerHTML = '<i class="fas fa-copy"></i> Copy Code';
}, 2000);
};
// Close button functionality
closeEmbedModalBtn.onclick = () => {
embedModal.style.display = 'none';
};
// Close modal when clicking outside
window.onclick = (event) => {
if (event.target === embedModal) {
embedModal.style.display = 'none';
}
};
}
}
// Add embed button to controls
function addEmbedButton() {
const controlsContainer = document.getElementById('controls');
const embedBtn = document.createElement('button');
embedBtn.className = 'btn';
embedBtn.innerHTML = '<i class="fas fa-code"></i> Get Embed Code';
embedBtn.onclick = showEmbedModal;
controlsContainer.appendChild(embedBtn);
}
// Initialize embed functionality
document.addEventListener('DOMContentLoaded', () => {
addEmbedButton();
});
// Additional CSS to add to your existing styles
const additionalStyles = `
.modal {
animation: fadeIn 0.3s ease-in-out;
}
.modal-content {
animation: slideIn 0.3s ease-in-out;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
#embed-code {
background: #f8f9fa;
font-family: 'Courier New', monospace;
font-size: 14px;
resize: none;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 15px;
margin: 15px 0;
transition: border-color 0.3s ease;
}
#embed-code:focus {
outline: none;
border-color: #5d3fbd;
}
.modal-buttons .btn {
min-width: 120px;
justify-content: center;
}
.modal-buttons .btn i {
margin-right: 8px;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideIn {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
#copy-embed:not(:hover) i {
transition: transform 0.3s ease;
}
#copy-embed:hover i {
transform: translateY(-2px);
}
`;