Cesar Mendivil f57ce90c11 feat: Enhance StudioControls with presentation and layout features
- 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.
2025-11-07 14:29:14 -07:00

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