-
+
+
"""
end
diff --git a/lib/algora_web/live/side_panel_live.ex b/lib/algora_web/live/side_panel_live.ex
deleted file mode 100644
index ba4d94c..0000000
--- a/lib/algora_web/live/side_panel_live.ex
+++ /dev/null
@@ -1,218 +0,0 @@
-defmodule AlgoraWeb.SidePanelLive do
- use AlgoraWeb, {:live_view, container: {:div, []}}
- alias Algora.{Chat, Library}
-
- on_mount {AlgoraWeb.UserAuth, :current_user}
-
- def render(assigns) do
- tabs =
- [:chat]
- |> append_if(length(assigns.subtitles) > 0, :transcript)
-
- assigns = assigns |> assign(:tabs, tabs)
-
- ~H"""
-
- """
- end
-
- def mount(_params, _session, socket) do
- {:ok, socket, layout: false, temporary_assigns: [subtitles: [], messages: []]}
- end
-
- def handle_event("show", %{"video_id" => video_id}, socket) do
- 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))
-
- video = Library.get_video!(video_id)
-
- socket =
- socket
- |> assign(video: video)
- |> assign(subtitles: subtitles)
- |> assign(messages: Chat.list_messages(video))
- |> assign_form(changeset)
- |> push_event("join_chat", %{id: video_id})
-
- {:noreply, socket}
- end
-
- def handle_event("save", %{"data" => %{"subtitles" => subtitles}, "save" => save_type}, socket) do
- save(save_type, subtitles)
- {:noreply, socket}
- end
-
- defp save("naive", subtitles) do
- Library.save_subtitles(subtitles)
- end
-
- defp save("fast", subtitles) do
- Fly.Postgres.rpc_and_wait(Library, :save_subtitles, [subtitles])
- end
-
- defp set_active_content(js \\ %JS{}, to) do
- js
- |> JS.hide(to: ".side-panel-content")
- |> JS.show(to: to)
- end
-
- defp set_active_tab(js \\ %JS{}, tab) do
- js
- |> JS.remove_class("active-tab text-white pointer-events-none",
- to: "#video-side-panel .active-tab"
- )
- |> JS.add_class("active-tab text-white pointer-events-none", to: tab)
- end
-
- defp system_message?(%Chat.Message{} = message) do
- message.sender_handle == "algora"
- end
-
- defp append_if(list, cond, extra) do
- if cond, do: list ++ [extra], else: list
- end
-
- defp assign_form(socket, %Ecto.Changeset{} = changeset) do
- assign(socket, :form, to_form(changeset, as: :data))
- end
-end
diff --git a/lib/algora_web/live/subtitle_live/index.ex b/lib/algora_web/live/subtitle_live/index.ex
index cd6be29..6af0c5a 100644
--- a/lib/algora_web/live/subtitle_live/index.ex
+++ b/lib/algora_web/live/subtitle_live/index.ex
@@ -45,7 +45,7 @@ defmodule AlgoraWeb.SubtitleLive.Index do
{:noreply,
socket
- |> push_event("js:play_video", %{
+ |> push_event("play_video", %{
detail: %{player: %{src: video.url, type: Library.player_type(video)}}
})}
end
diff --git a/lib/algora_web/live/subtitle_live/show.ex b/lib/algora_web/live/subtitle_live/show.ex
index 8151662..e978054 100644
--- a/lib/algora_web/live/subtitle_live/show.ex
+++ b/lib/algora_web/live/subtitle_live/show.ex
@@ -18,7 +18,7 @@ defmodule AlgoraWeb.SubtitleLive.Show do
{:noreply,
socket
- |> push_event("js:play_video", %{
+ |> push_event("play_video", %{
detail: %{player: %{src: video.url, type: Library.player_type(video)}}
})}
end
diff --git a/lib/algora_web/live/video_live.ex b/lib/algora_web/live/video_live.ex
new file mode 100644
index 0000000..f6634e6
--- /dev/null
+++ b/lib/algora_web/live/video_live.ex
@@ -0,0 +1,556 @@
+defmodule AlgoraWeb.VideoLive do
+ use AlgoraWeb, :live_view
+ require Logger
+
+ alias Algora.{Accounts, Library, Storage, Chat}
+ alias AlgoraWeb.{LayoutComponent, Presence}
+ alias AlgoraWeb.ChannelLive.{StreamFormComponent}
+
+ def render(assigns) do
+ tabs =
+ [:chat]
+ |> append_if(length(assigns.subtitles) > 0, :transcript)
+
+ assigns = assigns |> assign(:tabs, tabs)
+
+ ~H"""
+ <%!-- <:actions>
+ <.button
+ :if={@owns_channel? && not @channel.is_live}
+ id="stream-btn"
+ primary
+ patch={channel_stream_path(@current_user)}
+ >
+
+
+ Start streaming!
+
+
+ --%>
+
+
+
+
+ <%!--
+ """
+ end
+
+ def mount(%{"channel_handle" => channel_handle, "video_id" => video_id}, _session, socket) do
+ %{current_user: current_user} = socket.assigns
+
+ channel =
+ Accounts.get_user_by!(handle: channel_handle)
+ |> Library.get_channel!()
+
+ if connected?(socket) do
+ Library.subscribe_to_livestreams()
+ Library.subscribe_to_channel(channel)
+
+ Presence.track_user(channel_handle, %{
+ id: if(current_user, do: current_user.handle, else: "")
+ })
+
+ Presence.subscribe(channel_handle)
+ end
+
+ videos = Library.list_channel_videos(channel, 50)
+
+ video = Library.get_video!(video_id)
+
+ 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,
+ owns_channel?: current_user && Library.owns_channel?(current_user, channel),
+ videos_count: Enum.count(videos),
+ video: video,
+ subtitles: subtitles,
+ messages: Chat.list_messages(video)
+ )
+ |> assign_form(changeset)
+ |> stream(:videos, videos)
+ |> stream(:presences, Presence.list_online_users(channel_handle))
+
+ if connected?(socket), do: send(self(), {:play, video})
+
+ {:ok, socket}
+ end
+
+ def handle_params(params, _url, socket) do
+ LayoutComponent.hide_modal()
+ {:noreply, socket |> apply_action(socket.assigns.live_action, params)}
+ end
+
+ def handle_info({:play, video}, socket) do
+ socket =
+ socket
+ |> push_event("play_video", %{
+ detail: %{player: %{src: video.url, type: Library.player_type(video)}}
+ })
+ |> 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({Library, _}, socket), do: {:noreply, socket}
+
+ def handle_event("save", %{"data" => %{"subtitles" => subtitles}, "save" => save_type}, socket) do
+ save(save_type, subtitles)
+ {:noreply, socket}
+ end
+
+ defp save("naive", subtitles) do
+ Library.save_subtitles(subtitles)
+ end
+
+ defp save("fast", subtitles) do
+ Fly.Postgres.rpc_and_wait(Library, :save_subtitles, [subtitles])
+ end
+
+ defp set_active_content(js \\ %JS{}, to) do
+ js
+ |> JS.hide(to: ".side-panel-content")
+ |> JS.show(to: to)
+ end
+
+ defp set_active_tab(js \\ %JS{}, tab) do
+ js
+ |> JS.remove_class("active-tab text-white pointer-events-none",
+ to: "#side-panel .active-tab"
+ )
+ |> JS.add_class("active-tab text-white pointer-events-none", to: tab)
+ end
+
+ defp system_message?(%Chat.Message{} = message) do
+ message.sender_handle == "algora"
+ end
+
+ defp append_if(list, cond, extra) do
+ if cond, do: list ++ [extra], else: list
+ end
+
+ defp assign_form(socket, %Ecto.Changeset{} = changeset) do
+ assign(socket, :form, to_form(changeset, as: :data))
+ end
+
+ defp apply_action(socket, :stream, _params) do
+ if socket.assigns.owns_channel? do
+ socket
+ |> assign(:page_title, "Start streaming")
+ |> assign(:video, %Library.Video{})
+ |> show_stream_modal()
+ else
+ socket
+ |> put_flash(:error, "You can't do that")
+ |> redirect(to: channel_path(socket.assigns.current_user))
+ end
+ end
+
+ defp apply_action(socket, :show, params) do
+ socket
+ |> assign(:page_title, socket.assigns.channel.name || params["channel_handle"])
+ |> assign(:channel_handle, socket.assigns.channel.handle)
+ |> assign(:channel_name, socket.assigns.channel.name)
+ |> assign(:channel_tagline, socket.assigns.channel.tagline)
+ |> assign(:video, nil)
+ end
+
+ defp show_stream_modal(socket) do
+ LayoutComponent.show_modal(StreamFormComponent, %{
+ id: :stream,
+ confirm: {"Save", type: "submit", form: "stream-form"},
+ patch: channel_path(socket.assigns.current_user),
+ video: socket.assigns.video,
+ title: socket.assigns.page_title,
+ current_user: socket.assigns.current_user,
+ changeset: Accounts.change_settings(socket.assigns.current_user, %{})
+ })
+
+ socket
+ end
+end
diff --git a/lib/algora_web/router.ex b/lib/algora_web/router.ex
index 14b759c..447e9ac 100644
--- a/lib/algora_web/router.ex
+++ b/lib/algora_web/router.ex
@@ -62,6 +62,7 @@ defmodule AlgoraWeb.Router do
live "/", HomeLive, :show
live "/auth/login", SignInLive, :index
live "/:channel_handle", ChannelLive, :show
+ live "/:channel_handle/:video_id", VideoLive, :show
end
end
end
+
+
--%>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ LIVE
+
+
+
+
+ <%= @channel.name %>
+
+
+
+
+
+
+
+
+
+ -
+
-
+
+
<%= lang.name %>
+
+
+
+ @<%= @channel.handle %>
+
+
+
+
+
+ Watching now
+
+
+
+
+
+
+
+
+
+ <%= metas
+ |> Enum.filter(fn meta -> meta.id != @channel.handle end)
+ |> length() %>
+
+
+
+
+ Bounties collected
+
+
+
+
+
+ <%= @channel.bounties_count %>
+
+ 0}>
+
+
+ OSS projects contributed
+
+
+
+
+
+
+
+
+