Files
self f8e2bfaada 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.
2026-05-26 01:13:38 +02:00

84 lines
2.5 KiB
Elixir

defmodule TribeOne.TribesPlugin.Sender.Playback do
@moduledoc """
Builds browser playback metadata from synced stream state.
"""
require Ash.Query
alias TribeOne.TribesPlugin.Sender.Streaming
alias TribeOne.TribesPlugin.Sender.Streaming.{Rendition, Stream, StreamGeneration}
@hls_mime "application/vnd.apple.mpegurl"
@spec playback_info(String.t(), keyword()) :: {:ok, map()} | {:error, term()}
def playback_info(stream_id, opts \\ [])
def playback_info(stream_id, opts) when is_binary(stream_id) do
ash_opts = ash_opts(opts)
with {:ok, stream} <- Ash.get(Stream, stream_id, ash_opts),
{:ok, generation} when not is_nil(generation) <- live_generation(stream.id, ash_opts),
{:ok, renditions} <- renditions(generation.id, ash_opts) do
{:ok, response(stream, generation, renditions)}
else
{:ok, nil} -> {:error, :not_live}
{:error, error} -> {:error, error}
end
end
def playback_info(_stream_id, _opts), do: {:error, :invalid_stream_id}
defp live_generation(stream_id, ash_opts) do
StreamGeneration
|> Ash.Query.filter(stream_id == ^stream_id and status in [:live, :degraded])
|> Ash.Query.sort(started_at: :desc, inserted_at: :desc)
|> Ash.Query.limit(1)
|> Ash.read_one(ash_opts)
end
defp renditions(generation_id, ash_opts) do
Rendition
|> Ash.Query.filter(generation_id == ^generation_id and status in [:ready, :degraded])
|> Ash.Query.sort(video_bitrate: :desc, name: :asc)
|> Ash.read(ash_opts)
end
defp response(stream, generation, renditions) do
%{
"player" => "videojs-html-v10",
"stream" => %{
"id" => stream.id,
"slug" => stream.slug,
"title" => stream.title,
"latency_mode" => Atom.to_string(stream.latency_mode)
},
"generation" => %{
"id" => generation.id,
"status" => Atom.to_string(generation.status),
"started_at" => format_datetime(generation.started_at)
},
"sources" => Enum.map(renditions, &source/1)
}
end
defp source(rendition) do
%{
"src" => rendition.playlist_path,
"type" => @hls_mime,
"rendition" => rendition.name,
"width" => rendition.width,
"height" => rendition.height,
"video_bitrate" => rendition.video_bitrate
}
end
defp format_datetime(nil), do: nil
defp format_datetime(%DateTime{} = datetime), do: DateTime.to_iso8601(datetime)
defp ash_opts(opts) do
opts
|> Keyword.put_new(:domain, Streaming)
|> Keyword.put_new(:authorize?, false)
end
end