Skip to content

Conversation

@varsill
Copy link
Contributor

@varsill varsill commented Mar 7, 2025

This PR:

  • Adds Membrane.WebRTC.PhoenixSignaling
  • Adds Membrane.WebRTC.PhoenixSignaling.Channel and Membrane.WebRTC.PhoenixSignaling.Socket
  • changes structure of signaling messages from {Signaling, ...} to {:membrane_webrtc_signaling, ...}
  • Bumps version to 0.25.0

closes membraneframework/membrane_core#941

@varsill varsill marked this pull request as draft March 7, 2025 14:48
@varsill varsill marked this pull request as ready for review March 11, 2025 09:47
varsill added 4 commits March 11, 2025 13:26
…able. Move current example to websocket_signaling subdirectory. Implement phoenix signaling example
@varsill varsill requested a review from FelonEkonom March 11, 2025 14:13
Comment on lines 1 to 88
defmodule Membrane.WebRTC.PhoenixSignaling do
@moduledoc """
Provides signaling capabilities for WebRTC connections through Phoenix channels.
"""
use GenServer
alias Membrane.WebRTC.Signaling

@typedoc """
A type representing an unique identifier that is used to distinguish between different Phoenix Signaling
instances.
"""
@type signaling_id :: String.t()

@spec start(term()) :: GenServer.on_start()
def start(args) do
GenServer.start(__MODULE__, args, name: __MODULE__)
end

@spec start_link(term()) :: GenServer.on_start()
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end

@impl true
def init(_args) do
{:ok, %{signaling_map: %{}}}
end

@impl true
def handle_call({:get_or_create, signaling_id}, _from, state) do
case Map.get(state.signaling_map, signaling_id) do
nil ->
signaling = Signaling.new()
state = put_in(state, [:signaling_map, signaling_id], signaling)
{:reply, signaling, state}

signaling ->
{:reply, signaling, state}
end
end

@impl true
def handle_call({:get, signaling_id}, _from, state) do
{:reply, Map.get(state.signaling_map, signaling_id), state}
end

@doc """
Returns an instance of a Phoenix Signaling associated with given signaling ID.
"""
@spec new(signaling_id()) :: Signaling.t()
def new(signaling_id) do
get_or_create(signaling_id)
end

@doc """
Registers Phoenix.Channel process as WebRTC signaling peer
so that it can send and receive signaling messages.
"""
@spec register_channel(signaling_id(), pid() | nil) :: :ok
def register_channel(signaling_id, channel_pid \\ nil) do
channel_pid = channel_pid || self()
signaling = get_or_create(signaling_id)
Signaling.register_peer(signaling, message_format: :json_data, pid: channel_pid)
end

@doc """
Sends a signal message via the Phoenix Signaling instance associated with given signaling ID.
"""
@spec signal(signaling_id(), Signaling.message_content()) :: :ok | no_return()
def signal(signaling_id, msg) do
signaling = get!(signaling_id)
Signaling.signal(signaling, msg)
end

defp get_or_create(signaling_id) do
GenServer.call(__MODULE__, {:get_or_create, signaling_id})
end

defp get!(signaling_id) do
case GenServer.call(__MODULE__, {:get, signaling_id}) do
nil ->
raise "Couldn't find signaling instance associated with signaling_id: #{inspect(signaling_id)}"

signaling ->
signaling
end
end
end
Copy link
Member

Choose a reason for hiding this comment

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

For me it is little confusing, that one module at the same time

  • works more or less like Registry
  • provides functionalities similar to WebRTC.Singlaing

I would split these two functionalities to separate modules.
Another thing that is little confusing for me, is that if someone calls PhoenixSignaling.register_channel/2 then he will receive {Singlaing, ...} messages instead of {PhoenixSignaling, ...}. Maybe setting the first element of these tuples to :membrane_webrtc_signaling or something like that would be a good solution of this problem? @mat-hek WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

BTW this module could be wrapped into if Code.ensure_loaded?(Phoenix) do as well

Copy link
Contributor Author

@varsill varsill Mar 14, 2025

Choose a reason for hiding this comment

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

BTW this module could be wrapped into if Code.ensure_loaded?(Phoenix) do as well

It does not have any Phoenix dependency so it doesn't hurt us to leave it that way, but I can wrap it with Code.ensure_loaded?(Phoenix) for the sake of consistency

Copy link
Contributor Author

Choose a reason for hiding this comment

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

:membrane_webrtc_signaling as the first element of the tuple sounds good for me

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've also moved the GenServer capabilities from the PhoenixSignaling into custom Registry module

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've changed the structure of the {Signaling, ...} tuple into {:membrane_webrtc_signaling, ...}

@varsill varsill requested a review from FelonEkonom March 14, 2025 15:06
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.

Please ensure that docs of modules wrapped with Code.ensure_loaded?/1 are properly generated. If no, we could move it to the separate repo (maybe membrane_webrtc_plugin_phoenix?) or we have to find out the way to ensure that Phoenix is loaded during docs generation

Comment on lines +1 to +9
defmodule PhoenixSignaling do
@moduledoc """
PhoenixSignaling keeps the contexts that define your domain
and business logic.

Contexts are also responsible for managing your data, regardless
if it comes from the database, an external API or others.
"""
end
Copy link
Member

Choose a reason for hiding this comment

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

leftover?

Copy link
Contributor Author

@varsill varsill Mar 19, 2025

Choose a reason for hiding this comment

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

Actually it's a file automatically generated with mix phx.new and unfortunately it's called similarly to Membrane.WebRTC.PhoenixSignaling because the whole Phoenix exemplary project is called phoenix_signaling (see the directory name)

@varsill
Copy link
Contributor Author

varsill commented Mar 19, 2025

Please ensure that docs of modules wrapped with Code.ensure_loaded?/1 are properly generated. If no, we could move it to the separate repo (maybe membrane_webrtc_plugin_phoenix?) or we have to find out the way to ensure that Phoenix is loaded during docs generation

As discussed, phoenix optional dependency will be available when the docs are built: https://hexdocs.pm/mix/1.18.3/Mix.Tasks.Deps.html#module-dependency-definition-options

@varsill varsill requested a review from FelonEkonom March 19, 2025 10:12
@varsill varsill merged commit 99dc41a into master Mar 19, 2025
3 checks passed
@varsill varsill deleted the varsill/add_phoenix_signaling_channel branch March 19, 2025 11:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WebRTC Sink/Source signaling WebSockets are not secure

3 participants