|
<!DOCTYPE html> |
|
<html lang="fr"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>LiveConnect - Plateforme de vidéo conférence</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<style> |
|
.video-container { |
|
position: relative; |
|
padding-bottom: 56.25%; |
|
height: 0; |
|
overflow: hidden; |
|
} |
|
.video-container video { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
object-fit: cover; |
|
} |
|
.participant-grid { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); |
|
gap: 15px; |
|
} |
|
.participant { |
|
position: relative; |
|
border-radius: 10px; |
|
overflow: hidden; |
|
background: #2d3748; |
|
} |
|
.participant-name { |
|
position: absolute; |
|
bottom: 10px; |
|
left: 10px; |
|
background: rgba(0,0,0,0.5); |
|
color: white; |
|
padding: 2px 8px; |
|
border-radius: 4px; |
|
font-size: 12px; |
|
} |
|
.modal { |
|
transition: opacity 0.3s ease; |
|
} |
|
.modal-overlay { |
|
background: rgba(0,0,0,0.7); |
|
} |
|
#localVideo { |
|
transform: scaleX(-1); |
|
} |
|
.chat-messages { |
|
height: 300px; |
|
overflow-y: auto; |
|
} |
|
.message { |
|
word-wrap: break-word; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-900 text-white"> |
|
|
|
<div id="homePage" class="min-h-screen flex flex-col items-center justify-center p-4"> |
|
<div class="text-center mb-12"> |
|
<h1 class="text-5xl font-bold mb-4 text-blue-400">LiveConnect</h1> |
|
<p class="text-xl text-gray-300">Plateforme de vidéo conférence en temps réel</p> |
|
</div> |
|
|
|
<div class="w-full max-w-md bg-gray-800 rounded-xl p-8 shadow-2xl"> |
|
<div class="mb-6"> |
|
<label for="userName" class="block text-gray-300 mb-2">Votre nom</label> |
|
<input type="text" id="userName" placeholder="Entrez votre nom" |
|
class="w-full px-4 py-2 rounded-lg bg-gray-700 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
</div> |
|
|
|
<div class="mb-6"> |
|
<label for="roomId" class="block text-gray-300 mb-2">ID de la réunion</label> |
|
<input type="text" id="roomId" placeholder="12345" |
|
class="w-full px-4 py-2 rounded-lg bg-gray-700 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
</div> |
|
|
|
<div class="flex flex-col space-y-4"> |
|
<button id="joinBtn" class="bg-blue-600 hover:bg-blue-700 text-white py-3 px-6 rounded-lg font-medium transition duration-200 flex items-center justify-center"> |
|
<i class="fas fa-video mr-2"></i> Rejoindre |
|
</button> |
|
<button id="createBtn" class="bg-green-600 hover:bg-green-700 text-white py-3 px-6 rounded-lg font-medium transition duration-200 flex items-center justify-center"> |
|
<i class="fas fa-plus mr-2"></i> Créer une réunion |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="meetingPage" class="hidden min-h-screen flex flex-col"> |
|
|
|
<header class="bg-gray-800 py-4 px-6 flex justify-between items-center border-b border-gray-700"> |
|
<div class="flex items-center"> |
|
<h2 class="text-xl font-semibold text-blue-400">LiveConnect</h2> |
|
<span id="meetingIdDisplay" class="ml-4 bg-gray-700 px-3 py-1 rounded text-sm"></span> |
|
</div> |
|
<div class="flex items-center space-x-4"> |
|
<button id="chatToggleBtn" class="text-gray-300 hover:text-white"> |
|
<i class="fas fa-comment mr-1"></i> Chat |
|
</button> |
|
<button id="participantsBtn" class="text-gray-300 hover:text-white"> |
|
<i class="fas fa-users mr-1"></i> <span id="participantCount">1</span> |
|
</button> |
|
<button id="shareBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-1 rounded"> |
|
<i class="fas fa-share-alt mr-1"></i> Partager |
|
</button> |
|
</div> |
|
</header> |
|
|
|
|
|
<div class="flex flex-1"> |
|
|
|
<div class="flex-1 p-4"> |
|
<div id="videoContainer" class="participant-grid"> |
|
|
|
</div> |
|
</div> |
|
|
|
|
|
<div id="chatPanel" class="hidden w-80 bg-gray-800 border-l border-gray-700 flex flex-col"> |
|
<div class="p-4 border-b border-gray-700"> |
|
<h3 class="font-semibold">Chat de la réunion</h3> |
|
</div> |
|
<div id="chatMessages" class="chat-messages flex-1 p-4 overflow-y-auto"> |
|
|
|
</div> |
|
<div class="p-4 border-t border-gray-700"> |
|
<div class="flex"> |
|
<input id="chatInput" type="text" placeholder="Tapez votre message..." |
|
class="flex-1 px-4 py-2 rounded-l-lg bg-gray-700 border border-gray-600 focus:outline-none"> |
|
<button id="sendChatBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-r-lg"> |
|
<i class="fas fa-paper-plane"></i> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<footer class="bg-gray-800 py-3 px-6 border-t border-gray-700"> |
|
<div class="flex justify-center space-x-8"> |
|
<button id="micBtn" class="control-btn bg-gray-700 hover:bg-gray-600 text-white p-3 rounded-full"> |
|
<i class="fas fa-microphone"></i> |
|
</button> |
|
<button id="cameraBtn" class="control-btn bg-gray-700 hover:bg-gray-600 text-white p-3 rounded-full"> |
|
<i class="fas fa-video"></i> |
|
</button> |
|
<button id="screenShareBtn" class="control-btn bg-gray-700 hover:bg-gray-600 text-white p-3 rounded-full"> |
|
<i class="fas fa-desktop"></i> |
|
</button> |
|
<button id="leaveBtn" class="control-btn bg-red-600 hover:bg-red-700 text-white px-6 py-3 rounded-full"> |
|
<i class="fas fa-phone-slash mr-2"></i> Quitter |
|
</button> |
|
</div> |
|
</footer> |
|
</div> |
|
|
|
|
|
<div id="shareModal" class="modal fixed inset-0 flex items-center justify-center z-50 hidden"> |
|
<div class="modal-overlay absolute inset-0"></div> |
|
<div class="bg-gray-800 rounded-xl z-50 w-full max-w-md mx-4 p-6"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h3 class="text-xl font-semibold">Partager le lien</h3> |
|
<button id="closeShareModal" class="text-gray-400 hover:text-white"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
<div class="mb-4"> |
|
<p class="text-gray-300 mb-2">Copiez ce lien et envoyez-le aux participants :</p> |
|
<div class="flex"> |
|
<input id="shareLink" type="text" readonly |
|
class="flex-1 px-4 py-2 rounded-l-lg bg-gray-700 border border-gray-600"> |
|
<button id="copyLinkBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-r-lg"> |
|
<i class="fas fa-copy"></i> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
let localStream; |
|
let meetingId = "12345"; |
|
let userName = ""; |
|
let isMicOn = true; |
|
let isCameraOn = true; |
|
let isChatOpen = false; |
|
let participants = []; |
|
|
|
|
|
const homePage = document.getElementById('homePage'); |
|
const meetingPage = document.getElementById('meetingPage'); |
|
const userNameInput = document.getElementById('userName'); |
|
const roomIdInput = document.getElementById('roomId'); |
|
const joinBtn = document.getElementById('joinBtn'); |
|
const createBtn = document.getElementById('createBtn'); |
|
const meetingIdDisplay = document.getElementById('meetingIdDisplay'); |
|
const videoContainer = document.getElementById('videoContainer'); |
|
const micBtn = document.getElementById('micBtn'); |
|
const cameraBtn = document.getElementById('cameraBtn'); |
|
const screenShareBtn = document.getElementById('screenShareBtn'); |
|
const leaveBtn = document.getElementById('leaveBtn'); |
|
const shareBtn = document.getElementById('shareBtn'); |
|
const shareModal = document.getElementById('shareModal'); |
|
const closeShareModal = document.getElementById('closeShareModal'); |
|
const shareLink = document.getElementById('shareLink'); |
|
const copyLinkBtn = document.getElementById('copyLinkBtn'); |
|
const participantCount = document.getElementById('participantCount'); |
|
const chatPanel = document.getElementById('chatPanel'); |
|
const chatToggleBtn = document.getElementById('chatToggleBtn'); |
|
const chatMessages = document.getElementById('chatMessages'); |
|
const chatInput = document.getElementById('chatInput'); |
|
const sendChatBtn = document.getElementById('sendChatBtn'); |
|
|
|
|
|
joinBtn.addEventListener('click', joinMeeting); |
|
createBtn.addEventListener('click', createMeeting); |
|
micBtn.addEventListener('click', toggleMic); |
|
cameraBtn.addEventListener('click', toggleCamera); |
|
screenShareBtn.addEventListener('click', toggleScreenShare); |
|
leaveBtn.addEventListener('click', leaveMeeting); |
|
shareBtn.addEventListener('click', showShareModal); |
|
closeShareModal.addEventListener('click', hideShareModal); |
|
copyLinkBtn.addEventListener('click', copyShareLink); |
|
chatToggleBtn.addEventListener('click', toggleChat); |
|
sendChatBtn.addEventListener('click', sendMessage); |
|
chatInput.addEventListener('keypress', function(e) { |
|
if (e.key === 'Enter') sendMessage(); |
|
}); |
|
|
|
|
|
function joinMeeting() { |
|
userName = userNameInput.value.trim(); |
|
const roomId = roomIdInput.value.trim(); |
|
|
|
if (!userName) { |
|
alert("Veuillez entrer votre nom"); |
|
return; |
|
} |
|
|
|
if (roomId) { |
|
meetingId = roomId; |
|
} |
|
|
|
startMeeting(); |
|
} |
|
|
|
function createMeeting() { |
|
userName = userNameInput.value.trim(); |
|
|
|
if (!userName) { |
|
alert("Veuillez entrer votre nom"); |
|
return; |
|
} |
|
|
|
|
|
if (!roomIdInput.value.trim()) { |
|
meetingId = Math.random().toString(36).substring(2, 8).toUpperCase(); |
|
roomIdInput.value = meetingId; |
|
} else { |
|
meetingId = roomIdInput.value.trim(); |
|
} |
|
|
|
startMeeting(); |
|
} |
|
|
|
async function startMeeting() { |
|
try { |
|
|
|
localStream = await navigator.mediaDevices.getUserMedia({ |
|
audio: true, |
|
video: true |
|
}); |
|
|
|
|
|
homePage.classList.add('hidden'); |
|
meetingPage.classList.remove('hidden'); |
|
|
|
|
|
meetingIdDisplay.textContent = `ID: ${meetingId}`; |
|
|
|
|
|
addVideoStream(localStream, userName, true); |
|
|
|
|
|
setTimeout(() => { |
|
simulateParticipants(); |
|
}, 2000); |
|
|
|
|
|
updateShareLink(); |
|
|
|
} catch (error) { |
|
console.error("Erreur d'accès aux médias:", error); |
|
alert("Impossible d'accéder à la caméra ou au microphone. Veuillez vérifier vos permissions."); |
|
} |
|
} |
|
|
|
function addVideoStream(stream, name, isLocal = false) { |
|
const participantId = `participant-${Date.now()}`; |
|
const participant = { |
|
id: participantId, |
|
name: name, |
|
stream: stream, |
|
isLocal: isLocal |
|
}; |
|
|
|
participants.push(participant); |
|
updateParticipantCount(); |
|
|
|
const videoElement = document.createElement('video'); |
|
videoElement.id = participantId; |
|
videoElement.srcObject = stream; |
|
videoElement.autoplay = true; |
|
videoElement.playsInline = true; |
|
if (isLocal) { |
|
videoElement.muted = true; |
|
videoElement.classList.add('local-video'); |
|
} |
|
|
|
const participantDiv = document.createElement('div'); |
|
participantDiv.className = 'participant'; |
|
participantDiv.appendChild(videoElement); |
|
|
|
const nameDiv = document.createElement('div'); |
|
nameDiv.className = 'participant-name'; |
|
nameDiv.textContent = name + (isLocal ? ' (Vous)' : ''); |
|
participantDiv.appendChild(nameDiv); |
|
|
|
videoContainer.appendChild(participantDiv); |
|
} |
|
|
|
function toggleMic() { |
|
if (localStream) { |
|
const audioTracks = localStream.getAudioTracks(); |
|
audioTracks.forEach(track => { |
|
track.enabled = !track.enabled; |
|
}); |
|
|
|
isMicOn = !isMicOn; |
|
micBtn.classList.toggle('bg-red-600', !isMicOn); |
|
micBtn.classList.toggle('bg-gray-700', isMicOn); |
|
} |
|
} |
|
|
|
function toggleCamera() { |
|
if (localStream) { |
|
const videoTracks = localStream.getVideoTracks(); |
|
videoTracks.forEach(track => { |
|
track.enabled = !track.enabled; |
|
}); |
|
|
|
isCameraOn = !isCameraOn; |
|
cameraBtn.classList.toggle('bg-red-600', !isCameraOn); |
|
cameraBtn.classList.toggle('bg-gray-700', isCameraOn); |
|
} |
|
} |
|
|
|
function toggleScreenShare() { |
|
|
|
alert("Fonctionnalité de partage d'écran simulée. En production, vous utiliseriez l'API getDisplayMedia."); |
|
} |
|
|
|
function leaveMeeting() { |
|
|
|
if (localStream) { |
|
localStream.getTracks().forEach(track => track.stop()); |
|
} |
|
|
|
|
|
meetingPage.classList.add('hidden'); |
|
homePage.classList.remove('hidden'); |
|
|
|
|
|
videoContainer.innerHTML = ''; |
|
participants = []; |
|
} |
|
|
|
function showShareModal() { |
|
shareModal.classList.remove('hidden'); |
|
} |
|
|
|
function hideShareModal() { |
|
shareModal.classList.add('hidden'); |
|
} |
|
|
|
function updateShareLink() { |
|
const currentUrl = window.location.href.split('?')[0]; |
|
shareLink.value = `${currentUrl}?room=${meetingId}`; |
|
} |
|
|
|
function copyShareLink() { |
|
shareLink.select(); |
|
document.execCommand('copy'); |
|
alert('Lien copié dans le presse-papiers !'); |
|
} |
|
|
|
function updateParticipantCount() { |
|
participantCount.textContent = participants.length; |
|
} |
|
|
|
function toggleChat() { |
|
isChatOpen = !isChatOpen; |
|
chatPanel.classList.toggle('hidden', !isChatOpen); |
|
} |
|
|
|
function sendMessage() { |
|
const message = chatInput.value.trim(); |
|
if (message && userName) { |
|
const messageDiv = document.createElement('div'); |
|
messageDiv.className = 'message mb-2 p-2 bg-gray-700 rounded'; |
|
messageDiv.innerHTML = `<strong>${userName}:</strong> ${message}`; |
|
chatMessages.appendChild(messageDiv); |
|
chatInput.value = ''; |
|
|
|
|
|
chatMessages.scrollTop = chatMessages.scrollHeight; |
|
|
|
|
|
if (participants.length > 1) { |
|
setTimeout(() => { |
|
const replyDiv = document.createElement('div'); |
|
replyDiv.className = 'message mb-2 p-2 bg-blue-900 rounded'; |
|
replyDiv.innerHTML = `<strong>Participant:</strong> Merci pour votre message!`; |
|
chatMessages.appendChild(replyDiv); |
|
chatMessages.scrollTop = chatMessages.scrollHeight; |
|
}, 1000); |
|
} |
|
} |
|
} |
|
|
|
|
|
function simulateParticipants() { |
|
if (participants.length < 4) { |
|
const names = ["Alex", "Sophie", "Thomas", "Emma", "Lucas", "Camille"]; |
|
const randomName = names[Math.floor(Math.random() * names.length)]; |
|
|
|
|
|
navigator.mediaDevices.getUserMedia({ video: true, audio: true }) |
|
.then(stream => { |
|
addVideoStream(stream, randomName); |
|
}) |
|
.catch(() => { |
|
|
|
const blackVideo = document.createElement('video'); |
|
blackVideo.autoplay = true; |
|
blackVideo.playsInline = true; |
|
addVideoStream(blackVideo.captureStream(), randomName); |
|
}); |
|
|
|
|
|
if (participants.length < 3) { |
|
setTimeout(simulateParticipants, 3000); |
|
} |
|
} |
|
} |
|
|
|
|
|
window.addEventListener('DOMContentLoaded', () => { |
|
const urlParams = new URLSearchParams(window.location.search); |
|
const roomParam = urlParams.get('room'); |
|
|
|
if (roomParam) { |
|
roomIdInput.value = roomParam; |
|
} |
|
}); |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Adamchab/zoom" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |