//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 ``; } // 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 = ' Copied!'; setTimeout(() => { copyEmbedBtn.innerHTML = ' 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 = ' 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); } `;