454 lines
13 KiB
JavaScript
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);
|
|
}
|
|
`;
|
|
|