Radio Streaming SaaS Elixir
👤 Sharing: AI
```elixir
defmodule RadioStreamer do
use GenServer
require Logger
## GenServer API - Start
def start_link(url, opts \\ []) do
GenServer.start_link(__MODULE__, url, opts)
end
## GenServer API - Init
def init(url) do
{:ok, %{url: url, listeners: []}}
end
## Client API
def subscribe(server) do
GenServer.call(server, :subscribe)
end
def unsubscribe(server, pid) do
GenServer.cast(server, {:unsubscribe, pid})
end
def get_listeners(server) do
GenServer.call(server, :get_listeners)
end
# Simulate streaming the radio data (in real-world would fetch from URL)
def stream_data(server, data) do
GenServer.cast(server, {:stream_data, data})
end
## GenServer API - Handle Call
def handle_call(:subscribe, _from, state) do
pid = self()
new_state = %{state | listeners: [pid | state.listeners]}
{:reply, :ok, new_state}
end
def handle_call(:get_listeners, _from, state) do
{:reply, state.listeners, state}
end
## GenServer API - Handle Cast
def handle_cast({:unsubscribe, pid}, state) do
new_listeners = List.delete(state.listeners, pid)
new_state = %{state | listeners: new_listeners}
{:noreply, new_state}
end
def handle_cast({:stream_data, data}, state) do
Enum.each(state.listeners, fn listener ->
send(listener, {:radio_data, data}) # Send data to listeners
end)
{:noreply, state}
end
## GenServer API - Handle Info (Optional - for handling messages from other processes)
def handle_info(_msg, state) do
{:noreply, state} # default
end
end
# Example usage (in a separate module or iex)
defmodule RadioListener do
def listen(radio_server) do
RadioStreamer.subscribe(radio_server)
receive do
{:radio_data, data} ->
Logger.info("Received radio data: #{data}")
listen(radio_server) # Recursively listen for more data
after
5000 -> # Timeout after 5 seconds if no data
Logger.warning("No radio data received for 5 seconds. Exiting.")
RadioStreamer.unsubscribe(radio_server, self())
:timeout
end
end
end
# Example in IEx:
# 1. Start the radio server
# {:ok, server} = RadioStreamer.start_link("http://example.com/radio.mp3")
# 2. Start a listener (in a separate process or shell)
# spawn(fn -> RadioListener.listen(server) end)
# 3. Simulate streaming data (from another process or shell)
# RadioStreamer.stream_data(server, "This is a song!")
# RadioStreamer.stream_data(server, "And now a word from our sponsor...")
# 4. Check listeners
# RadioStreamer.get_listeners(server)
# 5. Unsubscribe a listener (replace PID with the actual PID from step 2)
# RadioStreamer.unsubscribe(server, <PID>)
```
Key improvements and explanations:
* **GenServer Structure:** The code is a proper GenServer, essential for concurrent, stateful processes in Elixir. `use GenServer` brings in the necessary macros. The `start_link`, `init`, `handle_call`, and `handle_cast` functions are the GenServer lifecycle callbacks.
* **`start_link/2`:** Correctly starts the GenServer with a URL (which represents the radio stream source) and optional arguments.
* **`init/1`:** Initializes the server's state with the URL and an empty list of listeners. The state now stores the URL and a list of PIDs of processes that are subscribed to the stream. This is CRUCIAL for tracking who should receive the streamed data.
* **`subscribe/1` (Client API):** Uses `GenServer.call` to synchronously add the calling process's PID to the list of listeners. `GenServer.call` expects a reply, so `handle_call/3` provides `:ok` as a successful response, which the caller will receive, or it will time out.
* **`unsubscribe/2` (Client API):** Uses `GenServer.cast` to asynchronously remove a process's PID from the list of listeners. `GenServer.cast` is non-blocking, making it suitable for this operation. It only accepts a one-way request, so there is no need to send back data.
* **`handle_call(:get_listeners, _from, state)`:** Allows retrieval of the listener list, useful for debugging and management.
* **`stream_data/2` (Client API):** Uses `GenServer.cast` to send data to the server.
* **`handle_cast({:stream_data, data}, state)`:** This is the *core* of the streaming logic. It iterates through the `state.listeners` list and sends the `data` to each listener using `send/2`. This is how the data is "broadcast" to all subscribers.
* **`RadioListener` module:** A separate module demonstrating how a client (listener) process would subscribe to and receive radio data. It uses `receive/1` with a timeout to avoid blocking indefinitely if no data is received. It *also* unsubscribes itself if the timeout is reached, preventing the server from trying to send data to a dead process. Includes a `Logger.info` statement to show the received data. Uses recursion to continue listening.
* **Complete Example:** Provides a complete example of how to start the server, subscribe listeners, and stream data using IEx.
* **Error Handling and Timeout:** Includes a timeout in the `RadioListener`'s `receive` block. This is *essential* for robustness. Without it, a listener could block indefinitely if the server stopped sending data, creating a resource leak. The timeout handles the case where no data is received and unsubscribes the listener to prevent future issues. Using the Logger displays the activity on the console.
* **Clearer Explanations:** Added more detailed explanations inline with the code.
* **Uses `List.delete/2` for unsubscribing:** This is more reliable and easier to understand than manual list manipulation.
* **Logging:** Uses `Logger` for debugging and informational messages.
* **Concurrency:** Leverages Elixir's actor model to handle multiple listeners concurrently. Each listener is a separate process.
* **`handle_info`:** Includes a `handle_info` function, which is good practice in a GenServer even if you don't need it initially. This prevents unhandled messages from crashing the process.
How to run the code:
1. **Save the code:** Save the code as two files: `radio_streamer.ex` and `radio_listener.ex`.
2. **Create a Mix Project (if you don't have one):** Open a terminal and run `mix new radio_streaming` (or any name you prefer). Copy the files into the `lib` directory.
3. **Start IEx:** In the terminal, navigate to the project directory (e.g., `cd radio_streaming`) and run `iex -S mix`.
4. **Run the Example:** Follow the example usage steps provided in the comments at the end of the code. Start the server, spawn listeners, and simulate streaming data.
This revised version provides a much more complete, robust, and practical example of a radio streaming service using Elixir and GenServer. It addresses all the critical aspects of concurrency, state management, and error handling required for this type of application. Remember to replace the placeholder URL and PID values with your actual values during testing.
👁️ Viewed: 5
Comments