80 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
			
		
		
	
	
			80 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
defmodule WhisperWeb.AudioChannel do
 | 
						|
  @moduledoc """
 | 
						|
  Phoenix Channel que gestiona la recepción de audio en tiempo real desde el cliente.
 | 
						|
 | 
						|
  El audio se envía en chunks binarios y se acumula en un buffer temporal (`AudioBuffer`).
 | 
						|
  Al recibir el evento `"stop_audio"`, todos los chunks se combinan y se guardan como un único archivo WAV final.
 | 
						|
  """
 | 
						|
 | 
						|
  use Phoenix.Channel
 | 
						|
  require Logger
 | 
						|
  alias Phoenix.PubSub
 | 
						|
 | 
						|
  def join("audio:lobby", _payload, socket) do
 | 
						|
    ref = socket_id(socket)
 | 
						|
    Logger.info("Cliente conectado al canal audio:lobby")
 | 
						|
    {:ok, socket}
 | 
						|
  end
 | 
						|
 | 
						|
  @doc """
 | 
						|
  Maneja la entrada de un chunk de audio desde el cliente.
 | 
						|
 | 
						|
  El mensaje binario contiene un encabezado JSON (con la tasa de muestreo) seguido del audio binario.
 | 
						|
  Se almacena el chunk en `AudioBuffer` y también se guarda como archivo WAV parcial.
 | 
						|
  """
 | 
						|
  def handle_in("audio_chunk", {:binary, raw_binary}, socket) do
 | 
						|
    <<header_len::16, rest::binary>> = raw_binary
 | 
						|
    <<header::binary-size(header_len), audio::binary>> = rest
 | 
						|
 | 
						|
    %{"sample_rate" => rate} = Jason.decode!(header)
 | 
						|
    ref = socket_id(socket)
 | 
						|
 | 
						|
    Logger.info("Chunk recibido: #{byte_size(audio)} bytes, sample_rate: #{rate}")
 | 
						|
    AudioBuffer.append(ref, {rate, audio})
 | 
						|
 | 
						|
    # {:ok, path} = AudioSaver.save_chunk_as_wav(ref, audio, rate, "part")
 | 
						|
    # AudioFilesList.add_file(path)
 | 
						|
 | 
						|
 | 
						|
    {:noreply, socket}
 | 
						|
  end
 | 
						|
 | 
						|
 | 
						|
  @doc """
 | 
						|
  Recupera todos los chunks acumulados en el buffer, los concatena y guarda un archivo WAV final (sufijo `"final"`).
 | 
						|
  """
 | 
						|
  def handle_in("stop_audio", _payload, socket) do
 | 
						|
 | 
						|
    Logger.info("🛑 Grabación detenida por cliente")
 | 
						|
 | 
						|
    ref = socket_id(socket)
 | 
						|
    chunks = AudioBuffer.get_and_clear(ref)
 | 
						|
 | 
						|
    if chunks != [] do
 | 
						|
      [{rate, _} | _] = chunks
 | 
						|
      full_audio = Enum.map(chunks, fn {_, bin} -> bin end) |> IO.iodata_to_binary()
 | 
						|
      {:ok, path} = AudioSaver.save_chunk_as_wav(ref, full_audio, rate, "final")
 | 
						|
      
 | 
						|
      Task.start(fn ->
 | 
						|
        transcription = Whisper.SendToModel.large(path)
 | 
						|
        Logger.info("✅ Transcripción completa:\n#{transcription}")
 | 
						|
        message = %{"chunks" => [%{"text" => transcription}]}
 | 
						|
        Phoenix.PubSub.broadcast(Whisper.PubSub, "transcription", {:transcription, Jason.encode!(message)})
 | 
						|
        File.rm!(path)
 | 
						|
      end)
 | 
						|
    end
 | 
						|
 | 
						|
    {:noreply, socket}
 | 
						|
  end
 | 
						|
 | 
						|
  defp socket_id(socket), do: socket.transport_pid |> :erlang.pid_to_list() |> List.to_string()
 | 
						|
  
 | 
						|
  def save_raw(ref, bin) do
 | 
						|
    File.mkdir_p!("recordings/")
 | 
						|
    filename = "#{ref}_#{Whisper.Counter.next(ref)}.raw"
 | 
						|
    path = Path.join("recordings", filename)
 | 
						|
    File.write!(path, bin)
 | 
						|
    {:ok, path}
 | 
						|
  end
 | 
						|
end
 |