diff --git a/nuxt4/app/app.vue b/nuxt4/app/app.vue index 526b86f..d3ae095 100644 --- a/nuxt4/app/app.vue +++ b/nuxt4/app/app.vue @@ -84,6 +84,9 @@ + + + diff --git a/nuxt4/app/components/streams/StreamPlayer.vue b/nuxt4/app/components/streams/StreamPlayer.vue new file mode 100644 index 0000000..bfba601 --- /dev/null +++ b/nuxt4/app/components/streams/StreamPlayer.vue @@ -0,0 +1,98 @@ + + + + + + + Cargando stream... + + + + + + + + Selecciona un stream para visualizar + + + + + + + + + + + + + + + + + diff --git a/nuxt4/app/components/streams/StreamViewer.vue b/nuxt4/app/components/streams/StreamViewer.vue new file mode 100644 index 0000000..e5ea396 --- /dev/null +++ b/nuxt4/app/components/streams/StreamViewer.vue @@ -0,0 +1,139 @@ + + + + + + + Streams de Video + + + Actualizar + + + + + + + + + {{ error }} + + Cerrar + + + + + + + + + + Stream + + + + + + + + Tipo de reproduccion + + + + + + + + + + + + + + {{ selectedStream }} + + + + {{ currentTypeDescription }} + + + + + + + diff --git a/nuxt4/app/composables/useStreams.ts b/nuxt4/app/composables/useStreams.ts new file mode 100644 index 0000000..1f818f2 --- /dev/null +++ b/nuxt4/app/composables/useStreams.ts @@ -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('streams_list', () => []) + const selectedStream = useState('streams_selected', () => null) + const selectedType = useState('streams_type', () => 'mse') + const isLoading = useState('streams_loading', () => false) + const error = useState('streams_error', () => null) + + /** + * Obtiene la lista de streams disponibles desde la API + */ + const fetchStreams = async (): Promise => { + isLoading.value = true + error.value = null + + try { + const response = await $fetch>(`${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 + } +}
Cargando stream...
Selecciona un stream para visualizar