From f8e2bfaadaaa5b63a6d90c8ad46f17e3e58a9ffd Mon Sep 17 00:00:00 2001 From: Steffen Beyer Date: Tue, 26 May 2026 01:13:38 +0200 Subject: [PATCH] refactor: use chat capability surfaces Move Sender modules under TribeOne.TribesPlugin.Sender and replace the Aether-specific chat integration with the public chat@1 surface contract. --- AGENTS.md | 10 +-- README.md | 4 +- config/config.exs | 4 +- docs/API.md | 24 +++---- docs/arch.md | 2 +- docs/progress.md | 12 ++-- lib/sender/application.ex | 29 +++++---- lib/sender/chat_integration.ex | 24 ++++--- lib/sender/edge_session_reconciler.ex | 4 +- lib/sender/hls.ex | 4 +- lib/sender/hls_cleanup.ex | 6 +- lib/sender/hls_readiness_watcher.ex | 4 +- lib/sender/local_streams.ex | 11 ++-- lib/sender/management.ex | 6 +- lib/sender/management/capabilities.ex | 4 +- lib/sender/management/endpoint_snapshots.ex | 8 +-- lib/sender/management/stream.ex | 6 +- lib/sender/management/stream_key.ex | 8 +-- lib/sender/management/stream_lifecycle.ex | 16 +++-- lib/sender/management/topology.ex | 6 +- lib/sender/media_backend.ex | 4 +- lib/sender/media_backend/ffmpeg.ex | 13 ++-- lib/sender/media_backend/ffmpeg/worker.ex | 4 +- lib/sender/media_backend/hls_pull_through.ex | 11 ++-- .../media_backend/hls_pull_through/worker.ex | 4 +- lib/sender/media_session.ex | 18 ++++-- lib/sender/playback.ex | 6 +- lib/sender/plugin.ex | 36 +++++------ lib/sender/snapshot_publisher.ex | 6 +- lib/sender/snapshot_worker.ex | 4 +- lib/sender/stats.ex | 2 +- lib/sender/streaming.ex | 16 ++--- lib/sender/streaming/endpoint_history.ex | 4 +- lib/sender/streaming/endpoint_snapshot.ex | 4 +- lib/sender/streaming/media_endpoint.ex | 4 +- lib/sender/streaming/rendition.ex | 4 +- lib/sender/streaming/resource_helpers.ex | 2 +- lib/sender/streaming/stream.ex | 4 +- lib/sender/streaming/stream_generation.ex | 4 +- lib/sender/streaming/stream_key.ex | 4 +- lib/sender/streaming/stream_keys.ex | 2 +- lib/sender_web/hls_plug.ex | 4 +- lib/sender_web/live/home_live.ex | 48 ++++++-------- lib/sender_web/streaming_api_plug.ex | 4 +- lib/tribes/plugins/sender/plugin.ex | 5 -- manifest.json | 2 +- mix.exs | 4 +- mix.lock | 2 +- ...1170000_create_sender_streaming_tables.exs | 2 +- scripts/sender_e2e_runner.exs | 4 +- test/sender/ffmpeg_backend_test.exs | 6 +- test/sender/hls_cleanup_test.exs | 6 +- test/sender/hls_pull_through_test.exs | 6 +- test/sender/home_page_test.exs | 58 +++++++++++------ test/sender/local_streams_test.exs | 8 +-- test/sender/management_test.exs | 64 ++++++++++++------- test/sender/media_session_test.exs | 14 ++-- test/sender/playback_api_test.exs | 54 +++++++++++----- test/sender/plugin_contract_test.exs | 4 +- test/sender/stats_snapshot_test.exs | 8 +-- test/sender/stream_keys_test.exs | 4 +- test/sender/streaming_schema_test.exs | 32 ++++++---- test/support/sender/data_case.ex | 4 +- 63 files changed, 383 insertions(+), 308 deletions(-) delete mode 100644 lib/tribes/plugins/sender/plugin.ex diff --git a/AGENTS.md b/AGENTS.md index b37320f..b3cd187 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,7 @@ test surfaces. ## Plugin Contract -- `manifest.json` `entry_module` must point at `Tribes.Plugins.Sender.Plugin`. +- `manifest.json` `entry_module` must point at `TribeOne.TribesPlugin.Sender.Plugin`. - Keep streaming supervision children restartable under normal OTP supervision. - Keep runtime spec fields aligned with `manifest.json`. @@ -547,8 +547,10 @@ source directory, the same document is at `../docs/plugins.md`. - Implement the runtime contract with `Tribes.Plugin` or `Tribes.Plugin.Base`. - Prefer `use Tribes.Plugin.Base, otp_app: :your_plugin` for normal plugins; it reads `manifest.json` and fills the manifest-backed spec fields. -- Keep the host-facing entry module under `Tribes.Plugins.*.Plugin`. It may be a - thin delegate to the plugin application's own module. +- Keep plugin modules under an owner-controlled namespace, such as + `TribeOne.TribesPlugin..Plugin` for first-party plugins or + `AcmeCorp.TribesPlugins.Foo.Plugin` for third-party plugins. The entry module + must end in `.Plugin` and should be the module named by `manifest.json`. - `register/1` must return a `Tribes.Plugin.Spec` struct or a map/struct that validates into that spec. @@ -617,7 +619,7 @@ checkout during development. For the full contract, see - `manifest.json` is the static build/runtime contract. Keep `name`, `version`, `entry_module`, `host_api`, `otp_app`, `provides`, `requires`, and `enhances_with` aligned with the runtime spec returned by `register/1`. -- `entry_module` must be under `Tribes.Plugins.*.Plugin` and end in `.Plugin`. +- `entry_module` must be a valid module ending in `.Plugin` under an owner-controlled namespace. - Capability versions are discrete breaking-change markers such as `ui@1` or `social@1`, not semver. Framework APIs are part of the `host_api` foundation, not separate manifest capabilities. - Use `requires` for hard dependencies and `enhances_with` for optional diff --git a/README.md b/README.md index eefea1a..967b3f5 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ your_plugin/ ```json { "name": "your_plugin", - "entry_module": "Tribes.Plugins.YourPlugin.Plugin", + "entry_module": "TribeOne.TribesPlugin.YourPlugin.Plugin", "host_api": "1", "otp_app": "your_plugin", "provides": ["some_capability@1"], @@ -117,7 +117,7 @@ your_plugin/ } ``` -- **entry_module** — must be `Tribes.Plugins.*.Plugin` +- **entry_module** — must be `..Plugin` - **otp_app** — required and must match `name` - **provides** — capabilities this plugin makes available - **requires** — hard dependencies (build fails without them) diff --git a/config/config.exs b/config/config.exs index 456f57c..d28fe27 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,8 +1,8 @@ import Config -config :sender, ash_domains: [Sender.Streaming] +config :sender, ash_domains: [TribeOne.TribesPlugin.Sender.Streaming] -config :sender, Sender.HLS, +config :sender, TribeOne.TribesPlugin.Sender.HLS, hls_time: 2, hls_list_size: 6, hls_delete_threshold: 3, diff --git a/docs/API.md b/docs/API.md index d2bd64e..4b95472 100644 --- a/docs/API.md +++ b/docs/API.md @@ -3,17 +3,17 @@ Sender exposes two public control surfaces: 1. **Plugin management methods** for remote/admin orchestration, including Legion. -2. **`Sender.LocalStreams`** for IEx and local no-Legion operation on the node. +2. **`TribeOne.TribesPlugin.Sender.LocalStreams`** for IEx and local no-Legion operation on the node. ## Local stream control from IEx -Use `Sender.LocalStreams` when you are on a node and want to operate Sender +Use `TribeOne.TribesPlugin.Sender.LocalStreams` when you are on a node and want to operate Sender without Legion: ```elixir -{:ok, live} = Sender.LocalStreams.start_origin() -Sender.LocalStreams.status() -Sender.LocalStreams.stop(live) +{:ok, live} = TribeOne.TribesPlugin.Sender.LocalStreams.start_origin() +TribeOne.TribesPlugin.Sender.LocalStreams.status() +TribeOne.TribesPlugin.Sender.LocalStreams.stop(live) ``` `start_origin/1` requires the local Tribes node identity to exist. It reads the @@ -25,20 +25,20 @@ To generate a test stream with local `ffmpeg` and push it to the node's RTMP listener: ```elixir -{:ok, live} = Sender.LocalStreams.start_origin(push_test?: true) +{:ok, live} = TribeOne.TribesPlugin.Sender.LocalStreams.start_origin(push_test?: true) ``` The generated publisher uses MuonTrap and runs under Sender supervision. Stop it without stopping the stream: ```elixir -Sender.LocalStreams.stop_test_video(live) +TribeOne.TribesPlugin.Sender.LocalStreams.stop_test_video(live) ``` Use a finite test-video duration if desired: ```elixir -{:ok, live} = Sender.LocalStreams.start_origin(push_test?: true, duration: 30) +{:ok, live} = TribeOne.TribesPlugin.Sender.LocalStreams.start_origin(push_test?: true, duration: 30) ``` Useful defaults: @@ -51,7 +51,7 @@ Useful defaults: Common overrides: ```elixir -Sender.LocalStreams.start_origin( +TribeOne.TribesPlugin.Sender.LocalStreams.start_origin( input_url: "rtmp://0.0.0.0:1936/live/source", push_url: "rtmp://127.0.0.1:1936/live/source", hls_base_url: "https://example.test/sender/hls", @@ -62,8 +62,8 @@ Sender.LocalStreams.start_origin( You can also start only the generated publisher after opening a stream: ```elixir -{:ok, test_video} = Sender.LocalStreams.push_test_video(duration: 10) -Sender.LocalStreams.stop_test_video(test_video["pid"]) +{:ok, test_video} = TribeOne.TribesPlugin.Sender.LocalStreams.push_test_video(duration: 10) +TribeOne.TribesPlugin.Sender.LocalStreams.stop_test_video(test_video["pid"]) ``` ## Remote management methods @@ -83,5 +83,5 @@ through Tribes' admin management API. The primary methods are: - `stream.status` - `endpoint_snapshots.report` -`Sender.LocalStreams` composes these same management handlers locally. It is not +`TribeOne.TribesPlugin.Sender.LocalStreams` composes these same management handlers locally. It is not a separate remote command API for running arbitrary ffmpeg commands. diff --git a/docs/arch.md b/docs/arch.md index c3741cf..407f9fa 100644 --- a/docs/arch.md +++ b/docs/arch.md @@ -431,7 +431,7 @@ Vinyl can sit in front of plugin HLS routes or external HLS origins. The plugin Start with a backend abstraction: ```elixir -defmodule Sender.MediaBackend do +defmodule TribeOne.TribesPlugin.Sender.MediaBackend do @callback start_ingest(StreamGeneration.t(), keyword()) :: {:ok, term()} | {:error, term()} @callback stop_ingest(StreamGeneration.t(), keyword()) :: :ok | {:error, term()} @callback health(term()) :: map() diff --git a/docs/progress.md b/docs/progress.md index dca8371..712671e 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -11,12 +11,12 @@ This file tracks the planned implementation from plugin template to clustered st - [x] Enable the plugin supervision tree in `mix.exs`. - [x] Decide initial media backend: supervised `ffmpeg` first, Membrane later behind a behaviour. - [x] Decide MVP control plane: Legion drives admin/orchestration through Tribes plugin management methods. -- [x] Decide local HLS spool root config key: `config :sender, Sender.HLS, spool_root: ...`. +- [x] Decide local HLS spool root config key: `config :sender, TribeOne.TribesPlugin.Sender.HLS, spool_root: ...`. - [x] Document required runtime binaries in deployment packaging: at least `ffmpeg`. ## Phase 1: Ash Domain and Synced Schema -- [x] Add `Sender.Streaming` Ash domain. +- [x] Add `TribeOne.TribesPlugin.Sender.Streaming` Ash domain. - [x] Register the domain through the plugin spec `ash_domains`. - [x] Add migrations for plugin tables. - [x] Add `Stream` resource with AshNostrSync. @@ -32,9 +32,9 @@ This file tracks the planned implementation from plugin template to clustered st ## Phase 2: Single-Node RTMP to HLS MVP -- [x] Add `Sender.MediaBackend` behaviour. -- [x] Implement `Sender.MediaBackend.FFmpeg`. -- [x] Add role-aware `Sender.MediaSession` runtime owner for `origin_ingest`. +- [x] Add `TribeOne.TribesPlugin.Sender.MediaBackend` behaviour. +- [x] Implement `TribeOne.TribesPlugin.Sender.MediaBackend.FFmpeg`. +- [x] Add role-aware `TribeOne.TribesPlugin.Sender.MediaSession` runtime owner for `origin_ingest`. - [x] Run FFmpeg through `MuonTrap` instead of bare Erlang ports. - [x] Supervise ingest pipeline processes. - [x] Generate a stream key and validate RTMP ingest attempts. @@ -135,7 +135,7 @@ This file tracks the planned implementation from plugin template to clustered st ## Phase 9: Membrane Evaluation - [ ] Identify Membrane packages needed for RTMP ingest, HLS output, and optional transcoding. -- [ ] Build a backend prototype behind `Sender.MediaBackend`. +- [ ] Build a backend prototype behind `TribeOne.TribesPlugin.Sender.MediaBackend`. - [ ] Compare operational behavior against ffmpeg: startup, failure handling, CPU, memory, latency. - [ ] Decide whether Membrane becomes primary, optional, or deferred. - [ ] Keep ffmpeg backend available unless Membrane fully covers production needs. diff --git a/lib/sender/application.ex b/lib/sender/application.ex index f641f44..6ef3430 100644 --- a/lib/sender/application.ex +++ b/lib/sender/application.ex @@ -1,4 +1,4 @@ -defmodule Sender.Application do +defmodule TribeOne.TribesPlugin.Sender.Application do @moduledoc """ OTP Application for this plugin. @@ -11,29 +11,32 @@ defmodule Sender.Application do @doc false def children do [ - Sender.Stats, - Sender.SnapshotWorker, - Sender.HLSCleanup, - {Registry, keys: :unique, name: Sender.MediaSessionRegistry}, + TribeOne.TribesPlugin.Sender.Stats, + TribeOne.TribesPlugin.Sender.SnapshotWorker, + TribeOne.TribesPlugin.Sender.HLSCleanup, + {Registry, keys: :unique, name: TribeOne.TribesPlugin.Sender.MediaSessionRegistry}, Supervisor.child_spec( - {DynamicSupervisor, strategy: :one_for_one, name: Sender.MediaSessionSupervisor}, - id: Sender.MediaSessionSupervisor + {DynamicSupervisor, + strategy: :one_for_one, name: TribeOne.TribesPlugin.Sender.MediaSessionSupervisor}, + id: TribeOne.TribesPlugin.Sender.MediaSessionSupervisor ), Supervisor.child_spec( - {DynamicSupervisor, strategy: :one_for_one, name: Sender.MediaBackendSupervisor}, - id: Sender.MediaBackendSupervisor + {DynamicSupervisor, + strategy: :one_for_one, name: TribeOne.TribesPlugin.Sender.MediaBackendSupervisor}, + id: TribeOne.TribesPlugin.Sender.MediaBackendSupervisor ), Supervisor.child_spec( - {DynamicSupervisor, strategy: :one_for_one, name: Sender.HLSReadinessSupervisor}, - id: Sender.HLSReadinessSupervisor + {DynamicSupervisor, + strategy: :one_for_one, name: TribeOne.TribesPlugin.Sender.HLSReadinessSupervisor}, + id: TribeOne.TribesPlugin.Sender.HLSReadinessSupervisor ), - Sender.EdgeSessionReconciler + TribeOne.TribesPlugin.Sender.EdgeSessionReconciler ] end @impl true def start(_type, _args) do - opts = [strategy: :one_for_one, name: Sender.Supervisor] + opts = [strategy: :one_for_one, name: TribeOne.TribesPlugin.Sender.Supervisor] Supervisor.start_link(children(), opts) end end diff --git a/lib/sender/chat_integration.ex b/lib/sender/chat_integration.ex index 06a299b..8076bb5 100644 --- a/lib/sender/chat_integration.ex +++ b/lib/sender/chat_integration.ex @@ -1,14 +1,15 @@ -defmodule Sender.ChatIntegration do +defmodule TribeOne.TribesPlugin.Sender.ChatIntegration do @moduledoc false @chat_capability "chat@1" - @aether_component_module Module.concat(["AetherWeb", "ChatPanelComponent"]) - def component_module do - if chat_available?() && Code.ensure_loaded?(@aether_component_module) do - {:ok, @aether_component_module} + def embedded_panel(slug, opts \\ []) when is_binary(slug) do + with {:ok, provider} <- provider_module(), + true <- function_exported?(provider, :embedded_panel, 2), + {:ok, surface} <- provider.embedded_panel(slug, opts) do + {:ok, surface} else - :unavailable + _other -> :unavailable end rescue _error -> :unavailable @@ -19,12 +20,19 @@ defmodule Sender.ChatIntegration do end def chat_available? do - Code.ensure_loaded?(Tribes.PluginRegistry) && - Tribes.PluginRegistry.provides?(@chat_capability) + match?({:ok, _provider}, provider_module()) rescue _error -> false end + defp provider_module do + if Code.ensure_loaded?(Tribes.Plugin.Registry) do + Tribes.Plugin.Registry.provider_module(@chat_capability) + else + :error + end + end + defp slugify(value) do value |> String.downcase() diff --git a/lib/sender/edge_session_reconciler.ex b/lib/sender/edge_session_reconciler.ex index 9ccfe8f..6c6f5af 100644 --- a/lib/sender/edge_session_reconciler.ex +++ b/lib/sender/edge_session_reconciler.ex @@ -1,11 +1,11 @@ -defmodule Sender.EdgeSessionReconciler do +defmodule TribeOne.TribesPlugin.Sender.EdgeSessionReconciler do @moduledoc false use GenServer require Logger - alias Sender.{HLS, Management, MediaSession, Streaming} + alias TribeOne.TribesPlugin.Sender.{HLS, Management, MediaSession, Streaming} @active_statuses [:pending, :listening, :live, :degraded, :ending] @default_initial_delay_ms 1_000 diff --git a/lib/sender/hls.ex b/lib/sender/hls.ex index 8e37ffb..041e8ee 100644 --- a/lib/sender/hls.ex +++ b/lib/sender/hls.ex @@ -1,9 +1,9 @@ -defmodule Sender.HLS do +defmodule TribeOne.TribesPlugin.Sender.HLS do @moduledoc """ Local HLS file serving helpers. """ - alias Sender.Streaming.StreamGeneration + alias TribeOne.TribesPlugin.Sender.Streaming.StreamGeneration @playlist_mime "application/vnd.apple.mpegurl" @mpegts_mime "video/mp2t" diff --git a/lib/sender/hls_cleanup.ex b/lib/sender/hls_cleanup.ex index 912d967..9ac451d 100644 --- a/lib/sender/hls_cleanup.ex +++ b/lib/sender/hls_cleanup.ex @@ -1,12 +1,12 @@ -defmodule Sender.HLSCleanup do +defmodule TribeOne.TribesPlugin.Sender.HLSCleanup do @moduledoc false use GenServer require Logger - alias Sender.HLS - alias Sender.Streaming.StreamGeneration + alias TribeOne.TribesPlugin.Sender.HLS + alias TribeOne.TribesPlugin.Sender.Streaming.StreamGeneration def child_spec(opts) do %{ diff --git a/lib/sender/hls_readiness_watcher.ex b/lib/sender/hls_readiness_watcher.ex index fc5961f..8b2c189 100644 --- a/lib/sender/hls_readiness_watcher.ex +++ b/lib/sender/hls_readiness_watcher.ex @@ -1,11 +1,11 @@ -defmodule Sender.HLSReadinessWatcher do +defmodule TribeOne.TribesPlugin.Sender.HLSReadinessWatcher do @moduledoc false use GenServer require Logger - alias Sender.{HLS, Management, Streaming} + alias TribeOne.TribesPlugin.Sender.{HLS, Management, Streaming} @default_poll_interval_ms 1_000 diff --git a/lib/sender/local_streams.ex b/lib/sender/local_streams.ex index adcd8db..135c49f 100644 --- a/lib/sender/local_streams.ex +++ b/lib/sender/local_streams.ex @@ -1,14 +1,14 @@ -defmodule Sender.LocalStreams do +defmodule TribeOne.TribesPlugin.Sender.LocalStreams do @moduledoc """ Local Sender stream control helpers. This module is intended for IEx and local operation when an external - orchestrator such as Legion is not managing Sender. It composes the same + orchestrator such as Legion is not managing TribeOne.TribesPlugin.Sender. It composes the same management handlers that remote orchestration uses, and can optionally launch a local ffmpeg test-video publisher with MuonTrap. """ - alias Sender.Management.{StreamLifecycle, Topology} + alias TribeOne.TribesPlugin.Sender.Management.{StreamLifecycle, Topology} alias Tribes.Plugin.ManagementContext @default_input_url "rtmp://0.0.0.0:1935/live/source" @@ -86,7 +86,7 @@ defmodule Sender.LocalStreams do @doc """ Start a local generated ffmpeg test-video publisher. - The publisher is started under `Sender.MediaBackendSupervisor` by default, so + The publisher is started under `TribeOne.TribesPlugin.Sender.MediaBackendSupervisor` by default, so it is not linked to the IEx shell process. Pass `duration: seconds` to make it exit on its own. """ @@ -291,7 +291,8 @@ defmodule Sender.LocalStreams do end defp start_muontrap(executable, args, opts) do - supervisor = Keyword.get(opts, :supervisor, Sender.MediaBackendSupervisor) + supervisor = + Keyword.get(opts, :supervisor, TribeOne.TribesPlugin.Sender.MediaBackendSupervisor) child_spec = %{ id: {__MODULE__, :test_video, System.unique_integer([:positive, :monotonic])}, diff --git a/lib/sender/management.ex b/lib/sender/management.ex index c43c6ad..be5a117 100644 --- a/lib/sender/management.ex +++ b/lib/sender/management.ex @@ -1,10 +1,10 @@ -defmodule Sender.Management do +defmodule TribeOne.TribesPlugin.Sender.Management do @moduledoc false require Ash.Query - alias Sender.Streaming - alias Sender.Streaming.{Stream, StreamGeneration} + alias TribeOne.TribesPlugin.Sender.Streaming + alias TribeOne.TribesPlugin.Sender.Streaming.{Stream, StreamGeneration} @default_stream_slug "default" diff --git a/lib/sender/management/capabilities.ex b/lib/sender/management/capabilities.ex index d1717e3..9aa1b8c 100644 --- a/lib/sender/management/capabilities.ex +++ b/lib/sender/management/capabilities.ex @@ -1,4 +1,4 @@ -defmodule Sender.Management.Capabilities do +defmodule TribeOne.TribesPlugin.Sender.Management.Capabilities do @moduledoc false def call(_context, _params) do @@ -7,7 +7,7 @@ defmodule Sender.Management.Capabilities do "ok" => true, "schema_version" => "1", "single_active_stream" => true, - "default_stream_slug" => Sender.Management.default_stream_slug(), + "default_stream_slug" => TribeOne.TribesPlugin.Sender.Management.default_stream_slug(), "stream_modes" => ["origin_ingest", "hls_edge"], "endpoint_types" => ["tribes_origin", "tribes_edge", "external_origin", "external_edge"], "management_methods" => [ diff --git a/lib/sender/management/endpoint_snapshots.ex b/lib/sender/management/endpoint_snapshots.ex index 72957ef..f4cc00c 100644 --- a/lib/sender/management/endpoint_snapshots.ex +++ b/lib/sender/management/endpoint_snapshots.ex @@ -1,9 +1,9 @@ -defmodule Sender.Management.EndpointSnapshots do +defmodule TribeOne.TribesPlugin.Sender.Management.EndpointSnapshots do @moduledoc false - alias Sender.Management - alias Sender.SnapshotPublisher - alias Sender.Streaming + alias TribeOne.TribesPlugin.Sender.Management + alias TribeOne.TribesPlugin.Sender.SnapshotPublisher + alias TribeOne.TribesPlugin.Sender.Streaming def report(context, params) do ash_opts = Management.ash_opts(context) diff --git a/lib/sender/management/stream.ex b/lib/sender/management/stream.ex index a25aeef..a1e2338 100644 --- a/lib/sender/management/stream.ex +++ b/lib/sender/management/stream.ex @@ -1,8 +1,8 @@ -defmodule Sender.Management.Stream do +defmodule TribeOne.TribesPlugin.Sender.Management.Stream do @moduledoc false - alias Sender.Management - alias Sender.Streaming + alias TribeOne.TribesPlugin.Sender.Management + alias TribeOne.TribesPlugin.Sender.Streaming def get_default(context, params) do ash_opts = Management.ash_opts(context) diff --git a/lib/sender/management/stream_key.ex b/lib/sender/management/stream_key.ex index 07707e8..ff87ea4 100644 --- a/lib/sender/management/stream_key.ex +++ b/lib/sender/management/stream_key.ex @@ -1,9 +1,9 @@ -defmodule Sender.Management.StreamKey do +defmodule TribeOne.TribesPlugin.Sender.Management.StreamKey do @moduledoc false - alias Sender.Management - alias Sender.Streaming - alias Sender.Streaming.StreamKeys + alias TribeOne.TribesPlugin.Sender.Management + alias TribeOne.TribesPlugin.Sender.Streaming + alias TribeOne.TribesPlugin.Sender.Streaming.StreamKeys def create(context, params) do ash_opts = Management.ash_opts(context) diff --git a/lib/sender/management/stream_lifecycle.ex b/lib/sender/management/stream_lifecycle.ex index f7238bb..469cb16 100644 --- a/lib/sender/management/stream_lifecycle.ex +++ b/lib/sender/management/stream_lifecycle.ex @@ -1,9 +1,9 @@ -defmodule Sender.Management.StreamLifecycle do +defmodule TribeOne.TribesPlugin.Sender.Management.StreamLifecycle do @moduledoc false - alias Sender.{HLS, HLSCleanup, Management, MediaSession} - alias Sender.Management.Topology - alias Sender.Streaming + alias TribeOne.TribesPlugin.Sender.{HLS, HLSCleanup, Management, MediaSession} + alias TribeOne.TribesPlugin.Sender.Management.Topology + alias TribeOne.TribesPlugin.Sender.Streaming def start(context, params) do ash_opts = Management.ash_opts(context) @@ -66,7 +66,8 @@ defmodule Sender.Management.StreamLifecycle do end defp start_hls_readiness_watcher(generation, rendition, params) do - supervisor = Map.get(params, "readiness_supervisor", Sender.HLSReadinessSupervisor) + supervisor = + Map.get(params, "readiness_supervisor", TribeOne.TribesPlugin.Sender.HLSReadinessSupervisor) opts = [ @@ -76,7 +77,10 @@ defmodule Sender.Management.StreamLifecycle do ] |> maybe_put_opt(:poll_interval_ms, Map.get(params, "readiness_poll_interval_ms")) - case DynamicSupervisor.start_child(supervisor, {Sender.HLSReadinessWatcher, opts}) do + case DynamicSupervisor.start_child( + supervisor, + {TribeOne.TribesPlugin.Sender.HLSReadinessWatcher, opts} + ) do {:ok, _pid} -> :ok {:error, {:already_started, _pid}} -> :ok {:error, _reason} = error -> error diff --git a/lib/sender/management/topology.ex b/lib/sender/management/topology.ex index 5afb827..9f0ad95 100644 --- a/lib/sender/management/topology.ex +++ b/lib/sender/management/topology.ex @@ -1,8 +1,8 @@ -defmodule Sender.Management.Topology do +defmodule TribeOne.TribesPlugin.Sender.Management.Topology do @moduledoc false - alias Sender.Management - alias Sender.Streaming + alias TribeOne.TribesPlugin.Sender.Management + alias TribeOne.TribesPlugin.Sender.Streaming def upsert_endpoint(context, params) do with {:ok, attrs} <- endpoint_attrs(params), diff --git a/lib/sender/media_backend.ex b/lib/sender/media_backend.ex index 4faadd9..4861576 100644 --- a/lib/sender/media_backend.ex +++ b/lib/sender/media_backend.ex @@ -1,9 +1,9 @@ -defmodule Sender.MediaBackend do +defmodule TribeOne.TribesPlugin.Sender.MediaBackend do @moduledoc """ Behaviour for local media ingest/transmux backends. """ - alias Sender.Streaming.StreamGeneration + alias TribeOne.TribesPlugin.Sender.Streaming.StreamGeneration @callback start_ingest(StreamGeneration.t(), keyword()) :: {:ok, pid()} | {:error, term()} diff --git a/lib/sender/media_backend/ffmpeg.ex b/lib/sender/media_backend/ffmpeg.ex index 56a7902..b2ef176 100644 --- a/lib/sender/media_backend/ffmpeg.ex +++ b/lib/sender/media_backend/ffmpeg.ex @@ -1,4 +1,4 @@ -defmodule Sender.MediaBackend.FFmpeg do +defmodule TribeOne.TribesPlugin.Sender.MediaBackend.FFmpeg do @moduledoc """ ffmpeg-backed RTMP-to-HLS media backend. @@ -7,18 +7,19 @@ defmodule Sender.MediaBackend.FFmpeg do flags without launching a long-running media process. """ - @behaviour Sender.MediaBackend + @behaviour TribeOne.TribesPlugin.Sender.MediaBackend - alias Sender.HLS - alias Sender.MediaBackend.FFmpeg.Worker - alias Sender.Streaming.StreamGeneration + alias TribeOne.TribesPlugin.Sender.HLS + alias TribeOne.TribesPlugin.Sender.MediaBackend.FFmpeg.Worker + alias TribeOne.TribesPlugin.Sender.Streaming.StreamGeneration @default_rtmp_bind_url "rtmp://0.0.0.0:1935/live/source" @executable_env "SENDER_FFMPEG_EXECUTABLE" @impl true def start_ingest(%StreamGeneration{} = generation, opts \\ []) do - supervisor = Keyword.get(opts, :supervisor, Sender.MediaBackendSupervisor) + supervisor = + Keyword.get(opts, :supervisor, TribeOne.TribesPlugin.Sender.MediaBackendSupervisor) DynamicSupervisor.start_child( supervisor, diff --git a/lib/sender/media_backend/ffmpeg/worker.ex b/lib/sender/media_backend/ffmpeg/worker.ex index 1b1accd..dfc4c1a 100644 --- a/lib/sender/media_backend/ffmpeg/worker.ex +++ b/lib/sender/media_backend/ffmpeg/worker.ex @@ -1,11 +1,11 @@ -defmodule Sender.MediaBackend.FFmpeg.Worker do +defmodule TribeOne.TribesPlugin.Sender.MediaBackend.FFmpeg.Worker do @moduledoc false use GenServer require Logger - alias Sender.MediaBackend.FFmpeg + alias TribeOne.TribesPlugin.Sender.MediaBackend.FFmpeg def child_spec(opts) do generation = Keyword.fetch!(opts, :generation) diff --git a/lib/sender/media_backend/hls_pull_through.ex b/lib/sender/media_backend/hls_pull_through.ex index 3f291cc..d8829f6 100644 --- a/lib/sender/media_backend/hls_pull_through.ex +++ b/lib/sender/media_backend/hls_pull_through.ex @@ -1,4 +1,4 @@ -defmodule Sender.MediaBackend.HLSPullThrough do +defmodule TribeOne.TribesPlugin.Sender.MediaBackend.HLSPullThrough do @moduledoc """ HLS pull-through backend for `tribes_edge` endpoints. @@ -7,14 +7,15 @@ defmodule Sender.MediaBackend.HLSPullThrough do Vinyl can then serve the local files from `/sender/hls/...`. """ - @behaviour Sender.MediaBackend + @behaviour TribeOne.TribesPlugin.Sender.MediaBackend - alias Sender.MediaBackend.HLSPullThrough.Worker - alias Sender.Streaming.StreamGeneration + alias TribeOne.TribesPlugin.Sender.MediaBackend.HLSPullThrough.Worker + alias TribeOne.TribesPlugin.Sender.Streaming.StreamGeneration @impl true def start_ingest(%StreamGeneration{} = generation, opts \\ []) do - supervisor = Keyword.get(opts, :supervisor, Sender.MediaBackendSupervisor) + supervisor = + Keyword.get(opts, :supervisor, TribeOne.TribesPlugin.Sender.MediaBackendSupervisor) DynamicSupervisor.start_child( supervisor, diff --git a/lib/sender/media_backend/hls_pull_through/worker.ex b/lib/sender/media_backend/hls_pull_through/worker.ex index 72359f6..f7a146a 100644 --- a/lib/sender/media_backend/hls_pull_through/worker.ex +++ b/lib/sender/media_backend/hls_pull_through/worker.ex @@ -1,11 +1,11 @@ -defmodule Sender.MediaBackend.HLSPullThrough.Worker do +defmodule TribeOne.TribesPlugin.Sender.MediaBackend.HLSPullThrough.Worker do @moduledoc false use GenServer require Logger - alias Sender.HLS + alias TribeOne.TribesPlugin.Sender.HLS @default_poll_interval_ms 1_000 diff --git a/lib/sender/media_session.ex b/lib/sender/media_session.ex index 2b04558..7994ea8 100644 --- a/lib/sender/media_session.ex +++ b/lib/sender/media_session.ex @@ -1,4 +1,4 @@ -defmodule Sender.MediaSession do +defmodule TribeOne.TribesPlugin.Sender.MediaSession do @moduledoc """ Owns one local node's runtime participation in a stream generation. @@ -10,8 +10,8 @@ defmodule Sender.MediaSession do use GenServer - alias Sender.MediaBackend.{FFmpeg, HLSPullThrough} - alias Sender.Streaming.StreamGeneration + alias TribeOne.TribesPlugin.Sender.MediaBackend.{FFmpeg, HLSPullThrough} + alias TribeOne.TribesPlugin.Sender.Streaming.StreamGeneration @roles [:origin_ingest, :hls_edge] @session_keys [ @@ -58,7 +58,8 @@ defmodule Sender.MediaSession do DynamicSupervisor.on_start_child() def start_role(role, %StreamGeneration{} = generation, opts) when role in @roles and is_list(opts) do - supervisor = Keyword.get(opts, :session_supervisor, Sender.MediaSessionSupervisor) + supervisor = + Keyword.get(opts, :session_supervisor, TribeOne.TribesPlugin.Sender.MediaSessionSupervisor) DynamicSupervisor.start_child( supervisor, @@ -88,7 +89,7 @@ defmodule Sender.MediaSession do @spec whereis(role(), String.t()) :: pid() | nil def whereis(role, generation_id) when role in @roles and is_binary(generation_id) do - case Registry.lookup(Sender.MediaSessionRegistry, {role, generation_id}) do + case Registry.lookup(TribeOne.TribesPlugin.Sender.MediaSessionRegistry, {role, generation_id}) do [{pid, _value}] -> pid [] -> nil end @@ -106,7 +107,9 @@ defmodule Sender.MediaSession do role = Keyword.fetch!(opts, :role) GenServer.start_link(__MODULE__, opts, - name: {:via, Registry, {Sender.MediaSessionRegistry, {role, generation.id}}} + name: + {:via, Registry, + {TribeOne.TribesPlugin.Sender.MediaSessionRegistry, {role, generation.id}}} ) end @@ -199,7 +202,8 @@ defmodule Sender.MediaSession do end defp backend_opts(opts) do - backend_supervisor = Keyword.get(opts, :backend_supervisor, Sender.MediaBackendSupervisor) + backend_supervisor = + Keyword.get(opts, :backend_supervisor, TribeOne.TribesPlugin.Sender.MediaBackendSupervisor) opts |> Keyword.drop(@session_keys) diff --git a/lib/sender/playback.ex b/lib/sender/playback.ex index f44c4b9..d576bc4 100644 --- a/lib/sender/playback.ex +++ b/lib/sender/playback.ex @@ -1,12 +1,12 @@ -defmodule Sender.Playback do +defmodule TribeOne.TribesPlugin.Sender.Playback do @moduledoc """ Builds browser playback metadata from synced stream state. """ require Ash.Query - alias Sender.Streaming - alias Sender.Streaming.{Rendition, Stream, StreamGeneration} + alias TribeOne.TribesPlugin.Sender.Streaming + alias TribeOne.TribesPlugin.Sender.Streaming.{Rendition, Stream, StreamGeneration} @hls_mime "application/vnd.apple.mpegurl" diff --git a/lib/sender/plugin.ex b/lib/sender/plugin.ex index 67a0326..490a28b 100644 --- a/lib/sender/plugin.ex +++ b/lib/sender/plugin.ex @@ -1,4 +1,4 @@ -defmodule Sender.Plugin do +defmodule TribeOne.TribesPlugin.Sender.Plugin do @moduledoc """ Tribes plugin entry point. """ @@ -21,20 +21,20 @@ defmodule Sender.Plugin do pages: [ %{ path: "/sender", - live_view: SenderWeb.HomeLive, + live_view: TribeOne.TribesPlugin.SenderWeb.HomeLive, layout: nil } ], - children: Sender.Application.children(), + children: TribeOne.TribesPlugin.Sender.Application.children(), api_routes: [ - {"streams", SenderWeb.StreamingAPIPlug}, - {"player-events", SenderWeb.StreamingAPIPlug} + {"streams", TribeOne.TribesPlugin.SenderWeb.StreamingAPIPlug}, + {"player-events", TribeOne.TribesPlugin.SenderWeb.StreamingAPIPlug} ], management_methods: [ %{ name: "capabilities", version: "1", - module: Sender.Management.Capabilities, + module: TribeOne.TribesPlugin.Sender.Management.Capabilities, action: :call, auth: :admin, description: "Describe Sender management capabilities." @@ -42,7 +42,7 @@ defmodule Sender.Plugin do %{ name: "stream.get_default", version: "1", - module: Sender.Management.Stream, + module: TribeOne.TribesPlugin.Sender.Management.Stream, action: :get_default, auth: :admin, description: "Fetch the default Sender stream." @@ -50,7 +50,7 @@ defmodule Sender.Plugin do %{ name: "stream.ensure_default", version: "1", - module: Sender.Management.Stream, + module: TribeOne.TribesPlugin.Sender.Management.Stream, action: :ensure_default, auth: :admin, description: "Create the default Sender stream if it does not exist." @@ -58,7 +58,7 @@ defmodule Sender.Plugin do %{ name: "stream.update_default", version: "1", - module: Sender.Management.Stream, + module: TribeOne.TribesPlugin.Sender.Management.Stream, action: :update_default, auth: :admin, description: "Update the default Sender stream." @@ -66,7 +66,7 @@ defmodule Sender.Plugin do %{ name: "stream_key.create", version: "1", - module: Sender.Management.StreamKey, + module: TribeOne.TribesPlugin.Sender.Management.StreamKey, action: :create, auth: :admin, description: "Create an OBS stream key and return it once.", @@ -75,7 +75,7 @@ defmodule Sender.Plugin do %{ name: "media_endpoints.upsert", version: "1", - module: Sender.Management.Topology, + module: TribeOne.TribesPlugin.Sender.Management.Topology, action: :upsert_endpoint, auth: :admin, description: "Create or update a Sender media endpoint." @@ -83,7 +83,7 @@ defmodule Sender.Plugin do %{ name: "renditions.upsert", version: "1", - module: Sender.Management.Topology, + module: TribeOne.TribesPlugin.Sender.Management.Topology, action: :upsert_rendition, auth: :admin, description: "Create or update Sender rendition metadata." @@ -91,7 +91,7 @@ defmodule Sender.Plugin do %{ name: "stream.start", version: "1", - module: Sender.Management.StreamLifecycle, + module: TribeOne.TribesPlugin.Sender.Management.StreamLifecycle, action: :start, auth: :admin, description: "Start the default stream in origin_ingest or hls_edge mode." @@ -99,7 +99,7 @@ defmodule Sender.Plugin do %{ name: "stream.stop", version: "1", - module: Sender.Management.StreamLifecycle, + module: TribeOne.TribesPlugin.Sender.Management.StreamLifecycle, action: :stop, auth: :admin, description: "Stop the active Sender stream generation." @@ -107,7 +107,7 @@ defmodule Sender.Plugin do %{ name: "stream.status", version: "1", - module: Sender.Management.StreamLifecycle, + module: TribeOne.TribesPlugin.Sender.Management.StreamLifecycle, action: :status, auth: :admin, description: "Return current Sender stream and session status." @@ -115,7 +115,7 @@ defmodule Sender.Plugin do %{ name: "endpoint_snapshots.report", version: "1", - module: Sender.Management.EndpointSnapshots, + module: TribeOne.TribesPlugin.Sender.Management.EndpointSnapshots, action: :report, auth: :admin, description: "Report current endpoint stats for playback routing." @@ -150,10 +150,10 @@ defmodule Sender.Plugin do } ], plugs: [ - {"/sender/hls", SenderWeb.HLSPlug, []} + {"/sender/hls", TribeOne.TribesPlugin.SenderWeb.HLSPlug, []} ], hooks: %{}, - ash_domains: [Sender.Streaming], + ash_domains: [TribeOne.TribesPlugin.Sender.Streaming], config_schema: %{ title: "Sender", description: "RTMP ingest and HLS playback defaults for this Tribes cluster.", diff --git a/lib/sender/snapshot_publisher.ex b/lib/sender/snapshot_publisher.ex index 668f059..f8c1004 100644 --- a/lib/sender/snapshot_publisher.ex +++ b/lib/sender/snapshot_publisher.ex @@ -1,9 +1,9 @@ -defmodule Sender.SnapshotPublisher do +defmodule TribeOne.TribesPlugin.Sender.SnapshotPublisher do @moduledoc """ Publishes coarse node-local runtime stats into synced endpoint snapshots. """ - alias Sender.Streaming + alias TribeOne.TribesPlugin.Sender.Streaming @stale_after_ms 15_000 @@ -17,7 +17,7 @@ defmodule Sender.SnapshotPublisher do local = Keyword.get_lazy(opts, :local_stats, fn -> - Sender.Stats.snapshot(endpoint_id, stream_id, generation_id) + TribeOne.TribesPlugin.Sender.Stats.snapshot(endpoint_id, stream_id, generation_id) end) health = derive_health(local, attrs, opts) diff --git a/lib/sender/snapshot_worker.ex b/lib/sender/snapshot_worker.ex index 6103fdb..7dc8fa4 100644 --- a/lib/sender/snapshot_worker.ex +++ b/lib/sender/snapshot_worker.ex @@ -1,9 +1,9 @@ -defmodule Sender.SnapshotWorker do +defmodule TribeOne.TribesPlugin.Sender.SnapshotWorker do @moduledoc false use GenServer - alias Sender.SnapshotPublisher + alias TribeOne.TribesPlugin.Sender.SnapshotPublisher def start_link(opts \\ []) do GenServer.start_link(__MODULE__, opts, name: __MODULE__) diff --git a/lib/sender/stats.ex b/lib/sender/stats.ex index 7a081d1..256acdc 100644 --- a/lib/sender/stats.ex +++ b/lib/sender/stats.ex @@ -1,4 +1,4 @@ -defmodule Sender.Stats do +defmodule TribeOne.TribesPlugin.Sender.Stats do @moduledoc """ Node-local live streaming counters. diff --git a/lib/sender/streaming.ex b/lib/sender/streaming.ex index 3273d9e..f9ce45e 100644 --- a/lib/sender/streaming.ex +++ b/lib/sender/streaming.ex @@ -1,4 +1,4 @@ -defmodule Sender.Streaming do +defmodule TribeOne.TribesPlugin.Sender.Streaming do @moduledoc """ Ash domain for Sender's stream topology and operational metadata. """ @@ -7,44 +7,44 @@ defmodule Sender.Streaming do otp_app: :sender resources do - resource Sender.Streaming.Stream do + resource TribeOne.TribesPlugin.Sender.Streaming.Stream do define(:create_stream, action: :create) define(:update_stream, action: :update) define(:list_streams, action: :read) define(:get_stream, action: :by_id, args: [:id]) end - resource Sender.Streaming.StreamKey do + resource TribeOne.TribesPlugin.Sender.Streaming.StreamKey do define(:create_stream_key, action: :create) define(:list_stream_keys, action: :read) end - resource Sender.Streaming.StreamGeneration do + resource TribeOne.TribesPlugin.Sender.Streaming.StreamGeneration do define(:create_stream_generation, action: :create) define(:update_stream_generation, action: :update) define(:get_stream_generation, action: :by_id, args: [:id]) define(:list_stream_generations, action: :read) end - resource Sender.Streaming.MediaEndpoint do + resource TribeOne.TribesPlugin.Sender.Streaming.MediaEndpoint do define(:upsert_media_endpoint, action: :upsert) define(:list_media_endpoints, action: :read) define(:get_media_endpoint, action: :by_id, args: [:id]) end - resource Sender.Streaming.Rendition do + resource TribeOne.TribesPlugin.Sender.Streaming.Rendition do define(:upsert_rendition, action: :upsert) define(:update_rendition, action: :update) define(:get_rendition, action: :by_id, args: [:id]) define(:list_renditions, action: :read) end - resource Sender.Streaming.EndpointSnapshot do + resource TribeOne.TribesPlugin.Sender.Streaming.EndpointSnapshot do define(:upsert_endpoint_snapshot, action: :upsert) define(:list_endpoint_snapshots, action: :read) end - resource Sender.Streaming.EndpointHistory do + resource TribeOne.TribesPlugin.Sender.Streaming.EndpointHistory do define(:upsert_endpoint_history, action: :upsert) define(:list_endpoint_histories, action: :read) end diff --git a/lib/sender/streaming/endpoint_history.ex b/lib/sender/streaming/endpoint_history.ex index f35ee48..15ea8ed 100644 --- a/lib/sender/streaming/endpoint_history.ex +++ b/lib/sender/streaming/endpoint_history.ex @@ -1,9 +1,9 @@ -defmodule Sender.Streaming.EndpointHistory do +defmodule TribeOne.TribesPlugin.Sender.Streaming.EndpointHistory do @moduledoc false use Ash.Resource, otp_app: :sender, - domain: Sender.Streaming, + domain: TribeOne.TribesPlugin.Sender.Streaming, data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer], extensions: [AshNostrSync] diff --git a/lib/sender/streaming/endpoint_snapshot.ex b/lib/sender/streaming/endpoint_snapshot.ex index 2611aca..895224a 100644 --- a/lib/sender/streaming/endpoint_snapshot.ex +++ b/lib/sender/streaming/endpoint_snapshot.ex @@ -1,9 +1,9 @@ -defmodule Sender.Streaming.EndpointSnapshot do +defmodule TribeOne.TribesPlugin.Sender.Streaming.EndpointSnapshot do @moduledoc false use Ash.Resource, otp_app: :sender, - domain: Sender.Streaming, + domain: TribeOne.TribesPlugin.Sender.Streaming, data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer], extensions: [AshNostrSync] diff --git a/lib/sender/streaming/media_endpoint.ex b/lib/sender/streaming/media_endpoint.ex index 983b40c..e31d9ba 100644 --- a/lib/sender/streaming/media_endpoint.ex +++ b/lib/sender/streaming/media_endpoint.ex @@ -1,9 +1,9 @@ -defmodule Sender.Streaming.MediaEndpoint do +defmodule TribeOne.TribesPlugin.Sender.Streaming.MediaEndpoint do @moduledoc false use Ash.Resource, otp_app: :sender, - domain: Sender.Streaming, + domain: TribeOne.TribesPlugin.Sender.Streaming, data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer], extensions: [AshNostrSync] diff --git a/lib/sender/streaming/rendition.ex b/lib/sender/streaming/rendition.ex index 1a5073a..8cb00b2 100644 --- a/lib/sender/streaming/rendition.ex +++ b/lib/sender/streaming/rendition.ex @@ -1,9 +1,9 @@ -defmodule Sender.Streaming.Rendition do +defmodule TribeOne.TribesPlugin.Sender.Streaming.Rendition do @moduledoc false use Ash.Resource, otp_app: :sender, - domain: Sender.Streaming, + domain: TribeOne.TribesPlugin.Sender.Streaming, data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer], extensions: [AshNostrSync] diff --git a/lib/sender/streaming/resource_helpers.ex b/lib/sender/streaming/resource_helpers.ex index 705555f..b0550ab 100644 --- a/lib/sender/streaming/resource_helpers.ex +++ b/lib/sender/streaming/resource_helpers.ex @@ -1,4 +1,4 @@ -defmodule Sender.Streaming.ResourceHelpers do +defmodule TribeOne.TribesPlugin.Sender.Streaming.ResourceHelpers do @moduledoc false def system_private_context(purpose \\ :sender_streaming) do diff --git a/lib/sender/streaming/stream.ex b/lib/sender/streaming/stream.ex index 221107e..8cb5116 100644 --- a/lib/sender/streaming/stream.ex +++ b/lib/sender/streaming/stream.ex @@ -1,9 +1,9 @@ -defmodule Sender.Streaming.Stream do +defmodule TribeOne.TribesPlugin.Sender.Streaming.Stream do @moduledoc false use Ash.Resource, otp_app: :sender, - domain: Sender.Streaming, + domain: TribeOne.TribesPlugin.Sender.Streaming, data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer], extensions: [AshNostrSync] diff --git a/lib/sender/streaming/stream_generation.ex b/lib/sender/streaming/stream_generation.ex index d04a148..f8678c8 100644 --- a/lib/sender/streaming/stream_generation.ex +++ b/lib/sender/streaming/stream_generation.ex @@ -1,9 +1,9 @@ -defmodule Sender.Streaming.StreamGeneration do +defmodule TribeOne.TribesPlugin.Sender.Streaming.StreamGeneration do @moduledoc false use Ash.Resource, otp_app: :sender, - domain: Sender.Streaming, + domain: TribeOne.TribesPlugin.Sender.Streaming, data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer], extensions: [AshNostrSync] diff --git a/lib/sender/streaming/stream_key.ex b/lib/sender/streaming/stream_key.ex index 1171bfd..64714f8 100644 --- a/lib/sender/streaming/stream_key.ex +++ b/lib/sender/streaming/stream_key.ex @@ -1,9 +1,9 @@ -defmodule Sender.Streaming.StreamKey do +defmodule TribeOne.TribesPlugin.Sender.Streaming.StreamKey do @moduledoc false use Ash.Resource, otp_app: :sender, - domain: Sender.Streaming, + domain: TribeOne.TribesPlugin.Sender.Streaming, data_layer: AshPostgres.DataLayer, authorizers: [Ash.Policy.Authorizer], extensions: [AshNostrSync] diff --git a/lib/sender/streaming/stream_keys.ex b/lib/sender/streaming/stream_keys.ex index dd0d64a..68e98ce 100644 --- a/lib/sender/streaming/stream_keys.ex +++ b/lib/sender/streaming/stream_keys.ex @@ -1,4 +1,4 @@ -defmodule Sender.Streaming.StreamKeys do +defmodule TribeOne.TribesPlugin.Sender.Streaming.StreamKeys do @moduledoc """ Helpers for generating and verifying RTMP stream keys. """ diff --git a/lib/sender_web/hls_plug.ex b/lib/sender_web/hls_plug.ex index ecea1c9..fde3ec5 100644 --- a/lib/sender_web/hls_plug.ex +++ b/lib/sender_web/hls_plug.ex @@ -1,9 +1,9 @@ -defmodule SenderWeb.HLSPlug do +defmodule TribeOne.TribesPlugin.SenderWeb.HLSPlug do @moduledoc false import Plug.Conn - alias Sender.{HLS, Stats} + alias TribeOne.TribesPlugin.Sender.{HLS, Stats} alias Tribes.RequestInfo def init(opts), do: opts diff --git a/lib/sender_web/live/home_live.ex b/lib/sender_web/live/home_live.ex index 607b107..1d81cfd 100644 --- a/lib/sender_web/live/home_live.ex +++ b/lib/sender_web/live/home_live.ex @@ -1,4 +1,4 @@ -defmodule SenderWeb.HomeLive do +defmodule TribeOne.TribesPlugin.SenderWeb.HomeLive do @moduledoc """ Example LiveView page for the plugin. @@ -14,37 +14,36 @@ defmodule SenderWeb.HomeLive do on_mount({Tribes.Plugin.LiveUserAuth, :live_user_optional}) - alias Sender.ChatIntegration + alias TribeOne.TribesPlugin.Sender.ChatIntegration alias Tribes.Plugin.Layouts @chat_component_id "sender-stream-chat-panel" def mount(_params, _session, socket) do - chat_component = - case ChatIntegration.component_module() do - {:ok, component} -> component + chat_slug = ChatIntegration.channel_slug() + + chat_surface = + case ChatIntegration.embedded_panel(chat_slug, + id: @chat_component_id, + backend: :public_sync, + current_user: socket.assigns[:current_user], + session_privkey: socket.assigns[:session_privkey] + ) do + {:ok, surface} -> surface :unavailable -> nil end {:ok, socket |> assign(:page_title, "Streaming") - |> assign(:chat_component, chat_component) - |> assign(:chat_component_id, @chat_component_id) - |> assign(:chat_slug, ChatIntegration.channel_slug())} + |> assign(:chat_surface, chat_surface) + |> assign(:chat_slug, chat_slug)} end - def handle_info( - {:aether_chat, :message, message}, - %{assigns: %{chat_component: component}} = socket - ) - when is_atom(component) do - send_update(component, id: socket.assigns.chat_component_id, incoming_message: message) - {:noreply, socket} + def handle_info(message, socket) do + Tribes.Plugin.Surface.handle_info(socket, socket.assigns[:chat_surface], message) end - def handle_info({:aether_chat, :message, _message}, socket), do: {:noreply, socket} - def render(assigns) do ~H""" @@ -61,8 +60,8 @@ defmodule SenderWeb.HomeLive do
- <.live_component - module={@chat_component} - id={@chat_component_id} - slug={@chat_slug} - backend={:public_sync} - embedded?={true} - current_user={@current_user} - /> +
diff --git a/lib/sender_web/streaming_api_plug.ex b/lib/sender_web/streaming_api_plug.ex index 5c96fa4..0e201ac 100644 --- a/lib/sender_web/streaming_api_plug.ex +++ b/lib/sender_web/streaming_api_plug.ex @@ -1,9 +1,9 @@ -defmodule SenderWeb.StreamingAPIPlug do +defmodule TribeOne.TribesPlugin.SenderWeb.StreamingAPIPlug do @moduledoc false import Plug.Conn - alias Sender.{Playback, Stats} + alias TribeOne.TribesPlugin.Sender.{Playback, Stats} alias Tribes.RequestInfo def init(opts), do: opts diff --git a/lib/tribes/plugins/sender/plugin.ex b/lib/tribes/plugins/sender/plugin.ex deleted file mode 100644 index ef28555..0000000 --- a/lib/tribes/plugins/sender/plugin.ex +++ /dev/null @@ -1,5 +0,0 @@ -defmodule Tribes.Plugins.Sender.Plugin do - @moduledoc false - - defdelegate register(context), to: Sender.Plugin -end diff --git a/manifest.json b/manifest.json index 957dbaf..1b77920 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "name": "sender", "version": "0.1.0", "description": "RTMP ingest and HLS/LL-HLS streaming for Tribes", - "entry_module": "Tribes.Plugins.Sender.Plugin", + "entry_module": "TribeOne.TribesPlugin.Sender.Plugin", "host_api": "1", "otp_app": "sender", "provides": ["sender@1"], diff --git a/mix.exs b/mix.exs index 8603c6a..a4d4c78 100644 --- a/mix.exs +++ b/mix.exs @@ -1,4 +1,4 @@ -defmodule Sender.MixProject do +defmodule TribeOne.TribesPlugin.Sender.MixProject do use Mix.Project def project do @@ -23,7 +23,7 @@ defmodule Sender.MixProject do def application do [ extra_applications: [:logger], - mod: {Sender.Application, []} + mod: {TribeOne.TribesPlugin.Sender.Application, []} ] end diff --git a/mix.lock b/mix.lock index cd07c1f..dc2a1ee 100644 --- a/mix.lock +++ b/mix.lock @@ -61,7 +61,7 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, - "parrhesia": {:git, "https://git.teralink.net/tribes/parrhesia.git", "0bd51b338ad3445830c00ede644270cf9c9d5abc", [ref: "0bd51b338ad3445830c00ede644270cf9c9d5abc"]}, + "parrhesia": {:hex, :parrhesia, "0.14.0", "7ff96a6d645740a19b66567c3346162735ba821a90377616e74493637c35195e", [:mix], [{:bandit, "~> 1.5", [hex: :bandit, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.12", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:lib_secp256k1, "~> 0.7", [hex: :lib_secp256k1, repo: "hexpm", optional: false]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus, "~> 1.1", [hex: :telemetry_metrics_prometheus, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5", [hex: :websock_adapter, repo: "hexpm", optional: false]}, {:websockex, "~> 0.4", [hex: :websockex, repo: "hexpm", optional: false]}], "hexpm", "2bda91e6f385b6990465f2ccc9e8e0266105a31f72e2b466ae247f846ec3ef28"}, "phoenix": {:hex, :phoenix, "1.8.5", "919db335247e6d4891764dc3063415b0d2457641c5f9b3751b5df03d8e20bbcf", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "83b2bb125127e02e9f475c8e3e92736325b5b01b0b9b05407bcb4083b7a32485"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"}, "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, diff --git a/priv/repo/migrations/20260501170000_create_sender_streaming_tables.exs b/priv/repo/migrations/20260501170000_create_sender_streaming_tables.exs index bfcd0cf..3bf96e5 100644 --- a/priv/repo/migrations/20260501170000_create_sender_streaming_tables.exs +++ b/priv/repo/migrations/20260501170000_create_sender_streaming_tables.exs @@ -1,4 +1,4 @@ -defmodule Sender.Repo.Migrations.CreateSenderStreamingTables do +defmodule TribeOne.TribesPlugin.Sender.Repo.Migrations.CreateSenderStreamingTables do use Ecto.Migration def change do diff --git a/scripts/sender_e2e_runner.exs b/scripts/sender_e2e_runner.exs index 1e1a580..76abb6c 100644 --- a/scripts/sender_e2e_runner.exs +++ b/scripts/sender_e2e_runner.exs @@ -1,4 +1,4 @@ -defmodule Sender.E2ERunner do +defmodule TribeOne.TribesPlugin.Sender.E2ERunner do alias Parrhesia.API.Auth @admin_pubkey "985826ac4ec99f6304785ebfaa303ec42973653a682cfa0a06caf9fec662eaa7" @@ -637,4 +637,4 @@ defmodule Sender.E2ERunner do end end -Sender.E2ERunner.main(System.argv()) +TribeOne.TribesPlugin.Sender.E2ERunner.main(System.argv()) diff --git a/test/sender/ffmpeg_backend_test.exs b/test/sender/ffmpeg_backend_test.exs index 84af540..ce72844 100644 --- a/test/sender/ffmpeg_backend_test.exs +++ b/test/sender/ffmpeg_backend_test.exs @@ -1,8 +1,8 @@ -defmodule Sender.FFmpegBackendTest do +defmodule TribeOne.TribesPlugin.Sender.FFmpegBackendTest do use ExUnit.Case, async: false - alias Sender.MediaBackend.FFmpeg - alias Sender.Streaming.StreamGeneration + alias TribeOne.TribesPlugin.Sender.MediaBackend.FFmpeg + alias TribeOne.TribesPlugin.Sender.Streaming.StreamGeneration setup_all do assert System.find_executable("ffmpeg"), "ffmpeg must be available on PATH" diff --git a/test/sender/hls_cleanup_test.exs b/test/sender/hls_cleanup_test.exs index de259b2..3d58bd3 100644 --- a/test/sender/hls_cleanup_test.exs +++ b/test/sender/hls_cleanup_test.exs @@ -1,8 +1,8 @@ -defmodule Sender.HLSCleanupTest do +defmodule TribeOne.TribesPlugin.Sender.HLSCleanupTest do use ExUnit.Case, async: true - alias Sender.HLSCleanup - alias Sender.Streaming.StreamGeneration + alias TribeOne.TribesPlugin.Sender.HLSCleanup + alias TribeOne.TribesPlugin.Sender.Streaming.StreamGeneration test "removes a stopped generation spool after the retention window" do stream_id = Ash.UUID.generate() diff --git a/test/sender/hls_pull_through_test.exs b/test/sender/hls_pull_through_test.exs index 5dd8891..b02d72b 100644 --- a/test/sender/hls_pull_through_test.exs +++ b/test/sender/hls_pull_through_test.exs @@ -1,8 +1,8 @@ -defmodule Sender.HLSPullThroughTest do +defmodule TribeOne.TribesPlugin.Sender.HLSPullThroughTest do use ExUnit.Case, async: true - alias Sender.MediaBackend.HLSPullThrough.Worker - alias Sender.Streaming.StreamGeneration + alias TribeOne.TribesPlugin.Sender.MediaBackend.HLSPullThrough.Worker + alias TribeOne.TribesPlugin.Sender.Streaming.StreamGeneration test "mirrors a live playlist and segments into the local spool" do stream_id = Ash.UUID.generate() diff --git a/test/sender/home_page_test.exs b/test/sender/home_page_test.exs index 97d90e3..0953095 100644 --- a/test/sender/home_page_test.exs +++ b/test/sender/home_page_test.exs @@ -1,21 +1,5 @@ -unless Code.ensure_loaded?(AetherWeb.ChatPanelComponent) do - defmodule AetherWeb.ChatPanelComponent do - use Phoenix.LiveComponent - - @impl true - def update(assigns, socket), do: {:ok, assign(socket, assigns)} - - @impl true - def render(assigns) do - ~H""" -
Chat component
- """ - end - end -end - -defmodule Sender.HomePageTest do - use Tribes.PluginTest.PageCase, plugin: Tribes.Plugins.Sender.Plugin +defmodule TribeOne.TribesPlugin.Sender.HomePageTest do + use Tribes.PluginTest.PageCase, plugin: TribeOne.TribesPlugin.Sender.Plugin test "renders the plugin home page for signed-out visitors", %{conn: conn} do {:ok, view, html} = live(conn, "/sender") @@ -31,7 +15,7 @@ defmodule Sender.HomePageTest do {:ok, view, _html} = live(conn, "/sender") assert has_element?(view, "#sender-stream-chat") - assert has_element?(view, ~s|#dummy-aether-chat-panel[data-slug="sender-stream-default"]|) + assert has_element?(view, ~s|#dummy-chat-panel[data-slug="sender-stream-default"]|) refute has_element?(view, "#sender-chat-frame") end @@ -41,6 +25,39 @@ defmodule Sender.HomePageTest do assert html =~ "Streaming" end + defmodule ChatProvider do + @behaviour Tribes.Capabilities.Chat.V1 + + @impl true + def embedded_panel(slug, _opts) do + {:ok, + Tribes.Plugin.Surface.live_component( + TribeOne.TribesPlugin.Sender.HomePageTest.ChatComponent, + "sender-chat-test-panel", + %{slug: slug}, + capability: "chat@1", + provider: __MODULE__ + )} + end + + @impl true + def standalone_path(slug), do: "/chat/" <> slug + end + + defmodule ChatComponent do + use Phoenix.LiveComponent + + @impl true + def update(assigns, socket), do: {:ok, assign(socket, assigns)} + + @impl true + def render(assigns) do + ~H""" +
Chat component
+ """ + end + end + defp register_chat_provider! do name = "sender_chat_test" _ = Tribes.PluginRegistry.unregister_plugin(name) @@ -52,7 +69,8 @@ defmodule Sender.HomePageTest do name: name, version: "0.1.0", provides: ["chat@1"], - provider_priority: -100 + provider_priority: -100, + entry_module: __MODULE__.ChatProvider }, File.cwd!() ) diff --git a/test/sender/local_streams_test.exs b/test/sender/local_streams_test.exs index 60ab392..a71bde3 100644 --- a/test/sender/local_streams_test.exs +++ b/test/sender/local_streams_test.exs @@ -1,8 +1,8 @@ -defmodule Sender.LocalStreamsTest do - use Sender.DataCase, async: false +defmodule TribeOne.TribesPlugin.Sender.LocalStreamsTest do + use TribeOne.TribesPlugin.Sender.DataCase, async: false - alias Sender.LocalStreams - alias Sender.Management + alias TribeOne.TribesPlugin.Sender.LocalStreams + alias TribeOne.TribesPlugin.Sender.Management alias Tribes.Cluster setup_all do diff --git a/test/sender/management_test.exs b/test/sender/management_test.exs index a85f4ca..1bd083b 100644 --- a/test/sender/management_test.exs +++ b/test/sender/management_test.exs @@ -1,13 +1,21 @@ -defmodule Sender.ManagementTest do - use Sender.DataCase, async: false +defmodule TribeOne.TribesPlugin.Sender.ManagementTest do + use TribeOne.TribesPlugin.Sender.DataCase, async: false import ExUnit.CaptureLog - alias Sender.{EdgeSessionReconciler, MediaSession} - alias Sender.Management - alias Sender.Management.{EndpointSnapshots, Stream, StreamKey, StreamLifecycle, Topology} - alias Sender.Streaming - alias Sender.Streaming.StreamKeys + alias TribeOne.TribesPlugin.Sender.{EdgeSessionReconciler, MediaSession} + alias TribeOne.TribesPlugin.Sender.Management + + alias TribeOne.TribesPlugin.Sender.Management.{ + EndpointSnapshots, + Stream, + StreamKey, + StreamLifecycle, + Topology + } + + alias TribeOne.TribesPlugin.Sender.Streaming + alias TribeOne.TribesPlugin.Sender.Streaming.StreamKeys alias Tribes.Cluster alias Tribes.Plugin.ManagementContext @@ -20,7 +28,7 @@ defmodule Sender.ManagementTest do } defmodule EdgeBackend do - @behaviour Sender.MediaBackend + @behaviour TribeOne.TribesPlugin.Sender.MediaBackend def start_ingest(_generation, opts) do :persistent_term.put({__MODULE__, :last_opts}, opts) @@ -41,7 +49,7 @@ defmodule Sender.ManagementTest do end test "plugin spec declares the Legion-facing management methods" do - spec = Sender.Plugin.register(%{}) + spec = TribeOne.TribesPlugin.Sender.Plugin.register(%{}) methods = Map.new(spec.management_methods, &{&1.name, &1}) assert methods |> Map.keys() |> Enum.sort() == [ @@ -58,31 +66,33 @@ defmodule Sender.ManagementTest do "stream_key.create" ] - assert methods["stream.start"].module == Sender.Management.StreamLifecycle + assert methods["stream.start"].module == + TribeOne.TribesPlugin.Sender.Management.StreamLifecycle + assert methods["stream.start"].action == :start assert methods["stream_key.create"].sensitive_response? end test "plugin spec exposes the Sender runtime supervision children" do - spec = Sender.Plugin.register(%{}) + spec = TribeOne.TribesPlugin.Sender.Plugin.register(%{}) child_ids = spec.children |> Enum.map(&Supervisor.child_spec(&1, [])) |> Enum.map(& &1.id) - assert Sender.Stats in child_ids - assert Sender.SnapshotWorker in child_ids - assert Sender.HLSCleanup in child_ids - assert Sender.MediaSessionRegistry in child_ids - assert Sender.MediaSessionSupervisor in child_ids - assert Sender.MediaBackendSupervisor in child_ids - assert Sender.HLSReadinessSupervisor in child_ids - assert Sender.EdgeSessionReconciler in child_ids + assert TribeOne.TribesPlugin.Sender.Stats in child_ids + assert TribeOne.TribesPlugin.Sender.SnapshotWorker in child_ids + assert TribeOne.TribesPlugin.Sender.HLSCleanup in child_ids + assert TribeOne.TribesPlugin.Sender.MediaSessionRegistry in child_ids + assert TribeOne.TribesPlugin.Sender.MediaSessionSupervisor in child_ids + assert TribeOne.TribesPlugin.Sender.MediaBackendSupervisor in child_ids + assert TribeOne.TribesPlugin.Sender.HLSReadinessSupervisor in child_ids + assert TribeOne.TribesPlugin.Sender.EdgeSessionReconciler in child_ids end test "plugin spec declares sender-owned stream rollup metrics" do - spec = Sender.Plugin.register(%{}) + spec = TribeOne.TribesPlugin.Sender.Plugin.register(%{}) metrics = Map.new(spec.metrics, &{&1.name, &1}) assert metrics["viewer_count"].query =~ "vinyl_hls_active_viewers" @@ -211,9 +221,17 @@ defmodule Sender.ManagementTest do spool_root = Path.join(System.tmp_dir!(), "sender-management-test-#{System.unique_integer([:positive])}") - previous_hls = Application.get_env(:sender, Sender.HLS, []) - Application.put_env(:sender, Sender.HLS, Keyword.put(previous_hls, :spool_root, spool_root)) - on_exit(fn -> Application.put_env(:sender, Sender.HLS, previous_hls) end) + previous_hls = Application.get_env(:sender, TribeOne.TribesPlugin.Sender.HLS, []) + + Application.put_env( + :sender, + TribeOne.TribesPlugin.Sender.HLS, + Keyword.put(previous_hls, :spool_root, spool_root) + ) + + on_exit(fn -> + Application.put_env(:sender, TribeOne.TribesPlugin.Sender.HLS, previous_hls) + end) assert {:ok, %{"endpoint" => endpoint}} = Topology.upsert_endpoint(@context, %{ diff --git a/test/sender/media_session_test.exs b/test/sender/media_session_test.exs index 03ac837..de0a6eb 100644 --- a/test/sender/media_session_test.exs +++ b/test/sender/media_session_test.exs @@ -1,5 +1,5 @@ -defmodule Sender.MediaSessionTest.DisconnectingBackend do - @behaviour Sender.MediaBackend +defmodule TribeOne.TribesPlugin.Sender.MediaSessionTest.DisconnectingBackend do + @behaviour TribeOne.TribesPlugin.Sender.MediaBackend @impl true def start_ingest(generation, opts) do @@ -32,11 +32,11 @@ defmodule Sender.MediaSessionTest.DisconnectingBackend do end end -defmodule Sender.MediaSessionTest do +defmodule TribeOne.TribesPlugin.Sender.MediaSessionTest do use ExUnit.Case, async: true - alias Sender.MediaSession - alias Sender.Streaming.StreamGeneration + alias TribeOne.TribesPlugin.Sender.MediaSession + alias TribeOne.TribesPlugin.Sender.Streaming.StreamGeneration test "origin_ingest keeps the session alive across backend disconnects" do generation = generation() @@ -44,7 +44,7 @@ defmodule Sender.MediaSessionTest do assert {:ok, pid} = MediaSession.start_origin_ingest(generation, - backend: Sender.MediaSessionTest.DisconnectingBackend, + backend: TribeOne.TribesPlugin.Sender.MediaSessionTest.DisconnectingBackend, owner: self(), exit_after_ms: 10, backend_restart_delay_ms: 10, @@ -64,7 +64,7 @@ defmodule Sender.MediaSessionTest do assert {:ok, pid} = MediaSession.start_origin_ingest(generation, - backend: Sender.MediaSessionTest.DisconnectingBackend, + backend: TribeOne.TribesPlugin.Sender.MediaSessionTest.DisconnectingBackend, owner: self(), exit_after_ms: 10, backend_restart_delay_ms: 1_000 diff --git a/test/sender/playback_api_test.exs b/test/sender/playback_api_test.exs index b8f1a00..8031ef8 100644 --- a/test/sender/playback_api_test.exs +++ b/test/sender/playback_api_test.exs @@ -1,7 +1,7 @@ -defmodule Sender.PlaybackAPITest do - use Tribes.PluginTest.PageCase, plugin: Tribes.Plugins.Sender.Plugin +defmodule TribeOne.TribesPlugin.Sender.PlaybackAPITest do + use Tribes.PluginTest.PageCase, plugin: TribeOne.TribesPlugin.Sender.Plugin - alias Sender.{Stats, Streaming} + alias TribeOne.TribesPlugin.Sender.{Stats, Streaming} @ash_opts [authorize?: false, context: %{ash_nostr_sync_skip_publish: true}] @@ -60,10 +60,15 @@ defmodule Sender.PlaybackAPITest do File.mkdir_p!(path) File.write!(Path.join(path, "media.m3u8"), "#EXTM3U\n") - previous = Application.get_env(:sender, Sender.HLS, []) - Application.put_env(:sender, Sender.HLS, Keyword.put(previous, :spool_root, root)) + previous = Application.get_env(:sender, TribeOne.TribesPlugin.Sender.HLS, []) - on_exit(fn -> Application.put_env(:sender, Sender.HLS, previous) end) + Application.put_env( + :sender, + TribeOne.TribesPlugin.Sender.HLS, + Keyword.put(previous, :spool_root, root) + ) + + on_exit(fn -> Application.put_env(:sender, TribeOne.TribesPlugin.Sender.HLS, previous) end) conn = conn @@ -85,10 +90,15 @@ defmodule Sender.PlaybackAPITest do File.mkdir_p!(path) File.write!(Path.join(path, "media.m3u8"), "#EXTM3U\n") - previous = Application.get_env(:sender, Sender.HLS, []) - Application.put_env(:sender, Sender.HLS, Keyword.put(previous, :spool_root, root)) + previous = Application.get_env(:sender, TribeOne.TribesPlugin.Sender.HLS, []) - on_exit(fn -> Application.put_env(:sender, Sender.HLS, previous) end) + Application.put_env( + :sender, + TribeOne.TribesPlugin.Sender.HLS, + Keyword.put(previous, :spool_root, root) + ) + + on_exit(fn -> Application.put_env(:sender, TribeOne.TribesPlugin.Sender.HLS, previous) end) conn |> Map.put(:remote_ip, {203, 0, 113, 20}) @@ -109,10 +119,15 @@ defmodule Sender.PlaybackAPITest do File.mkdir_p!(path) File.write!(Path.join(path, "media.m3u8"), "#EXTM3U\n") - previous = Application.get_env(:sender, Sender.HLS, []) - Application.put_env(:sender, Sender.HLS, Keyword.put(previous, :spool_root, root)) + previous = Application.get_env(:sender, TribeOne.TribesPlugin.Sender.HLS, []) - on_exit(fn -> Application.put_env(:sender, Sender.HLS, previous) end) + Application.put_env( + :sender, + TribeOne.TribesPlugin.Sender.HLS, + Keyword.put(previous, :spool_root, root) + ) + + on_exit(fn -> Application.put_env(:sender, TribeOne.TribesPlugin.Sender.HLS, previous) end) conn = conn @@ -141,7 +156,7 @@ defmodule Sender.PlaybackAPITest do ) |> Map.put(:remote_ip, {203, 0, 113, 30}) |> put_req_header("content-type", "application/json") - |> SenderWeb.StreamingAPIPlug.call([]) + |> TribeOne.TribesPlugin.SenderWeb.StreamingAPIPlug.call([]) assert decoded_response(conn, 202) == %{"ok" => true, "viewer_count" => 1} assert Stats.viewer_count(stream_id, generation_id) == 1 @@ -165,7 +180,7 @@ defmodule Sender.PlaybackAPITest do |> Map.put(:remote_ip, {127, 0, 0, 1}) |> put_req_header("content-type", "application/json") |> put_req_header("x-forwarded-for", "203.0.113.30") - |> SenderWeb.StreamingAPIPlug.call([]) + |> TribeOne.TribesPlugin.SenderWeb.StreamingAPIPlug.call([]) assert decoded_response(conn, 202) == %{"ok" => true} assert Stats.viewer_count(stream_id, generation_id) == 0 @@ -179,10 +194,15 @@ defmodule Sender.PlaybackAPITest do File.mkdir_p!(path) File.write!(Path.join(path, "media.m3u8"), "#EXTM3U\n") - previous = Application.get_env(:sender, Sender.HLS, []) - Application.put_env(:sender, Sender.HLS, Keyword.put(previous, :spool_root, root)) + previous = Application.get_env(:sender, TribeOne.TribesPlugin.Sender.HLS, []) - on_exit(fn -> Application.put_env(:sender, Sender.HLS, previous) end) + Application.put_env( + :sender, + TribeOne.TribesPlugin.Sender.HLS, + Keyword.put(previous, :spool_root, root) + ) + + on_exit(fn -> Application.put_env(:sender, TribeOne.TribesPlugin.Sender.HLS, previous) end) conn = get(conn, "/sender/hls/stream/generation/source/media.m3u8") diff --git a/test/sender/plugin_contract_test.exs b/test/sender/plugin_contract_test.exs index c3ff866..02af4b4 100644 --- a/test/sender/plugin_contract_test.exs +++ b/test/sender/plugin_contract_test.exs @@ -1,3 +1,3 @@ -defmodule Sender.PluginContractTest do - use Tribes.PluginTest.ContractTest, plugin: Tribes.Plugins.Sender.Plugin +defmodule TribeOne.TribesPlugin.Sender.PluginContractTest do + use Tribes.PluginTest.ContractTest, plugin: TribeOne.TribesPlugin.Sender.Plugin end diff --git a/test/sender/stats_snapshot_test.exs b/test/sender/stats_snapshot_test.exs index d651713..1106fb8 100644 --- a/test/sender/stats_snapshot_test.exs +++ b/test/sender/stats_snapshot_test.exs @@ -1,8 +1,8 @@ -defmodule Sender.StatsSnapshotTest do - use Sender.DataCase, async: false +defmodule TribeOne.TribesPlugin.Sender.StatsSnapshotTest do + use TribeOne.TribesPlugin.Sender.DataCase, async: false - alias Sender.{SnapshotPublisher, Stats} - alias Sender.Streaming + alias TribeOne.TribesPlugin.Sender.{SnapshotPublisher, Stats} + alias TribeOne.TribesPlugin.Sender.Streaming @ash_opts [authorize?: false, context: %{ash_nostr_sync_skip_publish: true}] diff --git a/test/sender/stream_keys_test.exs b/test/sender/stream_keys_test.exs index 19b665d..53781bd 100644 --- a/test/sender/stream_keys_test.exs +++ b/test/sender/stream_keys_test.exs @@ -1,7 +1,7 @@ -defmodule Sender.StreamKeysTest do +defmodule TribeOne.TribesPlugin.Sender.StreamKeysTest do use ExUnit.Case, async: true - alias Sender.Streaming.StreamKeys + alias TribeOne.TribesPlugin.Sender.Streaming.StreamKeys test "generates verifiable stream keys without storing raw secrets" do key = StreamKeys.generate_key() diff --git a/test/sender/streaming_schema_test.exs b/test/sender/streaming_schema_test.exs index 47091ab..2068d27 100644 --- a/test/sender/streaming_schema_test.exs +++ b/test/sender/streaming_schema_test.exs @@ -1,19 +1,27 @@ -defmodule Sender.StreamingSchemaTest do - use Sender.DataCase, async: false +defmodule TribeOne.TribesPlugin.Sender.StreamingSchemaTest do + use TribeOne.TribesPlugin.Sender.DataCase, async: false - alias Sender.Streaming + alias TribeOne.TribesPlugin.Sender.Streaming alias Tribes.Cluster @ash_opts [authorize?: false, context: %{ash_nostr_sync_skip_publish: true}] test "sender sync resources declare control and bulk lanes" do - assert AshNostrSync.Info.lane(Sender.Streaming.Stream) == :control - assert AshNostrSync.Info.lane(Sender.Streaming.StreamGeneration) == :control - assert AshNostrSync.Info.lane(Sender.Streaming.MediaEndpoint) == :control - assert AshNostrSync.Info.lane(Sender.Streaming.Rendition) == :control - assert AshNostrSync.Info.lane(Sender.Streaming.StreamKey) == :control - assert AshNostrSync.Info.lane(Sender.Streaming.EndpointSnapshot) == :bulk - assert AshNostrSync.Info.lane(Sender.Streaming.EndpointHistory) == :bulk + assert AshNostrSync.Info.lane(TribeOne.TribesPlugin.Sender.Streaming.Stream) == :control + + assert AshNostrSync.Info.lane(TribeOne.TribesPlugin.Sender.Streaming.StreamGeneration) == + :control + + assert AshNostrSync.Info.lane(TribeOne.TribesPlugin.Sender.Streaming.MediaEndpoint) == + :control + + assert AshNostrSync.Info.lane(TribeOne.TribesPlugin.Sender.Streaming.Rendition) == :control + assert AshNostrSync.Info.lane(TribeOne.TribesPlugin.Sender.Streaming.StreamKey) == :control + + assert AshNostrSync.Info.lane(TribeOne.TribesPlugin.Sender.Streaming.EndpointSnapshot) == + :bulk + + assert AshNostrSync.Info.lane(TribeOne.TribesPlugin.Sender.Streaming.EndpointHistory) == :bulk end test "creates the core stream topology records" do @@ -139,9 +147,9 @@ defmodule Sender.StreamingSchemaTest do end test "plugin spec exposes the streaming Ash domain" do - spec = Sender.Plugin.register(%{}) + spec = TribeOne.TribesPlugin.Sender.Plugin.register(%{}) - assert Sender.Streaming in spec.ash_domains + assert TribeOne.TribesPlugin.Sender.Streaming in spec.ash_domains end defp cluster_node(pubkey) do diff --git a/test/support/sender/data_case.ex b/test/support/sender/data_case.ex index c4958d8..d06b13f 100644 --- a/test/support/sender/data_case.ex +++ b/test/support/sender/data_case.ex @@ -1,11 +1,11 @@ -defmodule Sender.DataCase do +defmodule TribeOne.TribesPlugin.Sender.DataCase do @moduledoc false use ExUnit.CaseTemplate using do quote do - import Sender.DataCase + import TribeOne.TribesPlugin.Sender.DataCase end end