mirror of
https://github.com/algora-io/tv.git
synced 2024-11-16 00:58:59 +02:00
auto update chat overlay on new livestream (#50)
* remove unused stuff * remove more unused stuff * clean up * remove more unused stuff * render ChatLive on /:channel/chat * handle no video case * simplify * subscribe to new room on new livestream * show flash message on new livestream * add flash with action * handle no existing video case
This commit is contained in:
parent
aa34fc50bb
commit
6be4bb1ad9
@ -12,6 +12,10 @@ defmodule Algora.Chat do
|
||||
|
||||
@pubsub Algora.PubSub
|
||||
|
||||
def unsubscribe_to_room(%Video{} = video) do
|
||||
Phoenix.PubSub.unsubscribe(@pubsub, topic(video.id))
|
||||
end
|
||||
|
||||
def subscribe_to_room(%Video{} = video) do
|
||||
Phoenix.PubSub.subscribe(@pubsub, topic(video.id))
|
||||
end
|
||||
|
@ -260,7 +260,7 @@ defmodule Algora.Library do
|
||||
false -> %Events.LivestreamEnded{video: video}
|
||||
end
|
||||
|
||||
Phoenix.PubSub.broadcast!(@pubsub, topic_livestreams(), {__MODULE__, msg})
|
||||
broadcast!(topic_livestreams(), msg)
|
||||
|
||||
sink_url = Algora.config([:event_sink, :url])
|
||||
|
||||
|
@ -553,7 +553,7 @@ defmodule AlgoraWeb.CoreComponents do
|
||||
"""
|
||||
attr :id, :string, default: "flash", doc: "the optional id of flash container"
|
||||
attr :flash, :map, default: %{}, doc: "the map of flash messages to display"
|
||||
attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup"
|
||||
attr :kind, :atom, values: [:info, :note, :error], doc: "used for styling and flash lookup"
|
||||
attr :autoshow, :boolean, default: true, doc: "whether to auto show the flash on mount"
|
||||
attr :close, :boolean, default: true, doc: "whether the flash can be closed"
|
||||
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container"
|
||||
@ -571,15 +571,30 @@ defmodule AlgoraWeb.CoreComponents do
|
||||
class={[
|
||||
"fixed hidden top-2 right-2 w-80 sm:w-96 z-50 rounded-lg p-3 shadow-md shadow-gray-50/5 ring-1",
|
||||
@kind == :info && "bg-green-900 text-green-100 ring-green-900 fill-green-900",
|
||||
@kind == :note && "bg-purple-900 text-purple-100 ring-purple-900 fill-purple-900",
|
||||
@kind == :error && "bg-red-900 p-3 text-red-50 shadow-md ring-red-900 fill-red-50"
|
||||
]}
|
||||
{@rest}
|
||||
>
|
||||
<p class="flex items-center gap-1.5 text-[0.8125rem] font-semibold leading-6">
|
||||
<Heroicons.check_circle :if={@kind == :info} solid class="w-6 h-6" />
|
||||
<Heroicons.exclamation_circle :if={@kind == :error} solid class="w-6 h-6" />
|
||||
<%= msg %>
|
||||
</p>
|
||||
<%= case msg do %>
|
||||
<% %{body: body, action: %{ href: href, body: action_body }} -> %>
|
||||
<div class="flex gap-1.5 text-[0.8125rem] font-semibold leading-6">
|
||||
<Heroicons.check_circle :if={@kind == :info} solid class="w-6 h-6" />
|
||||
<Heroicons.information_circle :if={@kind == :note} solid class="w-6 h-6" />
|
||||
<Heroicons.exclamation_circle :if={@kind == :error} solid class="w-6 h-6" />
|
||||
<div>
|
||||
<div><%= body %></div>
|
||||
<.link navigate={href} class="underline"><%= action_body %></.link>
|
||||
</div>
|
||||
</div>
|
||||
<% body -> %>
|
||||
<p class="flex items-center gap-1.5 text-[0.8125rem] font-semibold leading-6">
|
||||
<Heroicons.check_circle :if={@kind == :info} solid class="w-6 h-6" />
|
||||
<Heroicons.information_circle :if={@kind == :note} solid class="w-6 h-6" />
|
||||
<Heroicons.exclamation_circle :if={@kind == :error} solid class="w-6 h-6" />
|
||||
<%= body %>
|
||||
</p>
|
||||
<% end %>
|
||||
<button
|
||||
:if={@close}
|
||||
type="button"
|
||||
@ -604,6 +619,7 @@ defmodule AlgoraWeb.CoreComponents do
|
||||
def flash_group(assigns) do
|
||||
~H"""
|
||||
<.flash kind={:info} title="Success!" flash={@flash} />
|
||||
<.flash kind={:note} title="Note" flash={@flash} />
|
||||
<.flash kind={:error} title="Error!" flash={@flash} />
|
||||
<.flash
|
||||
id="disconnected"
|
||||
|
@ -1,5 +1,6 @@
|
||||
<main>
|
||||
<p class="alert alert-info" role="alert"><%= Phoenix.Flash.get(@flash, :info) %></p>
|
||||
<p class="alert alert-note" role="alert"><%= Phoenix.Flash.get(@flash, :note) %></p>
|
||||
<p class="alert alert-danger" role="alert"><%= Phoenix.Flash.get(@flash, :error) %></p>
|
||||
<%= @inner_content %>
|
||||
</main>
|
||||
|
@ -305,6 +305,7 @@
|
||||
</div>
|
||||
</div> --%>
|
||||
<.flash flash={@flash} kind={:info} />
|
||||
<.flash flash={@flash} kind={:note} />
|
||||
<.flash flash={@flash} kind={:error} />
|
||||
<.connection_status>
|
||||
Re-establishing connection...
|
||||
|
@ -1,6 +1,7 @@
|
||||
<!-- Main column -->
|
||||
<div class="flex flex-col w-0 flex-1 overflow-hidden">
|
||||
<.flash flash={@flash} kind={:info} />
|
||||
<.flash flash={@flash} kind={:note} />
|
||||
<.flash flash={@flash} kind={:error} />
|
||||
<.connection_status>
|
||||
Re-establishing connection...
|
||||
|
@ -1,6 +1,7 @@
|
||||
<!-- Main column -->
|
||||
<div class="flex flex-col w-0 flex-1 overflow-hidden">
|
||||
<.flash flash={@flash} kind={:info} />
|
||||
<.flash flash={@flash} kind={:note} />
|
||||
<.flash flash={@flash} kind={:error} />
|
||||
<.connection_status>
|
||||
Re-establishing connection...
|
||||
|
@ -1,17 +0,0 @@
|
||||
defmodule AlgoraWeb.ChatPopoutController do
|
||||
use AlgoraWeb, :controller
|
||||
|
||||
alias Algora.{Accounts, Library}
|
||||
|
||||
def get(conn, %{"channel_handle" => channel_handle}) do
|
||||
user = Accounts.get_user_by!(handle: channel_handle)
|
||||
|
||||
case Library.get_latest_video(user) do
|
||||
nil ->
|
||||
redirect(conn, to: ~p"/#{user.handle}")
|
||||
|
||||
video ->
|
||||
redirect(conn, to: ~p"/#{user.handle}/#{video.id}/chat")
|
||||
end
|
||||
end
|
||||
end
|
@ -2,13 +2,10 @@ defmodule AlgoraWeb.ChatLive do
|
||||
use AlgoraWeb, :live_view
|
||||
require Logger
|
||||
|
||||
alias Algora.{Accounts, Library, Storage, Chat}
|
||||
alias AlgoraWeb.{LayoutComponent, Presence}
|
||||
alias AlgoraWeb.RTMPDestinationIconComponent
|
||||
alias Algora.{Accounts, Library, Chat}
|
||||
alias AlgoraWeb.{LayoutComponent, RTMPDestinationIconComponent}
|
||||
|
||||
def render(assigns) do
|
||||
assigns = assigns |> assign(tabs: [:chat])
|
||||
|
||||
~H"""
|
||||
<aside id="side-panel" class="w-[400px] rounded ring-1 ring-purple-300 m-1 overflow-hidden">
|
||||
<div>
|
||||
@ -64,11 +61,7 @@ defmodule AlgoraWeb.ChatLive do
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
:for={{tab, i} <- Enum.with_index(@tabs)}
|
||||
id={"side-panel-content-#{tab}"}
|
||||
class={["side-panel-content", i != 0 && "hidden"]}
|
||||
>
|
||||
<div id="side-panel-content-chat" class="side-panel-content">
|
||||
<div>
|
||||
<div
|
||||
id="chat-messages"
|
||||
@ -98,55 +91,33 @@ defmodule AlgoraWeb.ChatLive do
|
||||
"""
|
||||
end
|
||||
|
||||
def mount(%{"channel_handle" => channel_handle, "video_id" => video_id}, _session, socket) do
|
||||
channel =
|
||||
Accounts.get_user_by!(handle: channel_handle)
|
||||
|> Library.get_channel!()
|
||||
def mount(%{"channel_handle" => channel_handle} = params, _session, socket) do
|
||||
user = Accounts.get_user_by!(handle: channel_handle)
|
||||
channel = Library.get_channel!(user)
|
||||
|
||||
video = Library.get_video!(video_id)
|
||||
video =
|
||||
case params["video_id"] do
|
||||
nil -> Library.get_latest_video(user)
|
||||
id -> Library.get_video!(id)
|
||||
end
|
||||
|
||||
messages =
|
||||
case video do
|
||||
nil -> []
|
||||
video -> Chat.list_messages(video)
|
||||
end
|
||||
|
||||
if connected?(socket) do
|
||||
Library.subscribe_to_livestreams()
|
||||
Library.subscribe_to_channel(channel)
|
||||
Chat.subscribe_to_room(video)
|
||||
|
||||
Presence.subscribe(channel_handle)
|
||||
if video, do: Chat.subscribe_to_room(video)
|
||||
end
|
||||
|
||||
videos = Library.list_channel_videos(channel, 50)
|
||||
|
||||
subtitles = Library.list_subtitles(%Library.Video{id: video_id})
|
||||
|
||||
data = %{}
|
||||
|
||||
{:ok, encoded_subtitles} =
|
||||
subtitles
|
||||
|> Enum.map(&%{id: &1.id, start: &1.start, end: &1.end, body: &1.body})
|
||||
|> Jason.encode(pretty: true)
|
||||
|
||||
types = %{subtitles: :string}
|
||||
params = %{subtitles: encoded_subtitles}
|
||||
|
||||
changeset =
|
||||
{data, types}
|
||||
|> Ecto.Changeset.cast(params, Map.keys(types))
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(
|
||||
channel: channel,
|
||||
videos_count: Enum.count(videos),
|
||||
video: video,
|
||||
subtitles: subtitles
|
||||
)
|
||||
|> assign_form(changeset)
|
||||
|> stream(:videos, videos)
|
||||
|> stream(:messages, Chat.list_messages(video))
|
||||
|> stream(:presences, Presence.list_online_users(channel_handle))
|
||||
|
||||
if connected?(socket), do: send(self(), {:play, video})
|
||||
|
||||
{:ok, socket}
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:channel, channel)
|
||||
|> assign(:video, video)
|
||||
|> stream(:messages, messages)}
|
||||
end
|
||||
|
||||
def handle_params(params, _url, socket) do
|
||||
@ -154,68 +125,6 @@ defmodule AlgoraWeb.ChatLive do
|
||||
{:noreply, socket |> apply_action(socket.assigns.live_action, params)}
|
||||
end
|
||||
|
||||
def handle_info({:play, video}, socket) do
|
||||
socket = socket |> push_event("join_chat", %{id: video.id})
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_info({Presence, {:join, presence}}, socket) do
|
||||
{:noreply, stream_insert(socket, :presences, presence)}
|
||||
end
|
||||
|
||||
def handle_info({Presence, {:leave, presence}}, socket) do
|
||||
if presence.metas == [] do
|
||||
{:noreply, stream_delete(socket, :presences, presence)}
|
||||
else
|
||||
{:noreply, stream_insert(socket, :presences, presence)}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
{Storage, %Library.Events.ThumbnailsGenerated{video: video}},
|
||||
socket
|
||||
) do
|
||||
{:noreply,
|
||||
if video.user_id == socket.assigns.channel.user_id do
|
||||
socket
|
||||
|> stream_insert(:videos, video, at: 0)
|
||||
else
|
||||
socket
|
||||
end}
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
{Library, %Library.Events.LivestreamStarted{video: video}},
|
||||
socket
|
||||
) do
|
||||
%{channel: channel} = socket.assigns
|
||||
|
||||
{:noreply,
|
||||
if video.user_id == channel.user_id do
|
||||
socket
|
||||
|> assign(channel: %{channel | is_live: true})
|
||||
|> stream_insert(:videos, video, at: 0)
|
||||
else
|
||||
socket
|
||||
end}
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
{Library, %Library.Events.LivestreamEnded{video: video}},
|
||||
socket
|
||||
) do
|
||||
%{channel: channel} = socket.assigns
|
||||
|
||||
{:noreply,
|
||||
if video.user_id == channel.user_id do
|
||||
socket
|
||||
|> assign(channel: %{channel | is_live: false})
|
||||
|> stream_insert(:videos, video)
|
||||
else
|
||||
socket
|
||||
end}
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
{Chat, %Chat.Events.MessageDeleted{message: message}},
|
||||
socket
|
||||
@ -223,24 +132,46 @@ defmodule AlgoraWeb.ChatLive do
|
||||
{:noreply, socket |> stream_delete(:messages, message)}
|
||||
end
|
||||
|
||||
def handle_info({Chat, %Chat.Events.MessageSent{message: message}}, socket) do
|
||||
def handle_info(
|
||||
{Chat, %Chat.Events.MessageSent{message: message}},
|
||||
socket
|
||||
) do
|
||||
{:noreply, socket |> stream_insert(:messages, message)}
|
||||
end
|
||||
|
||||
def handle_info({Library, _}, socket), do: {:noreply, socket}
|
||||
def handle_info(
|
||||
{Library, %Library.Events.LivestreamStarted{video: video}},
|
||||
socket
|
||||
) do
|
||||
if video.user_id != socket.assigns.channel.user_id do
|
||||
{:noreply, socket}
|
||||
else
|
||||
Chat.unsubscribe_to_room(socket.assigns.video)
|
||||
Chat.subscribe_to_room(video)
|
||||
{:noreply, socket |> assign(:video, video)}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(_arg, socket), do: {:noreply, socket}
|
||||
|
||||
defp system_message?(%Chat.Message{} = message) do
|
||||
message.sender_handle == "algora"
|
||||
end
|
||||
|
||||
defp assign_form(socket, %Ecto.Changeset{} = changeset) do
|
||||
assign(socket, :form, to_form(changeset, as: :data))
|
||||
end
|
||||
|
||||
defp apply_action(socket, :show, params) do
|
||||
socket
|
||||
|> assign(:page_title, socket.assigns.channel.name || params["channel_handle"])
|
||||
|> assign(:page_description, socket.assigns.video.title)
|
||||
|> assign(:page_image, Library.get_og_image_url(socket.assigns.video))
|
||||
channel_name = socket.assigns.channel.name || params["channel_handle"]
|
||||
|
||||
case socket.assigns.video do
|
||||
nil ->
|
||||
socket
|
||||
|> assign(:page_title, channel_name)
|
||||
|> assign(:page_description, "Watch #{channel_name} on Algora TV")
|
||||
|
||||
video ->
|
||||
socket
|
||||
|> assign(:page_title, channel_name)
|
||||
|> assign(:page_description, video.title)
|
||||
|> assign(:page_image, Library.get_og_image_url(video))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -83,7 +83,6 @@ defmodule AlgoraWeb.EmbedLive do
|
||||
player_type: Library.player_type(video),
|
||||
channel_name: video.channel_name
|
||||
})
|
||||
|> push_event("join_chat", %{id: video.id})
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
@ -613,7 +613,6 @@ defmodule AlgoraWeb.VideoLive do
|
||||
channel_name: video.channel_name,
|
||||
current_time: t
|
||||
})
|
||||
|> push_event("join_chat", %{id: video.id})
|
||||
|
||||
schedule_watch_event()
|
||||
|
||||
@ -664,9 +663,29 @@ defmodule AlgoraWeb.VideoLive do
|
||||
|
||||
{:noreply,
|
||||
if video.user_id == channel.user_id do
|
||||
note =
|
||||
if channel.is_live do
|
||||
%{
|
||||
body: "Livestream moved to another URL!",
|
||||
action: %{
|
||||
href: ~p"/#{channel.handle}/#{video.id}",
|
||||
body: "Click here to continue watching"
|
||||
}
|
||||
}
|
||||
else
|
||||
%{
|
||||
body: "#{channel.name} just went live!",
|
||||
action: %{
|
||||
href: ~p"/#{channel.handle}/#{video.id}",
|
||||
body: "Click here to start watching"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
socket
|
||||
|> assign(channel: %{channel | is_live: true})
|
||||
|> stream_insert(:videos, video, at: 0)
|
||||
|> put_flash(:note, note)
|
||||
else
|
||||
socket
|
||||
end}
|
||||
@ -699,7 +718,7 @@ defmodule AlgoraWeb.VideoLive do
|
||||
{:noreply, socket |> stream_insert(:messages, message)}
|
||||
end
|
||||
|
||||
def handle_info({Library, _}, socket), do: {:noreply, socket}
|
||||
def handle_info(_arg, socket), do: {:noreply, socket}
|
||||
|
||||
defp fmt(num) do
|
||||
chars = num |> Integer.to_string() |> String.to_charlist()
|
||||
|
@ -53,13 +53,13 @@ defmodule AlgoraWeb.Router do
|
||||
pipe_through [:browser, :embed]
|
||||
|
||||
get "/:channel_handle/latest", VideoPopoutController, :get
|
||||
get "/:channel_handle/chat", ChatPopoutController, :get
|
||||
get "/:channel_handle/embed", EmbedPopoutController, :get
|
||||
get "/:channel_handle/:video_id/embed", EmbedPopoutController, :get_by_id
|
||||
|
||||
live_session :chat,
|
||||
layout: {AlgoraWeb.Layouts, :live_chat},
|
||||
root_layout: {AlgoraWeb.Layouts, :root_embed} do
|
||||
live "/:channel_handle/chat", ChatLive, :show
|
||||
live "/:channel_handle/:video_id/chat", ChatLive, :show
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user