1
0
mirror of https://github.com/algora-io/tv.git synced 2024-11-26 01:00:20 +02:00

add PlayerComponent (#52)

This commit is contained in:
Zafer Cesur 2024-06-09 13:30:44 +03:00 committed by GitHub
parent 8e88fbd5fd
commit cd2d8a2fbb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 116 additions and 180 deletions

View File

@ -150,8 +150,10 @@ const Hooks = {
mounted() {
const backdrop = document.querySelector("#video-backdrop");
this.player = videojs("video-player", {
autoplay: true,
this.playerId = this.el.id;
this.player = videojs(this.el, {
autoplay: "any",
liveui: true,
html5: {
vhs: {
@ -161,6 +163,7 @@ const Hooks = {
});
const playVideo = (opts: {
player_id: string;
id: string;
url: string;
title: string;
@ -168,6 +171,10 @@ const Hooks = {
current_time?: number;
channel_name: string;
}) => {
if (this.playerId !== opts.player_id) {
return;
}
const setMediaSession = () => {
if (!("mediaSession" in navigator)) {
return;
@ -193,22 +200,21 @@ const Hooks = {
: {}),
});
this.player.src({ src: opts.url, type: opts.player_type });
this.player.play();
setMediaSession();
if (opts.current_time && opts.player_type !== "video/youtube") {
this.player.currentTime(opts.current_time);
}
this.player.el().parentElement.classList.remove("hidden");
this.player.el().parentElement.classList.add("flex");
if (backdrop) {
backdrop.classList.remove("opacity-10");
backdrop.classList.add("opacity-20");
}
this.pushEventTo("#clipper", "video_loaded", { id: opts.id });
if (this.playerId === "video-player") {
this.pushEventTo("#clipper", "video_loaded", { id: opts.id });
}
};
this.handleEvent("play_video", playVideo);
@ -350,33 +356,6 @@ let liveSocket = new LiveSocket("/live", Socket, {
let routeUpdated = () => {
// TODO: uncomment
// Focus.focusMain();
const player = document.querySelector("#video-player")?.parentElement;
if (!player) {
return;
}
const { pathname } = new URL(window.location.href);
if (pathname.endsWith("/embed")) {
return;
}
const pipClasses = [
"fixed",
"bottom-0",
"right-0",
"z-[1000]",
"w-[100vw]",
"sm:w-[30vw]",
];
if (/^\/[^\/]+\/\d+$/.test(pathname)) {
player.classList.add("lg:pr-[24rem]");
player.classList.remove(...pipClasses);
} else {
player.classList.remove("lg:pr-[24rem]");
player.classList.add(...pipClasses);
}
};
// Show progress bar on live navigation and form submits

View File

@ -199,4 +199,21 @@ defmodule Algora.Events do
)
|> Repo.all()
end
def log_watched(user, video) do
actor_id = if user, do: "user_#{user.id}", else: "guest_#{hash_actor_id()}"
%Event{
actor_id: actor_id,
user_id: user && user.id,
video_id: video.id,
channel_id: video.user_id,
name: :watched
}
|> Event.changeset(%{})
|> Repo.insert()
end
# TODO:
defp hash_actor_id, do: ""
end

View File

@ -314,7 +314,6 @@
<.live_component module={AlgoraWeb.LayoutComponent} id="layout" />
<main class="flex-1 relative z-0 overflow-y-auto focus:outline-none">
<%= live_render(@socket, AlgoraWeb.PlayerLive, id: "player", session: %{}, sticky: true) %>
<%= live_render(@socket, AlgoraWeb.ClipperLive, id: "clipper", session: %{}, sticky: true) %>
<%= @inner_content %>
</main>

View File

@ -10,7 +10,6 @@
<.live_component module={AlgoraWeb.LayoutComponent} id="layout" />
<main class="flex-1 relative z-0 overflow-y-auto focus:outline-none">
<%= live_render(@socket, AlgoraWeb.PlayerLive, id: "player", session: %{}, sticky: true) %>
<%= @inner_content %>
</main>
</div>

View File

@ -3,17 +3,12 @@ defmodule AlgoraWeb.EmbedLive do
require Logger
alias Algora.{Accounts, Library, Storage, Chat}
alias AlgoraWeb.{LayoutComponent, Presence}
alias AlgoraWeb.{LayoutComponent, Presence, PlayerComponent}
def render(assigns) do
~H"""
<div class="w-full">
<video
id="video-player"
phx-hook="VideoPlayer"
class="video-js vjs-default-skin vjs-fluid flex-1 overflow-hidden"
controls
/>
<div class="w-full" id="embed-player-container" phx-update="ignore">
<.live_component module={PlayerComponent} id="embed-player" />
</div>
"""
end
@ -23,17 +18,23 @@ defmodule AlgoraWeb.EmbedLive do
Accounts.get_user_by!(handle: channel_handle)
|> Library.get_channel!()
videos = Library.list_channel_videos(channel, 50)
video = Library.get_video!(video_id)
if connected?(socket) do
Library.subscribe_to_livestreams()
Library.subscribe_to_channel(channel)
Presence.subscribe(channel_handle)
send_update(PlayerComponent, %{
id: "embed-player",
video: video,
current_user: nil
})
end
videos = Library.list_channel_videos(channel, 50)
video = Library.get_video!(video_id)
subtitles = Library.list_subtitles(%Library.Video{id: video_id})
data = %{}
@ -63,8 +64,6 @@ defmodule AlgoraWeb.EmbedLive do
|> stream(:videos, videos)
|> stream(:presences, Presence.list_online_users(channel_handle))
if connected?(socket), do: send(self(), {:play, video})
{:ok, socket}
end
@ -73,20 +72,6 @@ defmodule AlgoraWeb.EmbedLive do
{:noreply, socket |> apply_action(socket.assigns.live_action, params)}
end
def handle_info({:play, video}, socket) do
socket =
socket
|> push_event("play_video", %{
id: video.id,
url: video.url,
title: video.title,
player_type: Library.player_type(video),
channel_name: video.channel_name
})
{:noreply, socket}
end
def handle_info({Presence, {:join, presence}}, socket) do
{:noreply, stream_insert(socket, :presences, presence)}
end

View File

@ -0,0 +1,56 @@
defmodule AlgoraWeb.PlayerComponent do
use AlgoraWeb, :live_component
alias Algora.{Library, Events}
alias AlgoraWeb.Presence
@impl true
def render(assigns) do
~H"""
<video
id={@id}
phx-hook="VideoPlayer"
class="video-js vjs-default-skin aspect-video h-full w-full flex-1 lg:rounded-2xl overflow-hidden"
controls
/>
"""
end
@impl true
def update(assigns, socket) do
# TODO: log at regular intervals
# if socket.current_user && socket.assigns.video.is_live do
# schedule_watch_event(:timer.seconds(2))
# end
socket =
case assigns[:video] do
nil ->
socket
video ->
%{current_user: current_user} = assigns
Events.log_watched(current_user, video)
Presence.track_user(video.channel_handle, %{
id: if(current_user, do: current_user.handle, else: "")
})
socket
|> push_event("play_video", %{
player_id: assigns.id,
id: video.id,
url: video.url,
title: video.title,
player_type: Library.player_type(video),
channel_name: video.channel_name,
current_time: assigns[:current_time]
})
end
{:ok,
socket
|> assign(:id, assigns[:id])}
end
end

View File

@ -1,26 +0,0 @@
defmodule AlgoraWeb.PlayerLive do
use AlgoraWeb, {:live_view, container: {:div, []}}
on_mount {AlgoraWeb.UserAuth, :current_user}
def render(assigns) do
~H"""
<div class="lg:px-4">
<div class="w-full hidden lg:pr-[24rem]">
<video
id="video-player"
phx-hook="VideoPlayer"
class="video-js vjs-default-skin aspect-video h-full w-full flex-1 lg:rounded-2xl overflow-hidden"
controls
/>
</div>
</div>
"""
end
def mount(_params, _session, socket) do
{:ok, socket, layout: false, temporary_assigns: []}
end
def handle_info({Library, _}, socket), do: {:noreply, socket}
end

View File

@ -8,8 +8,6 @@ defmodule AlgoraWeb.SubtitleLive.Index do
def mount(%{"video_id" => video_id}, _session, socket) do
video = Library.get_video!(video_id)
if connected?(socket), do: send(self(), :play_video)
{:ok,
socket
|> assign(:video, video)
@ -39,21 +37,6 @@ defmodule AlgoraWeb.SubtitleLive.Index do
|> assign(:subtitle, nil)
end
@impl true
def handle_info(:play_video, socket) do
video = socket.assigns.video
{:noreply,
socket
|> push_event("play_video", %{
id: video.id,
url: video.url,
title: video.title,
player_type: Library.player_type(video),
channel_name: video.channel_name
})}
end
@impl true
def handle_info({AlgoraWeb.SubtitleLive.FormComponent, {:saved, subtitle}}, socket) do
{:noreply, stream_insert(socket, :subtitles, subtitle)}

View File

@ -7,26 +7,9 @@ defmodule AlgoraWeb.SubtitleLive.Show do
def mount(%{"video_id" => video_id}, _session, socket) do
video = Library.get_video!(video_id)
if connected?(socket), do: send(self(), :play_video)
{:ok, socket |> assign(:video, video)}
end
@impl true
def handle_info(:play_video, socket) do
video = socket.assigns.video
{:noreply,
socket
|> push_event("play_video", %{
id: video.id,
url: video.url,
title: video.title,
player_type: Library.player_type(video),
channel_name: video.channel_name
})}
end
@impl true
def handle_params(%{"id" => id}, _, socket) do
{:noreply,

View File

@ -5,14 +5,23 @@ defmodule AlgoraWeb.VideoLive do
alias Algora.{Accounts, Library, Storage, Chat, Repo}
alias Algora.Events.Event
alias AlgoraWeb.{LayoutComponent, Presence}
alias AlgoraWeb.{
LayoutComponent,
Presence,
RTMPDestinationIconComponent,
PlayerComponent
}
alias AlgoraWeb.ChannelLive.{StreamFormComponent}
alias AlgoraWeb.RTMPDestinationIconComponent
@impl true
def render(assigns) do
~H"""
<div class="lg:mr-[24rem] h-[calc(100svh-56.25vw-64px)] lg:h-auto">
<div class="px-4" id="video-player-container" phx-update="ignore">
<.live_component module={PlayerComponent} id="video-player" />
</div>
<div class="lg:border-b lg:border-gray-700 py-4">
<figure class="relative isolate -mt-4 pt-4 pb-4">
<svg
@ -539,11 +548,14 @@ defmodule AlgoraWeb.VideoLive do
Library.subscribe_to_channel(channel)
Chat.subscribe_to_room(video)
Presence.track_user(channel_handle, %{
id: if(current_user, do: current_user.handle, else: "")
})
Presence.subscribe(channel_handle)
send_update(PlayerComponent, %{
id: "video-player",
video: video,
current_user: current_user,
current_time: params["t"]
})
end
videos = Library.list_channel_videos(channel, 50)
@ -585,8 +597,6 @@ defmodule AlgoraWeb.VideoLive do
|> stream(:messages, Chat.list_messages(video))
|> stream(:presences, Presence.list_online_users(channel_handle))
if connected?(socket), do: send(self(), {:play, {video, params["t"]}})
{:ok, socket}
end
@ -602,34 +612,6 @@ defmodule AlgoraWeb.VideoLive do
end
@impl true
def handle_info({:play, {video, t}}, socket) do
socket =
socket
|> push_event("play_video", %{
id: video.id,
url: video.url,
title: video.title,
player_type: Library.player_type(video),
channel_name: video.channel_name,
current_time: t
})
schedule_watch_event()
{:noreply, socket}
end
def handle_info(:watch_event, socket) do
log_watch_event(socket.assigns.current_user, socket.assigns.video)
# TODO: enable later
# if socket.assigns.current_user && socket.assigns.video.is_live do
# schedule_watch_event(:timer.seconds(2))
# end
{:noreply, socket}
end
def handle_info({Presence, {:join, presence}}, socket) do
{:noreply, stream_insert(socket, :presences, presence)}
end
@ -884,25 +866,4 @@ defmodule AlgoraWeb.VideoLive do
socket
end
defp schedule_watch_event(ms \\ 0) do
Process.send_after(self(), :watch_event, ms)
end
defp log_watch_event(user, video) do
actor_id = if user, do: "user_#{user.id}", else: "guest_#{hash_actor_id()}"
%Event{
actor_id: actor_id,
user_id: user && user.id,
video_id: video.id,
channel_id: video.user_id,
name: :watched
}
|> Event.changeset(%{})
|> Repo.insert()
end
# TODO:
defp hash_actor_id, do: ""
end