You've already forked tribes-plugin-sender
forked from tribes/tribes-plugin-template
f8e2bfaada
Move Sender modules under TribeOne.TribesPlugin.Sender and replace the Aether-specific chat integration with the public chat@1 surface contract.
84 lines
2.5 KiB
Elixir
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
|