Files
nucleoWhisper/nuxt4/app/composables/useStreams.ts
josedario87 d780fd962f Agregar reproductor de streams con seleccion de tipo
- Nuevo composable useStreams.ts para gestionar streams de go2rtc
- Componente StreamPlayer.vue para reproduccion (iframe/video/img)
- Componente StreamViewer.vue con dropdowns de seleccion
- Integrado en app.vue despues del card de grabacion
- Soporta WebRTC, MSE, MP4, HLS y MJPEG
2025-12-30 02:40:51 -06:00

167 lines
3.9 KiB
TypeScript

/**
* Composable para gestionar streams de video desde go2rtc
* API: https://streams.nucleoriofrio.com/api/streams
*/
export type StreamType = 'webrtc' | 'mse' | 'mp4' | 'hls' | 'mjpeg'
export interface StreamTypeOption {
label: string
value: StreamType
description: string
useIframe: boolean
}
export const STREAM_TYPES: StreamTypeOption[] = [
{
label: 'WebRTC',
value: 'webrtc',
description: 'Menor latencia',
useIframe: true
},
{
label: 'MSE',
value: 'mse',
description: 'Media Source Extensions',
useIframe: true
},
{
label: 'MP4',
value: 'mp4',
description: 'Streaming MP4',
useIframe: false
},
{
label: 'HLS',
value: 'hls',
description: 'HTTP Live Streaming',
useIframe: false
},
{
label: 'MJPEG',
value: 'mjpeg',
description: 'Motion JPEG',
useIframe: false
}
]
export const useStreams = () => {
const BASE_URL = 'https://streams.nucleoriofrio.com'
// Estado reactivo
const streams = useState<string[]>('streams_list', () => [])
const selectedStream = useState<string | null>('streams_selected', () => null)
const selectedType = useState<StreamType>('streams_type', () => 'mse')
const isLoading = useState<boolean>('streams_loading', () => false)
const error = useState<string | null>('streams_error', () => null)
/**
* Obtiene la lista de streams disponibles desde la API
*/
const fetchStreams = async (): Promise<void> => {
isLoading.value = true
error.value = null
try {
const response = await $fetch<Record<string, unknown>>(`${BASE_URL}/api/streams`, {
credentials: 'include'
})
// Extraer nombres de streams del objeto
streams.value = Object.keys(response).sort()
// Seleccionar el primero si no hay ninguno seleccionado
if (streams.value.length > 0 && !selectedStream.value) {
selectedStream.value = streams.value[0]
}
} catch (err: unknown) {
const errorMessage = (err as Error)?.message || 'Error al cargar streams'
error.value = errorMessage
console.error('[Streams] Error fetching streams:', err)
} finally {
isLoading.value = false
}
}
/**
* Genera la URL del stream segun el tipo seleccionado
*/
const getStreamUrl = computed((): string | null => {
if (!selectedStream.value) return null
const streamName = encodeURIComponent(selectedStream.value)
switch (selectedType.value) {
case 'webrtc':
return `${BASE_URL}/stream.html?src=${streamName}`
case 'mse':
return `${BASE_URL}/stream.html?src=${streamName}&mode=mse`
case 'mp4':
return `${BASE_URL}/api/stream.mp4?src=${streamName}`
case 'hls':
return `${BASE_URL}/api/stream.m3u8?src=${streamName}`
case 'mjpeg':
return `${BASE_URL}/api/stream.mjpeg?src=${streamName}`
default:
return null
}
})
/**
* Determina si se debe usar iframe o video nativo
*/
const useIframe = computed((): boolean => {
const typeConfig = STREAM_TYPES.find(t => t.value === selectedType.value)
return typeConfig?.useIframe ?? true
})
/**
* Obtiene las opciones para el dropdown de tipos
*/
const streamTypeOptions = computed(() => {
return STREAM_TYPES.map(type => ({
label: type.label,
value: type.value
}))
})
/**
* Obtiene las opciones para el dropdown de streams
*/
const streamOptions = computed(() => {
return streams.value.map(name => ({
label: name.replace(/_/g, ' '),
value: name
}))
})
/**
* Limpia el error
*/
const clearError = () => {
error.value = null
}
return {
// Estado
streams: readonly(streams),
selectedStream,
selectedType,
isLoading: readonly(isLoading),
error: readonly(error),
// Computed
getStreamUrl,
useIframe,
streamTypeOptions,
streamOptions,
// Metodos
fetchStreams,
clearError,
// Constantes
STREAM_TYPES
}
}