Sound Forge Alchemy

Home > API Reference > WebSocket Channels

WebSocket Channels

Phoenix Channel and LiveView WebSocket documentation.

Table of Contents


Overview

SFA provides two WebSocket surfaces:

  1. Phoenix Channels (/socket) — For external clients (mobile apps, CLI tools) that need real-time job progress without a full browser/LiveView session.
  2. LiveSocket (/live) — The Phoenix LiveView WebSocket. All browser UI updates go through this connection.

Connection Setup

Phoenix Channel Client

import {Socket} from "phoenix"

const socket = new Socket("/socket", {
  params: {token: userToken}
})
socket.connect()

// Join the job channel
const channel = socket.channel("jobs:JOB_UUID_HERE", {})
channel.join()
  .receive("ok", resp => console.log("Joined channel", resp))
  .receive("error", resp => console.log("Unable to join", resp))

Authentication

Pass a token in the socket params. Tokens are generated at /users/settings → API Keys.

const socket = new Socket("/socket", {
  params: {token: "your-api-token"}
})

JobChannel

Module: SoundForgeWeb.JobChannel Topic pattern: "jobs:{job_id}"

Subscribe to real-time progress events for a specific job (download, processing, or analysis).

Joining

const channel = socket.channel("jobs:a1b2c3d4-e5f6-...", {})
channel.join()

Events Received from Server

job:progress

Sent periodically during job execution.

{
  "topic": "jobs:uuid",
  "event": "job:progress",
  "payload": {
    "job_id": "uuid",
    "status": "downloading",
    "progress": 45,
    "message": "Downloading audio..."
  }
}

status values: "queued", "downloading", "processing", "completed", "failed" progress: Integer 0–100

job:completed

Sent when a job finishes successfully.

{
  "event": "job:completed",
  "payload": {
    "job_id": "uuid",
    "job_type": "download",
    "track_id": "uuid",
    "result": {
      "output_path": "/files/downloads/...",
      "file_size": 8394752
    }
  }
}

job_type values: "download", "processing", "analysis"

job:failed

Sent when a job fails (after all retries exhausted).

{
  "event": "job:failed",
  "payload": {
    "job_id": "uuid",
    "job_type": "download",
    "error": "spotdl: track not available in your region"
  }
}

Client → Server Messages

ping

channel.push("ping", {})
  .receive("ok", resp => console.log("pong", resp))

LiveSocket

URL: /live Module: SoundForgeWeb.Endpoint (LiveView WebSocket)

The LiveSocket is used by the browser for all Phoenix LiveView interactions. It is initialized in assets/js/app.js:

import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"

const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")

const liveSocket = new LiveSocket("/live", Socket, {
  params: {_csrf_token: csrfToken},
  hooks: {
    AnalysisRadar,
    AnalysisChroma,
    AnalysisBeats,
    AnalysisMFCC,
    AnalysisSpectral,
    DjDeck,
    DawEditor,
    DawPreview
  }
})
liveSocket.connect()

LiveView Events

LiveView events are sent between the browser and the server automatically as part of the LiveView protocol. Key user-initiated events:

Event Trigger Handler
fetch_spotify Spotify URL form submit DashboardLive.handle_event/3
start_download Download button DashboardLive.handle_event/3
start_separation Separate stems button DashboardLive.handle_event/3
start_analysis Analyze button DashboardLive.handle_event/3
play_track Play button Routes to AudioPlayerLive or Spotify SDK
dismiss_pipeline Dismiss button DashboardLive.handle_event/3

Server Push to Hooks

The server can push events to JS hooks via push_event/3:

# In LiveView
socket = push_event(socket, "analysis_data", %{
  track_id: track.id,
  tempo: result.tempo,
  key: result.key,
  chroma: result.features["chroma"]
})

The hook receives it:

// In AnalysisChroma hook
mounted() {
  this.handleEvent("analysis_data", data => {
    this.renderChromaChart(data.chroma)
  })
}

PubSub Topics

Internal PubSub topics (BEAM process-level, not exposed to external WebSocket clients):

Topic Publisher Subscriber
"tracks" Music context DashboardLive
"jobs:{job_id}" Oban workers JobChannel, DashboardLive
"midi:events" MIDI.DeviceManager MidiLive, DjLive
"osc:events" OSC.Server DawLive

Event Reference

Complete Job Progress Event

# Broadcast from Oban worker
Phoenix.PubSub.broadcast(SoundForge.PubSub, "jobs:#{job_id}", {
  :job_progress,
  %{
    job_id: job_id,
    track_id: track_id,
    status: :processing,
    progress: 60,
    message: "Separating stems..."
  }
})

Track Created Event

# Broadcast from Music context
Phoenix.PubSub.broadcast(SoundForge.PubSub, "tracks", {
  :track_created,
  %Track{id: ..., title: ..., artist: ...}
})

DashboardLive handles this and calls stream_insert/3 to add the track to the library list.


See Also


← REST Endpoints Next: Changelog →