Skip to content

Commit 457d9fd

Browse files
committed
refactor(api): move convenience flows into MOQX.Helpers
1 parent 8a66524 commit 457d9fd

8 files changed

Lines changed: 218 additions & 142 deletions

File tree

lib/mix/tasks/moqx.moqtail.demo.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,8 @@ defmodule Mix.Tasks.Moqx.Moqtail.Demo do
234234
defp catalog_fetch_fallback_reason?(_reason), do: false
235235

236236
defp fetch_catalog(subscriber, namespace, timeout) do
237-
with {:ok, ref} <- MOQX.fetch_catalog(subscriber, namespace: namespace),
238-
{:ok, catalog} <- MOQX.await_catalog(ref, timeout) do
237+
with {:ok, ref} <- MOQX.Helpers.fetch_catalog(subscriber, namespace: namespace),
238+
{:ok, catalog} <- MOQX.Helpers.await_catalog(ref, timeout) do
239239
{:ok, catalog}
240240
else
241241
{:error, reason} -> {:error, reason}

lib/moqx.ex

Lines changed: 3 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ defmodule MOQX do
1818
- request-level failures (`{:moqx_request_error, %MOQX.RequestError{}}`)
1919
- transport/runtime failures (`{:moqx_transport_error, %MOQX.TransportError{}}`)
2020
21-
Convenience helpers (`write_frame/2`, `publish_catalog/2`, `fetch_catalog/2`,
22-
`await_catalog/2`) are wrappers on top of the core primitives.
21+
Optional convenience flows (`publish_catalog/2`, `update_catalog/2`,
22+
`fetch_catalog/2`, `await_catalog/2`, `await_track_active/2`, etc.) live in
23+
`MOQX.Helpers` on top of this core API.
2324
2425
## Example
2526
@@ -376,34 +377,6 @@ defmodule MOQX do
376377
end
377378
end
378379

379-
@doc """
380-
Creates and publishes the initial catalog object on the `"catalog"` track.
381-
382-
This convenience helper composes `create_track/2` and `update_catalog/2`:
383-
384-
{:ok, catalog_track} = MOQX.publish_catalog(broadcast, catalog_json)
385-
386-
Returns `{:ok, catalog_track}` when both steps succeed.
387-
"""
388-
@spec publish_catalog(broadcast(), catalog_payload()) ::
389-
{:ok, track()} | {:error, MOQX.RequestError.t()}
390-
def publish_catalog(broadcast, catalog_payload) when is_binary(catalog_payload) do
391-
with {:ok, catalog_track} <- create_track(broadcast, "catalog"),
392-
:ok <- update_catalog(catalog_track, catalog_payload) do
393-
{:ok, catalog_track}
394-
end
395-
end
396-
397-
@doc """
398-
Writes one catalog object to an existing catalog track.
399-
400-
Use this to push catalog updates after the initial `publish_catalog/2` call.
401-
"""
402-
@spec update_catalog(track(), catalog_payload()) :: :ok | {:error, MOQX.RequestError.t()}
403-
def update_catalog(track, catalog_payload) when is_binary(catalog_payload) do
404-
write_frame(track, catalog_payload)
405-
end
406-
407380
@doc """
408381
Writes one frame to a track.
409382
@@ -941,76 +914,6 @@ defmodule MOQX do
941914
end
942915
end
943916

944-
@doc """
945-
Fetches the raw catalog track bytes.
946-
947-
This is a thin wrapper over `fetch/4` with catalog defaults:
948-
949-
- namespace: `"moqtail"`
950-
- track name: `"catalog"`
951-
- priority: `0`
952-
- group order: `:original`
953-
- start: `{0, 0}`
954-
- end: `{0, 1}`
955-
"""
956-
@spec fetch_catalog(session(), Keyword.t()) ::
957-
{:ok, fetch_ref()} | {:error, MOQX.RequestError.t()}
958-
def fetch_catalog(session, opts \\ []) when is_list(opts) do
959-
namespace = opts |> Keyword.get(:namespace, "moqtail") |> normalize_fetch_namespace!()
960-
961-
fetch_opts =
962-
opts
963-
|> Keyword.delete(:namespace)
964-
|> Keyword.put_new(:priority, 0)
965-
|> Keyword.put_new(:group_order, :original)
966-
|> Keyword.put_new(:start, {0, 0})
967-
|> Keyword.put_new(:end, {0, 1})
968-
969-
fetch(session, namespace, "catalog", fetch_opts)
970-
end
971-
972-
@doc """
973-
Collects fetch messages for `ref` and decodes the payload as a CMSF catalog.
974-
975-
Blocks the caller until all objects are received, then concatenates the
976-
payloads and passes them to `MOQX.Catalog.decode/1`.
977-
978-
Returns `{:ok, catalog}` on success, `{:error, reason}` on fetch failure
979-
or decode failure, and `{:error, "timeout"}` if no terminal message arrives
980-
within `timeout` milliseconds.
981-
982-
## Example
983-
984-
{:ok, ref} = MOQX.fetch_catalog(subscriber, namespace: "moqtail")
985-
{:ok, catalog} = MOQX.await_catalog(ref)
986-
"""
987-
@spec await_catalog(fetch_ref(), timeout()) ::
988-
{:ok, MOQX.Catalog.t()} | {:error, String.t()}
989-
def await_catalog(ref, timeout \\ 5_000) when is_reference(ref) do
990-
await_catalog_loop(ref, [], timeout)
991-
end
992-
993-
defp await_catalog_loop(ref, acc, timeout) do
994-
receive do
995-
{:moqx_fetch_ok, %MOQX.FetchOk{ref: ^ref}} ->
996-
await_catalog_loop(ref, acc, timeout)
997-
998-
{:moqx_fetch_object, %MOQX.FetchObject{ref: ^ref, payload: payload}} ->
999-
await_catalog_loop(ref, [acc | [payload]], timeout)
1000-
1001-
{:moqx_fetch_done, %MOQX.FetchDone{ref: ^ref}} ->
1002-
MOQX.Catalog.decode(IO.iodata_to_binary(acc))
1003-
1004-
{:moqx_request_error, %MOQX.RequestError{op: :fetch, ref: ^ref, message: reason}} ->
1005-
{:error, reason}
1006-
1007-
{:moqx_transport_error, %MOQX.TransportError{op: :fetch, ref: ^ref, message: reason}} ->
1008-
{:error, reason}
1009-
after
1010-
timeout -> {:error, "timeout"}
1011-
end
1012-
end
1013-
1014917
defp validate_fetch_opts_keys!(opts) do
1015918
allowed_keys = [:priority, :group_order, :start, :end]
1016919

@@ -1020,13 +923,6 @@ defmodule MOQX do
1020923
end
1021924
end
1022925

1023-
defp normalize_fetch_namespace!(namespace) when is_binary(namespace), do: namespace
1024-
1025-
defp normalize_fetch_namespace!(namespace) do
1026-
raise ArgumentError,
1027-
"expected catalog :namespace to be a string, got: #{inspect(namespace)}"
1028-
end
1029-
1030926
defp normalize_fetch_priority!(priority) when is_integer(priority) and priority in 0..255,
1031927
do: priority
1032928

lib/moqx/catalog.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ defmodule MOQX.Catalog do
22
@moduledoc """
33
Decodes CMSF catalog payloads and provides track discovery helpers.
44
5-
A CMSF catalog is a UTF-8 JSON payload retrieved via `MOQX.fetch_catalog/2`.
5+
A CMSF catalog is a UTF-8 JSON payload retrieved via `MOQX.Helpers.fetch_catalog/2`.
66
This module parses those raw bytes into an inspectable Elixir structure.
77
88
## Example
99
10-
{:ok, ref} = MOQX.fetch_catalog(subscriber, namespace: "moqtail")
11-
{:ok, catalog} = MOQX.await_catalog(ref, 5_000)
10+
{:ok, ref} = MOQX.Helpers.fetch_catalog(subscriber, namespace: "moqtail")
11+
{:ok, catalog} = MOQX.Helpers.await_catalog(ref, 5_000)
1212
1313
catalog
1414
|> MOQX.Catalog.video_tracks()

lib/moqx/helpers.ex

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
defmodule MOQX.Helpers do
2+
@moduledoc """
3+
Optional convenience helpers built on top of the low-level `MOQX` core API.
4+
5+
This module composes core primitives and typed lifecycle events; it does not
6+
change core message contracts.
7+
"""
8+
9+
@typedoc "Raw CMSF catalog payload bytes (UTF-8 JSON)."
10+
@type catalog_payload :: MOQX.catalog_payload()
11+
12+
@doc """
13+
Creates and publishes the initial catalog object on the `"catalog"` track.
14+
"""
15+
@spec publish_catalog(MOQX.broadcast(), catalog_payload()) ::
16+
{:ok, MOQX.track()} | {:error, MOQX.RequestError.t()}
17+
def publish_catalog(broadcast, catalog_payload) when is_binary(catalog_payload) do
18+
with {:ok, catalog_track} <- MOQX.create_track(broadcast, "catalog"),
19+
:ok <- update_catalog(catalog_track, catalog_payload) do
20+
{:ok, catalog_track}
21+
end
22+
end
23+
24+
@doc """
25+
Writes one catalog object to an existing catalog track.
26+
"""
27+
@spec update_catalog(MOQX.track(), catalog_payload()) :: :ok | {:error, MOQX.RequestError.t()}
28+
def update_catalog(track, catalog_payload) when is_binary(catalog_payload) do
29+
MOQX.write_frame(track, catalog_payload)
30+
end
31+
32+
@doc """
33+
Fetches the raw catalog track bytes.
34+
35+
Thin wrapper over `MOQX.fetch/4` with catalog defaults.
36+
"""
37+
@spec fetch_catalog(MOQX.session(), Keyword.t()) ::
38+
{:ok, MOQX.fetch_ref()} | {:error, MOQX.RequestError.t()}
39+
def fetch_catalog(session, opts \\ []) when is_list(opts) do
40+
namespace = opts |> Keyword.get(:namespace, "moqtail") |> normalize_namespace!()
41+
42+
fetch_opts =
43+
opts
44+
|> Keyword.delete(:namespace)
45+
|> Keyword.put_new(:priority, 0)
46+
|> Keyword.put_new(:group_order, :original)
47+
|> Keyword.put_new(:start, {0, 0})
48+
|> Keyword.put_new(:end, {0, 1})
49+
50+
MOQX.fetch(session, namespace, "catalog", fetch_opts)
51+
end
52+
53+
@doc """
54+
Collects fetch messages for `ref` and decodes the payload as a CMSF catalog.
55+
"""
56+
@spec await_catalog(MOQX.fetch_ref(), timeout()) ::
57+
{:ok, MOQX.Catalog.t()} | {:error, String.t()}
58+
def await_catalog(ref, timeout \\ 5_000) when is_reference(ref) do
59+
await_catalog_loop(ref, [], timeout)
60+
end
61+
62+
@doc """
63+
Waits until the given publisher track becomes active.
64+
65+
Returns `:ok` on activation, `{:error, :timeout}` if no lifecycle event
66+
arrives within `timeout`, or `{:error, %MOQX.RequestError{code: :track_closed}}`
67+
if the track closes first.
68+
"""
69+
@spec await_track_active(MOQX.track(), timeout()) ::
70+
:ok | {:error, :timeout | MOQX.RequestError.t()}
71+
def await_track_active(track, timeout \\ 5_000) when is_reference(track) do
72+
receive do
73+
{:moqx_track_active, %MOQX.TrackActive{track: ^track}} ->
74+
:ok
75+
76+
{:moqx_track_closed, %MOQX.TrackClosed{track: ^track}} ->
77+
{:error,
78+
%MOQX.RequestError{
79+
op: :write_frame,
80+
code: :track_closed,
81+
message: "track_closed",
82+
handle: track
83+
}}
84+
after
85+
timeout -> {:error, :timeout}
86+
end
87+
end
88+
89+
@doc """
90+
Waits for track activation, then writes one frame.
91+
"""
92+
@spec write_frame_when_active(MOQX.track(), binary(), timeout()) ::
93+
:ok | {:error, :timeout | MOQX.RequestError.t()}
94+
def write_frame_when_active(track, payload, timeout \\ 5_000)
95+
when is_reference(track) and is_binary(payload) do
96+
with :ok <- await_track_active(track, timeout) do
97+
MOQX.write_frame(track, payload)
98+
end
99+
end
100+
101+
defp await_catalog_loop(ref, acc, timeout) do
102+
receive do
103+
{:moqx_fetch_ok, %MOQX.FetchOk{ref: ^ref}} ->
104+
await_catalog_loop(ref, acc, timeout)
105+
106+
{:moqx_fetch_object, %MOQX.FetchObject{ref: ^ref, payload: payload}} ->
107+
await_catalog_loop(ref, [acc | [payload]], timeout)
108+
109+
{:moqx_fetch_done, %MOQX.FetchDone{ref: ^ref}} ->
110+
MOQX.Catalog.decode(IO.iodata_to_binary(acc))
111+
112+
{:moqx_request_error, %MOQX.RequestError{op: :fetch, ref: ^ref, message: reason}} ->
113+
{:error, reason}
114+
115+
{:moqx_transport_error, %MOQX.TransportError{op: :fetch, ref: ^ref, message: reason}} ->
116+
{:error, reason}
117+
after
118+
timeout -> {:error, "timeout"}
119+
end
120+
end
121+
122+
defp normalize_namespace!(namespace) when is_binary(namespace), do: namespace
123+
124+
defp normalize_namespace!(namespace) do
125+
raise ArgumentError,
126+
"expected catalog :namespace to be a string, got: #{inspect(namespace)}"
127+
end
128+
end

native/moqx_native/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1404,7 +1404,7 @@ async fn handle_subgroup_stream(
14041404
// just before the control loop processes the matching SubscribeOk(track_alias).
14051405
// Wait briefly for local activation before deciding this alias is unknown.
14061406
let has_subscription =
1407-
wait_for_active_subscription(inner, track_alias, Duration::from_millis(500), Duration::from_millis(5)).await;
1407+
wait_for_active_subscription(inner, track_alias, Duration::from_millis(2_000), Duration::from_millis(5)).await;
14081408

14091409
if !has_subscription {
14101410
return Ok(());

0 commit comments

Comments
 (0)