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
This commit is contained in:
166
nuxt4/app/composables/useStreams.ts
Normal file
166
nuxt4/app/composables/useStreams.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user