- Added props for handling presentation and screen sharing actions. - Implemented buttons for opening presentation panel and changing layouts (grid/focus) and modes (video/audio). - Updated UI to reflect active presentations and added functionality to clear presentations. fix: Adjust StudioLeftSidebar and StudioRightPanel for full height - Modified styles to ensure both sidebars occupy full height of the container. feat: Introduce ParticipantsPanel for managing participants in the conference - Created ParticipantsPanel component to display connected and invited participants. - Integrated API calls to fetch invited participants and handle connection status. feat: Implement PresentationPanel for sharing presentations - Developed PresentationPanel component to handle file uploads and screen sharing. refactor: Update StudioVideoArea to support layout and mode changes - Refactored StudioVideoArea to accept layout and mode props, rendering appropriate conference views. feat: Add AudioConference and VideoConference prefabs for audio and video handling - Created AudioConference and VideoConference components to manage respective media streams. feat: Introduce Chat component for real-time messaging - Developed Chat component to facilitate messaging between participants. feat: Implement ControlBar for user controls in the conference - Created ControlBar component for managing participant actions like leaving the conference and toggling audio/video. feat: Add PreJoin component for pre-conference setup - Developed PreJoin component to allow users to preview video before joining the conference. chore: Update Vite configuration for better module resolution - Enhanced Vite config to include path aliases for easier imports across the project. chore: Add TypeScript definitions for environment variables - Created env.d.ts to define types for environment variables used in the project.
218 lines
7.9 KiB
TypeScript
218 lines
7.9 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
|
import { useLocalParticipant, useTracks } from '@livekit/components-react'
|
|
import { Track } from 'livekit-client'
|
|
import {
|
|
MdMic,
|
|
MdMicOff,
|
|
MdVideocam,
|
|
MdVideocamOff,
|
|
MdScreenShare,
|
|
MdStopScreenShare,
|
|
MdSettings,
|
|
MdPeople,
|
|
MdViewComfy,
|
|
MdFiberManualRecord,
|
|
MdStop,
|
|
} from 'react-icons/md'
|
|
|
|
interface Props {
|
|
onOpenPresentation?: () => void
|
|
onShareScreen?: () => void
|
|
sharedPresentation?: { type: 'screen' | 'file', url: string } | null
|
|
onClearPresentation?: () => void
|
|
layout?: 'grid' | 'focus'
|
|
mode?: 'video' | 'audio'
|
|
onChangeLayout?: (layout: 'grid'|'focus') => void
|
|
onChangeMode?: (mode: 'video'|'audio') => void
|
|
}
|
|
|
|
const StudioControls: React.FC<Props> = ({ onOpenPresentation, onShareScreen, sharedPresentation, onClearPresentation, layout, mode, onChangeLayout, onChangeMode }) => {
|
|
const { localParticipant } = useLocalParticipant()
|
|
const [micEnabled, setMicEnabled] = useState(true)
|
|
const [cameraEnabled, setCameraEnabled] = useState(true)
|
|
const [isScreenSharing, setIsScreenSharing] = useState(false)
|
|
const [isRecording, setIsRecording] = useState(false)
|
|
|
|
// Sincronizar estado con LiveKit
|
|
useEffect(() => {
|
|
if (localParticipant) {
|
|
setMicEnabled(localParticipant.isMicrophoneEnabled)
|
|
setCameraEnabled(localParticipant.isCameraEnabled)
|
|
setIsScreenSharing(localParticipant.isScreenShareEnabled)
|
|
}
|
|
}, [localParticipant])
|
|
|
|
const toggleMicrophone = async () => {
|
|
if (localParticipant) {
|
|
const enabled = !micEnabled
|
|
await localParticipant.setMicrophoneEnabled(enabled)
|
|
setMicEnabled(enabled)
|
|
}
|
|
}
|
|
|
|
const toggleCamera = async () => {
|
|
if (localParticipant) {
|
|
const enabled = !cameraEnabled
|
|
await localParticipant.setCameraEnabled(enabled)
|
|
setCameraEnabled(enabled)
|
|
}
|
|
}
|
|
|
|
const toggleScreenShare = async () => {
|
|
if (localParticipant) {
|
|
const enabled = !isScreenSharing
|
|
await localParticipant.setScreenShareEnabled(enabled)
|
|
setIsScreenSharing(enabled)
|
|
}
|
|
}
|
|
|
|
const toggleRecording = () => {
|
|
// TODO: Implementar grabación con LiveKit
|
|
setIsRecording(!isRecording)
|
|
console.log('Recording:', !isRecording)
|
|
}
|
|
|
|
return (
|
|
<div className="studio-controls bg-gray-800 border-t border-gray-700 px-6 py-4">
|
|
<div className="flex items-center justify-between max-w-7xl mx-auto">
|
|
{/* Izquierda - Info de sala */}
|
|
<div className="flex items-center space-x-4">
|
|
<span className="text-sm text-gray-300 font-medium">
|
|
Transmisión en vivo
|
|
</span>
|
|
{isRecording && (
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-3 h-3 bg-red-500 rounded-full animate-pulse"></div>
|
|
<span className="text-sm text-red-400">Grabando</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Centro - Controles principales */}
|
|
<div className="flex items-center space-x-2">
|
|
{/* Micrófono */}
|
|
<button
|
|
onClick={toggleMicrophone}
|
|
className={`control-button ${
|
|
!micEnabled ? 'bg-red-600 hover:bg-red-700' : 'bg-gray-700 hover:bg-gray-600'
|
|
} p-3 rounded-lg transition-colors`}
|
|
title={micEnabled ? 'Desactivar micrófono' : 'Activar micrófono'}
|
|
>
|
|
{micEnabled ? <MdMic size={24} /> : <MdMicOff size={24} />}
|
|
</button>
|
|
|
|
{/* Cámara */}
|
|
<button
|
|
onClick={toggleCamera}
|
|
className={`control-button ${
|
|
!cameraEnabled ? 'bg-red-600 hover:bg-red-700' : 'bg-gray-700 hover:bg-gray-600'
|
|
} p-3 rounded-lg transition-colors`}
|
|
title={cameraEnabled ? 'Desactivar cámara' : 'Activar cámara'}
|
|
>
|
|
{cameraEnabled ? <MdVideocam size={24} /> : <MdVideocamOff size={24} />}
|
|
</button>
|
|
|
|
{/* Compartir pantalla */}
|
|
<button
|
|
onClick={() => {
|
|
// Preferimos delegar a un handler externo (que abrirá el panel o iniciará screen share)
|
|
if (onOpenPresentation) return onOpenPresentation()
|
|
if (onShareScreen) return onShareScreen()
|
|
toggleScreenShare()
|
|
}}
|
|
className={`control-button ${
|
|
isScreenSharing ? 'bg-blue-600 hover:bg-blue-700' : 'bg-gray-700 hover:bg-gray-600'
|
|
} p-3 rounded-lg transition-colors`}
|
|
title={isScreenSharing ? 'Dejar de compartir' : 'Compartir pantalla'}
|
|
>
|
|
{isScreenSharing ? <MdStopScreenShare size={24} /> : <MdScreenShare size={24} />}
|
|
</button>
|
|
|
|
{/* Botón rápido para abrir el panel de presentación (subir archivos) */}
|
|
<button
|
|
onClick={() => onOpenPresentation && onOpenPresentation()}
|
|
className="control-button bg-green-600 hover:bg-green-700 p-3 rounded-lg transition-colors"
|
|
title="Compartir presentación"
|
|
>
|
|
<MdPeople size={20} />
|
|
</button>
|
|
|
|
<div className="w-px h-8 bg-gray-600 mx-2"></div>
|
|
|
|
{/* Layouts */}
|
|
<button
|
|
className="control-button bg-gray-700 hover:bg-gray-600 p-3 rounded-lg transition-colors"
|
|
title="Cambiar diseño"
|
|
>
|
|
<MdViewComfy size={24} />
|
|
</button>
|
|
|
|
{/* Layout selector */}
|
|
<div className="ml-2 flex items-center gap-2">
|
|
<button
|
|
onClick={() => onChangeLayout && onChangeLayout('grid')}
|
|
className={`px-2 py-1 rounded ${layout === 'grid' ? 'bg-pink-600' : 'bg-gray-700'}`}
|
|
title="Grid layout"
|
|
>
|
|
Grid
|
|
</button>
|
|
<button
|
|
onClick={() => onChangeLayout && onChangeLayout('focus')}
|
|
className={`px-2 py-1 rounded ${layout === 'focus' ? 'bg-pink-600' : 'bg-gray-700'}`}
|
|
title="Focus layout"
|
|
>
|
|
Focus
|
|
</button>
|
|
|
|
<button onClick={() => onChangeMode && onChangeMode('video')} className={`px-2 py-1 rounded ${mode === 'video' ? 'bg-pink-600' : 'bg-gray-700'}`}>Video</button>
|
|
<button onClick={() => onChangeMode && onChangeMode('audio')} className={`px-2 py-1 rounded ${mode === 'audio' ? 'bg-pink-600' : 'bg-gray-700'}`}>Audio</button>
|
|
</div>
|
|
|
|
{/* Configuración */}
|
|
<button
|
|
className="control-button bg-gray-700 hover:bg-gray-600 p-3 rounded-lg transition-colors"
|
|
title="Configuración"
|
|
>
|
|
<MdSettings size={24} />
|
|
</button>
|
|
|
|
<div className="w-px h-8 bg-gray-600 mx-2"></div>
|
|
|
|
{/* Grabar */}
|
|
<button
|
|
onClick={toggleRecording}
|
|
className={`control-button ${
|
|
isRecording ? 'bg-red-600 hover:bg-red-700' : 'bg-gray-700 hover:bg-gray-600'
|
|
} p-3 rounded-lg transition-colors`}
|
|
title={isRecording ? 'Detener grabación' : 'Grabar'}
|
|
>
|
|
{isRecording ? <MdStop size={24} /> : <MdFiberManualRecord size={24} />}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Derecha - Presentación / Salir */}
|
|
<div>
|
|
{sharedPresentation ? (
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<span className="text-sm text-green-300">Presentación activa</span>
|
|
<button onClick={() => onClearPresentation && onClearPresentation()} className="px-3 py-1 bg-gray-700 text-white rounded">Detener</button>
|
|
</div>
|
|
) : null}
|
|
<button
|
|
className="px-6 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors"
|
|
onClick={() => {
|
|
if (confirm('¿Estás seguro de que quieres salir del estudio?')) {
|
|
window.location.href = '/'
|
|
}
|
|
}}
|
|
>
|
|
Salir
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default StudioControls
|