se inician chanels para recibir y enviar chunks desde el front
This commit is contained in:
		
							
								
								
									
										17
									
								
								stt_recorder/lib/stt_recorder_web/channels/control_chanel.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								stt_recorder/lib/stt_recorder_web/channels/control_chanel.ex
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| defmodule SttRecorderWeb.Channels.ControlChanel do | ||||
|   use Phoenix.Channel | ||||
|  | ||||
|   def join("control:lobby", _payload, socket) do | ||||
|     {:ok, socket} | ||||
|   end | ||||
|  | ||||
|   def handle_in("set_parameter", %{"parameter" => param, "value" => value}, socket) do | ||||
|     GenServer.cast(SttServer.Transcriber, {:set_param, param, value}) | ||||
|     {:reply, {:ok, %{message: "Parameter updated"}}, socket} | ||||
|   end | ||||
|  | ||||
|   def handle_in("call_method", %{"method" => method}, socket) do | ||||
|     GenServer.cast(SttServer.Transcriber, {:call_method, method}) | ||||
|     {:reply, {:ok, %{message: "Method called"}}, socket} | ||||
|   end | ||||
| end | ||||
							
								
								
									
										26
									
								
								stt_recorder/lib/stt_recorder_web/channels/data_chanel.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								stt_recorder/lib/stt_recorder_web/channels/data_chanel.ex
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| defmodule SttServerWeb.DataChannel do | ||||
|   use Phoenix.Channel | ||||
|  | ||||
|   def join("data:lobby", _payload, socket) do | ||||
|     IO.puts("🟢 Cliente conectado al canal de datos") | ||||
|     {:ok, socket} | ||||
|   end | ||||
|  | ||||
|   # Recibe audio codificado en base64 (para transporte seguro) | ||||
|   def handle_in("audio_chunk", %{"data" => base64_chunk, "sample_rate" => sample_rate}, socket) do | ||||
|     case Base.decode64(base64_chunk) do | ||||
|       {:ok, binary_audio} -> | ||||
|         # Enviamos al transcriptor | ||||
|         GenServer.cast(SttServer.Transcriber, {:audio_chunk, binary_audio, sample_rate}) | ||||
|         {:noreply, socket} | ||||
|  | ||||
|       :error -> | ||||
|         IO.puts("⚠️ Error al decodificar base64") | ||||
|         {:reply, {:error, %{reason: "Invalid base64 audio"}}, socket} | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def handle_in(_unknown, _payload, socket) do | ||||
|     {:noreply, socket} | ||||
|   end | ||||
| end | ||||
							
								
								
									
										14
									
								
								stt_recorder/lib/stt_recorder_web/channels/user_socket.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								stt_recorder/lib/stt_recorder_web/channels/user_socket.ex
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| defmodule  SttRecorderWeb.Channels.UserSocket do | ||||
|   use Phoenix.Socket | ||||
|  | ||||
|   ## Channels | ||||
|   channel "control:*", SttRecorderWeb.Channels.ControlChanel | ||||
|   channel "data:*", SttServerWeb.DataChannel | ||||
|  | ||||
|  | ||||
|   @impl true | ||||
|   def connect(_params, socket, _connect_info), do: {:ok, socket} | ||||
|  | ||||
|   @impl true | ||||
|   def id(_socket), do: nil | ||||
| end | ||||
| @ -15,6 +15,9 @@ defmodule SttRecorderWeb.Endpoint do | ||||
|     websocket: [connect_info: [session: @session_options]], | ||||
|     longpoll: [connect_info: [session: @session_options]] | ||||
|  | ||||
|   socket "/socket", SttRecorderWeb.Channels.UserSocket, | ||||
|     websocket: true, | ||||
|     longpoll: false | ||||
|   # Serve at "/" the static files from "priv/static" directory. | ||||
|   # | ||||
|   # You should set gzip to true if you are running phx.digest | ||||
|  | ||||
							
								
								
									
										115
									
								
								stt_recorder/lib/stt_recorder_web/live/stt/test_with_channel.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								stt_recorder/lib/stt_recorder_web/live/stt/test_with_channel.ex
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | ||||
| defmodule SttRecorderWeb.Stt.TestWithChannel do | ||||
|    use SttRecorderWeb, :live_view | ||||
|  | ||||
|   def mount(_params, _session, socket) do | ||||
|     {:ok, socket} | ||||
|   end | ||||
|  | ||||
|   def render(assigns) do | ||||
|     ~H""" | ||||
|     <div id="container"> | ||||
|       <div id="status">Presioná "Start Recording"…</div> | ||||
|       <button id="startButton">Start Recording</button> | ||||
|       <button id="stopButton" disabled>Stop Recording</button> | ||||
|  | ||||
|       <div id="transcriptionContainer"> | ||||
|         <div id="transcription" class="realtime"></div> | ||||
|       </div> | ||||
|  | ||||
|       <div id="fullTextContainer"> | ||||
|         <div id="fullText"></div> | ||||
|       </div> | ||||
|  | ||||
|       <script type="module"> | ||||
|         import { Socket } from "https://cdn.skypack.dev/phoenix"; | ||||
|  | ||||
|         const statusDiv = document.getElementById("status"); | ||||
|         const transcriptionDiv = document.getElementById("transcription"); | ||||
|         const fullTextDiv = document.getElementById("fullText"); | ||||
|         const startButton = document.getElementById("startButton"); | ||||
|         const stopButton = document.getElementById("stopButton"); | ||||
|  | ||||
|         let socket, channel; | ||||
|         let audioContext, mediaStream, mediaProcessor; | ||||
|  | ||||
|         async function startRecording() { | ||||
|           startButton.disabled = true; | ||||
|           stopButton.disabled = false; | ||||
|           statusDiv.textContent = "Recording…"; | ||||
|           transcriptionDiv.textContent = ""; | ||||
|           fullTextDiv.textContent = ""; | ||||
|  | ||||
|           socket = new Socket("ws://localhost:4000/socket"); | ||||
|           socket.connect(); | ||||
|  | ||||
|           channel = socket.channel("data:lobby"); | ||||
|           channel.join() | ||||
|             .receive("ok", () => { | ||||
|               statusDiv.textContent = "🎙 Conectado a Phoenix STT"; | ||||
|               console.log("Canal conectado"); | ||||
|             }) | ||||
|             .receive("error", () => { | ||||
|               statusDiv.textContent = "❌ Error al conectar"; | ||||
|               console.error("Error al conectar canal"); | ||||
|             }); | ||||
|  | ||||
|           channel.on("realtime", payload => { | ||||
|             const words = payload.text.split(" "); | ||||
|             const lastWord = words.pop(); | ||||
|             transcriptionDiv.innerHTML = `${words.join(" ")} <span class="last-word">${lastWord}</span>`; | ||||
|           }); | ||||
|  | ||||
|           channel.on("fullSentence", payload => { | ||||
|             fullTextDiv.innerHTML += payload.text + " "; | ||||
|             transcriptionDiv.innerHTML = ""; | ||||
|           }); | ||||
|  | ||||
|           audioContext = new AudioContext(); | ||||
|           mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true }); | ||||
|           const input = audioContext.createMediaStreamSource(mediaStream); | ||||
|  | ||||
|           mediaProcessor = audioContext.createScriptProcessor(1024, 1, 1); | ||||
|           mediaProcessor.onaudioprocess = (event) => { | ||||
|             const float32Array = event.inputBuffer.getChannelData(0); | ||||
|             const int16Array = new Int16Array(float32Array.length); | ||||
|             for (let i = 0; i < float32Array.length; i++) { | ||||
|               int16Array[i] = Math.max(-1, Math.min(1, float32Array[i])) * 0x7FFF; | ||||
|             } | ||||
|  | ||||
|             const base64Audio = btoa(String.fromCharCode(...new Uint8Array(int16Array.buffer))); | ||||
|             channel.push("audio_chunk", { | ||||
|               data: base64Audio, | ||||
|               sample_rate: audioContext.sampleRate | ||||
|             }); | ||||
|           }; | ||||
|  | ||||
|           input.connect(mediaProcessor); | ||||
|           mediaProcessor.connect(audioContext.destination); | ||||
|         } | ||||
|  | ||||
|         function stopRecording() { | ||||
|           stopButton.disabled = true; | ||||
|           startButton.disabled = false; | ||||
|           statusDiv.textContent = "🛑 Grabación detenida."; | ||||
|  | ||||
|           if (mediaProcessor) mediaProcessor.disconnect(); | ||||
|           if (audioContext) audioContext.close(); | ||||
|           if (mediaStream) mediaStream.getTracks().forEach(track => track.stop()); | ||||
|           if (channel) channel.leave(); | ||||
|           if (socket) socket.disconnect(); | ||||
|         } | ||||
|  | ||||
|         document.getElementById("startButton").onclick = startRecording; | ||||
|         document.getElementById("stopButton").onclick = stopRecording; | ||||
|       </script> | ||||
|  | ||||
|       <style> | ||||
|         .last-word { | ||||
|           font-weight: bold; | ||||
|           color: orange; | ||||
|         } | ||||
|       </style> | ||||
|     </div> | ||||
|     """ | ||||
|   end | ||||
| end | ||||
| @ -18,7 +18,7 @@ defmodule SttRecorderWeb.Router do | ||||
|     pipe_through :browser | ||||
|  | ||||
|     get "/", PageController, :home | ||||
|     live "/sttrecorder", Stt.SttLive | ||||
|     live "/testchannel", Stt.TestWithChannel | ||||
|     live "/test", Stt.TestRecorder | ||||
|  | ||||
|   end | ||||
|  | ||||
		Reference in New Issue
	
	Block a user