| Server IP : 127.0.0.1 / Your IP : 216.73.216.48 Web Server : Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12 System : Windows NT DESKTOP-3H4FHQJ 10.0 build 19045 (Windows 10) AMD64 User : win 10 ( 0) PHP Version : 8.2.12 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : D:/xampp/htdocs-coblaa/pureFaith/ |
Upload File : |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Live Stream Preview</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #0F172A; /* Slate 900 */
}
.stream-container {
width: 100%;
max-width: 900px;
/* Tailwind classes will manage aspect ratio dynamically */
}
.aspect-16-9 {
aspect-ratio: 16 / 9;
}
.aspect-4-3 {
aspect-ratio: 4 / 3;
}
.aspect-1-1 {
aspect-ratio: 1 / 1;
}
#video-feed {
object-fit: cover; /* Use cover to ensure no black bars within the video feed itself */
width: 100%;
height: 100%;
background-color: #1E293B; /* Slate 800 */
}
</style>
</head>
<body class="min-h-screen flex flex-col items-center p-4">
<header class="text-center mb-8 w-full max-w-2xl">
<h1 class="text-3xl font-bold text-white mb-2">Webcam Stream Preview</h1>
<p class="text-indigo-400 text-sm">Access your camera feed instantly.</p>
</header>
<!-- Main Stream Container - Aspect Ratio Class is now controlled by JS -->
<div id="stream-container" class="stream-container aspect-16-9 rounded-xl shadow-2xl overflow-hidden relative border-4 border-indigo-500 mb-8">
<video id="video-feed" autoplay playsinline class="rounded-lg"></video>
<!-- Overlay for Status Messages -->
<div id="status-overlay" class="absolute inset-0 bg-gray-900 bg-opacity-90 flex items-center justify-center p-6 transition-opacity duration-300">
<div class="text-center space-y-4">
<svg id="status-icon" class="w-12 h-12 mx-auto text-indigo-400 animate-spin" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" class="opacity-25"></circle>
<path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" class="opacity-75"></path>
</svg>
<p id="status-text" class="text-lg font-semibold text-white"></p>
<div id="error-details" class="text-sm text-red-400 hidden p-2 bg-gray-700 rounded-lg"></div>
</div>
</div>
</div>
<!-- Controls -->
<div class="w-full max-w-xl space-y-4">
<!-- Stream Control Buttons (Start/Stop/Switch) -->
<div class="flex flex-col md:flex-row space-y-3 md:space-y-0 md:space-x-4">
<button id="start-btn" class="w-full md:w-1/3 bg-indigo-600 text-white p-3 rounded-lg font-semibold hover:bg-indigo-700 transition-colors duration-200 shadow-md">
Start Live Stream
</button>
<button id="switch-camera-btn" class="w-full md:w-1/3 bg-gray-600 text-white p-3 rounded-lg font-semibold hover:bg-gray-700 transition-colors duration-200 shadow-md hidden" disabled>
Switch Camera
</button>
<button id="stop-btn" class="w-full md:w-1/3 bg-red-600 text-white p-3 rounded-lg font-semibold hover:bg-red-700 transition-colors duration-200 shadow-md hidden" disabled>
Stop Stream
</button>
</div>
<!-- Recording Controls -->
<div class="p-3 bg-gray-800 rounded-xl shadow-lg">
<p class="text-sm font-semibold text-gray-300 mb-3 text-center">Recording Controls:</p>
<div class="flex justify-center space-x-3">
<button id="record-btn" class="p-2 w-1/2 rounded-lg bg-green-600 text-white text-sm font-medium hover:bg-green-700 transition-colors shadow-md disabled:opacity-50" disabled>
<span id="record-icon">
<svg class="w-5 h-5 inline-block mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"></path></svg>
</span>
<span id="record-text">Start Recording</span>
</button>
<button id="stop-record-btn" class="p-2 w-1/2 rounded-lg bg-red-800 text-white text-sm font-medium hover:bg-red-900 transition-colors shadow-md hidden disabled:opacity-50" disabled>
<svg class="w-5 h-5 inline-block mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9 9a1 1 0 011-1h1a1 1 0 110 2h-1a1 1 0 01-1-1z" clip-rule="evenodd"></path></svg>
Stop & Download
</button>
</div>
</div>
<!-- Aspect Ratio Controls -->
<div class="p-3 bg-gray-800 rounded-xl shadow-lg">
<p class="text-sm font-semibold text-gray-300 mb-3 text-center">Aspect Ratio:</p>
<div id="ratio-controls" class="flex justify-center space-x-3">
<button data-ratio="16/9" data-class="aspect-16-9" class="ratio-btn p-2 rounded-lg bg-indigo-600 text-white text-sm font-medium hover:bg-indigo-700 transition-colors shadow-md">16:9</button>
<button data-ratio="4/3" data-class="aspect-4-3" class="ratio-btn p-2 rounded-lg bg-gray-700 text-white text-sm font-medium hover:bg-indigo-500 transition-colors shadow-md">4:3</button>
<button data-ratio="1/1" data-class="aspect-1-1" class="ratio-btn p-2 rounded-lg bg-gray-700 text-white text-sm font-medium hover:bg-indigo-500 transition-colors shadow-md">1:1</button>
</div>
</div>
</div>
<!-- Mandatory Security Note -->
<div class="mt-8 p-4 bg-yellow-900 bg-opacity-30 border border-yellow-500 rounded-lg w-full max-w-xl text-yellow-300">
<p class="font-bold mb-2">Important Note on Access Errors:</p>
<p class="text-sm">For the webcam to work, this page <strong class="text-yellow-100">must be running on a secure connection (HTTPS)</strong> or on a local server (`localhost`). If you see an error, ensure your environment meets this security requirement.</p>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const videoFeed = document.getElementById('video-feed');
const streamContainer = document.getElementById('stream-container');
const startBtn = document.getElementById('start-btn');
const stopBtn = document.getElementById('stop-btn');
const switchCameraBtn = document.getElementById('switch-camera-btn');
const ratioButtons = document.querySelectorAll('.ratio-btn');
const statusOverlay = document.getElementById('status-overlay');
const statusText = document.getElementById('status-text');
const statusIcon = document.getElementById('status-icon');
const errorDetails = document.getElementById('error-details');
// Recording elements
const recordBtn = document.getElementById('record-btn');
const stopRecordBtn = document.getElementById('stop-record-btn');
const recordIcon = document.getElementById('record-icon');
const recordText = document.getElementById('record-text');
let currentStream = null;
let currentFacingMode = 'user';
let currentAspectRatio = 16 / 9;
let currentAspectRatioClass = 'aspect-16-9';
// Recording state
let mediaRecorder = null;
let recordedChunks = [];
// Use 'video/webm;codecs=vp8,opus' for wider browser support and audio
const mimeType = 'video/webm;codecs=vp8,opus';
// --- Utility Functions ---
function updateStatus(message, isError = false, details = '') {
statusOverlay.classList.remove('hidden');
statusText.textContent = message;
statusIcon.classList.toggle('hidden', isError);
statusIcon.classList.toggle('animate-spin', !isError);
statusText.classList.toggle('text-white', !isError);
statusText.classList.toggle('text-red-400', isError);
if (isError && details) {
errorDetails.textContent = details;
errorDetails.classList.remove('hidden');
} else {
errorDetails.classList.add('hidden');
}
if (videoFeed.srcObject) {
videoFeed.srcObject = null;
}
}
function hideStatus() {
statusOverlay.classList.add('opacity-0');
setTimeout(() => {
statusOverlay.classList.add('hidden');
statusOverlay.classList.remove('opacity-0');
}, 300);
}
function updateSwitchButtonText() {
if (currentFacingMode === 'user') {
switchCameraBtn.textContent = 'Switch to Back Camera';
} else {
switchCameraBtn.textContent = 'Switch to Front Camera';
}
}
function updateRatioButtonStyles() {
ratioButtons.forEach(btn => {
const btnRatio = btn.getAttribute('data-ratio');
const isActive = (eval(btnRatio) === currentAspectRatio);
if (isActive) {
btn.classList.add('bg-indigo-600', 'hover:bg-indigo-700');
btn.classList.remove('bg-gray-700', 'hover:bg-indigo-500');
} else {
btn.classList.add('bg-gray-700', 'hover:bg-indigo-500');
btn.classList.remove('bg-indigo-600', 'hover:bg-indigo-700');
}
});
}
// --- Recording Logic ---
function startRecording() {
if (!currentStream) {
updateStatus('Error: Stream not active.', true, 'Start the camera stream first before recording.');
return;
}
// Reset chunks array
recordedChunks = [];
try {
if (!window.MediaRecorder || !MediaRecorder.isTypeSupported(mimeType)) {
updateStatus('Error: Recording is not supported.', true, `Your browser does not support the required video/webm MIME type.`);
return;
}
// Initialize recorder
mediaRecorder = new MediaRecorder(currentStream, { mimeType: mimeType });
// Event to collect data chunks
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
recordedChunks.push(event.data);
}
};
// Event triggered when recording stops
mediaRecorder.onstop = () => {
// Create Blob from chunks and trigger download
const blob = new Blob(recordedChunks, { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
// Use a consistent, unique filename
a.download = `webcam-recording-${new Date().toISOString().replace(/[:.]/g, '-')}.webm`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
// Reset recording UI state
recordBtn.classList.remove('hidden');
recordBtn.disabled = false;
stopRecordBtn.classList.add('hidden');
stopRecordBtn.disabled = true;
recordText.textContent = 'Start Recording';
recordIcon.innerHTML = `<svg class="w-5 h-5 inline-block mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"></path></svg>`;
updateStatus('Recording downloaded successfully!', false, 'Click "Start Recording" again to record a new session.');
hideStatus();
};
mediaRecorder.start(100); // Start recording, collecting data every 100ms
// Update UI state
recordBtn.classList.add('hidden');
recordBtn.disabled = true;
stopRecordBtn.classList.remove('hidden');
stopRecordBtn.disabled = false;
// Display recording indicator (pulse)
recordText.textContent = 'Recording...';
recordIcon.innerHTML = `<svg class="w-5 h-5 inline-block mr-1 text-red-500 animate-pulse" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM10 10a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"></path></svg>`;
// Hide status overlay to see the stream
hideStatus();
} catch (e) {
updateStatus('Recording failed.', true, `Error during MediaRecorder initialization: ${e.message}`);
console.error('MediaRecorder init error:', e);
}
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
stopRecordBtn.disabled = true; // Disable while processing download
updateStatus('Processing recording for download...');
}
}
// --- Stream Control Logic ---
async function startStream() {
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
updateStatus('Error: Media devices are not supported by your browser.', true, 'Try updating your browser or using Chrome/Firefox/Edge.');
return;
}
updateStatus(`Requesting ${currentFacingMode === 'user' ? 'Front' : 'Back'} Camera at ${currentAspectRatio.toFixed(2)} ratio...`);
startBtn.disabled = true;
switchCameraBtn.disabled = true;
recordBtn.disabled = true;
if (currentStream) {
currentStream.getTracks().forEach(track => track.stop());
currentStream = null;
}
streamContainer.className = 'stream-container ' + currentAspectRatioClass + ' rounded-xl shadow-2xl overflow-hidden relative border-4 border-indigo-500 mb-8';
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: currentFacingMode,
aspectRatio: currentAspectRatio,
width: { ideal: 1280 },
height: { ideal: 720 }
},
// Request audio access as well for recording features
audio: true
});
currentStream = stream;
videoFeed.srcObject = stream;
await videoFeed.play();
hideStatus();
startBtn.classList.add('hidden');
// Show/Enable stream controls
stopBtn.classList.remove('hidden');
stopBtn.disabled = false;
switchCameraBtn.classList.remove('hidden');
switchCameraBtn.disabled = false;
updateSwitchButtonText();
// Show/Enable record button
recordBtn.classList.remove('hidden');
recordBtn.disabled = false;
updateRatioButtonStyles();
} catch (err) {
let errorMessage = 'An unknown error occurred while trying to access the camera.';
let errorHint = 'Ensure your device has a camera and try refreshing the page.';
if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
errorMessage = 'Access Denied.';
errorHint = 'You must grant camera/microphone permission in your browser settings to continue.';
} else if (err.name === 'NotFoundError' || err.name === 'OverconstrainedError') {
errorMessage = 'No Camera Found or Ratio Not Supported.';
errorHint = `No suitable camera device found for facing mode: ${currentFacingMode} with ratio ${currentAspectRatio}. Try switching cameras or aspect ratios.`;
} else if (err.name === 'SecurityError') {
errorMessage = 'Security Error: HTTPS Required.';
errorHint = 'This feature only works on secure connections (HTTPS) or localhost.';
}
updateStatus(errorMessage, true, errorHint);
console.error('getUserMedia Error:', err);
// Revert buttons to initial state on failure
startBtn.disabled = false;
startBtn.classList.remove('hidden');
stopBtn.classList.add('hidden');
switchCameraBtn.classList.add('hidden');
recordBtn.classList.add('hidden');
stopRecordBtn.classList.add('hidden');
}
}
function stopStream() {
// If recording is active, stop it first. This triggers the download.
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
}
if (currentStream) {
currentStream.getTracks().forEach(track => track.stop());
currentStream = null;
videoFeed.srcObject = null;
// Update buttons and status
stopBtn.disabled = true;
stopBtn.classList.add('hidden');
switchCameraBtn.classList.add('hidden');
startBtn.classList.remove('hidden');
startBtn.disabled = false;
// Hide and reset recording buttons
recordBtn.classList.add('hidden');
stopRecordBtn.classList.add('hidden');
recordBtn.disabled = true;
stopRecordBtn.disabled = true;
currentFacingMode = 'user';
updateSwitchButtonText();
updateStatus('Stream stopped. Click "Start Live Stream" to restart.');
}
}
function switchCamera() {
currentFacingMode = (currentFacingMode === 'user' ? 'environment' : 'user');
switchCameraBtn.disabled = true;
stopBtn.disabled = true;
recordBtn.disabled = true;
stopRecordBtn.disabled = true;
startStream();
}
function handleRatioSwitch(event) {
const ratioStr = event.currentTarget.getAttribute('data-ratio');
const ratioClass = event.currentTarget.getAttribute('data-class');
currentAspectRatio = eval(ratioStr);
currentAspectRatioClass = ratioClass;
if (currentStream) {
// Stop recorder if active before restarting stream
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
}
startStream();
} else {
streamContainer.className = 'stream-container ' + currentAspectRatioClass + ' rounded-xl shadow-2xl overflow-hidden relative border-4 border-indigo-500 mb-8';
updateRatioButtonStyles();
}
}
// --- Event Listeners ---
startBtn.addEventListener('click', startStream);
stopBtn.addEventListener('click', stopStream);
switchCameraBtn.addEventListener('click', switchCamera);
recordBtn.addEventListener('click', startRecording);
stopRecordBtn.addEventListener('click', stopRecording);
ratioButtons.forEach(button => {
button.addEventListener('click', handleRatioSwitch);
});
// Initial setup
updateStatus('Click "Start Live Stream" to begin.', false);
statusIcon.classList.add('hidden');
updateSwitchButtonText();
updateRatioButtonStyles();
});
</script>
</body>
</html>