You've already forked algora-tv
							
							
				mirror of
				https://github.com/algora-io/tv.git
				synced 2025-10-30 23:07:56 +02:00 
			
		
		
		
	add PlayerComponent (#52)
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										56
									
								
								lib/algora_web/live/player_component.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								lib/algora_web/live/player_component.ex
									
									
									
									
									
										Normal 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 | ||||
| @@ -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 | ||||
| @@ -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)} | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user