defmodule RiverConnectWeb.AudioChannel do use RiverConnectWeb, :channel alias RiverConnect.Audio @impl true def join("audio:lobby", _payload, socket) do {:ok, socket} end @impl true def handle_in("audio_start", %{"id" => id}, socket) do # Ensure upload directory exists upload_path = Application.app_dir(:river_connect, "priv/static/uploads") File.mkdir_p!(upload_path) user_id = socket.assigns[:user_id] || "guest" # Create DB record immediately with "recording" status {:ok, message} = Audio.create_message(%{ user_id: user_id, file_path: "/uploads/#{id}.webm", status: "recording" }) # Store temporary file path and message_id in socket assigns temp_file_path = Path.join(upload_path, "#{id}.webm") # Broadcast start and new message to other listeners broadcast_from!(socket, "audio_start", %{id: id}) broadcast!(socket, "audio_message_created", %{message: message}) {:noreply, assign(socket, :current_recording, %{id: id, path: temp_file_path, message_id: message.id})} end @impl true def handle_in("audio_chunk", {:binary, payload}, socket) do case socket.assigns[:current_recording] do %{path: path} -> # Append binary chunk to file File.write!(path, payload, [:append]) # Broadcast chunk to other listeners for live playback broadcast_from!(socket, "audio_chunk", %{data: Base.encode64(payload)}) {:noreply, socket} _ -> {:noreply, socket} end end @impl true def handle_in("audio_end", %{"duration_ms" => duration_ms}, socket) do case socket.assigns[:current_recording] do %{message_id: message_id} -> message = Audio.get_message!(message_id) # Update DB record to "processing" status {:ok, message} = Audio.update_message(message, %{ duration_ms: duration_ms, status: "processing" }) # Queue Oban job for processing %{id: message.id} |> RiverConnect.Workers.ProcessRecording.new() |> Oban.insert!() # Notify UI that message status changed broadcast!(socket, "audio_message_ready", %{id: message.id}) {:noreply, assign(socket, :current_recording, nil)} _ -> {:noreply, socket} end end end