Skip to content

Standalone server #88

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open

Standalone server #88

wants to merge 12 commits into from

Conversation

Noarkhh
Copy link
Contributor

@Noarkhh Noarkhh commented May 7, 2025

This PR:

  • Adds a Realtimer before HLS sink, since when the packets came in faster than in real time the sink deleted the segments too quickly.
  • Fixes a bug where an elixir stream sink could demand on a pad with eos and never get a buffer and never responding to the demanding process, effectively bricking the generator.
  • Makes that if the user passed audio options to the :stream input they are passed to the ElixirStream.Source.
  • Improved handling of audio format in ElixirStream.Source - remembering last format, either passed via options or received in a Boombox.Packet, and sending stream format if the format changed.
  • Add an Application specification that runs Boombox.Server when running a server release
  • Adds a standalone server, Boombox.Server, that allows for running Boombox in a separate process and communicate with it through synchronous functions, GenServer calls and functions.

To do:

  • Tests
  • Consider whether consuming and producing is the best nomenclature, maybe regular read, write and close could be clearer

@Noarkhh Noarkhh force-pushed the standalone-server branch 3 times, most recently from 93c744e to 2dcd808 Compare May 8, 2025 10:46
@Noarkhh Noarkhh marked this pull request as ready for review May 8, 2025 10:46
@Noarkhh Noarkhh requested review from mat-hek, FelonEkonom and varsill May 8, 2025 10:46
Copy link
Member

@FelonEkonom FelonEkonom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we spoke, let's remove Boombox.Server from the public API

Comment on lines 11 to 13
case System.get_env("RELEASE_NAME") do
"server" ->
node_to_ping = System.get_env("NODE_TO_PING")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we prefix these environment variables with "BOOMBOX_", like "BOOMBOX_NODE_TO_PING"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll prefix NODE_TO_PING, but RELEASE_NAME is an internal one and I don't have control over it.

Comment on lines +27 to +36
Mode in which Boombox is operating:
* `:consuming` - Boombox consumes packets provided with `consume_packet/2` calls,
`{:consume_packet, packet}` GenServer calls or receiving
`{:call, sender, {:consume_packet, packet}.
* `:producing` - Boombox produces packets in response to `produce_packet/1` calls,
being called directly with `:produce_packet` or receiving
`{:call, sender, :produce_packet}` messages.
* `:standalone` - Boombox neither consumes nor produces packets.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would specify that we are talking about %Boombox.Packet{}. Boombox.run{input: rtp, output: {:webrtc, signaling}) consumes and produces packets in some way, but they are not %Boombox.Packet{}

Copy link
Contributor Author

@Noarkhh Noarkhh May 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a line in the moduledoc comment clarifying that

@Noarkhh Noarkhh force-pushed the standalone-server branch from 0363590 to 18be1b9 Compare May 13, 2025 12:17
@Noarkhh Noarkhh requested a review from FelonEkonom May 13, 2025 13:04
@Noarkhh Noarkhh added this to Smackore May 13, 2025
@Noarkhh Noarkhh moved this to In Review in Smackore May 13, 2025
@Noarkhh Noarkhh self-assigned this May 13, 2025
@Noarkhh Noarkhh linked an issue May 13, 2025 that may be closed by this pull request
@Noarkhh Noarkhh removed a link to an issue May 13, 2025
@Noarkhh Noarkhh linked an issue May 13, 2025 that may be closed by this pull request
@Noarkhh Noarkhh removed a link to an issue May 13, 2025
@Noarkhh Noarkhh linked an issue May 13, 2025 that may be closed by this pull request
Base automatically changed from fix-specs to master May 15, 2025 14:44
@Noarkhh Noarkhh force-pushed the standalone-server branch from 18be1b9 to 74dba9f Compare May 21, 2025 13:53
@@ -31,8 +31,14 @@ defmodule Boombox.InternalBin.ElixirStream do
|> via_out(Pad.ref(:output, :audio))}
end)

audio_options =
case Map.take(options, @options_audio_keys) do
audio_options when map_size(audio_options) == 3 -> audio_options
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not use magic numbers like 3 here

def handle_info(:boombox_demand, ctx, state) do
available_pads =
state.last_pts
|> Enum.reject(fn {kind, _pts} -> ctx.pads[Pad.ref(:input, kind)].end_of_stream? end)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove the entry in last_pts on handle_end_of_stream instead?

node_to_ping = System.get_env("NODE_TO_PING")

if node_to_ping != nil do
Node.ping(String.to_atom(node_to_ping))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't you want to match the return value of that function?

Comment on lines 3 to 4
Boombox application. When released as a standalone server will start Boombox.Server under the
supervision tree.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about describing how the user should set RELEASE_NAME and NODE_TO_PING?

@@ -31,8 +31,14 @@ defmodule Boombox.InternalBin.ElixirStream do
|> via_out(Pad.ref(:output, :audio))}
end)

audio_options =
case Map.take(options, @options_audio_keys) do
audio_options when map_size(audio_options) == 3 -> audio_options
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That 3 looks like a magic number to me, wouldn't it be better to match on particular structure of the audio_options map?

@@ -61,6 +62,7 @@ defmodule Boombox.InternalBin.HLS do
output_stream_format: %H264{alignment: :au, stream_structure: :avc3},
transcoding_policy: transcoding_policy
})
|> child(:hls_video_realtimer, Membrane.Realtimer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it requires some further consideration. We currently use the default HTTPAdaptiveStream.SinkBin mode being :vod.
I am not sure if we want to generate VOD playlist in realtime. Perhaps we should add HLS specific option and either spawn SinkBin with :live mode and the realtimer, or spawn SinkBin with :vod and without the realtimer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right, but I think it's out of scope for this PR, so I'll just revert it.

end

@doc """
Starts the server for more information see `GenServer.start/3`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Starts the server for more information see `GenServer.start/3`
Starts the server.
For more information see `GenServer.start/3`

Comment on lines 95 to 96
def start_link(arg) do
GenServer.start_link(__MODULE__, arg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's get rid of passing arg to Server.start_link because it is ignored in init callback

Comment on lines 103 to 104
def start(arg) do
GenServer.start(__MODULE__, arg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same as in start_link

Comment on lines +139 to +141
def consume_packet(server, packet) do
GenServer.call(server, {:consume_packet, packet})
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure it should be a call, instead of cast or send?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the idea is that this call returns once Boombox is ready to consume a next packet, and is a packpressure mechanism

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get it, but be aware that it adds an additional complexity and it will behave badly when the CPU usage starts hitting max

Comment on lines +276 to +281
@impl true
def handle_info({:call, sender, message}, state) do
{:reply, reply, state} = handle_call(message, sender, state)
send(sender, reply)
{:noreply, state}
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Man, this looks weird

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but Pyrlang can communicate only through messages, there's no way to call the GenServer directly from there and I didn't want to use the internal GenServer message structures.

Copy link
Member

@FelonEkonom FelonEkonom May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So let's move the implementation of these fake GenServer calls to the function that doesn't have handle_call in the name. It would be misleading to e.g. see errors from handle_call function that called by handle_info. What about handle_synchronous_request?

Beyond that, the second argument in GenServer handle_call isn't just pid where we should send response.

Comment on lines 283 to 286
@impl true
def handle_info({:DOWN, _ref, :process, pid, :normal}, %State{boombox_pid: pid} = state) do
{:stop, :normal, state}
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's stop Server also when reason is not :normal, we shouldn't ignore it


[Boombox.Server]
defp start_server() do
case System.fetch_env("NODE_TO_PING") do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was supposed to be BOOMBOX_NODE_TO_PING

Comment on lines +95 to +96
def start_link(_arg) do
GenServer.start_link(__MODULE__, nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about def start_link() do

def handle_info({:DOWN, _ref, :process, pid, :normal}, %State{boombox_pid: pid} = state) do
{:stop, :normal, state}
def handle_info({:DOWN, _ref, :process, pid, reason}, %State{boombox_pid: pid} = state) do
{:stop, reason, state}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{:stop, reason, state}
reason =
case reason do
:normal -> :normal
reason -> {:boombox_crash, reason}
end
{:stop, reason, state}

"""
@spec start(term()) :: GenServer.on_start()
def start(arg) do
GenServer.start(__MODULE__, arg)
def start(_arg) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def start(_arg) do
def start() do

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Review
Development

Successfully merging this pull request may close these issues.

Boombox in Python MVP
4 participants