# Création d'un module de visioconférence intégrable pour site web import os import json # Structure simplifiée pour intégration web web_integration = { "videoconf-widget/": { "videoconf-widget.js": """// Module de visioconférence intégrable (function() { 'use strict'; class VideoConfWidget { constructor(config) { this.config = { containerId: config.containerId || 'videoconf-container', serverUrl: config.serverUrl || 'https://your-server.com', maxParticipants: config.maxParticipants || 100, enableRecording: config.enableRecording !== false, enableScreenShare: config.enableScreenShare !== false, theme: config.theme || 'dark', ...config }; this.socket = null; this.localStream = null; this.peers = new Map(); this.roomId = null; this.userId = this.generateId(); this.isInitialized = false; } // Initialiser le widget async init() { if (this.isInitialized) return; // Créer l'interface this.createUI(); // Connecter au serveur await this.connectToServer(); this.isInitialized = true; } createUI() { const container = document.getElementById(this.config.containerId); if (!container) { console.error('Container not found:', this.config.containerId); return; } container.innerHTML = `
`; // Injecter les styles si pas déjà présents if (!document.getElementById('vc-styles')) { const style = document.createElement('style'); style.id = 'vc-styles'; style.textContent = this.getStyles(); document.head.appendChild(style); } } getStyles() { return ` .vc-widget { position: fixed; bottom: 20px; right: 20px; width: 400px; height: 600px; background: #1a1a1a; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); display: flex; flex-direction: column; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; z-index: 9999; transition: all 0.3s ease; } .vc-widget.fullscreen { width: 100vw; height: 100vh; bottom: 0; right: 0; border-radius: 0; } .vc-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid #2a2a2a; } .vc-title { margin: 0; color: #fff; font-size: 1.2rem; } .vc-close { background: none; border: none; color: #fff; font-size: 1.5rem; cursor: pointer; padding: 0; width: 30px; height: 30px; } .vc-content { flex: 1; overflow: hidden; position: relative; } .vc-screen { display: none; width: 100%; height: 100%; padding: 1rem; } .vc-screen.active { display: flex; flex-direction: column; } .vc-join-screen { justify-content: center; align-items: center; } .vc-input { width: 100%; max-width: 300px; padding: 0.75rem; margin-bottom: 1rem; background: #2a2a2a; border: 1px solid #3a3a3a; border-radius: 8px; color: #fff; font-size: 1rem; } .vc-btn { width: 100%; max-width: 300px; padding: 0.75rem; margin-bottom: 0.5rem; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer; transition: all 0.3s; } .vc-btn-primary { background: #4a90e2; color: white; } .vc-btn-primary:hover { background: #357abd; } .vc-btn-secondary { background: transparent; color: #4a90e2; border: 1px solid #4a90e2; } .vc-meeting-screen { padding: 0; } .vc-video-grid { flex: 1; display: grid; gap: 10px; padding: 10px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); overflow-y: auto; } .vc-video-container { position: relative; background: #0a0a0a; border-radius: 8px; overflow: hidden; aspect-ratio: 16/9; } .vc-video-container video { width: 100%; height: 100%; object-fit: cover; } .vc-video-container .vc-name-tag { position: absolute; bottom: 5px; left: 5px; background: rgba(0,0,0,0.7); padding: 2px 8px; border-radius: 4px; font-size: 12px; color: #fff; } .vc-controls { display: flex; justify-content: center; gap: 10px; padding: 15px; background: #1a1a1a; border-top: 1px solid #2a2a2a; } .vc-control-btn { width: 45px; height: 45px; border-radius: 50%; border: none; background: #2a2a2a; color: #fff; cursor: pointer; font-size: 20px; transition: all 0.3s; } .vc-control-btn:hover { background: #3a3a3a; } .vc-control-btn.active { background: #4a90e2; } .vc-control-btn.vc-leave { background: #e74c3c; } /* Mode responsive */ @media (max-width: 768px) { .vc-widget { width: 100vw; height: 100vh; bottom: 0; right: 0; border-radius: 0; } } `; } async connectToServer() { return new Promise((resolve) => { const script = document.createElement('script'); script.src = this.config.serverUrl + '/socket.io/socket.io.js'; script.onload = () => { this.socket = io(this.config.serverUrl); this.setupSocketListeners(); resolve(); }; document.head.appendChild(script); }); } setupSocketListeners() { this.socket.on('user-connected', (userId, userName) => { this.createPeerConnection(userId, true); }); this.socket.on('offer', async (offer, fromUserId) => { const pc = await this.createPeerConnection(fromUserId, false); await pc.setRemoteDescription(offer); const answer = await pc.createAnswer(); await pc.setLocalDescription(answer); this.socket.emit('answer', answer, fromUserId, this.roomId); }); this.socket.on('answer', async (answer, fromUserId) => { const pc = this.peers.get(fromUserId); if (pc) { await pc.setRemoteDescription(answer); } }); this.socket.on('ice-candidate', async (candidate, fromUserId) => { const pc = this.peers.get(fromUserId); if (pc) { await pc.addIceCandidate(candidate); } }); this.socket.on('user-disconnected', (userId) => { this.removeVideoElement(userId); const pc = this.peers.get(userId); if (pc) { pc.close(); this.peers.delete(userId); } }); } async createPeerConnection(userId, createOffer) { const pc = new RTCPeerConnection({ iceServers: [ { urls: 'stun:stun.l.google.com:19302' } ] }); this.peers.set(userId, pc); // Ajouter les tracks locaux if (this.localStream) { this.localStream.getTracks().forEach(track => { pc.addTrack(track, this.localStream); }); } pc.onicecandidate = (event) => { if (event.candidate) { this.socket.emit('ice-candidate', event.candidate, userId, this.roomId); } }; pc.ontrack = (event) => { this.addVideoElement(userId, event.streams[0], 'Participant'); }; if (createOffer) { const offer = await pc.createOffer(); await pc.setLocalDescription(offer); this.socket.emit('offer', offer, userId, this.roomId); } return pc; } async joinRoom() { const username = document.getElementById('vc-username').value; const roomId = document.getElementById('vc-roomid').value; if (!username || !roomId) { alert('Veuillez remplir tous les champs'); return; } this.roomId = roomId; try { // Obtenir l'accès à la caméra et au micro this.localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); // Afficher l'écran de réunion document.querySelector('.vc-join-screen').classList.remove('active'); document.querySelector('.vc-meeting-screen').classList.add('active'); // Ajouter la vidéo locale this.addVideoElement(this.userId, this.localStream, username + ' (Vous)', true); // Rejoindre la salle this.socket.emit('join-room', roomId, this.userId, username); } catch (error) { console.error('Erreur:', error); alert('Impossible d\'accéder à la caméra/micro'); } } createRoom() { const roomId = 'room-' + Math.random().toString(36).substr(2, 9); document.getElementById('vc-roomid').value = roomId; // Copier dans le presse-papier navigator.clipboard.writeText(roomId).then(() => { alert('Code de réunion copié: ' + roomId); }); } addVideoElement(userId, stream, name, isLocal = false) { const container = document.createElement('div'); container.className = 'vc-video-container'; container.id = `vc-video-${userId}`; const video = document.createElement('video'); video.srcObject = stream; video.autoplay = true; video.playsInline = true; if (isLocal) video.muted = true; const nameTag = document.createElement('div'); nameTag.className = 'vc-name-tag'; nameTag.textContent = name; container.appendChild(video); container.appendChild(nameTag); document.getElementById('vc-video-grid').appendChild(container); } removeVideoElement(userId) { const element = document.getElementById(`vc-video-${userId}`); if (element) { element.remove(); } } toggleAudio() { if (this.localStream) { const audioTrack = this.localStream.getAudioTracks()[0]; audioTrack.enabled = !audioTrack.enabled; const btn = document.getElementById('vc-toggle-audio'); btn.classList.toggle('active', audioTrack.enabled); btn.textContent = audioTrack.enabled ? '🎤' : '🔇'; } } toggleVideo() { if (this.localStream) { const videoTrack = this.localStream.getVideoTracks()[0]; videoTrack.enabled = !videoTrack.enabled; const btn = document.getElementById('vc-toggle-video'); btn.classList.toggle('active', videoTrack.enabled); btn.textContent = videoTrack.enabled ? '📹' : '📷'; } } async toggleScreenShare() { // Implémenter le partage d'écran try { const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true }); const videoTrack = screenStream.getVideoTracks()[0]; const sender = this.peers.values().next().value?.getSenders() .find(s => s.track?.kind === 'video'); if (sender) { sender.replaceTrack(videoTrack); } videoTrack.onended = () => { this.stopScreenShare(); }; document.getElementById('vc-share-screen').classList.add('active'); } catch (error) { console.error('Erreur partage écran:', error); } } stopScreenShare() { const videoTrack = this.localStream.getVideoTracks()[0]; this.peers.forEach(pc => { const sender = pc.getSenders().find(s => s.track?.kind === 'video'); if (sender) { sender.replaceTrack(videoTrack); } }); document.getElementById('vc-share-screen').classList.remove('active'); } toggleRecording() { // Implémenter l'enregistrement const btn = document.getElementById('vc-record'); btn.classList.toggle('active'); btn.textContent = btn.classList.contains('active') ? '⏹️' : '⏺️'; } leaveRoom() { // Nettoyer les connexions this.peers.forEach(pc => pc.close()); this.peers.clear(); if (this.localStream) { this.localStream.getTracks().forEach(track => track.stop()); } // Retour à l'écran de connexion document.getElementById('vc-video-grid').innerHTML = ''; document.querySelector('.vc-meeting-screen').classList.remove('active'); document.querySelector('.vc-join-screen').classList.add('active'); // Déconnecter du serveur if (this.socket) { this.socket.disconnect(); } } close() { this.leaveRoom(); const container = document.getElementById(this.config.containerId); if (container) { container.innerHTML = ''; } } generateId() { return 'user-' + Math.random().toString(36).substr(2, 9); } } // Exposer globalement window.VideoConfWidget = VideoConfWidget; })();""", "integration-example.html": """Bienvenue sur votre site web avec visioconférence intégrée ! Cette page démontre comment intégrer facilement la fonctionnalité de visioconférence.
La visioconférence est intégrée directement dans votre site web. Vos utilisateurs peuvent :
Ajoutez simplement ces lignes à votre site :
<div id="videoconf-container"></div>
<script src="videoconf-widget.js"></script>
<script>
const videoConf = new VideoConfWidget({
containerId: 'videoconf-container',
serverUrl: 'https://your-server.com'
});
</script>